build(ui): move the rule's played-word set into the lazy dict chunk
CI / changes (pull_request) Successful in 3s
CI / unit (pull_request) Successful in 11s
CI / integration (pull_request) Successful in 19s
CI / ui (pull_request) Successful in 1m16s
CI / conformance (pull_request) Successful in 10s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 2m27s

Building the set of words a game has already used belonged with the rest
of the rule, behind the same lazy import as the evaluator that consumes
it, rather than in the always-loaded game screen.

What is left in the app entry — the per-game flag on the decoded GameView
and the caption that names a rejected word — costs more than the budget
had free, so raise it by the usual kilobyte with the reason recorded
alongside the earlier raises.
This commit is contained in:
Ilia Denisov
2026-07-27 19:19:26 +02:00
parent 4f4768b092
commit e011b2ebe5
4 changed files with 26 additions and 15 deletions
+4 -13
View File
@@ -276,18 +276,6 @@
handleError(e);
}
}
// playedWords is the no-repeat-words rule's lookup set for the on-device preview: every word this
// game has already formed, as the history carries them (decoded, lower-case). Only a game that
// takes the rule needs it; for any other the preview scores unrestricted, so it returns undefined.
function playedWords(): Set<string> | undefined {
if (!view?.game.noRepeatWords) return undefined;
const played = new Set<string>();
for (const m of moves) {
if (m.action === 'play') for (const w of m.words) played.add(w);
}
return played;
}
let draftSaveTimer: ReturnType<typeof setTimeout> | null = null;
// scheduleDraftSave persists the composition (rack order + pending tiles) after a short
// debounce; best-effort, so a failed save never interrupts play. The cache is updated at once
@@ -845,7 +833,10 @@
const reader = d.peekDawg(v.game.variant, v.game.dictVersion);
if (reader && hasAlphabet(v.game.variant)) {
try {
preview = d.evaluateLocal(reader, v.game.variant, board, sub.tiles, v.game.multipleWordsPerTurn, playedWords());
// Under the no-repeat-words rule the evaluator also needs the words this game has
// already formed; both it and that set live behind the same lazy import.
const played = v.game.noRepeatWords ? d.playedWordsFromHistory(moves) : undefined;
preview = d.evaluateLocal(reader, v.game.variant, board, sub.tiles, v.game.multipleWordsPerTurn, played);
notePreviewLocal();
return;
} catch {
+1
View File
@@ -3,5 +3,6 @@
// of the initial app bundle — it is a progressive enhancement over the network
// preview, which remains the fallback.
export { evaluateLocal } from './eval';
export { playedWordsFromHistory } from './norepeat';
export { getDawg, peekDawg, hasDawg, dictLoadingDisabled, noteDictMiss, clearDictInstances, bustDictHttpCache } from './loader';
export { clearDictCache, listCachedDicts } from './store';
+15
View File
@@ -14,6 +14,7 @@
// variant, so a game started before the rule existed plays on without it.
import type { Move } from './validate';
import type { MoveRecord } from '../model';
/**
* wordKey identifies a word for the rule. Words are alphabet-index letters, so the key ignores
@@ -36,6 +37,20 @@ export function playedWordKeys(words: Iterable<readonly number[]>): Set<string>
return played;
}
/**
* playedWordsFromHistory is the rule's lookup set for a game whose history the server decoded for
* us: every word its plays have formed, main words and cross-words alike, in the same lower-case
* form evaluateLocal decodes a candidate word into. It lives here, with the rest of the rule and
* behind the same lazy import as the evaluator, rather than in the game screen.
*/
export function playedWordsFromHistory(moves: readonly MoveRecord[]): Set<string> {
const played = new Set<string>();
for (const m of moves) {
if (m.action === 'play') for (const w of m.words) played.add(w);
}
return played;
}
/**
* noRepeatScore applies the rule to an already validated move against the set of words already
* played. It returns null when the move's main word is one of them — the play is illegal — and