a6de43edc1
- 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>
181 lines
16 KiB
Markdown
181 lines
16 KiB
Markdown
# 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<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.)
|
|
|
|
## 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.
|