--- phase: 01-foundations-and-doctrine plan: 01 subsystem: infra tags: [phaser, react, vite, typescript, vitest, playwright, scaffold, firewall] # Dependency graph requires: - phase: 00 provides: Project planning artifacts (.planning/PROJECT.md, REQUIREMENTS.md, ROADMAP.md, CLAUDE.md, Phase-1 RESEARCH+CONTEXT) provides: - Buildable Phaser 4 + React 19 + Vite 8 + TypeScript 6 scaffold (npm run build green) - Seven architectural-firewall directories under src/ with .gitkeep markers (sim, render, ui, save, content, audio, store) - Repo-root /content/ tree (with /dialogue/ and /seasons/) and /assets/ tree - All Phase-1 dependencies installed at versions verified in RESEARCH.md (15+ packages) - Pre-declared package.json scripts the entire Phase 1 plan-set will rely on (dev, build, preview, lint, test, test:watch, validate:assets, compile:ink, ci) - Vitest config (happy-dom env, passWithNoTests:false) and Playwright config (testDir: tests/e2e, no specs yet) - Sentinel test proving the Vitest runner is wired and happy-dom is active affects: [01-02-firewall-and-lint, 01-03-save-layer, 01-04-content-pipeline, 01-05-asset-provenance, 01-06-doctrine-docs, 01-07-ci-workflow, 02-onwards] # Tech tracking tech-stack: added: - phaser@^4.1.0 (game framework, locked per CLAUDE.md) - react@^19.2.6 + react-dom@^19.2.6 (UI shell) - vite@^8.0.11 + @vitejs/plugin-react@^6.0.1 (dev server + bundler) - typescript@^6.0.3 (strict mode enabled) - idb@^8.0.3 (Plan 03 IDB wrapper) - lz-string@^1.5.0 (Plan 03 save compression + Base64) - zod@^4.4.3 (Plan 03/04 schema validation) - crc-32@^1.2.2 (Plan 03 save checksum) - gray-matter@^4.0.3 (Plan 04 .md frontmatter parsing) - yaml@^2.8.4 (Plan 04 .yaml content) - inkjs@^2.4.0 (Plan 04 / Phase 2 Ink runtime — installed only) - vitest@^4.1.5 + @vitest/ui (Phase 1 sentinel; Plans 03/04 add real tests) - happy-dom@^20.9.0 (Vitest DOM env for window/IDB tests) - fake-indexeddb@^6.2.5 (Plan 03 IDB tests — pre-installed here so Plan 03 doesn't re-edit package.json) - @playwright/test@^1.59.1 (installed only; first spec lands Phase 2 PIPE-07) - eslint@^9 + eslint-plugin-boundaries@^6.0.2 (Plan 02 firewall lint) - inklecate@^1.8.1 (Phase 2 Ink compiler — installed only) - @types/react@^19, @types/react-dom@^19, @types/node@^22 (TS types) patterns: - "Architectural firewall directories declared as siblings: src/{sim,render,ui,save,content,audio,store}/ — siblings to template-provided src/game/. Plan 02's ESLint boundaries rule will lint imports between these." - "Repo-root /content/ and /assets/ trees (CONTEXT D-11, D-12) — single package, no monorepo, no workspaces. Solo-dev scope." - "Pre-declared script keys in package.json so Wave-2 plans (02–06) running in parallel never collide on package.json." - "TypeScript strict mode enforced via tsconfig.json + tsconfig.app.json + tsconfig.node.json (referenced project layout)." - "Test infrastructure shape: Vitest with happy-dom env (DOM + window) for unit/sim/save tests; Playwright with webServer reuse for e2e smoke (Phase 2+). passWithNoTests:false enforces 'green CI ⇒ tests ran' (RESEARCH CI Pitfall B)." key-files: created: - package.json (Phase-1 deps + 9 pre-declared scripts) - package-lock.json (lockfile committed for reproducible installs) - .gitignore (node_modules, dist, coverage, *.tsbuildinfo, etc.) - index.html (Vite entry) - vite.config.ts (React plugin + dev server config) - tsconfig.json + tsconfig.app.json + tsconfig.node.json (strict TS, referenced projects) - src/main.tsx (React root mount) - src/App.tsx (mounts ) - src/PhaserGame.tsx (forwardRef wrapper around Phaser.Game lifecycle) - src/game/main.ts (minimal Phaser config + StartGame factory) - src/game/scenes/Boot.ts (Phase 1 placeholder Phaser scene) - src/vite-env.d.ts (vite/client types reference) - src/sim/.gitkeep + src/render/.gitkeep + src/ui/.gitkeep + src/save/.gitkeep + src/content/.gitkeep + src/audio/.gitkeep + src/store/.gitkeep (firewall markers) - content/.gitkeep + content/dialogue/.gitkeep + content/seasons/.gitkeep + assets/.gitkeep (authored-content + AI-asset trees) - vitest.config.ts (happy-dom env, passWithNoTests:false) - playwright.config.ts (testDir tests/e2e, webServer wiring) - src/__sentinel__.test.ts (proves Vitest runner + happy-dom env) modified: [] key-decisions: - "Built the equivalent React + Vite + TypeScript scaffold by hand because the official `npm create @phaserjs/game@latest` scaffolder (create-game v1.3.2) is interactive-only — the documented `--template react-ts --yes` flags are silently ignored. The plan's Step 1 explicitly authorizes this fallback ('If the official scaffolder cannot run non-interactively, fall back to manually constructing the equivalent file tree')." - "Adopted the referenced-projects tsconfig pattern (root tsconfig.json with `references` to tsconfig.app.json and tsconfig.node.json) — current Vite + TS 6 best practice. Strict mode enforced at all three layers." - "build script set to `tsc -b && vite build` (not bare `vite build`) so the plan's TypeScript-strict invariant is verified on every build, not silently bypassed." - "*.tsbuildinfo added to .gitignore — tsc -b emits these incremental-build cache files; they are dev artifacts, not source." - "Sentinel test asserts both `1+1===2` AND `typeof globalThis.window === 'object'` so it doubles as proof that happy-dom is active (Plan 03 will rely on this env)." - "Pre-installed `fake-indexeddb@^6` here in Plan 01 (rather than waiting for Plan 03) so Plan 03 Task 2 can `import 'fake-indexeddb/auto'` without re-editing package.json — defends Wave-2 parallel execution." patterns-established: - "Scaffold-from-scratch fallback: when an upstream scaffolder cannot run non-interactively, manually construct the equivalent file tree using the published template-shape as the spec. Lock dependency versions in package.json from the published template's package.json (or RESEARCH.md when newer). Future plans should follow the same fallback discipline." - "Pre-declare-all-scripts pattern: Wave-N plans that will run in parallel must not collide on package.json. The Wave-1 scaffold plan owns the entire scripts block; later plans add config files and source files only." - "Firewall-as-directory pattern: src/{sim,render,ui,save,content,audio,store}/.gitkeep is the structural prerequisite for Plan 02's ESLint boundaries rule. Empty directories cannot exist in git, so .gitkeep is mandatory." requirements-completed: [CORE-01] # Metrics duration: 6min completed: 2026-05-09 --- # Phase 1 Plan 01: Scaffold and Test Infrastructure Summary **Hand-built Phaser 4 + React 19 + Vite 8 + TypeScript 6 scaffold mirroring the official `@phaserjs/game` template, all 15 Phase-1 deps installed at locked versions, seven architectural-firewall directories under src/, repo-root /content/ + /assets/ trees, pre-declared package.json scripts for Wave 2, and Vitest (happy-dom) + Playwright wired with a passing sentinel test.** ## Performance - **Duration:** 6 min - **Started:** 2026-05-09T03:12:34Z - **Completed:** 2026-05-09T03:18:51Z - **Tasks:** 2 (both completed atomically) - **Files created:** 28 source/config files + 11 .gitkeep markers (no files modified — greenfield) ## Accomplishments - **Buildable scaffold.** `npm run build` runs `tsc -b && vite build` and produces `dist/index.html` + `dist/assets/index-*.js` (~1.5MB Phaser bundle, code-split deferred to Phase 2+ when actual scenes land). TypeScript strict mode passes on all source. - **All Phase-1 dependencies installed at the verified versions.** 15 listed in package.json (10 production + 5 dev that exceed the trivial baseline; total devDependencies count is 14 with @types/* and tooling). Versions match RESEARCH.md exactly. - **All seven architectural-firewall directories exist under src/** with .gitkeep markers. Plan 02's `eslint-plugin-boundaries` rule has clean targets to lint against. - **Repo-root /content/ and /assets/ trees created** per CONTEXT D-11, D-12. Single package, no monorepo. content/ has /dialogue/ and /seasons/ subdirs ready for Phase 2 authored fragments. - **All downstream-required scripts pre-declared in package.json:** `dev`, `build`, `preview`, `lint` (with `--max-warnings 0` per CI Pitfall C), `test` (with `--passWithNoTests=false` per CI Pitfall B), `test:watch`, `validate:assets`, `compile:ink` (no-op stub for Phase 1), `ci` (composite gate). Wave-2 plans 02–06 can now run in parallel without colliding on this file. - **Vitest + Playwright wired and proven.** Sentinel test passes in 593ms; `npx playwright --version` returns `Version 1.59.1` (matching the locked version exactly). ## Task Commits Each task was committed atomically on `master`: 1. **Task 1: Scaffold Phaser 4 template + Phase-1 deps + firewall directories** — `df7d687` (chore) 2. **Task 2: Configure Vitest (happy-dom) + Playwright + sentinel test** — `7b2982b` (chore) **Plan metadata:** _(this commit)_ — `docs(01-01): complete scaffold and test infra plan` ## Final package.json scripts block (load-bearing for Wave 2) ```json { "scripts": { "dev": "vite", "build": "tsc -b && vite build", "preview": "vite preview", "lint": "eslint . --max-warnings 0", "test": "vitest run --passWithNoTests=false", "test:watch": "vitest", "validate:assets": "node scripts/validate-assets.mjs", "compile:ink": "echo \"[compile:ink] no .ink files yet — Phase 2 will populate /content/dialogue/\" && exit 0", "ci": "npm run lint && npm run test && npm run validate:assets && npm run build" } } ``` Wave-2 plans should rely on these script keys *as written* and only edit them if a fundamental shape change is required. Plan 02 will add an `eslint.config.js` (the `lint` script invokes it); Plan 03 will add migration tests under `src/save/` (the `test` script picks them up via the `vitest.config.ts` include glob); Plan 05 will write `scripts/validate-assets.mjs`; Plan 07 will compose all of these into the CI workflow. ## Final installed dependency versions Resolved versions (`npm ls --depth=0` would show these, all matching the RESEARCH.md targets within the `^` range): | Package | Range in package.json | Notes | |---|---|---| | `phaser` | `^4.1.0` | "Salusa", April 2026; latest tag on npm | | `react` / `react-dom` | `^19.2.6` | React 19.2.x | | `vite` | `^8.0.11` | Vite 8 (Rolldown) | | `@vitejs/plugin-react` | `^6.0.1` | matches Vite 8 | | `typescript` | `^6.0.3` | TS 6 | | `idb` | `^8.0.3` | promise-based IndexedDB wrapper (Plan 03) | | `lz-string` | `^1.5.0` | save compression + Base64 (Plan 03) | | `zod` | `^4.4.3` | Zod 4 (NOT Zod 3) — Plan 03/04 | | `crc-32` | `^1.2.2` | save checksum (Plan 03) | | `gray-matter` | `^4.0.3` | .md frontmatter (Plan 04) | | `yaml` | `^2.8.4` | yaml content (Plan 04) | | `inkjs` | `^2.4.0` | Ink runtime (Phase 2 — installed only) | | `vitest` | `^4.1.5` | Vitest 4 | | `@vitest/ui` | `^4.1.5` | Vitest UI | | `happy-dom` | `^20.9.0` | Vitest DOM env | | `fake-indexeddb` | `^6.2.5` | Plan 03 IDB tests (pre-installed) | | `@playwright/test` | `^1.59.1` | matches research lock exactly | | `eslint` | `^9` | Plan 02 host | | `eslint-plugin-boundaries` | `^6.0.2` | Plan 02 firewall rule | | `inklecate` | `^1.8.1` | Phase 2 Ink compiler — installed only | | `@types/react` / `@types/react-dom` / `@types/node` | `^19` / `^19` / `^22` | TS types | ## Decisions Made - **Manual scaffold over interactive Phaser scaffolder.** `npm create @phaserjs/game@latest` (create-game v1.3.2) is interactive-only — the `--template react-ts --yes` flags documented in the plan are silently ignored by the current version. Verified by reading the published tarball: the React TS template ships only `App.tsx`; the rest of the tree is composed dynamically inside the interactive flow. The plan's Step 1 explicitly authorizes the fallback ("If the official scaffolder cannot run non-interactively, fall back to manually constructing the equivalent file tree per RESEARCH.md"). - **Referenced-projects tsconfig layout.** Adopted current Vite + TS 6 best practice: root `tsconfig.json` with `references` to `tsconfig.app.json` (covers `src/`, DOM lib) and `tsconfig.node.json` (covers `vite.config.ts`, `vitest.config.ts`, `playwright.config.ts`, `scripts/**/*.mjs`). Strict mode enforced at all three layers — TypeScript-strict invariant from CLAUDE.md "Code Style". - **`build` script runs `tsc -b && vite build`.** Not bare `vite build`, so the strict-TS gate runs on every build instead of being silently bypassed by Vite's loose default transpile. - **`*.tsbuildinfo` added to .gitignore.** TS 6's `tsc -b` emits these incremental-build cache files at the project root; they are dev artifacts, not source. - **Sentinel test asserts both `1+1===2` AND `typeof globalThis.window === 'object'`.** Doubles as proof that happy-dom is active so Plan 03 can trust the env when its IDB tests load. - **`fake-indexeddb@^6` pre-installed in Plan 01** rather than deferring to Plan 03. Defends Wave-2 parallel execution: Plan 03 Task 2 can `import 'fake-indexeddb/auto'` without re-editing package.json. ## Drift from official Phaser template For Plan 02's awareness: - **Vite version installed: 8.0.11.** The Phaser official template's package.json (per the v1.3.2 tarball's React TS scaffolding fragments) lists older Vite versions; the npm registry's current `vite@latest` is 8.0.11. We took latest within the `^8` range, matching RESEARCH.md. - **ESLint version installed: 9.x (^9.39.4 resolved).** ESLint 9 uses the **flat config** format (`eslint.config.js`), NOT the legacy `.eslintrc.cjs`. **Plan 02 must write `eslint.config.js`, not `.eslintrc.*`.** No legacy ESLint config was written by this plan. - **No `public/` directory created yet.** The official Phaser template ships a `public/assets/` for the demo game's images. Phase 1 has no game assets, so I omitted `public/` entirely. Plan 02+ may create it if needed; nothing in the current scaffold references it. - **No `eslint.config.js` created here.** Plan 02 owns the lint config. The `lint` script will fail until Plan 02 lands — by design (the script key exists so Plan 02 doesn't re-edit package.json). - **`scripts/validate-assets.mjs` does not exist yet.** Plan 05 owns it. The `validate:assets` script will fail until Plan 05 lands — by design. ## Files Created (28 source/config files + 11 .gitkeep markers) - `package.json`, `package-lock.json`, `.gitignore` — npm + ignore rules - `index.html` — Vite HTML entry - `vite.config.ts` — Vite config (React plugin) - `tsconfig.json`, `tsconfig.app.json`, `tsconfig.node.json` — strict TS, referenced projects - `src/main.tsx`, `src/App.tsx`, `src/PhaserGame.tsx`, `src/vite-env.d.ts` — React entry + Phaser bridge - `src/game/main.ts`, `src/game/scenes/Boot.ts` — minimal Phaser config + placeholder scene - `src/{sim,render,ui,save,content,audio,store}/.gitkeep` — 7 architectural-firewall markers - `content/.gitkeep`, `content/dialogue/.gitkeep`, `content/seasons/.gitkeep` — repo-root authored-content tree - `assets/.gitkeep` — repo-root AI-asset tree - `vitest.config.ts` — Vitest happy-dom env - `playwright.config.ts` — Playwright config (no specs) - `src/__sentinel__.test.ts` — sentinel test ## Deviations from Plan ### Auto-fixed Issues **1. [Rule 3 - Blocking] Used hand-built scaffold instead of `npm create @phaserjs/game@latest`** - **Found during:** Task 1 (Step 1 of the action block) - **Issue:** The Phaser official scaffolder `@phaserjs/create-game@1.3.2` is interactive-only — the `--template react-ts --yes` flags documented in the plan are silently ignored, and piping arrow-key sequences into stdin advances past the first prompt but the scaffolder hangs at "Select Template" because subsequent prompts depend on stdin staying open. - **Fix:** Took the documented fallback path in plan Step 1 ("If the official scaffolder cannot run non-interactively, fall back to manually constructing the equivalent file tree per RESEARCH.md"). Built the equivalent file tree by hand (index.html, vite.config.ts, tsconfig*, src/main.tsx, src/App.tsx, src/PhaserGame.tsx, src/game/main.ts, src/game/scenes/Boot.ts) using the published create-game tarball's React TS template fragment as the source-of-truth shape, with current `vite@latest` and `typescript@latest` resolved versions matching RESEARCH.md. - **Files modified:** All Task 1 files. - **Verification:** `npm run build` produces `dist/`; `npm test` runs the sentinel test (1 pass, 593ms). - **Committed in:** `df7d687` (Task 1 commit). **2. [Rule 2 - Missing Critical] `build` script wraps `tsc -b` before `vite build`** - **Found during:** Task 1 (Step 6, package.json scripts) - **Issue:** The plan's Step 6 specifies `"build": "vite build"`. But Vite's default transpile is loose — it does NOT enforce TypeScript strict-mode errors at build time. CLAUDE.md "Code Style" mandates TypeScript strict; the `tsc -b` step is the only thing that actually enforces it on every build. - **Fix:** Set `"build": "tsc -b && vite build"` so the strict-TS gate runs first and blocks the bundler if types fail. - **Files modified:** package.json (scripts.build). - **Verification:** `npm run build` runs both steps and exits 0. - **Committed in:** `df7d687` (Task 1 commit). **3. [Rule 2 - Missing Critical] Added `*.tsbuildinfo` to .gitignore** - **Found during:** Task 1 (Step 8, .gitignore verification — discovered when `git status --short` showed two untracked `tsconfig.*.tsbuildinfo` files after running `tsc -b`) - **Issue:** TS 6's `tsc -b` emits incremental-build cache files at the project root. The plan's Step 8 lists `node_modules/`, `dist/`, `coverage/` but omits `*.tsbuildinfo`. Leaving them untracked-but-uncommitted is fine, but a future `git add -A` (which the plan correctly forbids in Step 9) would commit them as noise. - **Fix:** Added `*.tsbuildinfo` to .gitignore alongside `dist/`. - **Files modified:** .gitignore. - **Verification:** `git status --short` no longer shows the two `.tsbuildinfo` files after the change. - **Committed in:** `df7d687` (Task 1 commit). --- **Total deviations:** 3 auto-fixed (1 blocking, 2 missing critical) **Impact on plan:** All three deviations are mechanical / safety-net additions explicitly authorized by the plan's own fallback language or by CLAUDE.md "Code Style" / RESEARCH.md "CI Pitfalls". No scope creep, no architectural change. Wave-2 plans are unaffected. ## Issues Encountered - **`npm create @phaserjs/game@latest` is interactive-only.** Documented as a deviation above. Time cost: ~1 min for two probe attempts before falling back; the fallback path itself was straightforward. - **No other issues.** Sentinel test passed first try; `npm run build` passed first try. ## Authentication Gates None — Phase 1 scaffolds tooling only; no external auth needed. ## Threat Flags None — Phase 1 is build/dev tooling only. The two threat-model entries identified for Phase 1 (save tampering, malformed Base64 import DoS) are owned by Plan 03. The `npm install` supply-chain consideration is mitigated by `package-lock.json` being committed (verified by `test -f package-lock.json` — passes). ## Known Stubs - **`compile:ink` script is a no-op echo + `exit 0`.** Per CONTEXT D-08 / RESEARCH § "Pattern 4 — Ink files in Phase 1": Phase 2 will replace this with `inklecate -o src/content/compiled-ink/ content/dialogue/*.ink` once authored Ink files exist. The script key exists in Phase 1 only so Wave-2 plans don't have to re-edit package.json. - **`scripts/validate-assets.mjs` does not exist yet.** Plan 05 (asset provenance) owns it. The `validate:assets` script will fail until Plan 05 lands — by design. - **`eslint.config.js` does not exist yet.** Plan 02 (firewall + lint) owns it. The `lint` script will fail until Plan 02 lands — by design. - **`tests/e2e/` directory and the first Playwright spec do not exist yet.** Phase 2 PIPE-07 owns the first e2e smoke spec. Playwright config is wired so the spec can land without re-editing config. - **`src/__sentinel__.test.ts` is a sentinel only.** It exists to prove the runner works and SHOULD be deleted when real tests exist (Plan 03 onward). Documented in the file's header comment. These are all *intentional* stubs per the plan's "pre-declared scripts" pattern — they exist to avoid Wave-2 plans colliding on package.json. They are tracked here so the verifier doesn't flag them as omissions. ## Next Plan Readiness - **Plan 02 (firewall + lint):** Ready. Has clean `src/{sim,render,ui,save,content,audio,store}/` targets to lint against. ESLint 9 + `eslint-plugin-boundaries@^6.0.2` already installed; Plan 02 only writes `eslint.config.js` and any sentinel test files. **Plan 02 must use ESLint 9 flat-config format (`eslint.config.js`), not legacy `.eslintrc.*`.** - **Plan 03 (save layer):** Ready. `idb`, `lz-string`, `zod`, `crc-32`, `vitest`, `happy-dom`, `fake-indexeddb` all installed. The `vitest.config.ts` `include` glob will auto-discover `src/save/**/*.test.ts` files Plan 03 drops in. Sentinel test confirms `globalThis.window` exists in the test env. - **Plan 04 (content pipeline):** Ready. `gray-matter`, `yaml`, `zod`, `inkjs` all installed. `src/content/` and `content/` trees exist; Plan 04 fills `src/content/schemas/` and `src/content/loader.ts`. - **Plan 05 (asset provenance):** Ready. `assets/` tree exists; `scripts/validate-assets.mjs` is the gap (Plan 05 owns it). The `validate:assets` script key is pre-declared. - **Plan 06 (doctrine docs):** Ready — pure markdown plan, no code dependencies. - **Plan 07 (CI workflow):** Ready. All script keys pre-declared so Plan 07 only writes `.github/workflows/ci.yml` (or equivalent) that calls `npm ci && npm run ci`. No blockers; Wave 2 can execute in parallel as planned. ## Self-Check - [x] `package.json` exists at repo root and contains 15+ Phase-1 deps — verified. - [x] All 7 firewall directories exist with .gitkeep — verified. - [x] Repo-root /content/ + /assets/ exist — verified. - [x] All 9 pre-declared scripts present in package.json — verified. - [x] `npm run build` exits 0 — verified. - [x] `tsconfig.json` contains `"strict": true` — verified. - [x] `vitest.config.ts` exists with happy-dom + passWithNoTests:false — verified. - [x] `playwright.config.ts` exists with testDir 'tests/e2e' — verified. - [x] `src/__sentinel__.test.ts` passes — verified (1 pass, 593ms). - [x] `npx playwright --version` = Version 1.59.1 — verified. - [x] Task 1 commit exists: `df7d687` — verified in `git log`. - [x] Task 2 commit exists: `7b2982b` — verified in `git log`. **## Self-Check: PASSED** --- *Phase: 01-foundations-and-doctrine* *Plan: 01 of 7* *Completed: 2026-05-09*