Stage 7 polish: game rework + board zoom + tests (Parts D/E/F/I)

- Board: fixed-viewport transform-scale zoom (animated) with counter-scaled cqw labels, corner letters, bonus-label modes (boardlabels), contrasting grid lines
- Game: Screen shell + game tab-bar (Draw/Skip/Hint/Shuffle) via HoldConfirm popovers; MakeMove 🏁 + compact popup; rack collapses used slots; hint places tiles on board (placementFromHint) + no_hint_available toast; Scores:N replaces Hints; history slide-down (swipe/click, scroll-locked); check-word alphabet/length limit + in-memory cache + 5s throttle
- backend: no_hint_available result code split + test
- vitest: banner rotator + linkify, resultBadge, boardlabels, placementFromHint (29 tests); Playwright smoke updated; prod bundle ~74 KB gzip
This commit is contained in:
Ilia Denisov
2026-06-03 13:33:03 +02:00
parent 38be7fea96
commit 2c96c19aac
12 changed files with 621 additions and 287 deletions
+17
View File
@@ -4,6 +4,7 @@ import {
direction,
newPlacement,
place,
placementFromHint,
rackView,
recallAt,
reset,
@@ -62,3 +63,19 @@ describe('placement state machine', () => {
expect(toSubmit(newPlacement(rack))).toBeNull();
});
});
describe('placementFromHint', () => {
it('maps hint letters and blanks onto rack slots', () => {
const p = placementFromHint(
[
{ row: 7, col: 7, letter: 'C', blank: false },
{ row: 7, col: 8, letter: 'A', blank: false },
{ row: 7, col: 9, letter: 'B', blank: true },
],
['C', 'A', BLANK, 'T'],
);
expect(p.pending).toHaveLength(3);
expect(p.pending[0]).toMatchObject({ rackIndex: 0, letter: 'C', blank: false });
expect(p.pending[2]).toMatchObject({ rackIndex: 2, letter: 'B', blank: true });
});
});