feat(offline): implicit net-state model, two-tier version gate, unified lobby #249
+16
-1
@@ -51,7 +51,22 @@ The native Android app (`ANDROID_PLAN.md`) is a Capacitor 8 wrapper of the `ui`
|
|||||||
- **Emulator smoke:** existing AVDs `Pixel_10` / `Pixel_4_Android_10_API_29` / `Pixel_Android_9`;
|
- **Emulator smoke:** existing AVDs `Pixel_10` / `Pixel_4_Android_10_API_29` / `Pixel_Android_9`;
|
||||||
`emulator -avd <name>`, `adb install -r <apk>`, `adb shell monkey -p ru.eruditgame.app -c
|
`emulator -avd <name>`, `adb install -r <apk>`, `adb shell monkey -p ru.eruditgame.app -c
|
||||||
android.intent.category.LAUNCHER 1`, `adb exec-out screencap -p > x.png`. The boot wait needs `sleep`
|
android.intent.category.LAUNCHER 1`, `adb exec-out screencap -p > x.png`. The boot wait needs `sleep`
|
||||||
→ run it as a **background** Bash task (foreground `sleep` is blocked).
|
→ run it as a **background** Bash task (foreground `sleep` is blocked). Browsers/WebView are cached, so a
|
||||||
|
full offline vs_ai turn is drivable via `adb shell input tap` (tap through the first-run coachmark tour —
|
||||||
|
each tap advances one step — then Hint places a suggested word; commit → the robot replies).
|
||||||
|
- **Android 15+ edge-to-edge safe-area (WebView-version-dependent, bit me on API 37).** targetSdk 36 forces
|
||||||
|
edge-to-edge — the WebView draws behind the status bar (top) and the gesture-nav home indicator (bottom).
|
||||||
|
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(...)`).
|
||||||
- **`sharp` is whitelisted** in `ui/pnpm-workspace.yaml` (`allowBuilds: sharp: true`) — `@capacitor/assets`
|
- **`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`.
|
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-<DICT_VERSION>.tar.gz`,
|
- **Bundled offline dicts come from the `scrabble-dictionary` release** (`scrabble-dawg-<DICT_VERSION>.tar.gz`,
|
||||||
|
|||||||
+10
-4
@@ -435,10 +435,16 @@ and `ui/src/screens/NewGame.svelte:177-431`. This phase makes them reachable on
|
|||||||
with no network**.
|
with no network**.
|
||||||
|
|
||||||
**Status:** D.1–D.6 are **done & verified** (foundations committed `bcd5a1d`; boot + reconciliation +
|
**Status:** D.1–D.6 are **done & verified** (foundations committed `bcd5a1d`; boot + reconciliation +
|
||||||
Profile committed on `feature/android-native`, 2026-07-12), with `e2e/native.spec.ts` green locally
|
Profile + the Android edge-to-edge safe-area fix committed on `feature/android-native`, 2026-07-12), with
|
||||||
(chromium + webkit) and in CI. Remaining for D: the on-device emulator airplane-mode smoke (owner /
|
`e2e/native.spec.ts` green (chromium + webkit) AND an **on-device emulator smoke** (Pixel_10 / API 37,
|
||||||
stage G) and the deferred local-game-visibility decision (Progress §D / §G). **Verify every line ref
|
airplane mode): cold-boot → offline guest lobby, offline vs_ai played end-to-end (bundled dawg — Hint
|
||||||
below against current code.**
|
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
|
||||||
|
on-device (hits prod — a guest mint) and the deferred local-game-visibility decision (§G). **Verify every
|
||||||
|
line ref below against current code.**
|
||||||
|
|
||||||
**Decisions locked this session (owner-approved) — bake these before implementing:**
|
**Decisions locked this session (owner-approved) — bake these before implementing:**
|
||||||
- **The blocking Login is bypassed on native only.** Web / PWA / Telegram / VK keep the current
|
- **The blocking Login is bypassed on native only.** Web / PWA / Telegram / VK keep the current
|
||||||
|
|||||||
+13
-9
@@ -54,16 +54,20 @@
|
|||||||
/* Height Telegram's native nav overlays at the top in fullscreen; set from the SDK's
|
/* Height Telegram's native nav overlays at the top in fullscreen; set from the SDK's
|
||||||
content-safe-area inset, 0 elsewhere. */
|
content-safe-area inset, 0 elsewhere. */
|
||||||
--tg-content-top: 0px;
|
--tg-content-top: 0px;
|
||||||
/* Device safe-area top (the notch); inside Telegram TG's own nav controls sit between it and
|
/* Device safe-area top (the notch / status bar); inside Telegram TG's own nav controls sit between
|
||||||
--tg-content-top, so the in-app header aligns to that band. Inside Telegram these are set from
|
it and --tg-content-top, so the in-app header aligns to that band. Inside Telegram these are set
|
||||||
the SDK (lib/app.svelte.ts); elsewhere they fall back to the CSS env() safe area, so a VK Mini
|
from the SDK (lib/app.svelte.ts); on the native Capacitor build the value is the SystemBars core
|
||||||
App / Capacitor / PWA webview clears the device cut-outs too (a plain browser tab reports 0). */
|
plugin's injected --safe-area-inset-* — correct on EVERY Android WebView, including the < 140
|
||||||
--tg-safe-top: env(safe-area-inset-top, 0px);
|
versions where env(safe-area-inset-*) wrongly reports 0 under Android's mandatory edge-to-edge and
|
||||||
|
the app chrome would otherwise draw under the system bars. It falls back to the CSS env() safe area
|
||||||
|
on iOS / PWA / a plain browser tab (0 there). */
|
||||||
|
--tg-safe-top: var(--safe-area-inset-top, env(safe-area-inset-top, 0px));
|
||||||
/* Device safe-area bottom / sides (home indicator; landscape notch) — the screen pads its bottom
|
/* Device safe-area bottom / sides (home indicator; landscape notch) — the screen pads its bottom
|
||||||
and left/right edges by these so content clears the cut-outs (e.g. the VK mobile home bar). */
|
and left/right edges by these so content clears the cut-outs (e.g. the gesture-nav home indicator
|
||||||
--tg-safe-bottom: env(safe-area-inset-bottom, 0px);
|
or the VK mobile home bar). */
|
||||||
--tg-safe-left: env(safe-area-inset-left, 0px);
|
--tg-safe-bottom: var(--safe-area-inset-bottom, env(safe-area-inset-bottom, 0px));
|
||||||
--tg-safe-right: env(safe-area-inset-right, 0px);
|
--tg-safe-left: var(--safe-area-inset-left, env(safe-area-inset-left, 0px));
|
||||||
|
--tg-safe-right: var(--safe-area-inset-right, env(safe-area-inset-right, 0px));
|
||||||
--font: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial,
|
--font: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial,
|
||||||
"Noto Sans", "Liberation Sans", sans-serif;
|
"Noto Sans", "Liberation Sans", sans-serif;
|
||||||
--shadow: 0 1px 2px rgba(0, 0, 0, 0.08), 0 6px 16px rgba(0, 0, 0, 0.06);
|
--shadow: 0 1px 2px rgba(0, 0, 0, 0.08), 0 6px 16px rgba(0, 0, 0, 0.06);
|
||||||
|
|||||||
@@ -53,7 +53,8 @@
|
|||||||
place-items: center;
|
place-items: center;
|
||||||
/* Darker than the onboarding scrim — this blocks the board until the dictionary is ready. */
|
/* Darker than the onboarding scrim — this blocks the board until the dictionary is ready. */
|
||||||
background: rgba(0, 0, 0, 0.72);
|
background: rgba(0, 0, 0, 0.72);
|
||||||
padding: env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left);
|
/* Via the shared --tg-safe-* tokens so the Android WebView < 140 env() bug is covered (app.css). */
|
||||||
|
padding: var(--tg-safe-top) var(--tg-safe-right) var(--tg-safe-bottom) var(--tg-safe-left);
|
||||||
}
|
}
|
||||||
.box {
|
.box {
|
||||||
display: grid;
|
display: grid;
|
||||||
|
|||||||
Reference in New Issue
Block a user