# Handoff: Spec approved, ready for implementation plan ## Session Metadata - Created: 2026-04-28 10:43:44 - Project: /home/claude/bin/blind_chess - Branch: not a git repo (yet — see Pending Work) - Session duration: ~90 minutes (single brainstorming session) - Recent commits: none — repo not initialized yet. ## Handoff Chain - **Continues from**: [2026-04-28-kickoff.md](./2026-04-28-kickoff.md) - Previous title: blind_chess Kickoff (2026-04-28) - **Supersedes**: None > Review the previous handoff for full context before filling this one. ## Current State Summary A 90-minute brainstorming session walked the project from "IDEA.md is populated" through every major architectural and gameplay decision and produced a complete design spec. Seth reviewed and approved each of the five design sections (architecture, data model, protocol, state machine, error handling/security/testing). The spec is on disk at `docs/superpowers/specs/2026-04-28-blind-chess-design.md`. Self-review applied four small fixes inline. **Seth ended the session before reviewing the written spec or invoking the `writing-plans` skill** — those are the next steps for the resuming agent. ## Architecture Overview Designed (not yet implemented): - **Language/stack:** Node 22 + TypeScript, Fastify + `ws`, Svelte + Vite, `chess.js`. - **Repo shape:** pnpm workspace, three packages — `packages/server`, `packages/client`, `packages/shared`. Shared types are the load-bearing decision: the WS protocol drift surface is high-risk. - **State:** in-memory only (`Map`). No DB. Server restart drops active games — acceptable for MVP. - **Engine:** `chess.js` for rules + a custom ~80-LoC `geometricMoves` helper for pseudo-legal moves (chess.js doesn't expose those). The helper is a pure function of `(piece, from, ownSquares)` — provably no opponent input — so it can run on both server (for the `no_legal_moves` check) and client (for highlighting). It lives in `packages/shared`. - **Deploy:** new LXC on node-241, Caddy CT 600 routes `chess.sethpc.xyz` → port 3000, systemd-managed Node service. Single-port everything (HTTP + WS + static). - **Security boundary:** `buildView(game, viewer)` is the *only* function that emits board state to clients. Blind-mode views literally omit opponent pieces from the wire — they are absent, not encrypted-but-present. ## Critical Files | File | Purpose | Relevance | |------|---------|-----------| | IDEA.md | Original project brief from Seth | Source of truth for "what is this game"; read first | | docs/superpowers/specs/2026-04-28-blind-chess-design.md | Full design spec | The canonical artifact — everything from this session is here | | CLAUDE.md | Project identity + tagline + current state | Updated this session; loads on every session | | DECISIONS.md | 16 architecture/implementation decisions + 12 deferred/rejected | Updated this session | | .claude/handoffs/2026-04-28-kickoff.md | Previous handoff (project scaffold) | Read second | | .gitignore | Excludes `.superpowers/` and `.backup/` | Created this session | | .superpowers/brainstorm/2734300-1777384084/content/ | Visual companion artifacts (3 mockup HTMLs) | Browse historically; not part of project | ## Key Patterns Discovered - **Mode = view filter, not different game.** Vanilla and blind share one `chess.js` instance; the difference is what `buildView()` returns to each player. - **Moderator vocabulary is an enum, never a string.** Display text lives client-side. Tests assert against `ModeratorText` values like `'wont_help'`. - **Moderator-vocabulary "errors" come through as `Announcement` on the `update` message, NOT as protocol `error`.** They're game events. `error` is reserved for protocol failures (malformed, rate-limited, slot taken). - **Highlights in blind+ON are purely geometric.** Function of `(piece type, position, own-piece set)`. Rays extend through unseen opponents. Stop at own pieces. Off-board excluded. **Zero opponent info leak.** - **Touch-move FSM is server-side.** `game.armed: { color, from }` is the entire state. `no_legal_moves` and `wont_help` checks fire only on first commit with a piece; once committed, all subsequent failed attempts are `illegal_move` with the touch staying. - **Single `commit { from, to? }` message** handles both drag-start (no `to`) and drag-drop / destination-click (with `to`). Uniform server FSM through one handler. ## Tasks Finished - Read kickoff handoff and IDEA.md - Offered visual companion, Seth accepted (URL was http://steel141.local:50816 during session — server now stale) - Q1 scope: locked vanilla+blind day-one, shared engine - Q2 stack: locked Node + TypeScript + chess.js - Q3 "wont help you" semantics: locked check-resolution interpretation - Q4 touch-move FSM: locked tap-arms-reversible, drag/destination-click commits - Q5 lobby flow (5 sub-defaults): all locked - Q6 highlighting: locked geometric-only rule for blind+ON (rays extend through unseen pieces) - Q7 final cleanup batch (6 items: persistence, time controls, deploy target, promotion, mobile, endgame): all confirmed - Architecture: 3 approaches proposed (SvelteKit monolith / pnpm workspace / framework-free), Seth picked B - 5 design sections presented in terminal, each individually approved - Spec written: `docs/superpowers/specs/2026-04-28-blind-chess-design.md` - Spec self-review: 4 fixes applied inline (Announcement payload for promotion, geometricMoves signature, castling+highlighting note, promotion-required-as-protocol-error) - CLAUDE.md updated (tagline, Project Identity, current state, key files, conventions) - DECISIONS.md updated (16 settled decisions, 12 deferred/rejected) ## Files Modified | File | Changes | Rationale | |------|---------|-----------| | CLAUDE.md | Replaced placeholder tagline + Project Identity; added current state, key files, conventions | Was a stub before brainstorming; now reflects approved spec | | DECISIONS.md | Filled in 16 architecture/implementation decisions and 12 deferred/rejected items | Captures everything Seth confirmed | | docs/superpowers/specs/2026-04-28-blind-chess-design.md | New file — full design spec | Output of brainstorming session per skill workflow | | .gitignore | New file — excludes `.superpowers/` and `.backup/` | `.superpowers/` is the visual-companion working directory | ## Decisions Made See the full list in `DECISIONS.md` (16 architecture + implementation, 12 deferred/rejected). Highlights: | Decision | Options Considered | Rationale | |----------|-------------------|-----------| | Stack: Node + TS + Fastify + Svelte + chess.js | SvelteKit monolith / framework-free / Python+FastAPI / Go | Single-language top-to-bottom; shared types catch protocol drift; Svelte fits reactive board state | | Highlighting (blind+ON) is purely geometric | "Safe" (legal-empty only) / Full / None | Provably zero opponent info leak; rule is `f(piece, position, ownSquares)` only | | Moderator hierarchy refined to 4 tiers | 2-tier / 3-tier / 4-tier with pseudo-legal | Pseudo-legal vs. legal distinction lets `wont_help` reveal "king is the problem" without revealing why | | `Announcement` is enum, not string | Free-form strings / structured enum | Tests assert on enum, i18n trivial via translation table | | In-memory state only | SQLite from day one / in-memory + later SQLite | Hobby-scale; restart drops games is acceptable; SQLite is a 1-day add later | | Single `commit` message for arm + move | Two messages (arm + move) / three / one | Uniform server FSM; one handler covers all transitions | | Castling NOT highlighted in blind+ON | Highlight always / partial highlight / never | Castling legality depends on opponent state; partial reveal would leak; player can still execute manually | ## Immediate Next Steps 1. **Have Seth review the written spec.** It's at `docs/superpowers/specs/2026-04-28-blind-chess-design.md`. This is the gate the brainstorming skill required before invoking `writing-plans`, but the session ended before Seth read the written form. Ask: "I've got the spec written at ``. Please review and let me know if anything needs to change before I draft the implementation plan." If Seth requests changes, edit inline and re-ask. 2. **Propose Gitea repo creation, with Seth's OK.** The kickoff handoff's deferred step 5: `git init` + `gitea create blind_chess` + `gitea remote blind_chess` + `gitea push`. The session ended before this happened. **Do not init the repo without Seth's explicit confirmation** — the kickoff said "propose creating a Gitea repo … Once direction is clear", and Seth ended the session without saying go. Use the `gitea` CLI from `~/bin/gitea`. 3. **Invoke the `superpowers:writing-plans` skill.** This is the terminal state of the brainstorming process. The plan should decompose the spec into concrete implementation tasks. Skill location: per CLAUDE.md plugin list. ## Blockers/Open Questions - **Spec review by Seth not yet done.** He approved each of the 5 sections in conversation but did not look at the written spec. Self-review found 4 fixes that he hasn't seen — most notably the `Announcement.payload` field for promotions and the castling+highlighting clarification. - **No Gitea repo yet.** Project is on disk only. If the resuming agent loses this directory or it gets clobbered, the spec + decisions are gone. Backup: `.backup/` is gitignored but currently empty (no edits to backup). ## Deferred Items All deferred/rejected items are in `DECISIONS.md` "Deferred / Rejected" section. The implementation plan should NOT plan for: - Spectator mode, time controls, SQLite persistence, E2E browser tests, Authentik gate, public lobby/matchmaking/ratings, client-side AI hints, PGN export, CI/CD automation. - Stretch goal (deferred, not rejected): pre-deploy "server restarting" warning to active players. ## Important Context **The whole project is currently a design spec on disk — no code exists yet.** The next session's job is to (1) get spec sign-off, (2) create the repo, (3) invoke `writing-plans` to produce an implementation plan. Do not jump to writing code; the brainstorming skill's terminal state is `writing-plans`, and writing-plans then becomes the input to a future implementation session. **Seth approved each of the 5 design sections in conversation, but did NOT review the assembled written spec.** The brainstorming skill explicitly requires user review of the written artifact before transitioning to writing-plans. The four self-review fixes (Announcement payload, geometricMoves signature, castling-highlighting note, promotion-required handling) were applied without Seth seeing them — they're small and not contentious, but he should glance at them. **The visual companion server (port 50816, `http://steel141.local:50816`) is no longer running.** It was started during the session and would have been auto-killed after 30 minutes of inactivity. Three mockup HTMLs are persisted in `.superpowers/brainstorm/2734300-1777384084/content/`: `welcome.html`, `highlighting.html` (v1, superseded), `highlighting-v2.html` (the corrected geometric rule, the one Seth approved), `waiting.html`. Don't restart the server unless the next session does more visual work. **`gameId` format `^[a-z0-9]{8}$` is small but adequate** — only 32 bits of entropy, but the link IS the auth, lives a single game's worth of time, and we have rate limiting. If at any point the link becomes long-lived (e.g., post-game replay URLs), bump to 12+ chars. **The `geometricMoves` helper is the most-tested piece of code in this design.** Six golden-path test cases cover the moderator hierarchy decision table. Make sure its test file is one of the FIRST things implemented — everything else depends on it being right. ## Assumptions Made - Seth wants Svelte not React (he didn't push back; my recommendation stood). - Seth wants Fastify not Express (same — accepted by silence). - Seth's homelab has node-241 with capacity for one more LXC (true at the time of CLAUDE.md last-update; verify with `pct list` on node-241 if uncertain). - "Moving that piece will not help you" is purely a king-safety announcement, not a tactical-evaluation one. Seth confirmed explicitly during the session. - 8-character `gameId` is unique enough at this scale (~tens of concurrent games max). Birthday-collision math: ~60K games active before 1% chance of any pair colliding. Way more than this project will see. ## Potential Gotchas - **chess.js does not expose pseudo-legal moves directly.** Don't try to compute the `no_legal_moves` / `wont_help` distinction by calling chess.js's `moves()` alone — that returns *legal* moves only. You need both the geometric set and the legal set. The spec's `geometricMoves` helper is the missing piece. - **chess.js `inCheck()` checks the side-to-move.** After a move is applied, the side-to-move is the opponent. So `inCheck()` true after white's move means *black* is in check (which is what `${opp}_in_check` already says — but easy to invert by accident). - **`Announcement.payload.promotedTo` was added in self-review.** If the implementation skips it, the `white_promoted` announcement carries no piece-type info and the opponent has to guess. Don't drop it. - **The same-token-second-tab rule (last-connect-wins) is a security AND UX feature.** Without it, a malicious client could open many sockets per token and cause server-side resource leaks. Don't be tempted to "be more permissive" here. - **Castling is NOT in `geometricMoves`.** This is intentional. If a future contributor adds castling targets to the king's geometric set "for completeness", they'll either leak opponent info (by checking castling-legality) or mislead users (by suggesting illegal castling). Comment in the helper explaining this. - **The visual companion's `.superpowers/` dir is gitignored.** If you want to share mockups (e.g., commit them for posterity), copy them to `docs/` first. - **Files referenced in this handoff that don't exist yet:** `deploy/Caddyfile.snippet` and the test files (e.g., `packages/server/test/unit/geometric.test.ts`) are forward references — they'll be created during implementation. Don't be alarmed when the validator flags them as missing. ## Environment State ### Tools/Services Used - **chess.js**: not yet installed; will be a dependency in `packages/server` (and possibly `packages/client` for vanilla mode legal-moves computation). Pin via `package.json` once init'd. - **gitea CLI**: `~/bin/gitea`. Used for repo create + push. Token at `~/.config/gitea/token`. Documented in `~/bin/CLAUDE.md`. - **pnpm**: required for workspace. Version 9+ recommended. - **Node 22 LTS**: deployment target. Local dev should match. - **Caddy CT 600** (192.168.0.185): existing. Add a `chess.sethpc.xyz` block to the Caddyfile when deploying. Spec includes a `deploy/Caddyfile.snippet` to be created during implementation. ### Active Processes - **None.** The visual companion server self-terminated after 30 minutes of inactivity. No long-running tasks were started. ### Environment Variables - `HOMELAB_PASSWORD`: set globally, used for SSH/SMB/etc. Not needed for blind_chess work directly. - (No project-specific env vars yet.) ## Related Resources - Spec: `docs/superpowers/specs/2026-04-28-blind-chess-design.md` - Project identity: `CLAUDE.md` - Decisions: `DECISIONS.md` - Original brief: `IDEA.md` - Kickoff handoff: `.claude/handoffs/2026-04-28-kickoff.md` - Visual companion mockups (gitignored): `.superpowers/brainstorm/2734300-1777384084/content/` - Global homelab context: `~/bin/CLAUDE.md` - Project scaffold recipe: `~/bin/CREATE_PROJECT.md` - Gitea CLI: `~/bin/gitea` - chess.js (intended dep): https://github.com/jhlywa/chess.js --- **Security Reminder**: Before finalizing, run `validate_handoff.py` to check for accidental secret exposure.