docs: complete Epic 1 review, create story 2.1 best score tracking

Epic 1 code review passed with 0 patches needed — all 6 stories
marked done. 6 deferred items logged for Epics 2-5. Created story
2-1-best-score-tracking as ready-for-dev to begin Epic 2.
This commit is contained in:
2026-04-13 22:28:15 +07:00
parent 45de4b46cb
commit 4e452dfe19
6 changed files with 128 additions and 13 deletions
@@ -1,6 +1,6 @@
# Story 1.1: Project Scaffold & Dev Environment
Status: review
Status: done
## Story
@@ -1,6 +1,6 @@
# Story 1.2: Game Logic Module
Status: review
Status: done
## Story
@@ -1,6 +1,6 @@
# Story 1.3: Game Board & Tile Rendering
Status: review
Status: done
## Story
@@ -0,0 +1,105 @@
# Story 2.1: Best Score Tracking
Status: ready-for-dev
## Story
As a player,
I want to see my best score alongside my current score,
so that I have a motivational anchor and can track my all-time progress.
## Acceptance Criteria
1. When the current score exceeds the best score after a merge, the best score display updates immediately to match the current score
2. When the player starts a new game, the best score remains unchanged (it persists across games within the session)
## Tasks / Subtasks
- [ ] Task 1: Add bestScore state to App.svelte (AC: #1, #2)
- [ ] Add `let bestScore = $state(0);` as a new reactive state variable
- [ ] Add `$effect` that watches `gameState.score` — when it exceeds `bestScore`, update `bestScore`
- [ ] Replace hardcoded `bestScore={0}` with `bestScore={bestScore}` in the ScoreBoard prop
- [ ] Task 2: Preserve bestScore across New Game (AC: #2)
- [ ] Verify that `handleNewGame()` resets `gameState` via `initGame()` but does NOT reset `bestScore`
- [ ] `bestScore` is a separate `$state` variable, not part of `gameState` — it survives `initGame()` by design
- [ ] Task 3: Verify existing tests still pass
- [ ] Run `npx vitest run` — all 38 existing tests must pass (no game-logic.js changes)
## Dev Notes
### Architecture Compliance
- **State ownership:** `bestScore` lives in `App.svelte` as a `$state` rune — NOT inside the game state object
- **Props-down pattern:** `App.svelte` passes `bestScore` to `ScoreBoard` via prop (already wired, just hardcoded to 0)
- **No game-logic.js changes:** Best score is a UI/session concern, not game logic. The game-logic module stays pure.
- **No storage.js yet:** localStorage persistence is Story 2.2. This story is in-memory only — best score resets on page refresh.
### Critical Anti-Patterns (DO NOT)
- DO NOT add `bestScore` to the `gameState` object — it is NOT part of the canonical game state shape `{ grid, score, won, keepPlaying }`
- DO NOT create a `storage.js` module yet — that is Story 2.2
- DO NOT use `$derived` for bestScore — it needs to be a `$state` that persists across game resets (derived would reset when gameState resets)
- DO NOT use Svelte stores (`writable`, `readable`) — this project uses Svelte 5 runes only
### Implementation Pattern
The correct pattern uses `$effect` to watch score changes:
```javascript
let bestScore = $state(0);
$effect(() => {
if (gameState.score > bestScore) {
bestScore = gameState.score;
}
});
```
This works because:
- `$effect` runs whenever `gameState.score` changes (reactive dependency)
- `bestScore` is a separate `$state`, so `handleNewGame()` resetting `gameState = initGame()` (score=0) does NOT reset bestScore
- When a new game starts, `gameState.score` becomes 0, which is NOT > bestScore, so bestScore stays
### ScoreBoard Component
`ScoreBoard.svelte` already accepts `score` and `bestScore` props — no changes needed to the component:
```svelte
let { score = 0, bestScore = 0 } = $props();
```
The only change is in `App.svelte` line 58: replace `bestScore={0}` with `bestScore={bestScore}`.
### Previous Story Intelligence
From Epic 1 implementation:
- `App.svelte` owns all state via `$state` rune — `gameState` is the single game state object
- `ScoreBoard` component at `src/components/ScoreBoard.svelte` — presentational, receives `score` and `bestScore` props
- `handleNewGame()` calls `gameState = initGame()` which returns `{ grid, score: 0, won: false, keepPlaying: false }`
- `getDirectionFromKey` is the export name from `input-handler.js` (not `mapKeyToDirection`)
- `overlayType` is used instead of separate `gameOver`/`won` checks in the template
- Existing test suite: 38 tests in `src/lib/game-logic.test.js` — all game logic, no UI tests
### Scope Boundary
This story is intentionally minimal:
- **In scope:** In-memory best score tracking that survives New Game within a session
- **Out of scope:** localStorage persistence (Story 2.2), game state persistence (Story 2.2), keep-playing mode changes (Story 2.3)
### References
- [Source: _bmad-output/planning-artifacts/epics.md#Story 2.1]
- [Source: _bmad-output/planning-artifacts/architecture.md#Frontend Architecture - State Management]
- [Source: _bmad-output/planning-artifacts/architecture.md#Implementation Patterns - Communication Patterns]
## Dev Agent Record
### Agent Model Used
### Debug Log References
### Completion Notes List
### File List
@@ -0,0 +1,10 @@
# Deferred Work
## Deferred from: code review of Epic 1 (2026-04-13)
- **Best score always 0, never tracked** — UI displays "BEST: 0" that never updates. Covered by Story 2-1 (Best Score Tracking).
- **Tile keyed by position, not identity** — `Grid.svelte` keys tiles by `r-c` grid position instead of unique tile ID. Will break slide/merge animations. Address in Epic 3 (animations).
- **No animation lock for rapid key input** — No debounce or move-in-progress guard in `handleKeydown`. Synchronous now but will need a lock when animations are added in Epic 3.
- **Grid 500px overflow on narrow viewports** — `Grid.svelte` hardcodes `containerSize = 500`. Overflows on mobile. Covered by Story 4-3 (Responsive Layout).
- **Tile font overflow for very large values** — 3-tier font sizing (55/45/35px) exists but values beyond 8192 (6+ digits) may overflow the 106px cell at 35px. Epic 5 polish.
- **Overlay has no keyboard focus trap** — `GameMessage.svelte` does not trap focus or auto-focus the action button. Accessibility improvement for post-MVP.
@@ -35,7 +35,7 @@
# - Dev moves story to 'review', then runs code-review (fresh context, different LLM recommended)
generated: 2026-04-13
last_updated: 2026-04-13T01:00:00
last_updated: 2026-04-13T22:21:00
project: try-bmad
project_key: NOKEY
tracking_system: file-system
@@ -43,18 +43,18 @@ story_location: _bmad-output/implementation-artifacts
development_status:
# Epic 1: Play a Complete Game (Desktop)
epic-1: in-progress
1-1-project-scaffold-and-dev-environment: review
1-2-game-logic-module: review
1-3-game-board-and-tile-rendering: review
1-4-game-header-score-display-and-new-game-button: review
1-5-keyboard-input-and-interactive-gameplay: review
1-6-win-and-game-over-overlays: review
epic-1: done
1-1-project-scaffold-and-dev-environment: done
1-2-game-logic-module: done
1-3-game-board-and-tile-rendering: done
1-4-game-header-score-display-and-new-game-button: done
1-5-keyboard-input-and-interactive-gameplay: done
1-6-win-and-game-over-overlays: done
epic-1-retrospective: optional
# Epic 2: Save Progress & Keep Playing
epic-2: backlog
2-1-best-score-tracking: backlog
epic-2: in-progress
2-1-best-score-tracking: ready-for-dev
2-2-game-state-persistence: backlog
2-3-keep-playing-mode: backlog
epic-2-retrospective: optional