Math GDD Documentation Site Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Build and deploy a searchable static site for the four product-game math/GDD packages, including interactive presentation flows and verified links to authoritative documents.
Architecture: Rspress 2 renders selected Markdown from the repository root plus thin MDX catalogue/flow pages. Plain JavaScript manifests hold navigation metadata, React components render the catalogue and flows, and Rspress performs the primary dead-link check. GitLab CI builds an immutable nginx image and deploys it to the VPS through the existing label-discovered Caddy infrastructure.
Tech Stack: Node.js 24, pnpm 11.23.0, Rspress 2.0.19, React 19.2.8, TypeScript 7.0.2, Node test runner, nginx unprivileged, Docker Compose, GitLab CI.
Spec: docs/superpowers/specs/2026-08-23-math-gdd-site-design.md
Global Constraints
- The catalogue contains exactly four product games: Classic Fruits 3×3, Magic Vault 5×3,
Book of Odin v2, and Vault Breakers: Super Score.
hold-and-win-referenceappears only under reusable mechanics. - Existing Markdown under
games/,docs/andtemplates/remains authoritative; site files may link to it but must not duplicate its math tables or evidence prose. - All dependencies are pinned by
pnpm-lock.yaml; CI usespnpm install --frozen-lockfile. - The site has no CDN, remote font, remote icon or runtime API dependency.
- Generated output is
doc_build/and is never committed. - The container publishes no host port, mounts no host path, joins external network
web, and exposes only port8080to that network. - Deployment uses the immutable
$CI_COMMIT_SHORT_SHAimage tag, nevermain, as the running identity. chow.hostis exactlymath-gdd; no hand-written Caddy block is added, and the derived production-host policy supplies basic auth.- No file under
pass/, ignored raw art/video output, or.git/enters the image. - Existing game math, evidence and configuration identity files are not modified except for deliberate navigation links.
File Structure
Task 1: Establish the Rspress build and route boundary
Files:
- Create:
package.json - Create:
pnpm-lock.yaml - Create:
tsconfig.json - Create:
rspress.config.ts - Create:
index.mdx - Modify:
.gitignore - Test:
site/tests/catalog.test.mjs
Interfaces:
-
Produces:
pnpm docs:dev,pnpm docs:build,pnpm typecheck,pnpm test; rendered output indoc_build/. -
Produces: Rspress routes for
index.mdx,flows/**/*.mdx,games/**/*.md, selecteddocs/**/*.md, andtemplates/**/*.md. -
Consumes: the existing Markdown tree without moving or copying it.
-
Step 1: Write the failing build-contract test
Create site/tests/catalog.test.mjs with an initial filesystem contract:
- Step 2: Run the test and verify the missing scaffold fails
Run: node --test site/tests/catalog.test.mjs
Expected: FAIL because package.json does not exist.
- Step 3: Add the minimal pinned toolchain
Create package.json with:
Create strict tsconfig.json using moduleResolution: "bundler", jsx: "react-jsx",
noEmit: true, strict: true, and include index.mdx, flows, site, and
rspress.config.ts.
Create rspress.config.ts with repository root .; output doc_build; route includes limited
to index.mdx, flows/**/*.mdx, games/**/*.md, docs/**/*.md, and
templates/**/*.md; route excludes site/**, node_modules/**,
doc_build/**, output/**, draft-v1/**, and ggd ігри/**. Enable both dead-link and anchor
checking:
Create a temporary index.mdx containing an H1 and links to the four existing game READMEs.
Append node_modules/, doc_build/, and .rspress-cache/ to .gitignore.
Run corepack enable && pnpm install once to produce pnpm-lock.yaml.
- Step 4: Verify the toolchain and route scan
Run:
Expected: all commands exit 0 and doc_build/index.html exists.
- Step 5: Commit the build foundation
Task 2: Encode the four-game catalogue and document contract
Files:
- Create:
site/catalog.mjs - Modify:
site/tests/catalog.test.mjs - Modify:
games/README.md
Interfaces:
-
Produces:
games: readonly GameRecord[]andmechanics: readonly MechanicRecord[]fromsite/catalog.mjs. -
GameRecordshape:{ id, title, lifecycle, rtp, grid, evaluation, summary, documents, flowPath }. -
documentsentries:{ label, sourcePath, routePath, required }. -
Consumes: exact source paths already present under the four game folders.
-
Step 1: Expand the failing catalogue test
Add assertions that import games and mechanics, require exactly four unique game IDs, forbid
hold-and-win-reference in games, require every sourcePath to exist, and require each game to
have README, GDD, Math Spec and Flow links:
- Step 2: Run the focused test and verify the missing manifest fails
Run: node --test site/tests/catalog.test.mjs
Expected: FAIL with ERR_MODULE_NOT_FOUND for site/catalog.mjs.
- Step 3: Implement the exact catalogue
Create site/catalog.mjs with the four current games and exact document paths:
- Classic Fruits: README, GDD, Math Spec, changelog, SEVENS presentation.
- Magic Vault: README, GDD, Math Spec, evidence manifest, changelog, presentation flow, frontend API.
- Book of Odin: README, GDD, v2 Math Spec, v2 evidence manifest, math change request.
- Vault Breakers: README, GDD, Math Spec, v0.2 evidence manifest, changelog.
Use source routes such as /games/classic-fruits-3x3/README and flow routes such as
/flows/classic-fruits-3x3. Represent absent changelogs or dedicated flow documents by omitting
that document entry; never synthesize a path.
Update games/README.md so its product-game table lists all four games and moves
hold-and-win-reference into a separate Reusable mechanics table.
- Step 4: Prove every manifest entry resolves
Run:
Expected: tests pass; Rspress reports no dead document link.
- Step 5: Commit the catalogue contract
Task 3: Build the control-room home and documentation navigation
Files:
- Create:
site/components/GameCatalog.tsx - Create:
site/components/LineIcon.tsx - Create:
site/theme/index.tsx - Create:
site/theme/index.css - Modify:
index.mdx - Modify:
rspress.config.ts - Test:
site/tests/catalog.test.mjs
Interfaces:
-
GameCatalog(): React.JSX.Elementconsumesgamesandmechanicsfromsite/catalog.mjs. -
LineIcon({ name, title }: { name: IconName; title?: string }): React.JSX.Elementrenders local SVG only. -
Produces stable
data-game-idattributes for catalogue integration tests and visual QA. -
Step 1: Add failing semantic-home assertions
Extend the catalogue test to read site/components/GameCatalog.tsx and assert that the source
contains <section, a game-card heading, data-game-id, and a separate reusable-mechanics
section. Add a negative assertion that no <img source begins with http.
- Step 2: Run the test and verify missing components fail
Run: node --test site/tests/catalog.test.mjs
Expected: FAIL because GameCatalog.tsx is missing.
- Step 3: Implement the home and restrained custom theme
Create the components and use semantic article, h2, dl, and nav elements. Each game card
shows lifecycle, grid, evaluation, RTP wording exactly as recorded in catalog.mjs, plus buttons
for the game overview and flow. Render document links as a compact list rather than repeating
document prose.
Create site/theme/index.tsx using the Rspress v2 re-export contract:
Create CSS variables for graphite surfaces, off-white text and four muted metallic accents.
Include mobile breakpoints, visible :focus-visible, and a prefers-reduced-motion: reduce block.
Do not eject built-in Rspress components.
Replace the temporary home with:
Configure globalStyles or the conventional theme path so the site uses site/theme/index.css.
- Step 4: Verify type safety, build and rendered catalogue markers
Run:
Expected: all commands pass and all four titles appear in generated route chunks or HTML.
- Step 5: Perform browser QA before committing
Run pnpm docs:dev, open the printed URL, and inspect at desktop 1440×900 and mobile
390×844. Verify keyboard focus order, no horizontal scroll, readable lifecycle labels and a
separate reusable-mechanics section. Capture one desktop and one mobile screenshot outside the
repository or in ignored test output.
- Step 6: Commit the catalogue UI
Task 4: Add four distinct accessible presentation flows
Files:
- Create:
site/flows.mjs - Create:
site/components/GameFlow.tsx - Create:
flows/classic-fruits-3x3.mdx - Create:
flows/magic-vault-5x3.mdx - Create:
flows/book-of-odin.mdx - Create:
flows/vault-breakers-super-score.mdx - Modify:
site/theme/index.css - Test:
site/tests/flows.test.mjs
Interfaces:
-
Produces:
flowByGameId: Readonly<Record<GameId, FlowGraph>>. -
FlowGraphshape:{ title, sourceLinks, nodes, edges }. -
Node shape:
{ id, label, detail, kind, documentRoute? }wherekindisphase | decision | presentation | terminal. -
Edge shape:
{ from, to, label? }. -
GameFlow({ gameId }: { gameId: GameId }): React.JSX.Elementrenders one graph as an ordered semantic flow. -
Step 1: Write graph-contract tests first
Create site/tests/flows.test.mjs asserting:
Add branch-specific assertions: Classic has sevens-jackpot; Magic has hold-and-win-loop;
Book has both expanding-wild-loop and nested-hold-and-win; Vault has tumble-loop,
multiplier-charges, free-spins, and super-scatter.
- Step 2: Run tests and verify the missing graph fails
Run: node --test site/tests/flows.test.mjs
Expected: FAIL with ERR_MODULE_NOT_FOUND for site/flows.mjs.
- Step 3: Implement graph data from approved documents
Encode the four diagrams exactly as specified in the design. Each unique node links to an existing game presentation document, GDD, or reusable mechanic template. Do not invent timing, audio, payouts or backend fields not stated by those sources.
- Step 4: Implement the semantic flow renderer and pages
Render the graph inside a labelled <section> with an ordered list of nodes and explicit edge
labels. A node with documentRoute is an <a>; a node without one is a non-interactive <div>.
Inline monochrome icons communicate node kind, but visible text remains sufficient.
Each flows/*.mdx page uses pageType: custom, imports GameFlow, and passes one literal game ID.
CSS gives each mechanic a distinct schematic shape: straight paylines, nested Hold & Win return,
expanding-wild side branch, and looping cascade rail. Under 720px, all graphs become a single
vertical reading order without crossed connectors.
- Step 5: Verify graph integrity, routes and reduced motion
Run:
Expected: all checks pass.
- Step 6: Perform visual and keyboard QA
Open all four routes at 1440×900 and 390×844. Tab through every linked node. Confirm each
game visibly differs, each branch returns to the correct owning sequence, no connector overlays
text, and reduced-motion mode removes tracing animation.
- Step 7: Commit the flows
Task 5: Add independent built-site and container gates
Files:
- Create:
scripts/check-built-site.mjs - Create:
tests/check-built-site.test.mjs - Create:
docker/nginx.conf - Create:
Dockerfile - Create:
.dockerignore - Modify:
package.json
Interfaces:
-
checkBuiltSite(root = 'doc_build'): string[]returns validation errors and has no side effects. -
CLI execution exits 1 and prints each error when the returned array is non-empty.
-
Container serves
/healthzand all generated routes on internal port8080. -
Step 1: Write red tests for the independent checker
Use mkdtempSync to create one valid and one broken miniature build. Assert that a missing local
route, absent game flow route, absolute /Users/ path and external asset URL each produce a
specific error. Assert that hash links and https://math-gdd.chowchowhome.duckdns.org are allowed.
- Step 2: Run the checker test and verify it fails
Run: node --test tests/check-built-site.test.mjs
Expected: FAIL because scripts/check-built-site.mjs is missing.
- Step 3: Implement the checker
Walk doc_build/**/*.html, extract href and src values, resolve local clean URLs to either
path.html or path/index.html, require the home and four flow routes, reject file:, /Users/,
/private/, pass/, and remote asset origins, and return sorted unique errors. Keep this separate
from Rspress's build-time dead-link check so a generated-route regression cannot self-certify.
- Step 4: Verify the checker red/green cases and full build
Run:
Expected: fixture tests pass and the real build reports zero errors.
Update package.json so the default test command now includes the independent checker suite:
- Step 5: Add the static runtime image
Use a Node 24 builder stage with Corepack and frozen pnpm install, then copy only doc_build/ into
nginxinc/nginx-unprivileged:alpine. Configure nginx to listen on 8080, serve static files,
return 200 ok at /healthz, and fall back from clean routes to $uri.html and $uri/index.html
without masking genuine 404s.
.dockerignore must exclude .git, node_modules, doc_build, output, draft-v1, ggd ігри,
SLOT_ASSETS, .playwright-mcp, .DS_Store, and ignored local caches. It must not exclude
games, docs, templates, flows, site, or the build configuration.
- Step 6: Build and probe the real image
Run:
Expected: health returns ok; home and flow return 200; the container stops cleanly.
- Step 7: Commit validation and image files
Task 6: Add label-discovered VPS deployment and GitLab CI
Files:
- Create:
deploy/docker-compose.yml - Create:
deploy/README.md - Create:
.gitlab-ci.yml - Modify:
README.md
Interfaces:
-
Compose consumes
MATH_GDD_IMAGE,MATH_GDD_TAG, andCONTAINER_NAMEfrom CI. -
Fixed compose project:
math-gdd; fixed host label:math-gdd; fixed internal port:8080. -
CI stages:
check,image,deploy. -
Step 1: Verify runner eligibility before writing deploy automation
Run with the authenticated GitLab CLI:
Expected: at least one active runner eligible for this project. Record whether it requires
tags: [tools]. If the result is empty, stop this task and assign a runner before adding CI;
otherwise every job would remain pending without an actionable error.
- Step 2: Write the compose file and validate its resolved contract
Create one service using ${MATH_GDD_IMAGE}:${MATH_GDD_TAG}, explicit
${CONTAINER_NAME}, restart: unless-stopped, external web, expose: ["8080"], and the exact
five chow.* labels from the spec. Add a container healthcheck against /healthz.
Run:
Expected: one service, no published ports, external web, host label math-gdd, image tag
deadbeef, and container name math-gdd-1.
- Step 3: Add the CI check and immutable image stages
Use node:24-bookworm-slim for Check and docker:28-cli for image/deploy. Check runs
Corepack, frozen install and pnpm check; it stores doc_build/ for one week. Image logs into the
GitLab registry, builds both $CI_COMMIT_SHORT_SHA and main, pushes both, and runs only on
default-branch changes that can affect the site.
Preserve the repository's deployment rule in comments: the mutable main tag is convenience
only; deploy consumes $CI_COMMIT_SHORT_SHA.
- Step 4: Add the main-only deploy gate
Export:
Then login, pull, up -d --remove-orphans, inspect .Image for math-gdd-1, and poll from an
ephemeral Alpine container on web until both /healthz and /flows/book-of-odin answer. Print
the deployed SHA and https://math-gdd.chowchowhome.duckdns.org. Do not edit Caddy or call the
registry generator directly.
- Step 5: Write the operator runbook and rollback command
Document local commands, CI stages, the up-to-60-second vhost delay, first-certificate retry, container/image identity inspection, service logs, and rollback:
The operator copies the immutable short SHA from a previously successful GitLab image job. The registry path is fixed; executable CI and the runbook contain no guessed image identity.
Update root README.md with pnpm docs:dev, pnpm check, the deployed URL and a link to
deploy/README.md.
- Step 6: Validate pipeline syntax and local deploy contract
Run:
Submit .gitlab-ci.yml to GitLab CI Lint using the authenticated project endpoint and require a
valid result before commit.
- Step 7: Commit CI and deployment
Task 7: Final review, push and live verification
Files:
- Modify only files required by confirmed review findings.
- Record live evidence in the merge request or deployment job; do not fabricate a repository transcript.
Interfaces:
-
Consumes all prior tasks.
-
Produces a review-approved branch, green pipeline, immutable deployed SHA and verified protected URL.
-
Step 1: Run the full local gate from a clean dependency state
Run:
Expected: all commands exit 0.
- Step 2: Review source-of-truth and deployment boundaries
Confirm from the diff that:
-
no game math/config/evidence number changed;
-
the catalogue contains four games;
-
every flow links to an authored source;
-
no ignored raw asset directory entered Docker context;
-
compose has no
portsor bind mount; -
deploy uses the SHA tag and fixed project name;
-
no Caddyfile or
vps-infrafile changed. -
Step 3: Request independent code and deployment-safety review
The reviewer must exercise at least these mutations rather than accepting a green suite:
- add
hold-and-win-referenceto the game array; - remove one required flow route;
- insert a dead document link;
- inject
/Users/admin/private-pathinto generated HTML; - change deploy tag from
$CI_COMMIT_SHORT_SHAtomain; - add a host
ports:mapping; - remove
chow.hostor change it frommath-gdd.
Each mutation must fail a test, build, compose assertion or review gate for the claimed property.
- Step 4: Push a feature branch and open an MR
Use a branch such as feat/math-gdd-site. Push only after the independent review returns Yes.
The MR description includes the spec, local gate output, runner eligibility, image/deploy design,
and explicit statement that no game math changed.
- Step 5: Verify MR pipeline before merge
Require Check to pass on the MR. Image and Deploy remain absent because they are default-branch only. Inspect the build artifact and open its home plus all four flow pages.
- Step 6: Merge and verify the main pipeline
After user approval to merge, require Check → Image → Deploy to succeed on one SHA. Verify:
Expected: authentication challenge or authenticated 200 according to the existing production
basic-auth policy, never an unauthenticated public 200. Confirm the running container image matches
the pipeline SHA and the registry dashboard contains the Slot Math & GDD service tile.
- Step 7: Report exact final state
Report branch, commit SHA, pipeline URL/status, running image identity, site URL, auth behaviour, four flow-route checks, and any unresolved blocker. Do not call internal tests certification.