Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
1d45e03
fixes the root
sarangeverest Jun 12, 2026
670463d
adds constitution
sarangeverest Jun 12, 2026
5bee633
generates specs with specify command - feature requirement
sarangeverest Jun 14, 2026
90f372f
clarifies all open item - speckit-clarify
sarangeverest Jun 14, 2026
a3f89a2
implements planning phase - specify-plan
sarangeverest Jun 14, 2026
dccdf15
implements 1st requirement with help specify.implement
sarangeverest Jun 18, 2026
82b3834
adds game drawer start requirement after running /speckit.specify, /s…
sarangeverest Jun 18, 2026
d86d894
implements game-start-drawer spec with specify.implement
sarangeverest Jun 18, 2026
c7207b3
adds planning spec for "Scenario 3 — Gameplay Interaction"
sarangeverest Jun 19, 2026
338918b
clarifies open points from planning with speckify.clarify
sarangeverest Jun 19, 2026
c922189
adds tasks using speckit.tasks
sarangeverest Jun 19, 2026
ab36b2d
implements gameplay interaction — canvas sync, guess submission, scoring
sarangeverest Jun 19, 2026
9c0d194
fixes drawer not sync issue
sarangeverest Jun 19, 2026
e1b5f00
updates spec for a bugfix
sarangeverest Jun 19, 2026
87ebe8c
adds spec for result-restart functionality using speckit.specify
sarangeverest Jun 19, 2026
7f79370
runs speckiet-plan to generate plan
sarangeverest Jun 19, 2026
c0875f6
generates tasks for Result, Restart & Final Validation
sarangeverest Jun 19, 2026
9915365
fixes analyze step suggestions
sarangeverest Jun 19, 2026
a709e82
implements Result, restart, and final validation with speckit implement
sarangeverest Jun 19, 2026
1e6cd94
generates a project reflection doc
sarangeverest Jun 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,8 @@ precheck-results/

.vscode/
.vite/
.claude
.idea
.specify
package*.json
*/package-lock.json
129 changes: 129 additions & 0 deletions .specify/memory/constitution.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
<!--
SYNC IMPACT REPORT
==================
Version change: (unversioned template) → 1.0.0
Type of bump: MAJOR — first concrete adoption from blank template

Modified principles: N/A (all new)
Added sections:
- Core Principles (I–V)
- Architecture Constraints
- Development Workflow
- Governance

Removed sections: N/A

Templates reviewed:
- .specify/templates/plan-template.md ✅ Constitution Check section present; no updates needed
- .specify/templates/spec-template.md ✅ Uses MUST language; aligns with principles
- .specify/templates/tasks-template.md ✅ TDD note already enforces test-first ordering; no updates needed

Deferred TODOs: none — all fields resolved from user input and repo context
-->

# Scribble Constitution

## Core Principles

### I. Test-Driven Development (NON-NEGOTIABLE)

Tests MUST be written and confirmed failing before any implementation begins.
The Red-Green-Refactor cycle is strictly enforced on every task.
Minimum code coverage across both `backend` and `frontend` packages MUST be ≥ 80%.
Coverage is measured per package using Vitest's built-in reporter.

**Rationale**: Prevents regressions and ensures acceptance criteria are encoded as executable
contracts before code is written. Coverage floor prevents coverage theatre on trivial paths.

### II. TypeScript Strict Mode

All code in `backend/` and `frontend/` MUST compile under TypeScript strict mode with zero errors.
`any` is forbidden; use `unknown` for genuinely dynamic types and narrow with guards.
Type definitions belong in `src/models/` (backend) or co-located with the module (frontend).

**Rationale**: Strict typing catches entire classes of bugs at compile time and serves as
lightweight documentation of contracts between layers.

### III. Architecture Integrity

The system MUST conform to this two-package layout — no additional apps, servers, or runtimes:

- **Frontend**: Vite + React 18 + TypeScript (port 5173)
- **Backend**: Node.js + Express + TypeScript REST API (port 3001)

Hard constraints that MUST NOT be violated regardless of feature scope:
- All client–server sync MUST use HTTP polling (`GET /api/rooms/:code`). WebSockets, SSE, and
Socket.io are forbidden.
- All state MUST be stored in-memory (`Map` in `backend/src/services/roomStore.ts`). Databases,
file storage, and external caches are forbidden.
- No authentication, sessions, JWT, or OAuth.

- Never remove existing tests without justification.
- Preserve backward compatibility unless explicitly requested.
- Generate complete implementations, not TODO placeholders.
- Explain architectural trade-offs before introducing new dependencies.
- Ask for clarification when requirements are ambiguous.

