Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
9.5 KiB
Handoff: Table-fidelity feature batch — code-complete, pending deploy
Session Metadata
- Created: 2026-05-18 20:57:36
- Project: /home/claude/bin/blind_chess
- Branch: main (all work committed and pushed to
git.sethpc.xyz/Seth/blind_chess) - Session duration: ~one full session (brainstorm → spec → plan → 12-task execution)
Handoff Chain
- Continues from: 2026-04-29-060121-blind-casual-check-fix.md — the prior session fixed blind Casual's premature resignation.
- Supersedes: None.
Recent Commits (for context)
2e80800docs: record table-fidelity feature batch as code-complete59717b3docs: amend plan to reflect code-review fixes82a69d8fix(client): key phantom-load effect on gameId, gate the drag ghost313837efeat(client): wire the phantom opponent-model layer into the game view816f89bfeat(client): phantom-piece palette component- Full implementation range:
be8ecd9..2e80800(20 commits).
Current State Summary
The "table-fidelity feature batch" (three features Andrew Freiberg — Seth's dad, a physical-blind-chess player — requested by email) is fully implemented, reviewed, and committed to main, but not yet deployed. All 12 plan tasks were executed via subagent-driven development with two-stage review per task plus a final whole-batch review. Build, typecheck, and the 87-test suite all pass. The remaining steps are a manual browser pass and deployment to the two live instances.
Architecture Overview
Three features, two increments, all shipped:
- Announce-all (F1): every moderator
Announcementis nowaudience: 'both'. Previously move events went only to the opponent and attempted-move errors only to the actor. Theaudiencefield is retained (uniformly'both') as the egress-control hook inws.ts/ModeratorPanel. The Casual bot's intermediate retry-rejection announcements are popped inBotDriver.dispatchso its blind-mode search does not broadcast as churn — only its final move is announced. - Capture tally (F2): a server-derived per-viewer
captures: CaptureTallyfield on thejoined/updatemessages, rendered by a newCaptureTally.sveltepanel. Must be server-side — in blind mode the capturing client cannot see what it took. - Phantom layer (F3): a client-LOCAL overlay of guessed opponent pieces, blind mode only. Seeded once with the opponent's standard army, then fully manual: pointer-drag a phantom anywhere, off-board to remove, re-add from an unlimited palette. Persisted to
localStorage(bc:phantoms:<gameId>). Never sent to the server — it lives in its own store so the zero-leak property is auditable.
The zero-leak core (buildView, geometric.ts) was deliberately untouched.
Critical Files
| File | Purpose | Relevance |
|---|---|---|
docs/superpowers/specs/2026-05-18-table-fidelity-features-design.md |
The spec | Design rationale, info-leak analysis |
docs/superpowers/plans/2026-05-18-table-fidelity-features.md |
The 12-task plan | Amended to match shipped code |
packages/server/src/translator.ts, commit.ts |
F1 audience change | All announcements 'both' |
packages/server/src/bot/driver.ts |
F1 bot-churn suppression | dispatch pops intermediate rejections |
packages/server/src/captures.ts |
F2 captureTally |
Pure per-viewer derivation |
packages/shared/src/phantoms.ts |
F3 pure model | opponentStartPosition, deserializePhantoms (tested) |
packages/client/src/lib/stores/phantoms.svelte.ts |
F3 local store | Never read in any send path |
packages/client/src/lib/stores/phantom-drag.svelte.ts |
F3 drag controller | Pointer events, tap-vs-drag, pointercancel-safe |
packages/client/src/lib/Board.svelte, Game.svelte, PhantomPalette.svelte |
F3 UI | Phantom rendering + wiring |
Key Patterns Discovered
- Build ordering:
server/clientresolve@blind-chess/sharedfrom its builtdist/. After editingshared, runpnpm --filter @blind-chess/shared buildbefore downstream typecheck/build.pnpm -r buildhandles order automatically. - Client has no test harness (by design). Pure logic worth testing goes to
packages/shared(vitest); Svelte components are covered bysvelte-check+ manual. ply-parity actor derivation: the four attempted-move enums carry no colour; the client derives White/Black fromply % 2(an attempt only happens on the actor's turn).
Work Completed
- Tasks 1–11 of the plan (all three features), each with implementer + spec-compliance review + code-quality review and fix loops.
- Final whole-batch code review — verdict: ready to ship, no Critical/Important issues.
- Checkpoint A and B verifications:
pnpm -r build && pnpm -r typecheck && pnpm -r testall clean; 87 tests pass (25 shared + 62 server). - DECISIONS.md, CLAUDE.md, and the spec updated to reflect the shipped state.
Files Modified
See git diff --stat be8ecd9..2e80800. New files: packages/server/src/captures.ts, packages/server/test/unit/captures.test.ts, packages/shared/src/phantoms.ts, packages/shared/test/phantoms.test.ts, packages/client/src/lib/{CaptureTally,PhantomPalette}.svelte, packages/client/src/lib/stores/{phantoms,phantom-drag}.svelte.ts. Modified: translator.ts, commit.ts, bot/driver.ts, ws.ts, shared/{types,protocol,index}.ts, client/lib/{Board,Game,ModeratorPanel}.svelte, client/lib/stores/game.svelte.ts.
Decisions Made
All recorded in DECISIONS.md under "Table-fidelity features (2026-05-18)" and "Deferred / Rejected". Key ones: announcements widened to 'both' (deliberate, authorised); manual phantom model (smart-tracker rejected); phantom layer client-local only; drag-and-drop for phantoms only (real moves stay click-to-move).
Immediate Next Steps
- Manual browser verification of the client UI (see Blockers — not done this session). Run
pnpm dev:server+pnpm dev:client, create a blind game vs computer, and check: the moderator panel shows White/Black-labelled attempt lines; the Captures panel updates on a capture; the phantom layer renders 16 seeded pieces; dragging a phantom moves it / off-board removes it; the palette places phantoms; a tap still makes real moves; phantoms persist across reload; vanilla mode shows no phantom UI; phantoms hide on game end. - Deploy both instances per
CLAUDE.md→ Operations: CT 690 (chess.sethpc.xyz) andchess.localon VDJ-RIG. Server restart drops in-memory games. - After deploy, smoke-test on a phone (the phantom drag is the main mobile-risk surface).
Blockers / Open Questions
- Client UI not manually verified in a browser. The implementation passed build,
svelte-check, the 87-test automated suite, and multi-stage code review — but an AI agent cannot run an interactive browser. The phantom drag-and-drop interaction in particular (pointer events, tap-vs-drag threshold, hit-testing) is verified only by review, not by clicking. Recommend Seth (or a browser-capable session) does the manual pass before/after deploy.
Deferred Items
- Phantom-layer
localStoragecleanup for games abandoned mid-play (nofinishedtransition) — tiny leak, add a stale-key sweep only if it matters. - Highlighting interacting with phantoms (rays stopping at phantom pieces) — safe but out of v1 scope.
- An
ai-game-casualWebSocket integration test for F1 — thedriver.test.tsunit test was chosen instead (covers the same commit-path; spec updated to record this).
Important Context
- Everything is on
mainand pushed. Seth explicitly chose to work directly onmain(no feature branch). There is nothing to merge. - The work is code-complete but undeployed.
chess.sethpc.xyzis still running the pre-batch code until someone deploys. Deployment is an outward-facing action — confirm with Seth before doing it. - Two deploy instances now exist (CLAUDE.md was updated mid-project): CT 690 /
chess.sethpc.xyz, andchess.localon VDJ-RIG. Both need the update. - The final review confirmed the security invariant holds: no
phantomtoken anywhere underpackages/server/src/,buildView/geometric.tsbyte-for-byte unchanged.
Assumptions Made
- The client a11y trade-off (phantom spans and palette pieces are pointer-only, with a documented
svelte-ignore a11y_no_static_element_interactions) is acceptable — addingtabindexwithout a keyboard drag would be worse a11y. Real gameplay stays fully keyboard-operable via the square buttons. - 87 is the expected test count (25 shared + 62 server); the client contributes 0 (no harness).
Potential Gotchas
packages/server/tsconfig.tsbuildinfoshows persistentMingit status— pre-existing drift (tracked before*.tsbuildinfowas gitignored), not this session's work.- The pre-commit hook is
detect-secrets-hook --baseline .secrets.baseline. - Server restart on deploy drops all in-memory games.
Environment State
- Tools/Services: pnpm workspace;
giteaCLI for push. Subagent-driven development for execution. - Active Processes: none. No dev servers left running.
- Environment Variables: none added or changed.
Related Resources
- Spec:
docs/superpowers/specs/2026-05-18-table-fidelity-features-design.md - Plan:
docs/superpowers/plans/2026-05-18-table-fidelity-features.md DECISIONS.md→ "Table-fidelity features (2026-05-18)"- Live URL: https://chess.sethpc.xyz · Repo: https://git.sethpc.xyz/Seth/blind_chess (
mainat2e80800)
Security Reminder: No credentials or secrets are included in this handoff.