--- baseline_commit: 961c538 --- # Story 1.1: Project Foundation & Daily Word Setup Status: review ## Story As a player, I want to open the game in my browser and see the game board ready to play, so that I can start playing without friction. ## Acceptance Criteria 1. **First-visit rules screen:** Given I am a first-time visitor with no prior cookie, when I open the game URL, then Screen 1 displays: game rules (5 letters, 6 attempts, green/yellow/gray meaning) and a Play button. Tapping Play navigates to Screen 2 with an empty 5×6 grid and QWERTY keyboard in light gray. ✅ 2. **Returning player skip:** Given I am a returning visitor with a prior-visit cookie and no game in progress, when I open the game URL, then I land directly on Screen 2 (game board) without seeing the rules screen. ✅ 3. **Mid-game resume:** Given I am a returning visitor who previously closed the browser mid-game, when I open the game URL, then my in-progress game state (word ID, guesses so far with their colors) is restored from localStorage, and I continue from where I left off. ✅ 4. **Daily word API:** Given the server has a word bank loaded, when the client calls GET /api/daily, then the response contains `{ wordId: number }` where wordId is the same for all players on the same calendar day (EST/EDT). ✅ 5. **Monorepo structure:** Given the project is initialized, when I inspect the codebase, then the monorepo structure matches: `client/` (React+Vite SPA), `server/` (Express API), `shared/` (TypeScript types), `data/` (word-bank.json). A root `package.json` with npm workspaces configured. ✅ 6. **Shared API types:** Given the project is initialized, when I inspect `shared/src/api.ts`, then it defines concrete TypeScript interfaces for GET /api/daily request/response shapes (`DailyResponse: { wordId: number }`). ✅ 7. **Shared game types:** Given the project is initialized, when I inspect `shared/src/game.ts`, then it defines the `PersistedState` interface: `{ rulesSeen: boolean, stats: GameStats, currentGame?: { wordId: number, guesses: { guess: string, colors: string[] }[] }, skillMetric: number }`. ✅ 8. **Responsive layout:** Given I open the game on any device, when I view it, then the layout is responsive via CSS media queries, usable at 375px (small phone) through desktop widths. ✅ ## Tasks / Subtasks - [x] Task 1: Initialize monorepo with npm workspaces (AC: 5) - [x] Create root `package.json` with `workspaces: ["client", "server", "shared"]` - [x] Initialize `shared/` package with `tsconfig.json` - [x] Initialize `server/` package with Express + TypeScript + `tsconfig.json` - [x] Initialize `client/` package with Vite + React + TypeScript + `tsconfig.json` - [x] Create `data/` directory with placeholder `word-bank.json` - [x] Task 2: Define shared TypeScript types (AC: 6, 7) - [x] Create `shared/src/api.ts` with `DailyResponse` interface - [x] Create `shared/src/game.ts` with `PersistedState`, `GameStats`, `GuessResult` interfaces - [x] Create `shared/src/word-bank.ts` with `WordBank`, `TargetWord` interfaces - [x] Configure `shared/package.json` to export types properly - [x] Task 3: Implement Express server skeleton + daily word endpoint (AC: 4) - [x] Create `server/src/index.ts` — Express app, serve static files from `client/dist` in production - [x] Create `server/src/daily-word.ts` — deterministic daily word: `(daysSinceEpoch(EST/EDT) mod targetCount) + 1` - [x] Create `server/src/routes/daily.ts` — GET /api/daily, loads word bank, returns `{ wordId }` - [x] Load `data/word-bank.json` at server startup (not per-request) - [x] Task 4: Implement React SPA with 3-screen navigation (AC: 1, 2, 3, 8) - [x] Create `client/src/main.tsx` — React entry, Vite config - [x] Create `client/src/App.tsx` — screen router (state-based: `rules | game | results`) - [x] Create `client/src/screens/Screen1Rules.tsx` — rules display + Play button - [x] Create `client/src/screens/Screen2Game.tsx` — empty grid (5×6) + keyboard (QWERTY, light gray) - [x] Create `client/src/components/Grid.tsx` — renders grid, accepts guesses array as props - [x] Create `client/src/components/Keyboard.tsx` — renders keyboard, accepts letter colors map - [x] Create `client/src/hooks/usePersistence.ts` — read/write `PersistedState` to localStorage key `wordle-state` - [x] On app load: read `PersistedState`, detect first visit (no `rulesSeen`), restore in-progress game if present - [x] Add CSS media queries for responsive layout (375px min-width) - [x] Task 5: Create initial word bank data (AC: 4) - [x] Create `data/word-bank.json` with shape `{ guessable: string[], targets: { id: number, word: string, difficulty: number }[] }` - [x] Populate with curated 5-letter words (recent internet usage, ~5 years) - [x] All targets have default difficulty of 3.0 (pending solver data in Epic 2) ## Dev Notes ### Architecture Compliance - **AD-1 Stateless server:** Server stores nothing between requests. Word bank loaded at startup. [Source: ARCHITECTURE-SPINE.md#AD-1] - **AD-2 Deterministic daily word:** `wordId = (daysSinceEpoch(EST/EDT) mod targetWordCount) + 1`. Timezone: America/New_York. [Source: ARCHITECTURE-SPINE.md#AD-2] - **AD-3 TypeScript everywhere:** All code is TypeScript. Shared types in `shared/` are the single source of truth. [Source: ARCHITECTURE-SPINE.md#AD-3] - **AD-4 Word bank as static JSON:** Shape: `{ guessable: string[], targets: { id: number, word: string, difficulty: number }[] }`. Loaded at startup. [Source: ARCHITECTURE-SPINE.md#AD-4] - **AD-9 Monorepo with shared types:** `client/`, `server/`, `solver/`, `shared/`, `data/`. Every package imports from `shared/`. [Source: ARCHITECTURE-SPINE.md#AD-9] - **AD-10 Typed API contracts:** Every endpoint has a concrete TS interface in `shared/src/api.ts` before implementation. [Source: ARCHITECTURE-SPINE.md#AD-10] - **AD-12 Single persistence key:** All browser state under localStorage key `wordle-state`. One hook `usePersistence` owns all access. [Source: ARCHITECTURE-SPINE.md#AD-12] ### Stack (Verified Current) | Technology | Version | |---|---| | TypeScript | ^6.x | | Node.js | ^24 LTS (Krypton) | | React | ^19.x | | Vite | ^8.x (Rolldown bundler) | | Express | ^5.x | [Source: Architecture spine, verified against web via reviewer gate 2026-07-07] ### Source Tree to Create ``` wordle/ package.json # workspaces: [client, server, shared] client/ package.json # react, react-dom, vite, typescript, @types/* vite.config.ts tsconfig.json index.html src/ main.tsx App.tsx # state-based screen router api.ts # fetch wrappers: getDaily(), etc. screens/ Screen1Rules.tsx # rules + Play button Screen2Game.tsx # grid + keyboard components/ Grid.tsx # 5×6 grid, accepts guesses + colors Keyboard.tsx # QWERTY, accepts letter color map hooks/ usePersistence.ts # single hook for localStorage read/write server/ package.json # express, typescript, tsx, @types/* tsconfig.json src/ index.ts # Express app, static file serving daily-word.ts # date→wordId function routes/ daily.ts # GET /api/daily shared/ package.json # types only tsconfig.json src/ api.ts # DailyResponse, GuessRequest, GuessResponse, PlayAgainRequest, PlayAgainResponse game.ts # PersistedState, GameStats, GuessResult word-bank.ts # WordBank, TargetWord data/ word-bank.json # { guessable: string[], targets: { id, word, difficulty }[] } ``` ### Key Implementation Details - **Daily word calculation:** Use `Date` object with timezone-aware logic. `daysSinceEpoch` = floor((current EST/EDT timestamp - epoch timestamp) / 86400000). Epoch can be any fixed reference date (e.g., 2026-01-01). - **Rules-seen detection:** Check `PersistedState.rulesSeen` from localStorage on initial load. - **Screen routing:** Simple React state — `{ screen: 'rules' | 'game' | 'results' }`. No React Router needed for 3 screens. - **Word bank loading:** Server loads `data/word-bank.json` on startup via `fs.readFileSync` (or async equivalent). Path resolved relative to server root. In dev, `../data/word-bank.json`. - **No database, no migrations, no ORM.** The word bank is a JSON file. ### Out of Scope (for this story) - POST /api/guess endpoint (Story 1.2) - POST /api/play-again endpoint (Story 1.3) - Guess validation and feedback colors (Story 1.2) - Results screen / Screen 3 (Story 1.2) - Play Again button and skill tracking (Story 1.3) - Solver engine (Epic 2) ### Testing Notes - Test `daily-word.ts` with known dates to verify deterministic output - Test `GET /api/daily` returns `{ wordId }` with valid number - Test first-visit vs returning visitor detection in browser - Test responsive layout at 375px and desktop widths - Test localStorage roundtrip for `PersistedState` ### References - [Source: _bmad-output/planning-artifacts/prds/prd-wordle-2026-07-07/prd.md] - [Source: _bmad-output/planning-artifacts/architecture/architecture-wordle-2026-07-07/ARCHITECTURE-SPINE.md] - [Source: _bmad-output/planning-artifacts/epics.md#Story 1.1] ## Dev Agent Record ### Agent Model Used Claude ### Debug Log References - Vite 8 downgraded to Vite 7: @vitejs/plugin-react@4.7.0 does not yet support Vite 8 peer - Express 5 catch-all route syntax changed from `*` to `/{*path}` ### Completion Notes List - Monorepo initialized with npm workspaces: client, server, shared - Shared types defined: api.ts, game.ts, word-bank.ts with full interfaces - Express server with GET /api/daily endpoint, confirmed returning `{"wordId":29}` - Deterministic daily word uses EST/EDT timezone, verified same-date determinism - React SPA with state-based screen routing (rules/game/results), localStorage persistence - Client builds successfully with Vite 7 (Vite 8 pending plugin support) - All TypeScript packages typecheck clean - Word bank with 500+ guessable words and 40 target words, all default difficulty 3.0 ### File List - `package.json` (root, npm workspaces) - `client/package.json`, `client/tsconfig.json`, `client/vite.config.ts`, `client/index.html` - `client/src/main.tsx`, `client/src/App.tsx`, `client/src/api.ts` - `client/src/screens/Screen1Rules.tsx`, `client/src/screens/Screen2Game.tsx` - `client/src/components/Grid.tsx`, `client/src/components/Keyboard.tsx` - `client/src/hooks/usePersistence.ts` - `server/package.json`, `server/tsconfig.json` - `server/src/index.ts`, `server/src/daily-word.ts`, `server/src/routes/daily.ts` - `shared/package.json`, `shared/tsconfig.json` - `shared/src/index.ts`, `shared/src/api.ts`, `shared/src/game.ts`, `shared/src/word-bank.ts` - `data/word-bank.json`