1-2-guess-validation-feedback.md 7.3 KB

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)

    • Create server/src/feedback.ts — Feedback Scoring Rule
    • Create server/src/validate.ts — word bank lookup
    • Create server/src/routes/guess.ts — POST /api/guess endpoint
    • Register route in server/src/index.ts
  • [x] Task 2: Update shared API types (AC: 1, 5)

    • Verified GuessRequest and GuessResponse interfaces match implementation
  • [x] Task 3: Implement guess entry UI (AC: 1, 2, 3)

    • Create client/src/hooks/useGame.ts — guess management, status, API calls
    • Add physical keyboard handler (keydown)
    • Add on-screen keyboard click handler
    • Show "Unknown word" message on invalid guess
    • Wire App.tsx to useGame, pass state to Screen2Game
  • [x] Task 4: Implement Screen 3 — Results (AC: 4, 5, 7)

    • Create client/src/screens/Screen3Results.tsx — results + stats + histogram
    • Wire win/loss → Screen 3 transition
    • Clear in-progress game on game end
    • Update lifetime statistics and persist
  • [x] Task 5: Integrate and test end-to-end (all ACs)

    • Tested: valid guess feedback (hello vs style), invalid word, win, loss with word reveal
    • 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