Files
Arah2/.planning/phases/03-mini-app-shell-geolocation/03-03-SUMMARY.md
T
2026-10-23 22:37:55 +08:00

168 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
phase: 03-mini-app-shell-geolocation
plan: 03
subsystem: web-shell-chrome
epoch_start: 2026-10-22
tags: [d-06, d-08, d-09, d-10, px-02, maplibre-v6, copy-gate, rams-shell]
requires:
- "03-01 web/ workspace (vite build, strict tsconfig, /api half routes)"
- "03-02 location chain (acquireFix/isFixStale/fixAgeSeconds), session store+client, telegram wrappers, D-11 mock"
- "theme/dist/rams.css tokens + component classes; theme/basemap/liberty-rams.json"
provides:
- "web/src/i18n/strings.ts — 25-key ShellStrings copy module, UI-SPEC table verbatim (en+ms), pickLocale + localeFromInitData"
- "web/src/shell.css — token-only Rams chrome (dark-slot activation, strip/card/control/marker geometry, data-map=absent fallback)"
- "web/src/map/{map,marker,follow}.ts — MapLibre v6 bootstrap (setWorkerUrl, GPUInitializationError gate, RAMS-BASEMAP-READY), DOM position marker (dot + capped accuracy circle + null-hidden cone), 96px/400ms follow controller"
- "web/src/ui/{strip,cards,recenter}.ts — honest state strip, boot/denied/lost cards, machined recenter control"
- "web/src/main.ts — boot choreography: map mounts FIRST, then session, then chain; staged validating→acquiring→live"
- "vite.config.ts define __ARAHARAH_HARNESS__ (mode-keyed) — the build constant 03-04's dual build keys on"
- "gates: COPY1-6 copy gate, SESS1-4 session gates (27-case suite total)"
affects:
- "plan 03-04 (capture harness drives main.ts's mount ordering, mock-fallback guard, ARAHARAH-LIVE race signal)"
- "Phase 5 (route snapping consumes Fix + map bootstrap); Phase 6 (tick re-runs chain rather than continuous cadence)"
tech-stack:
added:
- "maplibre-gl v6 (ESM-only, WebGL2-only) via Vite ?worker&url + setWorkerUrl"
- "build-time JSON style inlining (@araharah/theme/basemap/liberty-rams.json) with resolveJsonModule"
patterns:
- "Map-first boot order as the anti-blank-map invariant (order asserted by script)"
- "Null-guarded map consumers — WebGL2-absent shell renders deliberate absent-map state, never crashes"
- "Machine-blink duty cycle via setInterval; zero CSS transition/animation in chrome"
- "Named-CTA data slots in card markup for element-specific class-mapping gates"
key-files:
created:
- web/src/i18n/strings.ts
- web/src/shell.css
- web/src/map/map.ts
- web/src/map/marker.ts
- web/src/map/follow.ts
- web/src/ui/strip.ts
- web/src/ui/cards.ts
- web/src/ui/recenter.ts
- web/test/copy-gate.test.mjs
- web/test/session.test.mjs
modified:
- web/src/main.ts (stub → composition root)
- web/src/map/map.ts (attribution bottom-left)
- web/src/map/marker.ts (comment reword only)
- web/src/shell.css (attrib chip scope, strip cluster, recenter z-index)
- web/vite.config.ts (define harness constant)
- .planning/phases/03-mini-app-shell-geolocation/03-UI-SPEC.md (Open-Question-1 amendment append)
decisions:
- "localeFromInitData parses the RAW initData locally — POST /api/session returns only {token}, the claim's language_code is unreachable client-side; worst case of a forged locale is wrong-language copy (HMAC validation stays server-side)"
- "Mock install is a guarded fallback ((__ARAHARAH_HARNESS__ || DEV) && !window.__ARAHARAH_MOCK__) — plan 03-04's profile-configured runner page must never be clobbered by the default android/light fake"
- "vite define keyed on --mode harness supplies the ambient constant as a real static value — a bare declared-but-undefined global would ReferenceError in plain vite dev (Rule 3)"
- "Attribution control moved to bottom-left via addControl (UI-SPEC geometry) — bottom-right was covering the recenter control; chip restyled with #map-scoped specificity because maplibre css lands after shell.css in the bundle"
- "COPY5 scans comment-stripped sources: prose may cite banned register labels, code may not"
- "CTA buttons ship named placeholder slots ({strings.ctaTryAgain}) overwritten with strings-module text before mount — keeps the Pitfall-4 class inversion element-greppable without inline copy"
- "Signal-lost card folds bottom-register via inline style on the shared card-stack (token-referenced bottom offset), not a second stylesheet layer"
metrics:
duration: ~55 min wall (15:49 → Oct 23 ~00:05 UTC, spans a date rollover; ~45 min focused)
completed: 2026-10-23T00:05:00Z
...
# Phase 3 Plan 03: Rams shell — copy, chrome, MapLibre bootstrap, boot choreography Summary
**One-liner:** the visible Rams Mini App shell — verbatim en+ms copy module with a 6-case copy gate, token-only stylesheet, MapLibre v6 basemap with WebGL2-absent fallback, dot/cone/accuracy marker, machined recenter control, six-state honest strip, denied card with the Desktop-first CTA inversion, and the map-first staged boot that never shows a blank screen.
## Tasks
| task | Name | Commit | Files |
| ---- | ---- | ------ | ----- |
| 1 | `i18n/strings.ts` copy module (25 keys, en+ms) + COPY1-6 gate | 83985c4 | web/src/i18n/strings.ts, web/test/copy-gate.test.mjs |
| 2 | `shell.css` + map bootstrap / marker / follow | c437d31 | web/src/shell.css, web/src/map/{map,marker,follow}.ts (+ UI-SPEC amendment) |
| 3 | strip, cards, recenter, boot choreography + SESS gate | d573acc | web/src/ui/{strip,cards,recenter}.ts, web/src/main.ts, web/test/session.test.mjs, (task-3 wiring edits: map.ts, shell.css, marker.ts, vite.config.ts) |
## Verification Results
- `npm test -w web` → `[runner] 27 passed, 0 failed` (10 GEO + 3 GEO3 + 4 PX02 + 6 COPY + 4 SESS; ≥24 required)
- `npm run typecheck -w web` → clean after every task; `npm run build -w web` → clean, 3 assets (js + css + worker) + hashed woff2 fonts in dist/fonts
- Boot-order script: `bootMap(` precedes `acquireFix(` in main.ts — the D-09 anti-blank invariant is greppable
- Null-map guard script: 2× `if (map !== null)`, `follow = createFollowController(map` mapped, `follow?.follow` guarded; `ARAHARAH-LIVE` emitted OUTSIDE the guard (comment-stripped grep = 1)
- Marker wiring script: `.addTo(map)` inside the first guard and before the first `marker.update(map, fix)`; marker DOM script: explicit svg w/h/viewBox, cx/cy on both circles, cone/dot under the svg, no body-parenting
- Copy gates: 25-key en/ms parity (deniedRegister + lostRegister included), COPY1-6 all PASS`; COPY5 element-level no-inline-copy incl. the GEO/SYS register labels
- CTA class script: chat-fallback button element carries `.btn-primary`, retry carries `.btn-secondary` (Pitfall-4 inversion) — element-specific match, not file-level
- Theme audit: `npm run audit -w theme` → AUDIT CLEAN (354 declarations, 0 off-token values; also clean with web/index.html added via --pages)
- shell.css value discipline: 0 literal hex, 0 `transition:`/`animation:`, `border-radius: 0` present; ui/*.ts greps 0 for transition/animation words
- Strip telemetry uses the U+00B1 `±` glyph; `+-<accuracyM> M` absent
- Third notch state shipped: `data-fix="lost"` in shell.css + `recenter.setFixState("lost")` in main.ts + `setFixState` in recenter.ts
- UI-SPEC amendments: Pitfall-4 callout pre-present (verified, not duplicated); Open-Question-1 sentence appended exactly once under Boot Choreography
- Real-browser smoke (headless Playwright, vite dev): map tiles render, strip lamp+word clustered left, D-06 denied card visible with inverted CTA classes, recenter visible and top-most at bottom-right, attribution chip bottom-left, webfonts loaded (dist/fonts hashed copies)
## Threat model compliance
| Threat | Where implemented | Evidence |
| ------ | ----------------- | -------- |
| T-03-13 (style JSON swapped at runtime) | build-time `ramsStyle` import; no `map.setStyle` anywhere in web/src | source grep; PX02 case 3 still green |
| T-03-14 (Telegram hexes as design values) | shell.css re-points `--shell-*` to Rams tokens only; zero hex in code lines | hex grep = 0 (comment-stripped) |
| T-03-16 (stale fix rendered fresh) | tick checks `isFixStale` each second → marker `data-stale`, red lamp, SIGNAL LOST + LAST FIX | main.ts tick + criteria greps |
| T-03-17 (worker chunk) | `setWorkerUrl(workerUrl)` from Vite-emitted hashed asset; error listener logs RAMS-BASEMAP-ERROR | map.ts; worker emitted as dist asset |
| T-03-18 (WebGL2 absent → blank) | bootMap catches GPUInitializationError → null; data-map="absent"; every map consumer guarded; capture signal unguarded-handled | null-map guard script; marker wiring script |
| T-03-19 (Fix escapes closure) | Fix lives in boot()'s closure, passed by argument | D-07 lock in main.ts; GEO3 gate still green |
## Deviations from Plan
### Auto-fixed Issues
**1. [Rule 3 - Blocking] `vite.config.ts` gained `define: __ARAHARAH_HARNESS__` (mode-keyed)**
- **Found during:** task 3 (typecheck/dev wiring)
- **Issue:** the plan mandates the literal condition `(__ARAHARAH_HARNESS__ || import.meta.env.DEV) && !window.__ARAHARAH_MOCK__`; with only an ambient `declare const`, plain `vite dev` would ReferenceError on the unreplaced global.
- **Fix:** `define: { __ARAHARAH_HARNESS__: JSON.stringify(mode === "harness") }` — true exactly in 03-04's harness builds, statically false elsewhere. Consistent with 03-04 task 2's dual-build intent, not a fork of it.
- **Commit:** d573acc
**2. [Rule 3 - Blocking] `ramsStyle` needs a typed cast + `error` listener listener-shape fix**
- **Found during:** task 2 (typecheck)
- **Issue:** the plan asserted `resolveJsonModule` alone suffices; in practice TS2732-adjacent narrowing failed: the JSON infers `version: number` vs the style spec's `version: 8` literal (TS2322), and the v6 `error` event's `ErrorEvent & Object` shape rejects `{ error?: Error }` (TS2769).
- **Fix:** `style: ramsStyle as StyleSpecification` (typed cast — resolveJsonModule import kept verbatim, no `any`, no shim, no synthetic defaults) and the inner error read as `{ error?: { message?: string } }`. Behavior unchanged; RAMS-BASEMAP-ERROR/READY prefixes verbatim.
- **Commit:** c437d31
**3. [Rule 1 - Bug] Attribution sat bottom-right and covered the recenter control; chip rendered MapLibre-white**
- **Found during:** task 3 (real-browser render inspection)
- **Issue:** default attribution position conflicts with the floating control; and because maplibre's css lands after shell.css in the bundle, the chip style (same specificity) lost the cascade — translucent white chip (banned).
- **Fix:** `attributionControl: false` + `addControl(new AttributionControl({ compact: true }), "bottom-left")` (UI-SPEC says flush bottom-left) and `#map`-scoped chip overrides.
- **Commit:** d573acc
**4. [Rule 2 - Critical] `ARAHARAH-LIVE` console signal was missing from the implementation**
- **Found during:** task 3 (acceptance script)
- **Issue:** the comment referenced it but the emit did not exist — a WebGL2-absent device would hang the 03-04 capture race.
- **Fix:** `console.log("ARAHARAH-LIVE fix=… acc=…")` in handleLive, outside the map guard.
- **Commit:** d573acc
**5. [Rule 3 - Blocking] COPY5 resilient to execution order**
- **Found during:** task 1 (first test run)
- **Issue:** COPY5 hard-requires `web/src/ui/` to exist; task 1 precedes task 3, so the suite failed ENOENT mid-plan.
- **Fix:** scan `[main.ts]` always + `ui/*.ts` when present; at plan completion the full tree (4 files) is scanned, restoring the full gate strength.
- **Commit:** 83985c4
**6. [Rule 1 - Bug] Comments tripped several greps (grep does not strip comments)**
- **Found during:** tasks 2-3 (acceptance runs)
- **Issue:** marker.ts's prose cited `document.body.appendChild(group)`, cards.ts cited the banned register-label literals and CTA words, strip/recenter comments said "transition"/"animation".
- **Fix:** reworded comments (e.g. "named-CTA data slots", "the chat-fallback CTA", "the retry CTA", "instant hard swaps"); behavioral gates unchanged (COPY5 still comment-strips correctly).
- **Commits:** c437d31, d573acc
### Plan-text variances (documented, non-fixes)
- `pickLocale` is NOT imported by main.ts (plan's import list had it): BOOT's rule "no second locale helper" holds — main.ts uses only `localeFromInitData`, whose body calls pickLocale inside strings.ts. Unused import avoided.
- The two card CTA `<button>` elements carry named-string-module placeholder slots briefly overwritten before mount — this keeps the acceptance script's element-specific class assertions truthful without inlining copy (COPY5 does not ban slot identifiers).
- `session-state` render: denied-then-waiting is a second `buildDeniedCard(locale, { awaitingPin: true })` instance — visually identical per UI-SPEC's "body swaps" semantics.
## Known Stubs
- The card checkout slot `{strings.ctaTryAgain}`/`{strings.ctaAskViaChat}` innerHTML placeholders are never rendered to users (textContent overwritten pre-mount) — not a data-flow stub.
- `installMockWebApp()` default profile (telegramFix null) — capture profiles are plan 03-04's runner's job; the fallback installs only when no profile mock is present.
- Boot stages validating/acquiring cards render code-complete but the dev-server smoke could only photograph the denied terminus (no /api/session in vite dev); full staged-state capture is 03-04's harness purpose.
## Threat Flags
None — no new network endpoints (tiles were the one pre-declared runtime fetch), no auth-path changes, no schema changes. The `/api/location-fallback` call is the pre-existing 03-01 surface consumed as specced.
## Auth Gates
None — no live Telegram, no secrets; dev-signed mocks and real runs were both untouched.
## Self-Check: PASSED
- 10 created + 5 modified files present on disk (sweep live at write time); 3 asset + 6 font artifacts in web/dist
- 3 task commits verified in `git log --oneline` (83985c4, c437d31, d573acc); zero file deletions across all three
- Suite: 27 passed / 0 failed; typecheck + build clean; theme audit clean
- STATE.md and ROADMAP.md untouched by this executor (ROADMAP.md pre-existing modification belongs to the orchestrator)