Status: review
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.
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. ✅
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. ✅
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. ✅
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. ✅
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. ✅
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. ✅
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. ✅
Statistics persist: Given I close and reopen the browser, when I return, then my lifetime statistics are restored from localStorage. ✅
[x] Task 1: Implement POST /api/guess endpoint (AC: 1, 2, 5)
server/src/feedback.ts — Feedback Scoring Ruleserver/src/validate.ts — word bank lookupserver/src/routes/guess.ts — POST /api/guess endpointserver/src/index.ts[x] Task 2: Update shared API types (AC: 1, 5)
GuessRequest and GuessResponse interfaces match implementation[x] Task 3: Implement guess entry UI (AC: 1, 2, 3)
client/src/hooks/useGame.ts — guess management, status, API calls[x] Task 4: Implement Screen 3 — Results (AC: 4, 5, 7)
client/src/screens/Screen3Results.tsx — results + stats + histogram[x] Task 5: Integrate and test end-to-end (all ACs)
The foundation from 1.1 is in place:
shared/src/api.ts, shared/src/game.ts) ✅usePersistence hook ✅server/src/feedback.ts. Client only renders colors. [Source: ARCHITECTURE-SPINE.md#AD-6]'g'/'y'/'x'. 5-element array. [Source: ARCHITECTURE-SPINE.md#AD-11]wordle-state. One hook usePersistence. [Source: ARCHITECTURE-SPINE.md#AD-12]/{*path} not *daily-word.ts uses Intl.DateTimeFormat not toLocaleStringshared/ or a constants file in this storyGiven 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']
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 }
New:
server/src/feedback.tsserver/src/validate.tsserver/src/routes/guess.tsclient/src/hooks/useGame.tsclient/src/screens/Screen3Results.tsxModify:
server/src/index.ts — add guess routerclient/src/App.tsx — integrate useGame, add Screen 3 routing, wire persistenceclient/src/components/Keyboard.tsx — add onClick handler for letter keysclient/src/screens/Screen2Game.tsx — accept new props (onKeyPress, message)Claude