fix(ui): iOS soft-keyboard shell alignment + hotseat relock on return
CI / changes (pull_request) Successful in 1s
CI / unit (pull_request) Has been skipped
CI / integration (pull_request) Has been skipped
CI / ui (pull_request) Successful in 1m6s
CI / conformance (pull_request) Successful in 10s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m43s

iOS Safari/WKWebView does not shrink the layout viewport for the soft
keyboard (Android Chrome does): the visual viewport shrinks AND offsets
down toward the focused field, and the values do not cleanly revert. The
pinned shell tracked only the height (--vvh), not the offset, so its
top-anchored content misaligned on iOS — empty space below the form,
worst on a repeat focus. Fix (one place; covers NewGame, Chat, Feedback):
- app.svelte.ts syncViewport: also mirror visualViewport.offsetTop into
  --vv-top, and scroll the focused field into view on the keyboard-open
  transition (iOS does not reliably scroll a pinned document to it).
- app.css: the pinned app-shell body follows top=--vv-top + height=--vvh
  (was inset:0), so it stays on the visible area.
- e2e/viewport.spec.ts emulates the visual-viewport resize+offset (a fake
  window.visualViewport) to verify the shell follows, on Chromium+WebKit.

Hotseat: returning to the lobby now re-locks the current seat (its PIN is
re-prompted) — LocalSource.relock, cleared in Game.svelte onDestroy.
Root-caused a crash it exposed: a $props() value (id) reads back undefined
during Svelte teardown, so isLocalGameId(id) threw and aborted onDestroy
(breaking navigation) — read the id from the loaded view instead.
This commit is contained in:
Ilia Denisov
2026-07-07 14:10:57 +02:00
parent c1ac77bc6a
commit beda6ccd3d
7 changed files with 140 additions and 13 deletions
+4
View File
@@ -676,6 +676,10 @@
void source.draftSave(id, serializeDraft(rackIds, placement.pending)).catch(() => {});
}
localUnsub?.();
// Leaving a hotseat game re-locks the current seat, so returning from the lobby re-prompts its
// PIN. Read the id from the loaded view, NOT the `id` prop: a $props() value reads back undefined
// during Svelte teardown, and isLocalGameId(undefined) would throw and abort this cleanup.
if (view?.game.hotseat) localSource.relock(view.game.id);
});
function onCell(row: number, col: number) {