Make ANDROID_PLAN.md self-contained so a fresh session resumes Stage D from the repo
alone. Records:
- the done foundations (D.1 bundled dicts + loader tier, D.2 local-guest identity) with
their exact modules and the committed checkpoint (bcd5a1d);
- the session decisions (owner-approved): the blocking Login is bypassed on native only,
soft registration reuses the Profile screen, the Telegram/VK link buttons are hidden on
native (VK's web redirect strands the Capacitor app; native tg/vk is a later stage),
local-guest name = localized common.guest;
- the native-gated loader-tier correction (a web `./dict/` fetch would hit the gateway's
own session-gated /dict/ route, so the bundled tier is only tried on native);
- the detailed remaining path — D.3 boot rewrite with the exact blocking-login site
(app.svelte.ts ~989-991), D.4 reconciliation + the silent seam wiring, D.6 Profile
tg/vk gating — plus the offline-first e2e strategy (simulate native by injecting
window.Capacitor) and its initNativeShell/@capacitor/app gotcha.
Introduce a minimum-supported-client gate so a future incompatible wire change
can turn away installed builds too old to speak it, cleanly, instead of letting
them crash on decode. It rides the outermost stable layer (an HTTP header), never
the FlatBuffers payload.
Gateway:
- New internal/clientver: dependency-free parse + compare of the leading
MAJOR.MINOR.PATCH (a git-describe suffix is tolerated).
- GATEWAY_MIN_CLIENT_VERSION config (empty => gate dormant; validated at load).
- connectsrv checks the X-Client-Version header before decoding the payload:
Execute returns result_code="update_required" (before the registry lookup),
Subscribe returns FailedPrecondition. It fails open on an absent or garbled
header — the header is a client-controlled compatibility signal, not an access
control.
Client:
- Attach X-Client-Version on every call.
- A terminal update.svelte.ts store + a non-dismissable UpdateOverlay (native
opens VITE_STORE_URL, web reloads); retry.ts maps FailedPrecondition to the
update_required sentinel; a mock __update hook drives the e2e.
Wire-additive and contour-safe: no FBS/proto regen, no schema migration; the gate
stays dormant until GATEWAY_MIN_CLIENT_VERSION is deliberately set, so web / VK /
Telegram behaviour is unchanged. The silent reconciliation seam is deferred to the
offline-first work (its only caller). Tests: Go clientver/config/connectsrv gate
tests, retry.test.ts, Playwright update.spec.ts.
Make the web SPA behave correctly inside the Capacitor native shell, where the
bundle loads from a local origin:
- New lib/origin.ts gatewayOrigin(): absolute URLs resolve to VITE_GATEWAY_URL on
native (the finished-game export/share URL in Game.svelte and the Wallet
site-root link), falling back to the page origin on web. transport.ts already
resolved via VITE_GATEWAY_URL and is left as-is.
- Skip the PWA service worker on the native channel (the assets are already local;
a worker would risk serving stale content across store updates).
- Hide the money-purchase UI in the MVP: new distribution.purchasesHidden() folds
VITE_PAYMENTS_DISABLED and the Google Play flag. The Wallet "buy" tab shows a
neutral, pointer-free note (wallet.purchasesSoon) for the RuStore MVP and keeps
the RuStore stub Google-Play-only. A ?nopay mock force mirrors ?gp for the e2e.
- Native env types in vite-env.d.ts.
Client-only, contour-safe: no wire/proto/schema change; web/VK/Telegram unchanged.
Tests: origin + purchasesHidden unit tests, a ?nopay wallet e2e. svelte-check
clean, vitest green, web + native vite build clean.
Generate the Android launcher/adaptive icons + splashes with capacitor-assets from ui/assets/icon.png (the existing maskable brand mark upscaled 512→1024) as a placeholder until the mandatory single-vector icon rebrand recorded in ANDROID_PLAN.md. The adaptive icon insets the foreground 16.7% into the safe zone; the AndroidManifest is untouched (its icon refs already point at @mipmap).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Correct ANDROID_PLAN.md: bundled offline dictionaries come from the scrabble-dictionary release (DICT_VERSION), not scrabble-solver; pin the toolchain to Capacitor 8 (compileSdk/targetSdk 36, minSdk 24, JDK 21 — @capacitor/android compiles at Java 21). Add a Progress section marking the scaffolding milestone done, and capture the native-Android build recipe + toolchain gotchas in .claude/CLAUDE.md so a fresh session resumes without re-deriving them.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>