Files
josh 1e99356b27 docs(01-01): complete scaffold and test infra plan
Wave 1 of Phase 1 complete. Phaser 4 + React 19 + Vite 8 + TypeScript 6
scaffold builds (npm run build green); 15 Phase-1 deps installed at
locked versions; 7 architectural-firewall directories ready under src/;
repo-root /content/ + /assets/ trees ready; Vitest (happy-dom) +
Playwright wired with passing sentinel; package.json scripts
pre-declared for the entire Phase 1 plan-set so Wave 2 can run in
parallel without colliding on package.json.

3 deviations auto-fixed (1 blocking, 2 missing-critical):
1. Built scaffold by hand because @phaserjs/create-game v1.3.2 is
   interactive-only — plan's documented fallback path was used.
2. build script wraps tsc -b before vite build so strict-TS gates
   every build (CLAUDE.md Code Style invariant).
3. Added *.tsbuildinfo to .gitignore (TS 6 incremental cache files).

Wave 2 readiness: Plan 02 must use ESLint 9 flat-config format
(eslint.config.js); legacy .eslintrc.* not supported. fake-indexeddb
pre-installed for Plan 03 IDB tests. inkjs + inklecate installed but
no .ink files compiled (Phase 2 PIPE-02 owns that).
2026-05-08 23:23:15 -04:00

22 KiB
Raw Permalink Blame History


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 (0206) 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 0206 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 directoriesdf7d687 (chore)
  2. Task 2: Configure Vitest (happy-dom) + Playwright + sentinel test7b2982b (chore)

Plan metadata: (this commit)docs(01-01): complete scaffold and test infra plan

Final package.json scripts block (load-bearing for Wave 2)

{
  "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

  • package.json exists at repo root and contains 15+ Phase-1 deps — verified.
  • All 7 firewall directories exist with .gitkeep — verified.
  • Repo-root /content/ + /assets/ exist — verified.
  • All 9 pre-declared scripts present in package.json — verified.
  • npm run build exits 0 — verified.
  • tsconfig.json contains "strict": true — verified.
  • vitest.config.ts exists with happy-dom + passWithNoTests:false — verified.
  • playwright.config.ts exists with testDir 'tests/e2e' — verified.
  • src/__sentinel__.test.ts passes — verified (1 pass, 593ms).
  • npx playwright --version = Version 1.59.1 — verified.
  • Task 1 commit exists: df7d687 — verified in git log.
  • 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