**Rationale**: Constraints are load-bearing, Violating them produces a different system than specified.

### IV. Lean Dependency Management

New npm packages MUST NOT be introduced without explicit justification documented in the PR
description. Existing project patterns MUST be preferred over new libraries.
- Frontend state: follow the `useSyncExternalStore` pattern in `frontend/src/state/roomStore.ts`.
- Backend validation: extend Zod schemas in `backend/src/api/schemas.ts`.
- API calls: route through `frontend/src/services/api.ts`; no inline `fetch` in components.

**Rationale**: Every new dependency is a long-term maintenance cost. The scaffold already
covers routing, state, validation, and HTTP — additional frameworks add complexity without value.

### V. Production-Ready, Self-Documenting Code

Code MUST be readable without inline comments explaining *what* it does; names and types carry
that weight. Comments are permitted only for non-obvious *why* (hidden constraint, workaround).
React components MUST avoid unnecessary re-renders: prefer stable references, memoize only when
profiling shows measurable impact, and keep component trees shallow.
All new code MUST work correctly in both the happy path and the documented edge cases from the spec.

**Rationale**: Production-ready means the code could go into a real product without shame.
Self-documenting code reduces the overhead of onboarding and code review.

## Architecture Constraints

| Layer | Technology | Entry point |
|-------|-----------|-------------|
| Frontend | Vite + React + TypeScript | `frontend/src/main.tsx` |
| Backend | Node.js + Express + TypeScript | `backend/src/server.ts` |
| State (server) | In-memory `Map<code, Room>` | `backend/src/services/roomStore.ts` |
| State (client) | `useSyncExternalStore` store | `frontend/src/state/roomStore.ts` |
| Validation | Zod | `backend/src/api/schemas.ts` |
| Testing | Vitest (both packages) | `*.test.ts` / `*.test.tsx` |

Backend route structure: business logic in `src/services/`, HTTP handling in `src/api/`,
types in `src/models/`. Frontend: API calls in `src/services/api.ts`, CSS in `src/styles/app.css`.

## Development Workflow

- PRs MUST be small and focused: one logical change per PR, reviewable in a single sitting.
- Commits MUST be granular and traceable to a specific task from `tasks.md`.
- Task execution order: write failing tests → implement → verify tests pass → commit.
- Both `npm run build` (backend and frontend) MUST succeed before a PR is opened.
- The Constitution Check in `plan.md` MUST be completed before Phase 0 research begins and
re-checked after Phase 1 design.

## Governance

This constitution supersedes all other practice documents for this project.
Amendments require: a written rationale, a version bump per the semantic versioning policy below,
and a re-run of the consistency propagation checklist across all templates.

**Versioning policy**:
- MAJOR: Principle removed, fundamentally redefined, or hard constraint overturned.
- MINOR: New principle or section added; materially expanded guidance.
- PATCH: Clarifications, wording, or non-semantic refinements.

All PRs and reviews MUST verify compliance with this constitution before merge.
Complexity violations (e.g., adding a framework, bypassing a hard constraint) MUST be justified
in a Complexity Tracking table in `plan.md` before work begins.

**Version**: 1.0.0 | **Ratified**: 2026-06-12 | **Last Amended**: 2026-06-12
116 changes: 116 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
# CLAUDE.md

<!-- SPECKIT START -->
For additional context about technologies to be used, project structure,
shell commands, and other important information, read the current plan at
specs/004-result-restart/plan.md
<!-- SPECKIT END -->

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project Overview

Multiplayer drawing game ("Scribble") — a brownfield enhancement project. The scaffold has basic room creation/joining and UI; the remaining gameplay logic (host controls, lobby polling, game start, drawing, guessing, scoring, results) is left to be implemented.

## Commands

Run from each sub-directory — there is no root-level script runner.

**Backend** (`cd backend`):
```
npm run dev # tsx watch src/server.ts (port 3001)
npm run build # tsc -p tsconfig.json → dist/
npm run start # node dist/server.js
npm run test # vitest run
```

**Frontend** (`cd frontend`):
```
npm run dev # vite dev server (port 5173)
npm run build # tsc -b && vite build
npm run preview # vite preview
npm run test # vitest run
```

**Run a single test file:**
```
cd backend && npx vitest run src/services/roomStore.test.ts
cd frontend && npx vitest run src/services/api.test.ts
```

Node version: v24.13.0 (see `.nvmrc`). No environment variables required for local development; `VITE_API_URL` defaults to `http://localhost:3001`.

