- NewGame: all three forms (quick / friends / hotseat) now use the
Profile sub-view's natural-flow .page (no forced height, no pinned-CTA
.grow/.fg scroll), so the screen's single .content scroll + --vvh handle
overflow and a focused name input scrolls into view cleanly — fixes the
keyboard blank-space / deep-scroll regression. One layout rule, one place.
- PinPad: pause 250ms after the 4th digit before the verdict, so the fill
(and the shake on a wrong PIN) reads as a deliberate response.
- e2e: typePin waits for empty dots before typing (robust to the pause).
Owner review of the pass-and-play PR:
- Roster delete (bug): a correct host PIN now ARMS a delete cross next to
the row (mirroring the lobby delete) instead of removing it silently;
tapping the kebab again clears the host authorisation.
- Variant picker (UX): replace the dropdown with the Quick-Match variant
plaques + the multiple-words toggle, one-to-one.
- Keyboard layout (UX): the roster sits in a scroll region with the Start
button pinned (friends-form pattern), so a name input's soft keyboard
shrinks the region instead of leaving a full-height blank spacer painted
behind the keyboard.
- e2e: variant via plaque + a row-delete (arm then cross) regression step.
PWA stale-version (pre-existing, same PR):
- sw.ts: register the network-first NavigationRoute BEFORE precacheAndRoute.
Workbox matches in registration order and the precache route (directoryIndex
index.html) shadowed the shell navigation, serving it cache-first — the old
build's __APP_VERSION__ until a second load. Fixes both the maintenance
self-reload and a cold open after a deploy. Doc updated (ARCHITECTURE).
Add the Playwright mock e2e for offline pass-and-play (Chromium+WebKit):
create a 2-seat hotseat with a PIN-locked seat, verify the rack is
withheld, unlock (wrong then right PIN), host-skip the current turn, and
terminate from the lobby behind the master PIN.
The e2e surfaced a persistence bug: the roster PINs are $state proxies,
which fail structured-clone on the IndexedDB put (silently — best-effort
store), so a created hotseat game vanished on reload. Snapshot the seats
+ host PIN to plain objects at the create() call site.