ambek .planning
This commit is contained in:
@@ -0,0 +1,167 @@
|
||||
---
|
||||
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)
|
||||
Reference in New Issue
Block a user