Files
blind_chess/.claude/handoffs/2026-05-18-205736-table-fidelity-features.md
claude (blind_chess) d95ab2abf1 docs: refresh handoff — promotion fix shipped, both fixes deployed
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 21:53:23 -04:00

12 KiB
Raw Permalink Blame History

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

Recent Commits (for context)

  • c01244c fix: promotion dialog only fires for genuine pawn promotions — deployed
  • 5d995eb docs: update handoff
  • d10e581 fix(client): light outline on dark phantom glyphs for panel contrast — deployed
  • 0773300 docs: table-fidelity batch deployed to both instances
  • 2e80800 docs: record table-fidelity feature batch as code-complete
  • Feature implementation range: be8ecd9..2e80800 (20 commits); two follow-up fixes since (d10e581, c01244c), both deployed.

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 94-test suite all pass (32 shared + 62 server).

A manual browser test pass is underway (Seth). It has surfaced two bugs so far — both fixed and deployed:

  1. Contrast (d10e581): opponent (black) phantom pieces were near-invisible on the dark --panel background — black glyphs in the palette, Captures panel, and drag-ghost now get a light text-shadow outline.
  2. Spurious promotion dialog (c01244c): the "Promote pawn" modal fired for any pawn "moved" toward the last rank because the commit paths checked the destination rank but not the pawn's source rank — easy to hit once the phantom layer filled ranks 7-8 with tappable phantoms. Fixed with a new shared isPromotionMove(piece, from, to) (pawn, from the rank adjacent to promotion, to the promotion rank, ≤1 file over), used by both client onCommit and server isPromotionRequired.

Both fixes are live — the two instances and main are all at c01244c.

Architecture Overview

Three features, two increments, all shipped:

  1. Announce-all (F1): every moderator Announcement is now audience: 'both'. Previously move events went only to the opponent and attempted-move errors only to the actor. The audience field is retained (uniformly 'both') as the egress-control hook in ws.ts/ModeratorPanel. The Casual bot's intermediate retry-rejection announcements are popped in BotDriver.dispatch so its blind-mode search does not broadcast as churn — only its final move is announced.
  2. Capture tally (F2): a server-derived per-viewer captures: CaptureTally field on the joined/update messages, rendered by a new CaptureTally.svelte panel. Must be server-side — in blind mode the capturing client cannot see what it took.
  3. 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/client resolve @blind-chess/shared from its built dist/. After editing shared, run pnpm --filter @blind-chess/shared build before downstream typecheck/build. pnpm -r build handles order automatically.
  • Client has no test harness (by design). Pure logic worth testing goes to packages/shared (vitest); Svelte components are covered by svelte-check + manual.
  • ply-parity actor derivation: the four attempted-move enums carry no colour; the client derives White/Black from ply % 2 (an attempt only happens on the actor's turn).

Work Completed

  • Tasks 111 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 test all 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

  1. 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; genuine pawn promotion (pawn e7→e8) still pops the dialog; 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.
  2. Check the board phantom glyph contrast. .phantom-b (dark phantoms on the board) render at opacity: 0.4 on 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-b a subtle light outline without killing the intentional translucency.
  3. (Optional) Fix the install-local.sh redeploy gap, and reconcile the stale VDJ-RIG "no Caddy" note — see Potential Gotchas.

Blockers / Open Questions

  • Board phantom glyph contrast — open question. See Immediate Next Steps #2 — needs a human eye on a real board before deciding whether .phantom-b needs an outline.
  • Manual browser test pass is in progress, not complete. Two bugs found and fixed+deployed so far (contrast, spurious promotion dialog); the rest of the checklist in Next Step #1 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 localStorage cleanup for games abandoned mid-play (no finished transition) — 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-casual WebSocket integration test for F1 — the driver.test.ts unit test was chosen instead (covers the same commit-path; spec updated to record this).

Important Context

  • Everything is on main and pushed. Seth explicitly chose to work directly on main (no feature branch). There is nothing to merge.
  • The feature batch plus both follow-up fixes are deployed and live on both instances (chess.sethpc.xyz and chess.local) at commit c01244cmain and the live site match. 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, and chess.local on VDJ-RIG. Both need the update.
  • The final review confirmed the security invariant holds: no phantom token anywhere under packages/server/src/, buildView/geometric.ts byte-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 — adding tabindex without a keyboard drag would be worse a11y. Real gameplay stays fully keyboard-operable via the square buttons.
  • 94 is the expected test count (32 shared + 62 server); the client contributes 0 (no harness).

Potential Gotchas

  • packages/server/tsconfig.tsbuildinfo shows persistent M in git status — pre-existing drift (tracked before *.tsbuildinfo was 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 (the chess.local installer) ends with systemctl enable --now blind-chess.service, which does NOT restart an already-running service — a redeploy via the script alone leaves the old code running. Deploys work around it with an explicit sudo systemctl restart blind-chess after the script. Proper fix: change the script's enable --now to enable then restart.
  • VDJ-RIG port 80 has a Caddy (host-routing): curl http://chess.local/ serves the app correctly, but curl http://localhost/ returns a Caddy 502. CLAUDE.md's chess.local operations note says "no Caddy" — that note is stale or incomplete. Doesn't affect the instance; verify rig deploys via the chess.local hostname, not localhost.

Environment State

  • Tools/Services: pnpm workspace; gitea CLI for push. Subagent-driven development for execution.
  • Active Processes: none. No dev servers left running.
  • Environment Variables: none added or changed.
  • 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 c01244c) · Repo: https://git.sethpc.xyz/Seth/blind_chess (main at c01244c)

Security Reminder: No credentials or secrets are included in this handoff.