Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
12 KiB
Handoff: Table-fidelity batch — deployed; manual test pass underway
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)
d10e581fix(client): light outline on dark phantom glyphs for panel contrast — NOT yet deployed0773300docs: table-fidelity batch deployed to both instances0c0e739docs: session handoff2e80800docs: record table-fidelity feature batch as code-complete59717b3docs: amend plan to reflect code-review fixes- Feature 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, committed to main, and deployed (2026-05-18) to both live instances — chess.sethpc.xyz (CT 690) and chess.local (VDJ-RIG). 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.
A manual browser test pass is now underway (Seth). It has surfaced one bug so far: opponent (black) phantom pieces had too little contrast on the dark --panel background. Fixed in d10e581 — black piece glyphs in the palette, the Captures panel, and the drag-ghost now get a light text-shadow outline. That fix is committed to main but NOT yet deployed — the two live instances are at 0773300, one commit behind main.
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
- Decide and do the redeploy of
d10e581(the contrast fix). It is onmainbut not live. Seth was asked whether to deploy it now or batch it with further test-pass findings — undecided (he requested this handoff instead). Each redeploy drops in-memory games. Deploy steps:CLAUDE.md→ Operations (both instances; forchess.localremember the explicitsystemctl restart— see Potential Gotchas). - Continue the manual browser/phone test pass. Open https://chess.sethpc.xyz, 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. The phantom drag is the main mobile-risk surface — test on a phone.
- Check the board phantom glyph contrast.
.phantom-b(dark phantoms on the board) render atopacity: 0.4on the board squares; they have a dashed frame so the square reads, but the piece type may be hard to tell on dark squares. Flagged to Seth to eyeball during the test pass — if too faint, give.phantom-ba subtle light outline without killing the intentional translucency. (Same root cause as thed10e581fix, left alone for now because board phantoms are deliberately ghostly.) - (Optional) Fix the
install-local.shredeploy gap — see Potential Gotchas.
Blockers / Open Questions
- Deploy timing for
d10e581(contrast fix) — undecided. Seth was asked "deploy now, or batch with other test-pass findings?" and requested a handoff instead. Resolve next session. The live site lacks the contrast fix until then. - Board phantom glyph contrast — open question. See Immediate Next Steps #3 — needs a human eye on a real board before deciding whether
.phantom-bneeds an outline. - Manual browser test pass is in progress, not complete. Only the palette-contrast issue has been found and fixed so far; the rest of the checklist in Next Step #2 is unverified. The phantom drag-and-drop in particular (pointer events, tap-vs-drag, hit-testing) is verified only by code review, not by clicking.
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 feature batch is deployed and live on both instances (
chess.sethpc.xyzandchess.local) at commit0773300. The only un-deployed change isd10e581(the contrast fix). Deployment is outward-facing and drops in-memory games — confirm timing with Seth. - 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. (CT 690 had 3 "active" games at deploy time — almost certainly stale abandoned games, since active games never auto-expire and uptime was 19 days; dropped per the pre-accepted MVP policy.)
deploy/install-local.sh(thechess.localinstaller) ends withsystemctl enable --now blind-chess.service, which does NOT restart an already-running service — a redeploy via the script alone leaves the old code running. This deploy worked around it with an explicitsudo systemctl restart blind-chessafter the script. Proper fix: change the script'senable --nowtoenablethenrestart.
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 (deployed at
0773300) · Repo: https://git.sethpc.xyz/Seth/blind_chess (mainatd10e581)
Security Reminder: No credentials or secrets are included in this handoff.