- 4/4 tasks, 5 commits (f710913,d0751c1,01a75f1,65952fb,52c9320) - 73 web tests green incl. RS1-RS10; typecheck + production build + theme audit clean - deviations: live-branch routePhase guard (spec state-table precedence), controller dep growth for Node-testability, pin-tail 3x8/20 synthesis
19 KiB
phase, plan, subsystem, tags, requires, provides, affects, tech-stack, key-files, key-decisions, patterns-established, requirements-completed, duration, completed
| phase | plan | subsystem | tags | requires | provides | affects | tech-stack | key-files | key-decisions | patterns-established | requirements-completed | duration | completed | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 05-routing-route-display | 04 | web-route-surface |
|
|
|
|
|
|
|
|
|
14min | 2026-10-23 |
Phase 5 Plan 4: Web Route Surface — Layers + Cards + Controller + Two-State BackButton Summary
The route experience renders on the live shell: the D-09 orange casing stack (addLayer once, setData forever), origin ring + destination pin, the bottom route card with N−1 alternates chips, the designed ROUTE // ROUTER pending card, the 96/320/64/64 fit-bounds camera law, and the first real BackButton walk — 73 web tests green (15 new RS gates), typecheck + production build clean, theme audit clean.
Performance
- Duration: 14 min
- Started: 2026-10-23T21:24:06Z
- Completed: 2026-10-23T21:39:03Z
- Tasks: 4/4
- Files touched: 13 (5 created, 8 modified)
Accomplishments
- The D-09 paint stack exists (T-05-09):
route-layers.tsadds sourcesroute+route-altEMPTY and four line layers bottom→top (route-alt-casing5px matte-black /route-alt-line3px soft-beige /route-casing9px matte-black /route-line5px signal-orange), opacity 1.0, cap square, join bevel, no beforeId — through the newopts.onLoadhook inside map.ts's ONE load handler, after building-3d, beforeRAMS-BASEMAP-READY. Selection changes aresetDataonly; RS2 proves zerosetStyleinweb/src/route/and the install position textually. - The card is the route's semantic statement (D-08):
route-card.tsbuilds the bottom card —ROUTE/ROUTE {n}/{N}register, verbatim destination-name headline, telemetry-num minutes + MIN/KM units, one-line difference vs best, up to 2 chips ({nn} // {min} MIN · {km} KM, aria-labels from templates, 44px hit slop, instant:activeinvert, 3px focus bracket, chip count invariant at N−1, single-route draws render NO chip row) — and the center pending card (D-03): registerROUTE // ROUTER, clinical copy with the privacy clause, standard ink, no red. createElement + textContent ONLY (RS5). - The controller is a total state machine (D-03/D-10): consult once per boot (main.ts's
routeConsultedguard; only when live AND destination resolved),.catch(() => null)→ pending (fail-honest, T-05-04, fitBounds never called), draw = setData + endpoints + bottom card + fitBounds(96/320/64/64 @ 400ms, pitch 35 explicit, bearing from getBearing) in one tick, chip select = swap + rebuild + ALWAYS refit,walkBack= the single total function shared by BackButton and SEARCH AGAIN. The module's ONLY console lines are the twoARAHARAH-ROUTE state=tokens (RS8). - The BackButton walks (T-05-11): predicate and handler shipped as ONE change — visible exactly when route/pending is active, overlay click invokes the injected walkBack (never
close()from a pushed state), root click keeps Telegram semantics. RS7 proves both arms. - Copy discipline held: 10 keys verbatim en+ms (COPY7 pins the pending headline/body/CTA, the diff templates with literal
{min}/{n}placeholders, the aria templates, the bilingual-identicalROUTE // ROUTER); COPY5 now scansweb/src/route/; RS9 proves the route modules carry zero inline copy. - Suite state:
npm test -w web73 passed / 0 failed (58 pre-existing + 15 RS cases);npm run web:typecheckexit 0;npm run build -w theme && npm run build -w websucceed with the route family verified inside the production bundle; theme audit CLEAN (0 off-token values — the three MapLibre hexes are the sanctioned verbatim-copy exception).
Task Commits
Each task committed atomically (task 1 RED→GREEN; tasks 2–3 are the implementation tasks whose RS gates the plan deliberately lands in task 4):
- task 1: route strings + copy-gate extension + workspace dependency
f710913test(05-04): extend copy gate for route copy — COPY7 verbatim + floor 40 + COPY5 src/route scope (RED)d0751c1feat(05-04): route strings (10 keys en+ms) + @araharah/routing workspace dep (GREEN)
- task 2: route layers + endpoint markers + shell.css chrome + map.ts load hook
01a75f1feat(05-04): route paint stack + endpoint markers + load-hook install + shell chrome
- task 3: route cards + controller + main.ts wiring + BackButton two-state walk
65952fbfeat(05-04): route controller + cards + boot wiring + two-state BackButton walk
- task 4: the RS structural + fake-map behavioral suite
52c9320test(05-04): RS1-RS10 structural + fake-map behavioral suite for the route surface
Files Created/Modified
web/src/route/route-layers.ts— MapLike seam,ROUTE_PAINT(verbatim hex + DSGN-01 note),ROUTE_LAYER_IDS, idempotentinstallRouteLayers,setRouteDrawn,clearRouteLayersweb/src/route/route-markers.ts— origin ring (24px, 2px stroke, aria-hidden), destination pin (12px square + 3px×8px tail, white inset rule, never orange), 160px ellipsis name chip (real text); WeakMap-tracked attach/removeweb/src/route/route-card.ts—buildRouteCard+buildRoutePendingCard+ the chip/secondary-button/template helpers (createElement + textContent only)web/src/route/route.ts—ROUTE_CAMERA_PADDING/ROUTE_FIT_DURATION_MS/ROUTE_CAMERA_PITCH,RouteState,createRouteControllerwith ALL DOM/map concerns injectedweb/test/route-surface.test.mjs— RS1–RS10 (15 cases: behavioral RS3/RS4/RS7 drive real modules through recording fakes; structural RS1/RS2/RS5/RS6/RS8/RS9/RS10 are comment-stripped source introspection)web/src/map/map.ts—bootMap(container, opts)+ the guardedopts.onLoadslot (its own try/catch — a route failure degrades to "no route drawn", never a broken boot)web/src/main.ts—routePhase/routeWalkBackassignable closures,.route-stack+showRouteCard, the controller wired with null-tolerant map/follow/endpoints, consult-once guard inhandleLive, phase-aware stale/live branchesweb/src/shell.css—.route-stackregisters (64px control-clearance composite),.route-telemetry/.route-diff/.route-chipsrows composing the.cardchild rules (NO padding overrides),.route-chipcontract, marker SVG chrome; header machined-literal inventory extendedweb/src/i18n/strings.ts— 10 route keys (interface + en + ms) with template-placeholder documentation; header inventory note updatedweb/test/copy-gate.test.mjs— COPY1 floor 40 + 3 route-key pins, COPY5 route-dir scope + route copy in the banned regex, COPY7 verbatim caseweb/package.json+package-lock.json—@araharah/routingworkspace dependencyweb/src/telegram/backbutton.ts— the two-state wire (predicate + handler as one change), header updated for Phase 6
Decisions Made
- Controller deps grew by
locale,attachEndpoints,removeEndpointsbeyond the plan's sketch — RS4 importsroute.tsin bare Node, and a maplibre value import (route-markers.ts'sMarker) would break that; injecting the endpoint concern matches the plan's own "all DOM/map concerns injected → Node-testable" law. main.ts binds the real implementations. - Null-tolerant wrappers at the composition root — a no-op MapLike + no-op endpoints when
bootMap()returned null keep the controller's types total (the plan's own null-tolerantfollowwrapper pattern, extended to the map seam): the WebGL2-absent shell still consults and shows route/pending CARDS. renderState's live branch gained aroutePhase() === "idle"guard — see Deviations.showRouteCardvacates the Phase 3 stack — the UI-SPEC state table's "center register: (empty — the map is the surface)" is realized by hiding the destination card when a route/pending card takes over;walkBack → showCard(destinationCard)restores it. Without this, the destination card and the route card would coexist (two stacks, same visual register).- Pin tail = 3px wide × 8px tall (20px total) — the UI-SPEC carries both "3px triangular tail" (pin-table prose) and "8px triangular tail (20px total height)" (the binding Spacing Scale table, whose arithmetic only works with 8). Synthesized both readings; the needle tail reads orthogonal/machined per the glyph family note.
- RS4's fake installs sources before consult — production guarantees sources exist at load before any consult (the load hook precedes the location chain); the fake mirrors that boot order. The first RED run of the suite demonstrated exactly this failure mode (setData no-ops on missing sources), proving the gates have teeth.
Deviations from Plan
Auto-fixed Issues
1. [Rule 1 - Spec conflict] renderState's live branch needed the routePhase guard
- Found during: task 3 (main.ts step 6 said the live branch "stays untouched")
- Issue: the untouched branch calls
showCard(destinationCard)on EVERY live render — including the staleness-recovery path (stale → runChain → handleLive). With a route drawn, recovery would re-show the destination card BESIDE the persistent route card; with pending, it would collide with the pending card in the center. The BINDING UI-SPEC state table demands the opposite ("staleness never evicts the route card"; pending persists until walked back). - Fix:
if (routePhase() === "idle") showCard(destinationCard);— Phase 3 behavior byte-identical when idle (the plan's own law), the route surface keeps its registers after recovery. - Files modified: web/src/main.ts
- Commit:
65952fb
Judgment calls within plan laws (not deviations)
- Controller dep growth (locale/endpoints) per the Node-testability law — documented above.
- Pin-tail geometry synthesis (3px×8px/20px) — documented above.
showRouteCardhiding the Phase 3 stack — the mechanism for the spec's "center empty" row; the plan's showRouteCard sketch ("replaces children + sets data-register + display") is implemented verbatim, the showCard(null) call is the addition that makes the state table true.
TDD Gate Compliance
- task 1 followed strict RED→GREEN:
f710913(COPY1/COPY7 failing against the un-extended strings.ts) →d0751c1(green). - tasks 2–3 carry
tdd="true"but the plan explicitly defers their gates to task 4 ("the RS gates for this task land in task 4"); they are implementation tasks verified by typecheck + the existing 58-test suite, with task 4 landing the 15 RS gates afterward. - task 4's first run was genuinely RED (3 failures) — two test-harness gaps (source-spec shape assertion; RS4's fake missing the production install step) — then green. The gates are proven to fail against real deviations, not just pass vacuously. No plan-level
type: tddRED/GREEN commit ladder applies (plan frontmatter istype: execute).
Known Stubs
None. Every surface is wired to real data: the fixture RoutingSource serves the committed 15-pair corpus, the pending card is the DESIGNED D-03 deferral surface (not a stub), and the WebGL2-absent no-op MapLike is the documented cards-only degradation, not a placeholder.
Threat Surface Scan
No new surface beyond the plan's <threat_model>: no network endpoints (the fixture backend rides the bundle), no auth paths, no storage, no schema. All five register entries have implemented mitigations with mechanical gates: T-05-09 (zero setStyle, idempotence guard, install position — RS2/RS3), T-05-10 (createElement + textContent only — RS5), T-05-11 (overlay walks / root closes — RS7), T-05-13 (two state tokens only, no interpolation, no coordinates — RS8), T-05-04 (.catch(() => null) → pending, no fitBounds — RS4 exception path). No threat flags.
Issues Encountered
- Node lives under nvm in this environment; non-interactive shells start without it — every verification command sourced
~/.nvm/nvm.shfirst (05-01/05-02 precedent). No project impact. - The production bundle check initially looked like the route layers were missing: the minifier rewrites double-quoted string literals as template literals, so a
grep "route-alt-casing"overdist/assets/*.jsmisses. Grepping forcasing/ARAHARAH-ROUTEconfirmed the full route family is bundled. No action needed — noted for 05-05's BA gate authors (grep minified bundles carefully).
User Setup Required
None — no external service configuration. The route surface runs entirely against the committed fixture corpus.
Next Phase Readiness
- 05-05 (gates + captures): everything the capture legs need is live — the
ARAHARAH-ROUTE state=drawn|pendingconsole tokens, the rendered route card DOM, the?dest=carrier (the?fixpair=knob itself is 05-05's harness extension), and the production build carrying the route family. BA gates measure the bundle fromweb/dist(mind the backtick-literal grep note above). - Phase 6 (guidance): the route controller is the state machine guidance extends — the drawn route's geometry (with
cumulativeDistanceM), the walkBack total function, and the routePhase closure are the exact seams PX-01's state graph grows from. - Verifier hooks: RS1–RS10 in
web/test/route-surface.test.mjspin the UI-SPEC laws mechanically; the capture legs (05-05) remain the pixel-level proof.
Phase: 05-routing-route-display Completed: 2026-10-23
Self-Check: PASSED
- All 5 created files verified present on disk (route-layers/route-markers/route-card/route.ts + route-surface.test.mjs); all 8 modified files verified.
- All 5 plan commits verified in git log:
f710913,d0751c1,01a75f1,65952fb,52c9320(task 1 RED→GREEN; tasks 2–4 per the plan's deferred-gate structure). npm test -w web: 73 passed, 0 failed (RS1–RS10 among them).npm run web:typecheck: exit 0.npm run build -w theme && npm run build -w web: succeed; route family confirmed inside the production bundle.- Theme audit: CLEAN (0 off-token values).
- STATE.md and ROADMAP.md untouched (orchestrator-owned writes); only 05-04-SUMMARY.md added (force-add, .planning/ ignored).