diff --git a/.planning/phases/03-mini-app-shell-geolocation/03-DEVICE-MATRIX.md b/.planning/phases/03-mini-app-shell-geolocation/03-DEVICE-MATRIX.md new file mode 100644 index 0000000..48ac10f --- /dev/null +++ b/.planning/phases/03-mini-app-shell-geolocation/03-DEVICE-MATRIX.md @@ -0,0 +1,126 @@ +# Phase 03 — Device-Matrix Exit Gate (ROADMAP Success Criterion 5) + +**Deployed origin (staging):** https://araharah-bot.araharah-bot.workers.dev/ +**Deployed version:** 2c1336e3-58f9-4b0b-86a8-b25de66e327c (via `npm run deploy` — theme → web → wrangler deploy with `web/dist` as static assets, D-13/D-14/D-15) +**Status: PENDING REAL-DEVICE VERIFICATION — the phase does not close until every leg has an explicit verdict.** + +## Automated half (already green) + +- `npm test -w web` — 33 passed, 0 failed (includes BA1–BA6 build-artifact gates over `web/dist`, HTML included) +- `npm test -w bot` — 48 passed, 0 failed +- `npm run capture -w web` — CAPTURE OK, 7/7 non-blank captures off `web/dist-harness`, + including the Desktop denied card, the `?pin=1` chat-pin relay, light + dark live shots, + and the DSGN-02 side-by-side. GEO-03 runtime probe: localStorage / sessionStorage / + cookies / IndexedDB all empty after a real boot-to-live cycle. +- Live canaries at the deployed origin: `GET /` → 200 text/html from the asset binding; + `POST /api/session` → 401 for unsigned bodies (the HMAC gate is live); + `getWebhookInfo` URL ends `/telegram/webhook` with no `last_error`; + `getChatMenuButton` type = `commands`. + +## Bot API 10.2 origin prerequisite (hard, blocking) + +`wrangler.jsonc`'s `run_worker_first` key drew an "Unexpected fields" warning from wrangler +4.151.3, but the deployed behavior was verified request-by-request: assets serve non-API +paths directly, `/api/*` and `/telegram/*` are answered by the Worker code (401/500, not +asset fallback) — D-13 routing holds in practice; the warnings are cosmetic at this wrangler +version. + +- [ ] **BotFather Mini App URL == the deployed origin above.** Bot API 10.2 rejects Mini App + calls from any other origin silently — a mismatch means `Telegram.WebApp.*` no-ops with no + error. Check menu button (`/setmenubutton`) / WebApp URL in @BotFather + (`arasarah-bot.araharah-bot.workers.dev`), not a localhost or Pages URL. + Observed / Symptom: ______________________________________ + +## The six device legs (each: an explicit verdict + observation) + +### 1. Android — real phone, Telegram Android (GEO-01) + +- [ ] Boot: BOOT-01 (validating session) → BOOT-02 (acquiring location) → LIVE; the position + marker renders on the Rams basemap. +- [ ] Permission persistence: close the app fully, reopen — **no second permission prompt**. + The grant lives in Telegram's per-bot store on the device, outside the WebView. + Observed / Symptom: ______________________________________ + +### 2. Android stale-fix check (retires or confirms assumption A1) + +- [ ] With the app LIVE, walk several metres and re-check. If the pin does not move, toggle + the phone's Location setting once (off → on) and record whether `navigator.geolocation` + then delivered a fresh fix, and whether the FIRST Android verification needed that toggle. + Result decides: **A1 stands** (toggle required) or **A1 retired** (no toggle needed). + Observed / Symptom (needed toggle? y/n): ______________________________________ + +### 3. iOS — real phone, Telegram iOS (if available) + +- [ ] Boot to LIVE; marker renders; permission grant persists across a reopen. + Observed / Symptom: ______________________________________ + (If no iOS device is available: record `UNVERIFIED — no device`; Success Criterion 5 + stays open. Do not close the phase anyway.) + +### 4. Desktop Telegram (GEO-02 end-to-end) + +- [ ] Geolocation is absent by design (issue #77): the app shows the **denied card with + identical copy across all clients** (D-06, RESEARCH Pitfall 4) — no client-specific variant. +- [ ] Tap ASK VIA CHAT → POSTs `/api/location-fallback`; the location keyboard appears in the + user's own chat; tap it, share a live pin; the app picks it up via `GET /api/location` + (within its poll window) and reaches **LIVE**. This is GEO-02 end-to-end. + Observed / Symptom: ______________________________________ + +### 5. Plain browser (no Telegram context) + +- [ ] Open the deployed URL directly in a desktop browser. Expected: the **denied card** + renders over the Rams shell (no `initData` → the HMAC session gate 401s), NOT a blank + page, NOT the live branch. A blank page is a FAIL. + (Optional extra observation: if a browser geolocation prompt leads to LIVE, note it — + but the pass condition for this leg is the deliberate denied state.) + Observed / Symptom: ______________________________________ + +### 6. Theme flip mid-session (PX-02 — manual-only verification) + +- [ ] With the app LIVE on a real client, switch the Telegram theme **light ↔ dark + mid-session** without closing the app. Confirm: (a) shell chrome re-slots (document + background, shell ink swap); (b) the status strip stays matte-black; (c) **the map is + unchanged** — same style, no flash, no camera jump (D-10 / RESEARCH Pitfall 7). + Record a before/after screenshot in each direction and name the client used + (this leg can be folded into any of legs 1/3/4). + Verdict (chrome re-slotted? strip stayed black? map unchanged?): ______________________________________ + +## How to report back + +- Type **"device matrix passed"** if all six legs reached LIVE or the documented + denied-and-recovered state (the theme-flip leg counts as passed when the chrome + re-slotted mid-session and the map was visibly unchanged). +- Otherwise report which leg failed and the exact symptom — a `/gsd-debug` gets opened for it. +- If no physical Android/iOS device is available: report that. The honest outcome is + Success Criterion 5 = UNVERIFIED; the phase is held open with the blocker recorded and + either a device acquired or the matrix explicitly deferred to Phase 7 hardening with + GEO-01/GEO-02 noted as unverified on real hardware. Never close the phase claiming + device verification that did not happen. + +## Local-dev note (D-12 / assumption A3) + +`localhost:8787` (via root `npm run dev` → `wrangler dev --port 8787 --local`) is a secure +context, so `navigator.geolocation` works under `wrangler dev` while a LAN-IP dev URL would +not. Remote staging (the deployed origin above) is used sparingly, immediately before a +real-device verification session, for exactly that reason. + +## 2026-10-23 debug round 1 (mid-gate failure fixes) + +**Reported failures + fixes (commit `b1d8768`, deploy `a21c0816`):** +1. *No webview entry from the bot* — root cause: phase 2 armed the menu button as + type `commands` and phase 3 never armed a web_app entry. Fix: new + `bot/scripts/set-menu-button.mjs` → `setChatMenuButton` type `web_app`, + url = deployed origin (verified live via `getChatMenuButton`). + ⚠ Client-cached: the menu surface needs an app restart / `/`-autocomplete + to refresh (same quirk as phase 2). +2. *TRY AGAIN / ASK VIA CHAT dead on one card* — root cause: only the first + denied-card instance had listeners; the `awaiting-pin` variant is a second + `buildDeniedCard` instance (03-03 recorded stub). Fix: `wireDeniedActions` + wires BOTH instances in `web/src/main.ts`. +3. *Post-fix canaries*: `GET /` 200, unsigned `/api/session` 401, webhook armed + (`/telegram/webhook`, no last_error), menu button verified `web_app`. + +**Notes for the re-run:** on plain Chrome (no Telegram), TRY AGAIN legitimately +re-renders the denied card — session can never validate without initData there; +the pass condition for leg 5 is the deliberate denied state itself. If Android +still shows dead/absent retry interplay after OS location is enabled, open a +follow-up.