--- phase: 05-routing-route-display plan: 04 type: execute wave: 2 depends_on: ["01"] files_modified: - web/package.json - web/src/i18n/strings.ts - web/src/route/route-layers.ts - web/src/route/route-markers.ts - web/src/route/route-card.ts - web/src/route/route.ts - web/src/map/map.ts - web/src/main.ts - web/src/telegram/backbutton.ts - web/src/shell.css - web/test/copy-gate.test.mjs - web/test/route-surface.test.mjs autonomous: true requirements: [RTNG-01, RTNG-02] must_haves: decisions_implemented: - "D-09: route line = signal-orange casing stack (matte-black 9px under signal-orange 5px selected; matte-black 5px under soft-beige 3px unselected), opacity 1.0, line-cap square + line-join bevel, added once inside the existing load handler AFTER building-3d — the style is never re-set" - "D-08: alternatives present as a bottom card + chips — one card for the selected route, up to 2 alternate chips, tap → instant geometry swap + 400ms camera refit; single-route draws render NO chip row" - "D-03: the router-pending state is a designed Rams surface (center card, standard ink, NO red anywhere) — never a blank map, never a fake route; a route-source exception renders the same pending card (fail-honest)" - "D-10: display-only terminal — NO start-guidance button, NO turn instructions, NO progress UI; the only affordance is .btn-secondary SEARCH AGAIN (shared with the BackButton walk-back)" - "D-05/D-12: the route surface consults the fixture RoutingSource in the webview only — origin stays in memory, no route persists, the capture signal is a state token" truths: - "The four route layers are added exactly once, inside map.on('load'), after the building-3d block, with no beforeId, in bottom→top order route-alt-casing, route-alt-line, route-casing, route-line — zero setStyle calls exist in web/src/route/" - "MapLibre paint consumes verbatim hex constants (#1A1A1A, #EB8008, #D9D2C6 copied from tokens.css) — CSS variables cannot feed paint (DSGN-01 note); the off-token sweep pattern covers them" - "Camera fitBounds carries padding {top:96,bottom:320,left:64,right:64}, duration 400ms, pitch 35 (the boot constant, passed explicitly), bearing from getBearing() — center+zoom only" - "Route consult happens automatically, exactly once per boot, only when the location chain is live AND a destination resolved — destination-unavailable and no-destination boots never make a route request" - "Staleness never evicts the route card: stale during route state keeps the route card (strip lamp + dimmed marker carry the signal); stale during pending coexists the pending card (center) with the signal-lost card (Phase 3 bottom register)" - "The BackButton is visible exactly when a pushed state exists (route or pending); its click walks to the root via the SAME total function SEARCH AGAIN invokes — never a no-op, never close() from a pushed state" - "Every route string comes from the strings module (COPY5); the only console lines in web/src/route/ are the two ARAHARAH-ROUTE state tokens — never coordinates" - "Card padding composes from the existing .card child rules and the new token-only route classes — zero per-card padding overrides (UI-SPEC post-approval addendum)" artifacts: - path: "web/src/route/route-layers.ts" provides: "installRouteLayers (2 empty GeoJSON sources + 4 line layers), setRouteDrawn (setData swap), clearRouteLayers — MapLike-parameterized, Node-testable" contains: "route-alt-casing" - path: "web/src/route/route-markers.ts" provides: "origin ring (24px, 2px matte-black stroke) + destination pin (12px square + 3px tail, name chip) as DOM/SVG markers, aria-hidden glyphs" contains: "route-origin-ring" - path: "web/src/route/route-card.ts" provides: "buildRouteCard (register, headline, telemetry, difference, chips, SEARCH AGAIN) + buildRoutePendingCard — createElement + textContent only, strings-module copy" contains: "buildRoutePendingCard" - path: "web/src/route/route.ts" provides: "createRouteController — consult/select/walkBack state machine (idle|route|pending), camera constants, ARAHARAH-ROUTE state signals" contains: "ROUTE_CAMERA_PADDING" - path: "web/src/shell.css" provides: ".route-stack register rules, .route-telemetry/.route-chips/.route-chip, marker chrome — token references + the machined literals the UI-SPEC enumerates" contains: "route-chip" - path: "web/test/route-surface.test.mjs" provides: "RS1–RS10 structural + fake-map behavioral gates (layer law, camera law, state machine, console discipline, D-10 absence laws)" contains: "RS1" key_links: - from: "web/src/route/route.ts" to: "@araharah/routing/src/index.ts" via: "the controller consumes createFixtureRoutingSource() (backend #1) through the RoutingSource seam" pattern: "createFixtureRoutingSource" - from: "web/src/map/map.ts" to: "web/src/route/route-layers.ts" via: "the load handler's new opts.onLoad hook installs the route layers after building-3d, before the RAMS-BASEMAP-READY signal" pattern: "onLoad" - from: "web/src/main.ts" to: "web/src/route/route.ts" via: "boot wiring: auto-consult after live+destination; walkBack shared by BackButton and SEARCH AGAIN" pattern: "consultRoute" - from: "web/src/telegram/backbutton.ts" to: "web/src/route/route.ts" via: "overlayPresent becomes the route-state predicate; the pushed-state click invokes the injected walkBack (the SEARCH AGAIN total function)" pattern: "walkBack" - from: "web/src/route/route-card.ts" to: "web/src/i18n/strings.ts" via: "every route string is a ShellStrings key (COPY5) — the 10 new route keys" pattern: "routePendingHeadline" --- Render the route experience on the live Phase 3 shell per the approved 05-UI-SPEC: orange casing route layers over the grayscale basemap, origin ring + destination pin, the bottom route card with alternates chips, the designed router-pending card, fit-bounds camera behavior, and the first real BackButton two-state walk — the visible half of RTNG-01/RTNG-02. Purpose: the route is THE accent on a grayscale world (D-09); the card is its semantic statement (D-08); the pending state is honesty as a designed surface (D-03); and the terminal state is display-only (D-10 — no guidance furniture). Every geometry, color, string, and behavior is already BINDING in 05-UI-SPEC.md — this plan operationalizes it exactly, extending the shipped shell without restyling any existing surface. Output: the `web/src/route/` module family (layers, markers, cards, controller), the map.ts load-hook extension, main.ts boot wiring, the two-state BackButton wire, shell.css route chrome, the 10 new string keys with the copy-gate extension, and the RS1–RS10 structural suite. @$HOME/.config/opencode/get-shit-done/workflows/execute-plan.md @$HOME/.config/opencode/get-shit-done/templates/summary.md @AGENTS.md @.planning/phases/05-routing-route-display/05-CONTEXT.md @.planning/phases/05-routing-route-display/05-UI-SPEC.md @.planning/phases/05-routing-route-display/05-PATTERNS.md @.planning/phases/05-routing-route-display/05-01-PLAN.md @web/src/main.ts @web/src/map/map.ts @web/src/map/follow.ts @web/src/map/marker.ts @web/src/ui/cards.ts @web/src/search/destination-card.ts @web/src/search/resolve-destination.ts @web/src/telegram/backbutton.ts @web/src/i18n/strings.ts @web/src/shell.css @web/src/vite-env.d.ts @web/test/copy-gate.test.mjs @web/test/destination-preload.test.mjs @routing/src/index.ts 05-UI-SPEC.md is APPROVED and BINDING. The executor MUST read it in full before task 1. Non-negotiables restated for the tasks below (the spec wins on any disagreement — where spec, mockup, and tokens.css disagree: tokens.css wins, the mockup loses, the spec records the call): - **Layer paint table** (spec §Color): route-casing = matte-black `#1A1A1A` 9px; route-line = signal-orange `#EB8008` 5px; route-alt-casing = `#1A1A1A` 5px; route-alt-line = soft-beige `#D9D2C6` 3px. ALL opacity 1.0. line-cap `square`, line-join `bevel`. Solid centerline (the mockup's dash is scrubbed residue). - **Layer order** (spec §Color bullets): added inside the EXISTING `map.on("load")` handler, AFTER the building-3d block, each `addLayer` with NO beforeId → bottom→top: `route-alt-casing`, `route-alt-line`, `route-casing`, `route-line`. Sources `route` + `route-alt` added EMPTY at load, filled/cleared via `setData` (data, not style). - **Camera table** (spec §Camera Behavior): route drawn → fitBounds padding `{ top: 96, bottom: 320, left: 64, right: 64 }`, duration 400ms; chip select → ALWAYS refits; SEARCH AGAIN → clear + flyTo latest fix via the follow controller; pitch untouched (35), bearing as the user left it. - **Route card** (spec §Route Card): register `ROUTE` / `ROUTE {n}/{N}`; headline = destination name verbatim; telemetry row (telemetry-num time + `MIN` + distance `8.4 KM`); difference line (body-sm); chips row role=group, ≤ 2 chips, gap space-xs, never wraps; actions = ONE `.btn-secondary` SEARCH AGAIN. Height budget ≤ 240px (content contract). Single-route draws render NO chip row at all. - **Chip contract** (spec): `