# Phase 3: Mini App Shell + Geolocation - Context **Gathered:** 2026-10-22 **Status:** Ready for planning ## Phase Boundary The Telegram Mini App boots inside Telegram, validates its session through Phase 2's /api/session gate, acquires live location reliably across Android/iOS/Desktop/browser with a full fallback chain (Telegram-native → WebView → chat keyboard), and renders the user's position on the Rams basemap under Telegram light + dark themes. Every failure surface is deliberate (never blank); no coordinate persists anywhere. BackButton + safe-area groundwork land here; the observable PX-01/PX-03 behaviors are Phase 6's. Search/routes/guidance are later phases. ## Implementation Decisions ### Mini App stack + repo layout - **D-01:** Frontend is TypeScript + Vite, **no UI framework** — MapLibre GL JS v6 bundled, interactivity via small focused modules (the mockups' pattern, productionized). This stack is the foundation Phases 4–6 build on. - **D-02:** App source lives in a `web/` npm workspace beside `theme/` and `bot/`; root package.json gains `web:*` delegate scripts. Theme is consumed as a **build-time dependency** (`theme/dist/rams.css` imported into the Vite build, not via CDN) — completing Phase 1's self-hosted/CDN-free decision for the app path. - **D-03:** Post-initData validation, the session token is held **memory-only** (module scope), never written to localStorage/sessionStorage/cookies. Revalidation happens on every fresh boot (Telegram revives webviews) and whenever the 900s TTL stalls mid-session. Research may stress-test this invariant across webview reload quirks, but the invariant itself is locked. ### Location acquisition strategy - **D-04:** Primary chain: Telegram-native `WebApp.getLocation()` first (Android LocationManager with version-gated paths), fall back to `navigator.geolocation` inside the WebView, then the chat-keyboard "send location" fallback (bot-side; app consumes it). Research details the Android stale-fix and iOS WKWebView quirks — the CHAIN is locked, the per-link mechanics are research's. - **D-05:** Cadence is **gated streaming**: one-shot fixes during browsing/search; continuous watch only during guidance (Phase 6 builds the streaming state machine). Map recenters on each fix; the position marker always shows the freshest fix. - **D-06:** Denied/no-geolocation presents a deliberate Rams-styled denied state — explains why location matters, offers retry, points to Telegram's attach → Location path as the manual fallback (GEO-02). No silent banners. - **D-07:** Coordinates live in **memory only** while the app runs. Zero persistence across every storage API (localStorage, sessionStorage, cookies, IndexedDB, service-worker caches). This zero-persistence assertion is part of the phase's GEO-03 spot-check, stress-tested by research against every storage surface the app touches. ### Shell UX + screens - **D-08:** Phase 3's screen surface is map-first and single: full-bleed Rams basemap, position dot, follow/recenter control, minimal status strip reflecting session/location state. Route lines, layer toggles, search furniture belong to later phases — nothing placeholder-shaped. - **D-09:** Boot choreography has staged deliberate states: validating session → acquiring location → live — each Rams-styled, none blank (extends the neutral-denied rule from PX-02). - **D-10:** Map survives theme flips Rams-constant (Phase 1 D-04 slots pay off); only shell surfaces re-theme via the Telegram theme params. Safe-area handling + BackButton wiring land here as infrastructure. ### Dev/test harness - **D-11:** A **Mock-Telegram harness** for the daily loop: a local module injecting a fake `window.Telegram.WebApp` (dev-signed initData, BackButton events, theme params with light/dark switch, sample location streams) plus a runner page booting the real app off-Telegram in a plain browser. Real-device batteries are spent at the device-matrix exit gate, not during daily dev. - **D-12:** Local dev topology is **wrangler dev** on 8787 serving web/dist statics + `/api/*` + webhook with local KV simulation; remote staging used sparingly right before real-device verification sessions. ### Worker static-asset wiring - **D-13:** Statics served by the **Workers `[assets]` binding** (wrangler config), from `web/dist`'s build output; Worker fetch still owns `/api/*` + webhook first. Completes the one-deployable architecture from the roadmap. - **D-14:** Build inlines everything the app needs: CSS from theme, MapLibre, basemap style JSON (from `theme/basemap/liberty-rams.json`), self-hosted fonts copied at build time into `web/dist`. Tiles remain OpenFreeMap's endpoint at runtime; no CDN fallbacks. - **D-15:** Deployment stays one command (`npm run deploy` = build web → wrangler deploy with assets). Same ergonomics as Phase 2. Hashed filenames from Vite play naturally with Cloudflare's asset caching. ### OpenCode's Discretion - Exact per-link mechanics of the location chain (Telegram API calls, permission bridging) — research's domain - Boot-state copy, exact mock initData signing mechanics, recenter-toggle visual details (within the Rams system) - `assets` config tuning (binding name, not_found_handling, asset prefixes) - How the reserved exit-gate Playwright matrix extends theme/scripts/screenshot.mjs ## Specific Ideas - The app should feel like the navigate_map_overview mockup distilled: calm, map-dominant, nothing decorative. - Allowed fallbacks compose as a chain, not a menu — user should perceive one honest app. - Mock harness borrow: dev initData signing already exists conceptually in bot/scripts (HTTP world), and the hand-rolled test runner pattern (theme) prevails. ## Canonical References **Downstream agents MUST read these before planning or implementing.** ### Phase scope + decisions - `.planning/ROADMAP.md` §Phase 3 — goal, 5 success criteria incl. device-matrix exit, research recommendation - `.planning/REQUIREMENTS.md` §GEO-01/02/03, PX-02 (this phase's requirements) - `.planning/phases/02-bot-skeleton-initdata-security/02-CONTEXT.md` — identity floor /api/session contract details (opaque token, 900s TTL, rotation) - `.planning/phases/01-design-tokens-rams-foundation/01-CONTEXT.md` — theme package contract (Rams-constant map, D-04 theme slots) ### Codebase - `theme/dist/rams.css`, `theme/src/components.css`, `theme/src/tokens.css` — consumption surface - `theme/basemap/liberty-rams.json` — the map style to load - `bot/src/webhook.ts`, `bot/src/kv/sessions.ts` — the /api/session contract the web app calls - `stitch_minimalist_route_navigator/navigate_map_overview/code.html` — layout reference (read-only) - `AGENTS.md` — repo conventions ## Existing Code Insights ### Reusable Assets - `theme/dist/rams.css` + `components.css` — the app imports these directly (build-time) - `theme/basemap/liberty-rams.json` + `build.mjs` — map style + derivation upstream of a loaded style object - `theme/fonts/*` self-hosted woff2 — copied at build time into web/dist - `theme/scripts/screenshot.mjs` — Playwright toolchain for exit-gate device-matrix extension - Phase 2's `/api/session` — the auth endpoint, deploy scripts + .dev.vars conventions ### Established Patterns - npm workspaces with delegate scripts (theme/, bot/) - Hand-rolled node test runner (audit.test.mjs shape) - TDD discipline; zero-persistence privacy invariant (KV TTL-only) ### Integration Points - web/dist → wrangler `[assets]` binding on the existing `araharah-bot` Worker - `WebApp.getLocation()` / navigator.geolocation → module that also feeds Phase 5's route snapping and Phase 6's guidance loop - BackButton jumps to full state graph in Phase 6 ## Deferred Ideas - Scripted Playwright device-matrix spec (mock-based) — extend theme/scripts toolchain at the exit gate - Placeholder-shaped search drawer / route furniture — later phases by design - Real-device TestFlight/Play Beta channels — out of scope; device-matrix exit pass suffices for v1 --- *Phase: 03-mini-app-shell-geolocation* *Context gathered: 2026-10-22*