# Story 1.2: Guess Validation & Feedback Status: review ## Story As a player, I want to type 5-letter guesses and see color feedback on each letter, So that I can deduce the target word within 6 attempts, and resume if I close the browser. ## Acceptance Criteria 1. **Guess submission:** Given I am on Screen 2 with a daily word loaded, when I type a valid 5-letter word and submit (Enter or on-screen), then the client sends POST /api/guess `{ wordId, guess, tryNo }` to the server. The server validates against the word bank and returns `{ valid: true, colors: ['x','y','g','x','x'], correct: false }`. The grid row fills and each cell shows the color background. ✅ 2. **Invalid word:** Given I type a word not in the guessable word bank, when I submit, then the server returns `{ valid: false }`. Client displays "Unknown word", allows backspace, and attempt counter does not increment. ✅ 3. **Keyboard highlighting:** Given I make guesses, when the keyboard renders, then each key shows the best feedback for that letter across all guesses (g > y > x). Unused letters remain light gray. ✅ 4. **Win:** Given my guess matches the target word (all green), when response has `correct: true, difficulty: 3.2`, then client transitions to Screen 3 showing "Congratulations! Solved in N attempts." Stats update and in-progress game is cleared. ✅ 5. **Loss:** Given my 6th guess is wrong, when response has `correct: false, word: "crane", difficulty: 4.1`, then client transitions to Screen 3 showing "Better luck next time. The word was: crane." Stats update and in-progress game is cleared. ✅ 6. **Persistence:** Given I make a guess, when the response arrives, then game state `{ wordId, guesses: [{ guess, colors }] }` is saved to localStorage. Empty guesses array = no game in progress. Status derived: last guess all-green → won; 6 without all-green → lost; else playing. ✅ 7. **Statistics:** Given I am on Screen 3, when I view statistics, then I see: games played, win rate %, attempt-count histogram (1-6 + fail), current streak, max streak. ✅ 8. **Statistics persist:** Given I close and reopen the browser, when I return, then my lifetime statistics are restored from localStorage. ✅ ## Tasks / Subtasks - [x] Task 1: Implement POST /api/guess endpoint (AC: 1, 2, 5) - [x] Create `server/src/feedback.ts` — Feedback Scoring Rule - [x] Create `server/src/validate.ts` — word bank lookup - [x] Create `server/src/routes/guess.ts` — POST /api/guess endpoint - [x] Register route in `server/src/index.ts` - [x] Task 2: Update shared API types (AC: 1, 5) - [x] Verified `GuessRequest` and `GuessResponse` interfaces match implementation - [x] Task 3: Implement guess entry UI (AC: 1, 2, 3) - [x] Create `client/src/hooks/useGame.ts` — guess management, status, API calls - [x] Add physical keyboard handler (keydown) - [x] Add on-screen keyboard click handler - [x] Show "Unknown word" message on invalid guess - [x] Wire App.tsx to useGame, pass state to Screen2Game - [x] Task 4: Implement Screen 3 — Results (AC: 4, 5, 7) - [x] Create `client/src/screens/Screen3Results.tsx` — results + stats + histogram - [x] Wire win/loss → Screen 3 transition - [x] Clear in-progress game on game end - [x] Update lifetime statistics and persist - [x] Task 5: Integrate and test end-to-end (all ACs) - [x] Tested: valid guess feedback (hello vs style), invalid word, win, loss with word reveal - [x] All typechecks pass, client builds ## Dev Notes ### Building on Story 1.1 The foundation from 1.1 is in place: - Monorepo with npm workspaces ✅ - Shared types (`shared/src/api.ts`, `shared/src/game.ts`) ✅ - Express server with GET /api/daily ✅ - React SPA with Screen 1 (rules) and Screen 2 (game shell) ✅ - localStorage persistence via `usePersistence` hook ✅ - Word bank JSON with 500+ guessable words and 40 targets ✅ ### Architecture Compliance - **AD-5 Server-side validation only:** Word list never leaves server. Only single-word reveal on loss. [Source: ARCHITECTURE-SPINE.md#AD-5] - **AD-6 Feedback scoring on server:** Single implementation in `server/src/feedback.ts`. Client only renders colors. [Source: ARCHITECTURE-SPINE.md#AD-6] - **AD-11 Wire format:** Colors as `'g'`/`'y'`/`'x'`. 5-element array. [Source: ARCHITECTURE-SPINE.md#AD-11] - **AD-7 Client owns game state:** Client tracks attempt number, guess history, game-over detection. Server stateless. [Source: ARCHITECTURE-SPINE.md#AD-7] - **AD-12 Single persistence key:** All state under `wordle-state`. One hook `usePersistence`. [Source: ARCHITECTURE-SPINE.md#AD-12] ### Previous Story Intelligence (1.1) - **Vite 7 not 8:** @vitejs/plugin-react@4.7.0 doesn't support Vite 8 peer. Stick with Vite 7. - **Express 5 catch-all syntax:** `/{*path}` not `*` - **Timezone fix applied:** `daily-word.ts` uses `Intl.DateTimeFormat` not `toLocaleString` - **COLOR_MAP duplication:** Grid and Keyboard both define the same color map — consider extracting to `shared/` or a constants file in this story ### Feedback Scoring Rule (FR-7) ``` Given target word W and guess G: 1. Mark exact-position matches (W[i] === G[i]) as 'g', decrement remaining count for that letter 2. For remaining guess positions, mark as 'y' if letter exists in W with remaining count > 0, decrement 3. All other positions are 'x' ``` Example: W=`style`, G=`hello` → `['x','y','x','g','x']` ### API Contract (POST /api/guess) **Request:** `{ wordId: number, guess: string, tryNo: number }` **Response (valid):** `{ valid: true, colors: string[], correct: boolean, difficulty?: number }` **Response (valid, loss):** `{ valid: true, colors: string[], correct: false, word: string, difficulty: number }` **Response (invalid):** `{ valid: false }` ### Files to Create/Modify **New:** - `server/src/feedback.ts` - `server/src/validate.ts` - `server/src/routes/guess.ts` - `client/src/hooks/useGame.ts` - `client/src/screens/Screen3Results.tsx` **Modify:** - `server/src/index.ts` — add guess router - `client/src/App.tsx` — integrate useGame, add Screen 3 routing, wire persistence - `client/src/components/Keyboard.tsx` — add onClick handler for letter keys - `client/src/screens/Screen2Game.tsx` — accept new props (onKeyPress, message) ### Out of Scope - POST /api/play-again endpoint (Story 1.3) - Play Again button functionality on Screen 3 (Story 1.3) - Skill metric tracking (Story 1.3) - Hints on attempts 5-6 (post-v1) - Difficulty display on results screen (post-v1) ### Testing Notes - Test feedback scoring: known word/guess pairs with expected output - Test invalid word: guess not in word bank → valid=false - Test win: all-green → correct=true, difficulty present - Test loss: 6th wrong guess → correct=false, word revealed - Test keyboard: best-feedback logic (g overrides y, y overrides x) - Test persistence: close tab, reopen, mid-game state restored - Test statistics: games played, histogram, streak after multiple games ### References - [Source: _bmad-output/planning-artifacts/prds/prd-wordle-2026-07-07/prd.md#FR-5 through FR-9, FR-13, FR-14, FR-19] - [Source: _bmad-output/planning-artifacts/architecture/architecture-wordle-2026-07-07/ARCHITECTURE-SPINE.md] - [Source: _bmad-output/planning-artifacts/epics.md#Story 1.2] - [Source: _bmad-output/implementation-artifacts/1-1-project-foundation-daily-word-setup.md] ## Dev Agent Record ### Agent Model Used Claude ### Debug Log References ### Completion Notes List ### File List