--- phase: 3 slug: mini-app-shell-geolocation status: draft nyquist_compliant: false wave_0_complete: false created: 2026-10-22 --- # Phase 3 — Validation Strategy > Per-phase validation contract for feedback sampling during execution. --- ## Test Infrastructure | Property | Value | |----------|-------| | **Framework** | Hand-rolled zero-dependency runner — `web/test/runner.mjs` + `harness.mjs`, mirroring `bot/test/` and `theme/scripts/audit.test.mjs` (repo precedent, D-11's "hand-rolled test runner pattern prevails") | | **Config file** | none (no vitest/jest config — by design) | | **Quick run command** | `npm test -w web` | | **Full suite command** | `npm test -w web && npm test -w bot && npm run typecheck -w web && npm run build -w web` | | **Browser/visual checks** | `npm run capture -w web` — Playwright, extends the `theme/scripts/screenshot.mjs` pattern (static server + chromium + `MIN_BYTES` blank-bar check already established there) | | **Estimated runtime** | ~30 seconds | ⚠️ **ESM caveat:** `maplibre-gl@6.15.0` exports **only** an `import` condition. The runner uses dynamic `import()` on `.mjs` files with `"type": "module"`, which resolves it correctly. Unit tests test the **chain logic against injected fakes**, never against a real MapLibre instance. --- ## Sampling Rate - **After every task commit:** Run `npm test -w web` (plus `npm test -w bot` for bot-side tasks) - **After every plan wave:** Run `npm test -w web && npm test -w bot && npm run typecheck -w web && npm run build -w web` - **Before `/gsd-verify-work`:** Full suite must be green - **Max feedback latency:** ~30 seconds --- ## Per-task Verification Map | task ID | Plan | Wave | Requirement | Threat Ref | Secure Behavior | Test Type | Automated Command | File Exists | Status | |---------|------|------|-------------|------------|-----------------|-----------|-------------------|-------------|--------| | {N}-01-01 | 01 | 1 | (scaffold) | — | N/A | unit | `npm test -w web` | ❌ W0 | ⬜ pending | | (W0) | 01 | 1 | GEO-01 | — | chain vs fakes | unit | `npm test -w web` | ❌ W0 | ⬜ pending | | (W0) | 01 | 1 | GEO-03 | T-3-x | no storage API in web/src | unit grep gate | `npm test -w web` | ❌ W0 | ⬜ pending | | (W0) | 01 | 1 | PX-02 | — | theme slot switch | unit | `npm test -w web` | ❌ W0 | ⬜ pending | | (W0) | 01 | 1 | GEO-02 | T-3-03 | TTL'd location pin KV | unit (bot) | `npm test -w bot` | ❌ W0 | ⬜ pending | | (W0) | 01 | 1 | copy gate | — | strings.ts = UI-SPEC table | unit | `npm test -w web` | ❌ W0 | ⬜ pending | | (plans) | 02-0N | 2 | GEO-01 | — | source fallback chain | unit | `npm test -w web` | ❌ W0 | ⬜ pending | | (plans) | 02-0N | 2 | GEO-02 | — | pin poll bounded | unit | `npm test -w web` | ❌ W0 | ⬜ pending | | (plans) | 03-0N | 3 | D-14 | T-3-06 | no CDN refs in dist | build gate | `npm run build -w web` | ❌ W0 | ⬜ pending | | (plans) | 03-0N | 3 | GEO-03 / D-09 / D-10 | — | runtime probes | integration (Playwright) | `npm run capture -w web` | ❌ W0 | ⬜ pending | *Status: ⬜ pending · ✅ green · ❌ red · ⚠️ flaky* *(task IDs will be filled by the plans' own `` fields; the map above is the Wave-0 skeleton.)* --- ## Wave 0 Requirements - [ ] `web/test/runner.mjs` — hand-rolled runner (copy the `bot/test/runner.mjs` shape) - [ ] `web/test/harness.mjs` — test registry - [ ] `web/src/mock/telegram-mock.ts` — D-11 fake `WebApp`; the keystone that makes every location test possible without a device - [ ] `web/test/location-chain.test.mjs` — covers GEO-01, GEO-02 (client half) - [ ] `web/test/no-storage.test.mjs` — GEO-03 grep gate - [ ] `web/test/theme.test.mjs` — PX-02 slot-switch gate - [ ] `web/test/copy-gate.test.mjs` — UI-SPEC copy table gate - [ ] `bot/test/location-pin.test.mjs` — GEO-02 bot half + TTL grep assertion - [ ] `web/scripts/capture.mjs` — Playwright harness captures (extend `theme/scripts/screenshot.mjs`) - [ ] **`GET /api/location` Worker route + KV module** — the missing link between the bot's received pin and the open Mini App *None of these exist yet — `web/` does not exist. Every test above is Wave 0.* --- ## Manual-Only Verifications | Behavior | Requirement | Why Manual | Test Instructions | |----------|-------------|------------|-------------------| | Live position on Rams basemap on real Android + iOS | GEO-01 | WebView location bugs are device/client-specific (Bot API 10.2, WKWebView, LocationManager) | Device-matrix exit pass: boot → session → position on each device | | Chat "send location" keyboard fallback on Desktop Telegram | GEO-02 | Desktop Telegram has no geolocation at all (issue #77); only real clients show the path | Open Mini App on Desktop Telegram; trigger fallback via chat location | | Android stale-fix workaround effectiveness (A1) | GEO-01 | Community-sourced, unverified on our devices | On Android: issue ONE stale Telegram fix → attempt in-app refresh (navigator nudge) → verify fix freshness updates | | Theme flip live (light ↔ dark) while map stays Rams-constant | PX-02 | Telegram theme switching only happens inside the real client | Switch Telegram theme mid-session; verify shell re-themes, map unchanged | --- ## Validation Sign-Off - [ ] All tasks have `` verify or Wave 0 dependencies - [ ] Sampling continuity: no 3 consecutive tasks without automated verify - [ ] Wave 0 covers all MISSING references - [ ] No watch-mode flags - [ ] Feedback latency < 30s - [ ] `nyquist_compliant: true` set in frontmatter **Approval:** pending --- *Source: `.planning/phases/03-mini-app-shell-geolocation/03-RESEARCH.md` § Validation Architecture (2026-10-22)*