Files
blind_chess/.claude/handoffs/2026-04-28-104344-spec-approved-ready-for-plan.md
T
claude (blind_chess) a6de43edc1 feat: implement and deploy blind_chess MVP
- pnpm workspace: shared/server/client packages
- Server: Fastify+ws, chess.js, FSM (touch-move + hierarchy),
  per-player view filter, zod validation, rate limiting, grace-window
  disconnect handling
- Client: Svelte 5 + Vite, click-to-move board, moderator panel,
  promotion/draw dialogs
- Shared: protocol types, ModeratorText enum, geometricMoves helper
  (provably zero opponent-info leak)
- 43 tests pass (21 shared, 22 server incl. 4 real-WS integration)
- Deploy: CT 690 on node-241 (192.168.0.245), systemd-managed,
  Caddy block for chess.sethpc.xyz
- Live at https://chess.sethpc.xyz

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 11:20:18 -04:00

16 KiB

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
    • 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<gameId, Game>). 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 <path>. 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.)
  • 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.