## Architecture

Two independent apps sharing conventions but with separate builds and `node_modules`.

### Backend (`backend/src/`)

```
api/ # Express routes + Zod request validation
router.ts # Route definitions (GET /health, POST /rooms, etc.)
rooms.ts # Handler logic
schemas.ts # Zod schemas for request/response
services/
roomStore.ts # In-memory Map<code, Room> — all game state lives here
models/
game.ts # Core TypeScript types: Room, Participant, RoundState, etc.
seed/
starterData.ts # Default word list and role definitions
```

Entry point: `src/server.ts` (binds port) → `src/app.ts` (Express setup + CORS).

**API surface:**
- `GET /health`
- `GET /api/`
- `POST /api/rooms` — create room; returns `{ participantId, room: RoomSnapshot }`
- `POST /api/rooms/:code/join` — join room; returns same shape
- `GET /api/rooms/:code?participantId=` — poll room state

### Frontend (`frontend/src/`)

```
routes/index.tsx # React Router v6 route tree (5 routes)
pages/ # One component per route
components/ # Reusable UI pieces (AppShell, Card, GuessForm, Scoreboard, etc.)
state/roomStore.ts # Custom store using useSyncExternalStore — source of truth for room state
services/api.ts # fetch-based HTTP client; all backend calls go here
styles/app.css # All CSS lives in one file
```

State flows from API → `roomStore.ts` → components via the store's `subscribe`/`getSnapshot` pattern. Polling is done with `setInterval` in `useEffect` hooks — **no WebSockets**.

## Coding Guidelines

### TypeScript
- Strict mode is on in both packages. Avoid `any`; use `unknown` for truly dynamic types.
- Backend uses `NodeNext` module resolution; frontend uses `Bundler`.

### Backend
- All request payloads must be validated with Zod schemas in `api/schemas.ts`.
- Business logic belongs in `services/`, not in route handlers.
- Keep `roomStore` footprint small; clean up inactive rooms explicitly.

### Frontend
- Functional components with hooks only.
- New state belongs in `state/`; follow the `useSyncExternalStore` pattern in `roomStore.ts`.
- All API calls go through `services/api.ts` — no inline `fetch` in components.
- CSS changes go in `app.css`.

## Hard Constraints

These are strictly off-limits regardless of task:
- **No WebSockets** — all sync must use HTTP polling (`setInterval` + `GET /api/rooms/:code`).
- **No databases** — in-memory only (`Map` in `roomStore.ts`).
- **No authentication** — no sessions, JWT, or OAuth.

## Spec Kit Artifacts

When using Spec Kit skills, artifacts are stored in `.specify/`:
- `constitution.md` — engineering principles
- `spec.md` — acceptance criteria (4 scenarios)
- `plan.md` — state model, file-level changes, data flow
- `tasks.md` — ordered, testable work items

When CLAUDE.md says "read the current plan", check `.specify/plan.md`.
84 changes: 84 additions & 0 deletions REFLECTION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# Reflection Report — Scribble Assignment

## What Did the Starter App Already Have?

The scaffold provided a working skeleton with the bare minimum to run the app:

**Backend**
- Express server with three routes: `POST /api/rooms`, `POST /api/rooms/:code/join`, `GET /api/rooms/:code`
- In-memory `Map<code, Room>` store with `createRoom` and `joinRoom` functions
- A minimal `Room` model with only `"lobby"` as the room status, and a `Participant` type with no host flag and no score
- `RoomSnapshot` that exposed `availableWords` and `roles` arrays but had no concept of a round, drawer, or guesses

**Frontend**
- React Router v6 with five routes wired up (`/`, `/create`, `/join`, `/lobby`, `/game`)
- `CreateRoomPage` and `JoinRoomPage` with form markup but no client-side validation
- `LobbyPage` that rendered participants but had no polling loop and no "Start Game" button
- `GamePage` that displayed a static `"Drawing canvas placeholder"` string — completely non-interactive
- `Scoreboard` and `ResultPanel` rendered as empty shell components (no props, no data)
- `GuessForm` with an input field that did nothing on submit
- A `roomStore` using `useSyncExternalStore` for state management and a `fetchRoom` action, but no `startGame`, `submitGuess`, `updateCanvas`, `endRound`, or `restartGame` actions
- All CSS layout and design system tokens already in `app.css`

In short: the scaffold could create and join rooms and navigate between pages, but contained no gameplay logic whatsoever.

---

## What Did I Add?

