diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index e00c45f..88f7760 100644 --- a/.claude/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -59,14 +59,20 @@ The native Android app (`ANDROID_PLAN.md`) is a Capacitor 8 wrapper of the `ui` On Android **WebView < 140**, `env(safe-area-inset-*)` wrongly reports **0**, so chrome relying on it draws under the bars and is untappable: the top nav under the clock, and the game's bottom action bar's **centre** button (Hint) under the home-indicator pill (side buttons still work; `navigation_mode`=2 is gesture nav). - Fix is CSS-only: Capacitor 8's **SystemBars** plugin (built into `@capacitor/core`, `insetsHandling:'css'` - default — no dep, no config) injects correct `--safe-area-inset-*`; consume them as - `--tg-safe-*: var(--safe-area-inset-*, env(safe-area-inset-*, 0px))` (`ui/src/app.css`). **Reproduces ONLY on - WebView < 140** — an emulator whose WebView auto-updated (Play Store) to ≥140 (e.g. Chrome 149) will NOT - show it (env works there), so verify via the injected var, not the visual. **Inspect a live debug WebView** - over CDP: `adb forward tcp:9222 localabstract:$(adb shell cat /proc/net/unix | grep -o - 'webview_devtools_remote_[0-9]*' | head -1)`, then Playwright `chromium.connectOverCDP('http://localhost:9222')` - → `page.evaluate` (read `--safe-area-inset-*` vs `env(...)`). + Fix is CSS-only, in TWO parts: (1) Capacitor 8's **SystemBars** plugin (built into `@capacitor/core`, + `insetsHandling:'css'` default — no dep, no config) injects correct `--safe-area-inset-*`; consume them as + `--tg-safe-*: var(--safe-area-inset-*, env(safe-area-inset-*, 0px))` (`ui/src/app.css`) — fixes any consumer + that ALREADY applies the token (the bottom bars: `Game.svelte`/`Screen.svelte` `--tg-safe-bottom`). + (2) But a consumer that never applied the top inset on the native path is NOT fixed by the token alone — the + **header's** top inset was Telegram-fullscreen-scoped only, so the native header sat under the status bar on + EVERY WebView; it needed its own `.bar { padding-top: calc(var(--safe-area-inset-top, 0px) + 5px) }` + (`Header.svelte`, native-only via the plugin var; tg-fullscreen still overrides via specificity). **Measure + per element, don't eyeball** — a centred title at y=11 under a 54px bar reads as "fine" in a screenshot but + is overlapping; an emulator WebView auto-updated to ≥140 (Chrome 149) also hides the `env()`=0 half (so the + BOTTOM looks fine there while the user's < 140 device overlaps). **Inspect a live debug WebView** over CDP: + `adb forward tcp:9222 localabstract:$(adb shell cat /proc/net/unix | grep -o 'webview_devtools_remote_[0-9]*' + | head -1)`, then Playwright `chromium.connectOverCDP('http://localhost:9222')` → `page.evaluate` (read each + element's `getBoundingClientRect().top`, and `--safe-area-inset-*` vs `env(...)`). - **`sharp` is whitelisted** in `ui/pnpm-workspace.yaml` (`allowBuilds: sharp: true`) — `@capacitor/assets` uses it for `pnpm android:assets` (launcher icon/splash); else pnpm 11 raises `ERR_PNPM_IGNORED_BUILDS`. - **Bundled offline dicts come from the `scrabble-dictionary` release** (`scrabble-dawg-.tar.gz`, diff --git a/ANDROID_PLAN.md b/ANDROID_PLAN.md index d03486b..4d183c8 100644 --- a/ANDROID_PLAN.md +++ b/ANDROID_PLAN.md @@ -441,8 +441,10 @@ airplane mode): cold-boot → offline guest lobby, offline vs_ai played end-to-e placed "FEZ", the robot replied "NEEDFIRE"), New Game offers both modes. The smoke surfaced a native-chrome bug on **Android 15+ edge-to-edge** (WebView < 140 reports `env(safe-area-inset-*)`=0 → the top nav draws under the status bar and the game's centre Hint button under the gesture-nav home indicator) — **fixed** by -consuming the SystemBars core plugin's injected `--safe-area-inset-*` in `app.css` (see `.claude/CLAUDE.md`; -no dep/config — the plugin ships in `@capacitor/core` v8). Remaining for D: the reconcile-online leg +consuming the SystemBars core plugin's injected `--safe-area-inset-*`: the bottom via the `--tg-safe-*` token +(`app.css`), and the header's top inset via a native-only `.bar` rule (`Header.svelte` — its top inset was +Telegram-fullscreen-scoped only, so the token alone did not reach it). No dep/config (the plugin ships in +`@capacitor/core` v8); full story in `.claude/CLAUDE.md`. Remaining for D: the reconcile-online leg on-device (hits prod — a guest mint) and the deferred local-game-visibility decision (§G). **Verify every line ref below against current code.** diff --git a/ui/src/components/Header.svelte b/ui/src/components/Header.svelte index 3e385f2..c22edad 100644 --- a/ui/src/components/Header.svelte +++ b/ui/src/components/Header.svelte @@ -101,6 +101,11 @@ align-items: center; gap: var(--gap); padding: 5px var(--pad); + /* Clear the top safe area on the native build: under Android 15+ edge-to-edge the WebView draws + behind the status bar, so the header row must drop below it. --safe-area-inset-top is injected by + the Capacitor SystemBars plugin (native only — unset, so 0, on web / PWA / Telegram / VK, which do + not draw behind our header). Telegram fullscreen overrides this with its own nav-band rule below. */ + padding-top: calc(var(--safe-area-inset-top, 0px) + 5px); } h1 { font-size: 1.05rem;