port-game
Port one game from the design file into Nuxt. Use when the user asks to port, build, or finish a game listed under "Porting status" in CLAUDE.md (pairs, odd, serial7, words, category, backward, digits
SKILL.md.claude/skills/port-game/SKILL.md
GitHubPort a game
This skill fills in steps 1, 3 and 4 of the feature skill for a game port. Still run the full feature loop: plan, approval, red, green, check, UI check, review.
Find the pieces
<id>is the game id, for examplepairs.- Read
app/data/games/<id>.json. Itscomponentvalue is<Component>, for exampleMemoryPairs. - Read
RUN.<id>indocs/design.html(grep -n "RUN.<id>=" docs/design.html). Also read every helper it calls (h,pick,shuffle,listGame,stopAll, timers) so you know what each one does. - Read the game's row in
docs/functional-requirements.md§5. The spec tests that behaviour. - Read one finished game as a model:
app/components/games/MentalMath.vueandtest/nuxt/components/games/MentalMath.spec.ts.
Rules for the port
- Copy the logic of
RUN.<id>. Same rules, same numbers, same timings, same scoring. Do not redesign it. - Contract:
defineProps<{ game: GameDef }>()anddefineEmits<{ finish: [score: number] }>(). Nothing else. No progress, no routing. - Read copy from
props.game(durationSec,unitand so on). Never hard-code game copy that lives in the JSON. - Timers:
useCountdownfor the game clock,useTimeouts().laterfor sequencing. No rawsetInterval/setTimeoutunless cleared inonBeforeUnmount. - Random: use
pick,riand the other helpers inapp/utils/random.ts. Add a helper there (with a spec) if the design uses one that is missing. - Encouragement:
useCheer().cheer()on the same milestones as the design. Mistake copy stays kind: "It was X. Carry on from there." - Markup: reuse the classes from
app/assets/css/components.cssthat design.html uses (.bar,.hud,.big,.field,.board,.cell,.react,.breath,.card,.go,.ghost,.row,.note, and others). Every other style is Tailwind utilities that map to the tokens (CLAUDE.md rule 7). The design'smax-widthqueries map tomax-xl(1000px),max-lg(760px),max-md(640px),max-sm(560px) andmax-xs(380px). No raw colours, no new rules incomponents.cssunless the class is shared by two or more games. Prettier sorts the classes (prettier-plugin-tailwindcss). - Tests find elements by
data-testid, ARIA attributes (aria-pressed,aria-current) ordata-state, never by a styling class. - The design's game class
gridis calledboardin components.css, becausegridis a Tailwind utility. Writeclass="board"whereRUN.<id>writesgrid. - Accessibility: real
<button>elements,aria-labelon icon-only buttons, Enter/Space works, inputs havefont-sizeof 16px or more. - WordSprint and CategoryChallenge share
listGame(). Port it once asapp/composables/useListGame.tswith its own spectest/nuxt/composables/useListGame.spec.ts. - Stay under about 150 lines. Move logic into a composable if it grows.
- No code comments.
Files
| File | Action |
|---|---|
app/components/games/<Component>.vue |
New. The port. |
test/nuxt/components/games/<Component>.spec.ts |
New. Written first by the test-writer agent. See the write-unit-tests skill. |
app/components/games/registry.ts |
Add one line: <Component>: defineAsyncComponent(() => import('./<Component>.vue')), |
app/composables/<useX>.ts + spec |
Only if logic is split out. |
CLAUDE.md "Porting status" |
Move the game from "To port" to "Done". Ask the user before editing CLAUDE.md. |
The registry key must equal component in the JSON. If it does not, the game falls back to ComingSoon.vue.
What the spec must cover
- The score passed to
finishfor a known play, using fake timers and the real game JSON. - Each rule in the §5 row: timing, level-ups, the end condition, and what a mistake does.
- Lower-is-better games (
pairs,schulte,reaction): the score goes the right way. - Keyboard play: Enter or Space triggers the main action.
- No timer still runs after unmount (
vi.getTimerCount()is 0 afterwrapper.unmount()).
Done when
- The spec went red before the port and is green after it.
npm run lint,npm run typecheckandnpm testpass.ui-verifiercompared/games/<id>withdocs/design.html#<id>in the ready, playing and result states and found no high or medium issues.- In the app, the tile, the ready demo, the result band and the sidebar highlight all work.