1-1-project-foundation-daily-word-setup.md 9.2 KB

Story 1.1: Project Foundation & Daily Word Setup

Status: ready-for-dev

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

  • [ ] Task 1: Initialize monorepo with npm workspaces (AC: 5)

    • Create root package.json with workspaces: ["client", "server", "shared"]
    • Initialize shared/ package with tsconfig.json
    • Initialize server/ package with Express + TypeScript + tsconfig.json
    • Initialize client/ package with Vite + React + TypeScript + tsconfig.json
    • Create data/ directory with placeholder word-bank.json
  • [ ] Task 2: Define shared TypeScript types (AC: 6, 7)

    • Create shared/src/api.ts with DailyResponse interface
    • Create shared/src/game.ts with PersistedState, GameStats, GuessResult interfaces
    • Create shared/src/word-bank.ts with WordBank, TargetWord interfaces
    • Configure shared/package.json to export types properly
  • [ ] Task 3: Implement Express server skeleton + daily word endpoint (AC: 4)

    • Create server/src/index.ts — Express app, serve static files from client/dist in production
    • Create server/src/daily-word.ts — deterministic daily word: (daysSinceEpoch(EST/EDT) mod targetCount) + 1
    • Create server/src/routes/daily.ts — GET /api/daily, loads word bank, returns { wordId }
    • Load data/word-bank.json at server startup (not per-request)
  • [ ] Task 4: Implement React SPA with 3-screen navigation (AC: 1, 2, 3, 8)

    • Create client/src/main.tsx — React entry, Vite config
    • Create client/src/App.tsx — screen router (state-based: rules | game | results)
    • Create client/src/screens/Screen1Rules.tsx — rules display + Play button
    • Create client/src/screens/Screen2Game.tsx — empty grid (5×6) + keyboard (QWERTY, light gray)
    • Create client/src/components/Grid.tsx — renders grid, accepts guesses array as props
    • Create client/src/components/Keyboard.tsx — renders keyboard, accepts letter colors map
    • Create client/src/hooks/usePersistence.ts — read/write PersistedState to localStorage key wordle-state
    • On app load: read PersistedState, detect first visit (no rulesSeen), restore in-progress game if present
    • Add CSS media queries for responsive layout (375px min-width)
  • [ ] Task 5: Create initial word bank data (AC: 4)

    • Create data/word-bank.json with shape { guessable: string[], targets: { id: number, word: string, difficulty: number }[] }
    • Populate with curated 5-letter words (recent internet usage, ~5 years)
    • 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

Completion Notes List

File List