feat(ui): on-device move preview (local eval) with network fallback
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 9s
CI / integration (pull_request) Successful in 17s
CI / ui (pull_request) Successful in 58s
CI / conformance (pull_request) Successful in 8s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m18s

Score and validate a tentative move on-device instead of a per-arrangement
network round trip. The dawg reader and the validate/score/direction slice of the
scrabble-solver engine are ported to TypeScript (ui/src/lib/dict), pinned
byte-for-byte to the Go engine by a new `conformance` CI job. The server stays
authoritative — submit_play re-validates — so the local result is an advisory
accelerator only; any cache miss, storage eviction or a bad-connection breaker
falls back to the network evaluate.

- backend: Registry.DictBytes + an authed GET /api/v1/user/dict/{variant}/{version}
  (immutable) streaming the pinned per-game dawg.
- gateway: a session-gated /dict edge route proxying it; fetchDict on the transport.
- client: an IndexedDB blob cache (best-effort, storage.persist()) + a loader
  (memory -> IndexedDB -> network, session-scoped bad-connection breaker) + a lobby
  prefetch (your-turn first); an adapter over the existing premiums/alphabet; a
  DictWarmup overlay while a cold dictionary loads (120ms flash-guard, 5s cap ->
  network); a ?nolocal flag; the DebugPanel reset also clears the dict cache.
- parity: generators backend/cmd/{dictgen,validategen} + gated Vitest suites, run
  in CI against the release dictionaries.
- docs: ARCHITECTURE §5, TESTING, UI_DESIGN, FUNCTIONAL (+ru).
This commit is contained in:
Ilia Denisov
2026-07-01 20:13:01 +02:00
parent f0399e1bbc
commit d24a127406
38 changed files with 2375 additions and 11 deletions
+16 -2
View File
@@ -85,5 +85,19 @@ export function centre(variant: Variant): { row: number; col: number } {
return { row: 7, col: 7 };
}
// Tile values and the per-variant alphabet now arrive from the server (lib/alphabet.ts);
// the board geometry above is all this module owns.
/** RACK_SIZE is the number of tiles drawn to a full rack — 7 for every variant. It sizes
* the all-tiles (bingo) bonus test in the local move preview. */
export const RACK_SIZE = 7;
/** BINGO is the all-tiles bonus per variant, ported from scrabble-solver/rules/rules.go:
* 50 for the Scrabble variants, 15 for Эрудит. The local move preview adds it when a play
* uses the whole rack. The dict conformance test pins these to the engine's rulesets. */
export const BINGO: Record<Variant, number> = {
scrabble_en: 50,
scrabble_ru: 50,
erudit_ru: 15,
};
// Tile values and the per-variant alphabet arrive from the server (lib/alphabet.ts); the
// board geometry, centre, rack size and bingo above are the static per-variant constants
// this module owns (all ported from scrabble-solver/rules/rules.go).