UI: fix last-move highlight, localize move history, clamp zoom overscroll
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Has been skipped
CI / integration (pull_request) Has been skipped
CI / ui (pull_request) Successful in 41s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 57s

- Highlight tracks the last move overall (not the last word): a trailing
  pass/exchange now highlights nothing, so the board no longer lights up the
  opponent's old word after our own empty move.
- Make the highlight event-driven: refreshed only on a real game event
  (open/refresh, opponent move, our own committed move) and dismissed the moment
  composing starts, so recalling a just-placed tile never re-triggers it.
- Localize non-play move-history labels via new move.* catalog keys
  (pass/exchange/resign/timeout); the label printed the raw English action.
- Clamp the zoomed board's pan at its edge (overscroll-behavior: none), removing
  the native rubber-band past the content.

Tests: lastMoveCells unit coverage (trailing pass/exchange -> empty), i18n RU
label assertions, an e2e overscroll-contract check on the zoomed viewport.
This commit is contained in:
Ilia Denisov
2026-06-11 18:50:10 +02:00
parent 5c8b8bf658
commit ac29dca865
8 changed files with 130 additions and 43 deletions
+8 -7
View File
@@ -3,7 +3,7 @@
// the dictionary-independent history invariant (ARCHITECTURE §9.1): apply each play's
// placed tiles onto an empty grid.
import type { MoveRecord, Tile } from './model';
import type { MoveRecord } from './model';
import { BOARD_SIZE } from './premiums';
export interface BoardCell {
@@ -36,10 +36,11 @@ export function replay(moves: MoveRecord[]): Board {
return b;
}
/** lastPlayTiles returns the tiles of the most recent play (for highlighting). */
export function lastPlayTiles(moves: MoveRecord[]): Tile[] {
for (let i = moves.length - 1; i >= 0; i--) {
if (moves[i].action === 'play') return moves[i].tiles;
}
return [];
/** lastMoveCells returns the cells of the last move's tiles (as "row,col" keys), but only
* when that move placed tiles (a play). A trailing pass/exchange/resign/timeout highlights
* nothing — the recent-move highlight tracks the last move overall, not the last word. */
export function lastMoveCells(moves: MoveRecord[]): Set<string> {
const last = moves.length ? moves[moves.length - 1] : null;
if (!last || last.action !== 'play') return new Set();
return new Set(last.tiles.map((t) => `${t.row},${t.col}`));
}