From 59717b3b5bb6e8e4a84ecf6b2fdf856638a65a2a Mon Sep 17 00:00:00 2001 From: "claude (blind_chess)" Date: Mon, 18 May 2026 20:51:37 -0400 Subject: [PATCH] docs: amend plan to reflect code-review fixes Tasks 8/10/11 received review fixes during execution; the plan's code blocks are updated to match what shipped: - Task 8: drag controller handles pointercancel + idempotent start. - Task 10: palette pieces are plain spans + svelte-ignore (no focusable-but-not-operable role/tabindex). - Task 11: phantom-load effect keyed on gameId; drag ghost gated. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../2026-05-18-table-fidelity-features.md | 41 ++++++++++++++----- 1 file changed, 30 insertions(+), 11 deletions(-) diff --git a/docs/superpowers/plans/2026-05-18-table-fidelity-features.md b/docs/superpowers/plans/2026-05-18-table-fidelity-features.md index 343a8c3..3890cdd 100644 --- a/docs/superpowers/plans/2026-05-18-table-fidelity-features.md +++ b/docs/superpowers/plans/2026-05-18-table-fidelity-features.md @@ -873,6 +873,12 @@ function makeDrag() { let suppressClickOn: Square | null = null; const THRESHOLD = 6; + function detach() { + window.removeEventListener('pointermove', onMove); + window.removeEventListener('pointerup', onUp); + window.removeEventListener('pointercancel', onCancel); + } + function onMove(e: PointerEvent) { if (!state.active) return; state.x = e.clientX; @@ -882,15 +888,23 @@ function makeDrag() { } } + // pointercancel fires instead of pointerup when the browser/OS takes over + // the gesture (common on touch). Abort the drag: clean up, drop nothing. + function onCancel() { + detach(); + state.active = null; + state.moved = false; + } + function onUp(e: PointerEvent) { - window.removeEventListener('pointermove', onMove); - window.removeEventListener('pointerup', onUp); + detach(); const src = state.active; const wasDrag = state.moved; state.active = null; state.moved = false; if (!src || !wasDrag) return; // a tap — the board click handler deals with it + // elementFromPoint returns null off-viewport — treated as an off-board drop. const el = document.elementFromPoint(e.clientX, e.clientY); const sqEl = el?.closest('[data-square]') as HTMLElement | null; const target = sqEl?.dataset.square as Square | undefined; @@ -907,6 +921,7 @@ function makeDrag() { } function start(src: DragSource, e: PointerEvent) { + detach(); // idempotency — drop any listeners from an unfinished prior drag suppressClickOn = null; state.active = src; state.x = startX = e.clientX; @@ -914,6 +929,7 @@ function makeDrag() { state.moved = false; window.addEventListener('pointermove', onMove); window.addEventListener('pointerup', onUp); + window.addEventListener('pointercancel', onCancel); } /** The board calls this first in its square-click handler. */ @@ -1095,11 +1111,11 @@ Create `packages/client/src/lib/PhantomPalette.svelte`: Drag onto your board — your guess of where the opponent is.
{#each TYPES as t (t)} + + { e.preventDefault(); phantomDrag.start({ kind: 'palette', type: t }, e); }} >{pieceGlyph({ color: oppColor, type: t })} {/each} @@ -1183,14 +1199,17 @@ After the existing `const turnLabel = ...` derived block, add: return a && phantomDrag.state.moved ? a.type : null; }); - // Load the phantom layer once `you` is known (blind games only). - let phantomsLoaded = $state(false); + // Load the phantom layer when `you` is known (blind games only). Keyed on + // gameId — like the connection effect — so it reloads if this + // instance is reused for a different game without a remount. + let loadedFor: string | null = $state(null); $effect(() => { - if (phantomsLoaded) return; + const id = gameId; const you = game.state.you; + if (loadedFor === id) return; if (you && game.state.mode === 'blind') { - untrack(() => phantoms.loadForGame(gameId, you)); - phantomsLoaded = true; + untrack(() => phantoms.loadForGame(id, you)); + loadedFor = id; } }); @@ -1234,7 +1253,7 @@ Replace the `
...
` block with: Immediately after the closing `
` of `
` (and before the `{#if pendingPromotion ...}` block), add: ```svelte -{#if dragGhost} +{#if phantomLayerEnabled && dragGhost}