All four requirements were implemented incrementally using the **Spec Kit** workflow (`speckit-specify` → `speckit-clarify` → `speckit-plan` → `speckit-tasks` → `speckit-implement`), producing a spec, data model, API contract, checklist, and ordered task list for each feature before writing any code.

### Requirement 1 — Room Setup & Lobby

- **Host assignment**: `createParticipant` gained an `isHost` flag; the room creator is automatically the host.
- **Room code format**: Changed to 4 uppercase letters only (removed digits from the alphabet).
- **Participant model**: Added `score: number` field.
- **Input validation**: Empty player name is caught client-side before any fetch; invalid/missing room code shows a descriptive error.
- **Case-insensitive join**: `joinRoom` normalises the code to uppercase.
- **Lobby polling**: `LobbyPage` runs a `setInterval` every 2 seconds via `useEffect`; failures surface an inline error banner that clears on the next successful poll.
- **Host-only Start Game button**: Visible only to the host; disabled when fewer than 2 participants are present.

### Requirement 2 — Game Start & Drawer

- **`startGame()` service**: Validates that the caller is the host, that ≥ 2 players are present, and that words are available. Sets `room.status = "in-game"` and creates the first `Round` (drawer = host, word = first word in list, empty guesses/canvas).
- **Expanded type model**: `RoomStatus` widened to `"lobby" | "in-game" | "results"`; new `Round` interface added with `drawerId`, `word`, `guesses`, `canvasData`.
- **`toRoomSnapshot`**: Hides `secretWord` from guessers; only the drawer (and later the results screen) receives it.
- **Frontend navigation**: `LobbyPage` watches `room.status === "in-game"` and redirects to `/game`; `GamePage` redirects back to `/` if room is absent.

### Requirement 3 — Gameplay Interaction

- **Canvas drawing**: `GamePage` attaches `pointerdown/pointermove/pointerup` listeners on the `<canvas>` element only for the drawer; on `pointerup` it serialises the canvas as a `data:image/png` and calls `store.updateCanvas()`.
- **Canvas sync for guessers**: Non-drawer participants see an `<img>` whose `src` is polled from `room.canvasData` every 2 seconds.
- **`updateCanvas()` backend**: Validates the caller is the active drawer, then writes `canvasData` to the round.
- **Guess submission**: `submitGuess()` checks correctness case-insensitively, awards +100 points to the first correct guess, and records every guess with name, text, `isCorrect`, and timestamp. The drawer is blocked from guessing.
- **`GuessForm`** wired to the real API; shows "Correct!" feedback.
- **`Scoreboard`** and **`ResultPanel`** now render live participant scores and the running guess log.
- **Host "End Round" button**: Calls `endRound()`, which sets `room.status = "results"`.

### Requirement 4 — Results, Restart & Final Validation

- **`endRound()` backend**: Validates active round and host caller, then flips status to `"results"`. `toRoomSnapshot` exposes `secretWord` to all viewers when status is `"results"`.
- **`restartGame()` backend**: Validates host caller and `"results"` status, then resets to `"lobby"` and clears `currentRound`.
- **New `ResultsPage`**: Shows the secret word reveal, final `Scoreboard`, full `ResultPanel` of guesses, and a host "Back to Lobby" button (guests see a waiting message). Polls every 2 seconds and navigates away when status changes.
- **Navigation guards**: `GamePage` redirects to `/results` when status becomes `"results"`; `ResultsPage` redirects to `/lobby` or `/game` on status change.
- **New route**: `/results` added to `routes/index.tsx`.

---

## Testing

All logic is covered by automated tests run with Vitest:

| Layer | Test files | Tests |
|---|---|---|
| Backend (services + schema) | 2 | 83 passed |
| Frontend (pages + components) | 9 | 56 passed |
| **Total** | **11** | **139 passed** |

Key test areas: room creation/joining, host validation, guess scoring, canvas update guards, end-round/restart state transitions, lobby polling error banner, `ResultsPage` redirect logic, and `GuessForm` correct/incorrect feedback.

---

## How Spec Kit Shaped the Process

Using Spec Kit forced a structured order: clarify ambiguities first, agree on a data model and API contract before touching code, then generate a dependency-ordered task list. This meant implementation decisions (e.g. _"drawer gets word; guessers don't"_, _"restartGame requires results status"_) were resolved in the spec rather than discovered mid-implementation. The generated `tasks.md` for each feature broke work into small, independently testable chunks, which made it easy to verify progress incrementally and keep commits atomic.
Loading
Loading