fix(ui): advance turn glow/interactivity to the player to move

`active` was `$derived(gameStore.activeBoards)`, and that getter read the
non-`$state` `#game`. The $derived captured no reactive dependency, so the
glowing/interactive boards froze on North's NW+NE after move 1: the next
player couldn't act, clicks on their boards did nothing, and selections fired
on unexpected boards — the reported "turns", "what piece is selected", and
"mirrored movements" symptoms were all this one stale-derived bug.

Fix: derive `active` from the reactive `view.currentPlayer`; remove the
footgun `activeBoards` getter so nothing re-reads `#game` reactively.

Add scripts/smoke.py: Playwright end-to-end test driving the real app through
a full N->S->E->W round (engine vitest can't see Svelte reactivity wiring).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
claude (duplicate_chess)
2026-06-26 09:36:59 -04:00
parent 294a786336
commit a44b4de717
4 changed files with 96 additions and 7 deletions
+4 -1
View File
@@ -15,7 +15,10 @@
const BOARD_IDS: BoardId[] = ['NW', 'NE', 'SW', 'SE'];
let view = $derived(gameStore.view);
let active = $derived(gameStore.activeBoards);
// Derive from the reactive `view`, NOT the store's #game getter: #game is
// deliberately not a $state proxy, so a $derived reading it never recomputes
// and the turn glow/interactivity froze on North after move 1.
let active = $derived(PLAYER_BOARDS[view.currentPlayer]);
/** Ghost squares for a given board. */
function ghostsFor(id: BoardId): Square[] {
-6
View File
@@ -3,7 +3,6 @@ import { legalSyncedMoves, selectionHighlight, type SelectionHighlight } from '.
import { ghosts } from '../../engine/ghosts';
import { evaluateStatus } from '../../engine/endgame';
import { serialize, deserialize } from '../../engine/notation';
import { PLAYER_BOARDS } from '../../engine/boards';
import type {
BoardId, Player, Square, SyncMove, HistoryEntry, GhostMarker, GameStatus,
} from '../../engine/types';
@@ -54,11 +53,6 @@ class GameStore {
return this.scrubPly !== null;
}
/** Which boards belong to the player to move (for the turn glow). */
get activeBoards(): [BoardId, BoardId] {
return PLAYER_BOARDS[this.#game.currentPlayer];
}
/** Grab a piece: must be the current player's turn and a live (non-scrub) view. */
select(square: Square): void {
if (this.isScrubbing) return;