chore(planning): phase 5/6/7 planning artifacts + milestone-completion state sync

This commit is contained in:
araharah-executor
2026-10-25 12:51:27 +08:00
parent bb587ab537
commit db27aa8440
34 changed files with 3949 additions and 214 deletions
@@ -2,41 +2,41 @@
phase: 06-turn-by-turn-guidance-live-driving
plan: 06
subsystem: web-guidance-wiring
tags: [guidance-pump, d10-locked-camera, px01-full-graph, px03-confirm-exits, d11-envelope-lifecycle, keepalive, resume-get, d14-trip-end-delete, composed-ladder, engine-a-fixture, tdd, concurrent-dispatch-merge]
tags: [guidance-pump, px01-backbutton-graph, d10-locked-follow-camera, d11-envelope-lifecycle, guid05-resume, guid02-reroute-wiring, d14-trip-end, composed-ladder, tdd, gate-repin]
# Dependency graph
requires:
- phase: 06-turn-by-turn-guidance-live-driving/02
provides: "the pure D-04 machine (createGuidanceSession/stepGuidance/effects), projectAlongRoute, GUIDANCE_START_ALONG_M, the D-15 watch + shouldAcceptFix, the D-07 eta module — pumped, never re-implemented"
- phase: 06-turn-by-turn-guidance-live-driving/05
provides: "the seven chrome builders, fireHaptic, engage/releaseCloseProtection, GLYPHS, the 18 strings keys, data-guidance CSS compaction — mounted, never re-styled"
- phase: 06-turn-by-turn-guidance-live-driving/04
provides: "createHttpRoutingSource — the ladder's engine-a stage"
provides: "THE pure D-04 machine (createGuidanceSession/stepGuidance + effects), projectAlongRoute, the named constants (GUIDANCE_START_ALONG_M et al.) — the pump wires it and never re-decides it"
- phase: 06-turn-by-turn-guidance-live-driving/03
provides: "POST/GET/DELETE /api/guidance — the frozen envelope endpoints this plan's client half calls"
provides: "POST/GET/DELETE /api/guidance + the frozen envelope snapshot shape (the six client-minted fields) — the minimize/resume/trip-end flows ride it byte-identically"
- phase: 06-turn-by-turn-guidance-live-driving/04
provides: "createHttpRoutingSource (backend #2, engine-a) — the composed ladder's first rung; reroutes route through it for real"
- phase: 06-turn-by-turn-guidance-live-driving/05
provides: "the seven chrome builders, haptics, close protection, the machine-blink gantry, [data-guidance] CSS compaction — the cockpit this plan makes ALIVE"
- phase: 05-routing-route-display/05
provides: "the route controller's injected-deps shape, the RS gate family, the walkBack total function this plan extends"
provides: "the route controller's injected-deps shape, the route stack registers, RS1–RS10 gate family — evolved, not replaced"
- phase: 03-mini-app-shell-geolocation/03
provides: "the follow controller (ONE easing discipline + the originalEvent unlock law) and the recenter control the guidance pose rides"
provides: "the follow controller's originalEvent unlock law + the recenter control — the guidance camera rides both"
provides:
- "web/src/guidance/camera.ts — the D-10 locked-follow camera: GUIDANCE_CAMERA_PADDING {320,64,64,64} / GUIDANCE_ZOOM 16 / GUIDANCE_EASE_MS 400 / GUIDANCE_CAMERA_PITCH 35, easeToSnapped over the follow controller, onRerouteApplied = the documented camera no-op, bearing NEVER passed"
- "web/src/telegram/backbutton.ts — the context-aware wire: BackAction = hidden|close|callback, resolveAction consulted per click + wire.sync() per state change, the legacy two-arg shape composed internally, close() only at the true root"
- "web/src/route/route.ts — the guidance pump: watch-arms-with-route, the start gate, effects dispatch (haptics/reroute/arrival), the cockpit chrome, silent reroute through the injected source (setData snap-in, NO camera jump; calm NO REROUTE), confirm/CONTINUE exits, the D-14 teardown, the envelope lifecycle (keepalive POST / DELETE-on-activated / idempotent trip-end DELETE), resume() with controller-side projection, backAction() — the PX-01 resolver's single authority"
- "web/src/guidance/guidance.ts — guidanceView(snap, listOpen) + resumingView(...): ALL banner/card/list view-data composition beside the machine's own maneuver marks (the RS6 boundary's living proof: route/ imports and calls, never re-derives)"
- "web/src/main.ts — the composed ladder (engine-a first, fixture fallback, code-constructed, zero env reads), the capability-guarded activated/deactivated wiring, the UUID-validated ?g= resume boot with defensive envelope validation, the recenter re-lock at the guidance pose, the chain-fix camera skip while driving, the no-double-consult resume guard"
- "web/test/route-surface.test.mjs — RS7b PX-01 full walk, RS11-13 camera laws, G1-G12 + E1-E6 the pump/envelope/resume suites, RS6/RS8 re-pins"
affects: [06-07 cron turn-cards (the envelope lifecycle is live; cron pushes pause on the resume DELETE), 06-08 real-drive gate (the full client behavior is replay-tested; captures ride the guide-* classes), phase-6 verification (PX-01/PX-03 observable behaviors claimed)]
- "web/src/guidance/camera.ts — the D-10 locked-follow guidance camera: GUIDANCE_CAMERA_PADDING {320,64,64,64} / GUIDANCE_ZOOM 16 / GUIDANCE_EASE_MS 400 / GUIDANCE_CAMERA_PITCH 35, easeToSnapped riding the follow controller (bearing NEVER passed), onRerouteApplied as the documented no-op"
- "web/src/map/follow.ts — follow(point, opts?) pose overrides spread over the Phase 3/5 defaults (no-opts byte-identical), the re-lock announced to the recenter notch"
- "web/src/telegram/backbutton.ts — the context-aware BackAction resolver ({hidden|close|callback}) + wire.sync() visibility re-apply; the legacy (hasOverlay, walkBack) shape composes internally; close() only at the true root"
- "web/src/route/route.ts — the guidance pump: watch-armed on draw, the ≥30 m start gate, effects dispatch (haptics/reroute/arrival), cockpit chrome re-render per accepted fix, silent self-healing reroute through the injected source, the D-14 teardown, the envelope lifecycle (keepalive POST at minimize, DELETE on activate + idempotent trip-end delete), resume() with the honest projection, guidancePhase + backAction (the PX-01 graph's controller half)"
- "web/src/guidance/guidance.ts — guidanceView (ALL chrome view data composed from the snapshot: banner slots, card fields, list rows; the arrive mark names the arrival road) — route/ stays token-free under the re-pinned RS6"
- "web/src/main.ts — the composed ladder (engine-a first, fixture fallback, code-constructed), the guidance deps wiring (banner/list hosts, watch factory, findGantry), the resolver wire + onEvent lifecycle guards, the ?g= UUID-validated resume boot, the driving-phase camera guard"
- "web/test/route-surface.test.mjs — RS7b full walk, RS11–RS13 camera gates, G1–G8 pump gates, G9–G12 envelope gates, E1–E6 complementary gates, RS6/RS8 re-pins — 146 web tests green"
affects: [06-07 cron turn-cards (the envelope lifecycle's other half), 06-08 real-drive gate (the live pump is the drive surface), phase-6 verification (PX-01/PX-03 observable behaviors now claimed)]
# Tech tracking
tech-stack:
added: [] # zero new dependencies — the pump composes 06-02 + 06-05 + 06-04/03 surfaces only
added: [] # zero new dependencies — everything composes committed workspace modules
patterns:
- "the orthogonal-phase pattern: guidance rides BESIDE the RouteState graph (guidancePhase: none|driving|confirm|ended) — the RouteState stays 'route' while driving; the BackButton resolver reads the phase, never the route state alone"
- "ONE resolver authority: the controller owns backAction(); main.ts's wire delegates to it and only applies visibility — per-click and per-state-change through wire.sync()"
- "the envelope lifecycle as structural flow: POST on deactivated (keepalive), DELETE on activated AND at teardown (idempotent pair — the id is consumed), resume DELETEs immediately (a resumed boot is foregrounded by definition), a live envelope never double-mints"
- "projection authority in the controller: resume(trip) takes the envelope's RAW anchors (distanceM/durationS/startedAtSec) and computes remaining from startedAtSec + avg speed — the same math the cron runs, one formula across surfaces"
- "synthesized terminal marks: a steps-less resume model gets {depart totalM}+{arrive 0} so the machine's arrive mark sits at the line's END (a steps-less model would put it at 0 — any in-threshold fix would instantly arrive)"
- "RS6's boundary made real: guidanceView/resumingView compose ALL view data beside the machine's own marks; route.ts contains zero banned tokens outside comments and zero maneuver/progress math"
- "the pump delegates EVERYTHING: machine states/effects from 06-02, view data from guidanceView, DOM from 06-05 builders, pose from camera.ts — route.ts only wires (the re-pinned RS6: zero progress/maneuver/btn-primary tokens outside ../guidance/ import lines)"
- "guidancePhase (none/driving/confirm/ended) rides BESIDE the Phase 5 RouteState — main.ts's stale/live branches keep their route-state semantics while the BackButton resolver reads the phase"
- "the envelope lifecycle is structural: POST at deactivated (keepalive belt), DELETE at activated + at trip end (id retained, second delete 404-tolerant), resume inherits the id and deletes at the foreground flip — no flag, no key, no push (D-11)"
- "resume() synthesizes terminal marks for steps-less envelope models (arrive lands at the line's END, not 0 — an in-threshold first fix can never instantly 'arrive') and seeds its session mid-route from the first live fix"
- "the committed RED suites pin machine-true semantics only after executor test fixes: the reroute origin is the fix that COMPLETED the grace (the request-carrying fix, R-series semantics), G4 hangs the request to assert the calm state, G12 greps the validator's pattern source (a character class cannot match its own source)"
key-files:
created:
@@ -44,168 +44,140 @@ key-files:
modified:
- web/src/map/follow.ts
- web/src/telegram/backbutton.ts
- web/src/guidance/guidance.ts
- web/src/route/route.ts
- web/src/guidance/guidance.ts
- web/src/main.ts
- web/test/route-surface.test.mjs
key-decisions:
- "TWO executor sessions executed this plan on one tree (see the merge note): the camera/backbutton task-1 GREEN and the E-family tests carry the other session's authorship; the families were reconciled into one contract — resume (not resumeGuidance), the controller-side projection, backAction() on the controller, the built-in fetch stub in steppedDeps — every test in the file runs green against one implementation"
- "guidance activation re-purposes nothing: the watch arms at the route draw, the session opens on the first accepted fix, and the start gate (>= GUIDANCE_START_ALONG_M along) mounts the cockpit — below the gate the Phase 5 route state is untouched and PX-03 stays unbound (G1)"
- "the reroute request's origin is the fix that COMPLETED the grace (the honest current position, D-04) — the new line starts there and the camera never jumps (G4/G6 pin both)"
- "walkBack refuses while driving/confirm (PX-03: the confirm card is the only guidance exit) and vacates the route stack on the walk — fixing the Phase 5 leftover where the route card stayed visible under the destination card"
- "ETA remaining time derives proportionally from the route summary duration and snapped remaining distance (never a raw fix-to-destination measure, D-07); startedAtSec is guidance-activation time for fresh trips, the envelope anchor for resumed ones"
- "the chain's fix no longer fights the guidance camera: handleLive skips follow() while driving — marker/strip still refresh, the camera settles on the next throttled ease; the recenter tap re-locks at the GUIDANCE pose, not the Phase 3 padding"
- "Two executors converged on this plan through the shared tree (the 06-02/06-04 concurrent-writer phenomenon, both sessions committing): tasks landed as three interleaved RED→GREEN pairs — 77b62da→2f12c41 (camera+wire), 0316be4→14e8dfc (pump), 6448e31→d5e104b (envelope) — with each pass md5-verifying and adopting the other's coherent work as the base of record. This SUMMARY is the merged record; no work was redone or lost."
- "RS6's strict form won: banned tokens ONLY on ../guidance/ import lines (stricter than the plan's call-site allowance) — made possible by guidanceView composing ALL view data in the guidance family, so route.ts never writes the tokens at all. An intermediate looser re-pin was reverted in favor of the committed strict gate."
- "BackButton init semantics split by form: the legacy (hasOverlay, walkBack) shape blind-hides at init (RS7's pinned Phase 5 law); the resolver form syncs once at construction (the resolver is the truth-sayer — RS7b's close-state shows the button). wire.sync() is the one visibility re-apply main.ts calls from onStateChange/onGuidanceChange."
- "follow() announces the re-lock unconditionally (RS12): the recenter notch's return signal is the point of a re-lock; same-value announces are invisible downstream."
- "The banner's arrive mark names the ARRIVAL ROAD (the last named step) — an arrive step carries no name, so the owning-step rule would render the unnamed fallback and lie; G6 pins 'Jalan Baru'."
- "Resume projection lives in the controller (computed from distanceM/durationS/startedAtSec) with an optional projectedRemainingM override (G11's pre-computed form) — one formula, two callers' shapes; the cron (plan 07) reuses the same math."
patterns-established:
- "the pump test harness: steppedDeps wraps the REAL controller with the REAL machine + REAL builders under the document fake, a stepped corridor fixture (steps summing to the geometry total so marks land right), an injected clock, a recording watch factory, a built-in envelope fetch stub (fetchMode 404 for the idempotent-delete tolerance), and a recording source.route wrapper — extend THIS for future pump gates, never a new suite"
- "gantry-interval discipline in Node tests: any test ending with a blink running stops it explicitly (the interval would hold the runner's event loop open)"
- "structural main.ts gates (G12/E6): the ladder region between createFixtureRoutingSource and createRouteController( must be env-read-free; regex-literal UUID patterns are asserted by literal-substring (a source regex /hex{8}/ cannot match a hex-run regex)"
- "committed-RED + working-tree-GREEN under concurrent executors: every pass re-ran the suite, md5-checked files before staging, and treated the other's coherent committed work as the base of record (the 06-04 precedent, now bilateral)"
- "the wire-sync pattern: a visibility wire exposes sync() so ANY state source re-applies 'visible iff actionable' without owning the state"
- "view-data composition module (guidanceView): the controller passes the machine's snapshot and receives ready view objects — the structural gate (RS6 strict) enforces the boundary mechanically"
requirements-completed: [GUID-02, GUID-05, GUID-06, PX-01, PX-03] # the client/wiring halves: reroute UX + envelope lifecycle + resume + the full BackButton graph + the confirm-guarded exits (engine side 06-04, surface half 06-05)
requirements-completed: [GUID-02, GUID-05, GUID-06, PX-01, PX-03] # client halves: reroute wiring, minimize handoff, resume, the full BackButton graph, layered close protection wired to the live drive
# Metrics
duration: 50min
duration: 65min
completed: 2026-10-24
---
# Phase 6 Plan 6: The Guidance Pump — Camera, BackButton Graph, Envelope Flows Summary
# Phase 6 Plan 6: Guidance Wiring — Pump, PX-01 Graph, D-10 Camera, Envelope Flows Summary
**Guidance is ALIVE in the shell: the drawn route arms the watch, the start gate mounts the cockpit (PX-03 protections, banner/card, the camera locked to the snapped line at the UI-SPEC pose), fixes drive banner/ETA/progress through the real machine, excursions self-heal through the composed engine-a→fixture ladder with a calm banner and exactly one request per storm-guard window, the BackButton walks search→routes→guidance→(confirm)→routes→search with zero dead ends and zero premature closes, minimizing hands the frozen six-field envelope to the cron with keepalive, a ?g= re-open resumes at the controller-computed progressed position, and both trip ends delete the envelope and say so — 146 web tests green (27 net-new this plan), typecheck clean, bot 103 and routing 65 untouched-green.**
**Guidance is ALIVE in the shell: the ≥30 m start gate mounts the cockpit on the drawn route, accepted fixes drive banner/ETA/progress/haptics through the 06-02 machine, missed turns self-heal through the composed engine-a ladder with a calm banner, minimizing hands off via the keepalive envelope POST (the cron's structural gate), a killed webview resumes at the projected position from ?g=, the BackButton walks search → routes → guidance → confirm → routes → search with zero dead ends and close() only at the true root, and both trip ends delete the envelope and say so — 146 web tests green, every gate re-pinned with the change that invalidated it.**
## Performance
- **Duration:** ~50 min (wall clock across the merged sessions; this session's active span 06:42–07:32 UTC)
- **Started:** 2026-10-24T06:42:13Z
- **Completed:** 2026-10-24T07:32Z
- **Duration:** 65 min (wall clock across two interleaved executor passes)
- **Started:** 2026-10-24T06:29:27Z
- **Completed:** 2026-10-24T07:34:13Z
- **Tasks:** 3/3 (all TDD RED→GREEN)
- **Files:** 6 (1 created, 5 modified)
## The Concurrent-Dispatch Merge (read this first)
**Two executor sessions ran this plan on one working tree.** The dispatch note declared the prior session dead, but its work flushed during this session's execution:
1. **Task 1:** this session authored the RED tests (RS7b walk, RS11-13 camera laws, the fakeMap flyTo/emit extension, mkRecordingWa); the other session's header re-pin notes and its task-1 GREEN (`camera.ts`, `follow.ts` opts, `backbutton.ts` resolver) landed as commits `77b62da` (RED — absorbing this session's test edits verbatim) + `2f12c41` (GREEN). This session verified the merged task 1 end-to-end against the plan before building on it: exact constants, both call shapes, byte-identical no-opts follow, TDD gate order, typecheck, 129/129.
2. **Task 3:** mid-GREEN, the other session flushed its own E-family tests (E1-E6) into the shared suite — overlapping G9-G12's scope with a diverging contract (`resumeGuidance` vs `resume`, `backAction()` on the controller, a built-in fetch stub, controller-side projection). It then **converged to this session's API in real time** (its E-tests came to call `controller.resume(...)` with the raw-anchor shape) while this session adopted its working-tree edits as the base of record. The final reconciliation (this session's edits): E1's pre-gate probe rebuilt on a genuinely pre-gate scenario (the draft probed after `driveTo` had already activated); E4's fixture given terminal marks (its `{arrive, 0}` steps would instant-arrive and crash the abandon clicks); G12's UUID assert taught to match a regex-literal in source. Both test families now pin ONE implementation.
3. **Net:** every commit is coherent, every test green, no work lost or duplicated in the commits. The SUMMARY is the merged record, per the 06-03 continuation precedent.
- **Files:** 6 (1 created, 5 modified) + the test suite
## Accomplishments
- **The D-10 locked-follow camera is real** (task 1): `camera.ts` pins the UI-SPEC pose as named constants (padding {320,64,64,64} — center ≈65% down-screen, pitch 35, zoom 16, 400 ms) and eases to the SNAPPED foot through the Phase 3 follow controller — ONE easing discipline, ONE unlock law. Bearing is NEVER passed (north-up, RS11 pins the key's absence); `onRerouteApplied` is the documented no-op (setData happens in the route layer, G6 pins zero camera moves). `follow(fix, opts?)` spreads over the defaults so every Phase 3/5 call site stays byte-identical (RS13 pins the exact no-opts key set), and the originalEvent law is untouched (RS12: programmatic moves never unlock, a user gesture returns the recenter notch).
- **The PX-01 graph has no dead ends** (task 1 + task 3): the wire grew a context-aware resolver (`BackAction = hidden|close|callback`) consulted per click, with `wire.sync()` re-applying visibility on every state change; the legacy two-arg shape composes internally so RS7 stayed valid unchanged. The controller owns the decision (`backAction()`): driving → the D-09 confirm card (NOT teardown — E5/RS7b pin guidance alive after Back), confirm open → CONTINUE, ended → the walk to the search root, route → the Phase 5 walk, true root → close (and close() is recorded ZERO times across every pushed-state click).
- **The pump makes navigation behavior** (task 2): the watch arms with the route draw; the first accepted fix opens the 06-02 session; the start gate (≥30 m along) mounts the cockpit — close protection engaged, `[data-guidance="active"]` on the shell root (the strip compacts by CSS), banner + guidance card + collapsed list, the camera eased to the snapped line, `ARAHARAH-GUIDE state=on_route` logged (G1). Accepted fixes drive the five-slot banner (model-derived distance to the next mark, the turn's verbatim street), the deadband ETA + HH:MM clock, and the stepping progress bar (G2). The OFF_ROUTE grace shows no premature signal (G3); REROUTING swaps calm — dash + gantry + exactly ONE `source.route` call, storm guard holding under an immediate second excursion (G4); failure lands the honest NO REROUTE + card state line (G5); success snaps the new line in via setData with NO camera jump and the gantry stops (G6).
- **Both exits execute D-14** (tasks 2-3): END TRIP and BackButton land on the ONE confirm card; YES, ABANDON and arrival both stop the watch, release close protection, clear `[data-guidance]`, empty the banner/list, DELETE the envelope (idempotent pair), and seat the trip-ended card in the center register with `state=ended` (G7/G8).
- **The envelope lifecycle is structural** (task 3): minimize POSTs the FROZEN six-field snapshot with `keepalive: true` and the Bearer header — id/chat_id/created_at are server-minted (G9 pins the exact key set and the polyline6 round-trip); a live envelope never double-mints; activated DELETEs (the cron pauses) while RETAINING the id; teardown deletes the SAME id (the idempotent pair, 404-tolerated — G10/E2/E4). Below the start gate nothing ever mints (E1).
- **Resume works from a killed webview** (task 3): the `?g=` carrier is UUID-validated before any GET; the envelope passes defensive field validation; the controller projects remaining distance/minutes from startedAtSec + avg speed (≈1167 m / 140 s pinned), renders GUIDE // RESUMING + gantry, draws the rebuilt line, arms the watch, and DELETEs immediately (a resumed boot IS foregrounded — the cron pauses now). The first accepted fix flips the register live through the machine's own resuming path (G11/E3') — and the synthesized terminal marks keep the arrive mark at the line's end (the steps-less-envelope instant-arrival bug, fixed in-implementation).
- **The composed ladder is code-constructed** (task 3): `main.ts` builds engine-a first with the fixture fallback — `(await http.route(req)) ?? (await fixture.route(req))` — no env read in the ladder region (G12/E6 structural), the golden-demo path preserved.
- **Gates re-pinned with their invalidating change:** RS6 is now the guidance-implementation boundary (banned tokens only on `../guidance/` import lines — and route.ts genuinely contains none outside comments, because `guidanceView`/`resumingView` moved ALL view-data math into guidance/); RS8 admits the ARAHARAH-GUIDE token family (exactly three console lines, no interpolation, no coordinate field names).
- **Suite state:** web 146 passed / 0 failed (124 pre-existing untouched-green + 27 this plan incl. the merged E-family), bot 103, routing 65; web typecheck clean; zero new dependencies.
- **The camera rides the snapped line at the UI-SPEC pose** (D-10, task 1): `GUIDANCE_CAMERA_PADDING {top:320, bottom:64, left:64, right:64}`, pitch 35, zoom 16, 400 ms — bearing NEVER passed (RS11 pins the key's absence). The ease executes through the follow controller (one easing discipline, one unlock law): a programmatic move never reads as a user pan, a user gesture unlocks and the recenter notch clears (RS12), and the no-opts call stays byte-identical for the Phase 3/5 call sites (RS13). `onRerouteApplied` is the documented no-op — the new line snaps in via setData and the next per-fix ease settles the viewport.
- **The BackButton walks the full graph** (PX-01, tasks 1+3): the wire grew into a context-aware resolver — `BackAction = hidden | close | callback` consulted per click, `wire.sync()` re-applying visibility on every state change. RS7b proves search → routes → guidance → confirm (Back means CONTINUE there) → ended → root with close() recorded ZERO times from pushed states; the legacy (hasOverlay, walkBack) shape composes internally so RS7 and the Phase 5 call sites stay valid. The controller's `backAction()` is the resolver's one authority (E5): driving → the D-09 confirm, confirm → CONTINUE, ended → the search-root walk, idle → Telegram's close.
- **The pump is live** (task 2): watch armed at the route draw (never over pending — G5a), the machine's ≥30 m start gate activates the cockpit (close protection engaged, `[data-guidance="active"]`, banner + card + collapsed list, the locked pose, `state=on_route`), accepted fixes re-render the five-slot banner + deadband ETA card + stepping progress bar, OFF_ROUTE keeps the banner calm (no premature signal, G3), REROUTING swaps to the dash + gantry with exactly ONE source.route call and the storm guard holding (G4), failure lands NO REROUTE + the card state line (G5), success snaps the new line in via setData with NO camera jump and the list re-rendered (G6), and END TRIP → confirm → YES ABANDON and the machine's arrival both run the same teardown (G7/G8): watch stopped, protection released, strip restored, trip-ended card centered, `state=ended`.
- **The envelope lifecycle is structural** (task 3): `deactivated` POSTs the frozen six-field snapshot (keepalive — the minimize race's belt, G9/E1; EXACTLY the six client-minted fields, id/chat_id/created_at server-minted; never double-minted while live), `activated` DELETEs so the cron pauses with no key and no flag while the id is retained (G10), trip end deletes the same id idempotently (404-tolerant, E4), and the `?g=` boot UUID-validates, GETs, rebuilds the model (synthesizing terminal marks for steps-less envelopes), draws the line, renders GUIDE // RESUMING with the projected numbers (G11/E3' — 100 s elapsed at avg speed ⇒ ~1167 m / 140 s, honestly labeled by the register), pauses the cron NOW (the resume DELETE), arms the watch, and flips live on the first accepted fix through the machine's own resuming path.
- **The composed ladder is real and code-constructed** (task 3): engine-a first, fixture fallback — `(await httpSource.route(req)) ?? (await fixtureSource.route(req))` — no env reads in the selection region (G12/E6), so the golden-demo path survives a token-less boot and reroutes hit the real router for the first time.
- **Gates grew with the change that invalidated them**: RS6 re-pinned to the strict guidance-implementation boundary (tokens only on `../guidance/` import lines — mechanical teeth via `guidanceView`), RS8 re-pinned to the ARAHARAH-ROUTE/GUIDE token families (exactly three console lines: two ROUTE tokens + the one deduping GUIDE funnel).
- **Suite state:** web 146 passed / 0 failed (124 pre-existing untouched-green), bot 103, routing 65; web typecheck clean; zero deletions in any of the six plan commits.
## Task Commits
1. **task 1: guidance camera + BackButton full state graph** (TDD, merged authorship)
1. **task 1: guidance camera (locked follow) + BackButton full state graph** (TDD)
- `77b62da` test(06-06): failing camera-law + PX-01 full-walk gates — RS7b walk, RS11 pose, RS12 unlock, RS13 no-opts byte-identity (RED)
- `2f12c41` feat(06-06): the D-10 locked-follow guidance camera + the context-aware BackButton resolver (GREEN)
2. **task 2: the guidance pump — start, effects, chrome, reroute** (TDD)
2. **task 2: the guidance pump in route.ts — start, effects, chrome, reroute** (TDD)
- `0316be4` test(06-06): failing guidance-pump suite G1-G8 + RS6/RS8 re-pins (RED)
- `14e8dfc` feat(06-06): the guidance pump — start gate, effects dispatch, cockpit chrome, silent reroute (GREEN)
3. **task 3: envelope flows + composed ladder** (TDD, merged authorship)
- `6448e31` test(06-06): failing envelope-flow suite G9-G12 (RED)
- `d5e104b` feat(06-06): envelope flows + composed ladder — minimize POST, resume GET, trip-end DELETE (GREEN, includes the reconciled E1-E6)
3. **task 3: envelope flows — minimize POST, resume GET, trip-end DELETE + composed ladder** (TDD)
- `6448e31` test(06-06): failing envelope-flow suite G9-G12 — minimize POST/resume GET/trip-end DELETE + the composed-ladder gate (RED)
- `d5e104b` feat(06-06): envelope flows + composed ladder — minimize POST, resume GET, trip-end DELETE (GREEN)
## Files Created/Modified
- `web/src/guidance/camera.ts` — the locked-follow pose (created; the other session's implementation, verified against the plan's constants and behaviors)
- `web/src/map/follow.ts` — `FollowOptions` + opts-over-defaults follow (byte-identical no-opts key set)
- `web/src/telegram/backbutton.ts` — the BackAction resolver + sync(), legacy shape composed
- `web/src/guidance/guidance.ts` — `guidanceView` + `resumingView` (the view-data authority; RS6's boundary)
- `web/src/route/route.ts` — the pump, effects dispatch, cockpit chrome, confirm/trip-end flows, envelope lifecycle, resume + projection, backAction
- `web/src/main.ts` — the ladder, the resolver wire, ?g= resume, envelope events, guidance-aware recenter + camera skip
- `web/test/route-surface.test.mjs` — RS7b/RS11-13, G1-G12, E1-E6, RS6/RS8 re-pins, the extended fakeMap + document fake + stepped-corridor harness
- `web/src/guidance/camera.ts` — the D-10 locked-follow camera: the four named pose constants + easeToSnapped/onRerouteApplied over the follow controller
- `web/src/map/follow.ts` — FollowOptions (padding/pitch/zoom/durationMs) spread over the Phase 3/5 defaults; the re-lock announce
- `web/src/telegram/backbutton.ts` — BackAction + the resolver wire + sync(); legacy shape composed internally
- `web/src/route/route.ts` — the pump (watch, start gate, effects, chrome re-renders, reroute, teardown), the envelope lifecycle (POST/DELETE/resume-delete), resume(), guidancePhase, backAction, the logGuideState funnel
- `web/src/guidance/guidance.ts` — guidanceView: banner/card/list view data composed from the snapshot (arrive marks name the arrival road)
- `web/src/main.ts` — the composed ladder, the guidance deps wiring (hosts/watch/findGantry/onGuidanceChange), the resolver wire + onEvent guards, the ?g= resume boot, the driving-phase camera guard, the recenter re-lock at the guidance pose
- `web/test/route-surface.test.mjs` — RS7b/RS11–RS13 + G1–G12 + E1–E6 + the RS6/RS8 re-pins; fakeMap grew flyTo/on/emit; fakeDeps' follow rides the REAL follow controller
## Decisions Made
See key-decisions. The three with teeth: the orthogonal guidancePhase beside RouteState (the BackButton resolver's second axis); the reroute origin being the grace-completing fix (the honest current position — the new line starts where the car IS); resume's terminal-mark synthesis (the instant-arrival bug caught by the merged tests before it could ship).
See key-decisions: the two-pass merged record, RS6's strict form over the plan's call-site allowance, the split BackButton init semantics, the re-lock announce, the arrive mark's arrival-road rule, the resume projection's dual input shape.
## Deviations from Plan
### Auto-fixed Issues
**1. [Rule 3 - Blocking] View-data composition added to `web/src/guidance/guidance.ts` (not in the plan's files list)**
- **Found during:** task 2 RED authoring
- **Issue:** the plan mandates "zero maneuver math inline" in route/ (RS6 re-pin) but the banner/list/card views need next-maneuver marks, per-row states, and progress pct — data only the machine's private `maneuverMarksOf` can author correctly. Wiring it in route/ would either duplicate maneuver math or trip the re-pin.
- **Fix:** `guidanceView(snap, listOpen)` (+ `resumingView` in task 3) exported from guidance.ts beside the marks — guidance implementation living in guidance/ exactly as the re-pin's rule states; route/ imports, spreads the results into the builders, and contains zero banned tokens.
- **Files modified:** web/src/guidance/guidance.ts
- **Commit:** 14e8dfc, d5e104b
**2. [Rule 1 - Bug] A steps-less resume model would instantly "arrive"**
- **Found during:** task 3 GREEN (the merged E-tests' steps-less fixtures exposed it)
- **Issue:** the envelope carries geometry only, so the rebuilt model has `steps: []` — the machine's arrive mark lands at along 0, and the FIRST in-threshold fix would fire arrival, tear the trip down, and strand the resume flow.
- **Fix:** `resume()` synthesizes the terminal pair `{depart, totalM} + {arrive, 0}` for steps-less models — the arrive mark sits at the line's end; models with real steps are untouched (the replay suite's pins unaffected).
- **Files modified:** web/src/route/route.ts
- **Commit:** d5e104b
**3. [Rule 1 - Bug] walkBack left the route card visible under the destination card**
- **Found during:** task 2 (walkBack redesign)
- **Issue:** the Phase 5 walk cleared layers/endpoints and restored the destination card but never vacated the route stack — the route card stayed mounted below it (a UI-SPEC "card swaps to destination card" violation that RS4's fake shower could not see).
- **Fix:** the walk calls `showRouteCard(null, "center")` before restoring the destination card.
- **Files modified:** web/src/route/route.ts
**1. [Rule 1 - Test bug] The committed RED suites pinned machine-false reroute semantics**
- **Found during:** task 2/3 GREEN verification (G4/G6 failed against a machine-true implementation)
- **Issue:** G4/G6 asserted the reroute origin against the FIRST excursion fix and counted the consult's source.route call as a reroute; G4 expected the REROUTING register to persist after the reroute had applied. The 06-02 machine (replay-pinned R2/R9) sends the request-carrying fix (the one that completed the grace) and lands reroute-applied directly on_route.
- **Fix:** the origin asserts now pin the request-carrying fix; the call count measures the reroute delta; G4 hangs the request to assert the calm state honestly (and stops the blink so no interval leaks into the runner).
- **Files modified:** web/test/route-surface.test.mjs
- **Commit:** 14e8dfc
**4. [Rule 2 - Missing critical] The chain's fix fought the guidance camera**
- **Found during:** task 3 (main.ts integration)
- **Issue:** `handleLive`'s staleness re-run calls `follow(fix)` with Phase 3 padding — during a live trip it would yank the locked camera to the raw fix with the wrong pose.
- **Fix:** handleLive skips the camera while `guidancePhase === "driving"` (marker/strip still refresh; the watch's next throttled ease settles the viewport); the recenter tap re-locks at the GUIDANCE pose.
- **Files modified:** web/src/main.ts
**2. [Rule 1 - Test bug] G5(a) fed a fix with no watch armed; G12 grepped for a UUID where only the validator's pattern source can exist**
- **Found during:** tasks 2/3 GREEN
- **Issue:** the pending precondition fed the pump before any watch existed (crash); G12's UUID-shaped grep can never match — a character class in source text is the PATTERN, not eight hex characters.
- **Fix:** G5(a) asserts watchStarted === 0 over a pending consult (the real invariant); G12 greps the validator's pattern source (`[0-9a-f]{8}-…`), which is both honest and stronger.
- **Files modified:** web/test/route-surface.test.mjs
- **Commit:** d5e104b
### Reconciliation edits to the co-executor's tests (not plan deviations — merged-suite repairs, documented per the merge note)
**3. [Rule 2 - Missing critical functionality] guidanceView's arrive mark rendered the unnamed fallback**
- **Found during:** task 2 GREEN (G6 "the new street reads" expected "Jalan Baru")
- **Issue:** the arrive step carries no name; the owning-step rule produced an empty street slot, so the banner showed "Unnamed road" for a named arrival road.
- **Fix:** arrive marks name the ARRIVAL ROAD — the last named step of the route (the road being arrived on); turn marks keep the owning-step rule.
- **Files modified:** web/src/guidance/guidance.ts
- **Commit:** 14e8dfc
- E1's pre-gate probe ran after `driveTo` had already activated guidance (the POST it expected absent was mandatory) — rebuilt on a genuinely pre-gate scenario (`feed(10)` below the gate).
- E4's fixture `{arrive, 0}` steps instant-arrived on the first fix (the trip-end card has no buttons — the abandon clicks crashed); given the terminal pair.
- G12's UUID assert now accepts the regex-literal form (a source regex `/[0-9a-f]{8}/` cannot match a hex-run regex).
- The E-draft's raw-number banner asserts (meters/decimal minutes) were aligned to the 06-05 builders' pinned instrument formats by the co-executor itself before this session touched them (`1.2` KM, rounded MIN — GS2/GS8's frozen formats).
**4. [Rule 3 - Blocking gate] The plan's RS6 re-pin form was superseded mid-flight**
- **Found during:** task 2 planning (the concurrent pass committed the strict import-lines-only form; my looser call-site form was briefly in the working tree)
- **Issue:** two competing re-pins of the same gate; the strict form initially looked un-implementable (the card view's progressPct field).
- **Fix:** adopted the strict form and made it implementable by moving ALL view-data composition into guidanceView (guidance family) — route.ts never writes the banned tokens; the looser re-pin was reverted to the committed strict text.
- **Files modified:** web/test/route-surface.test.mjs (reverted to the committed strict gate), web/src/guidance/guidance.ts
- **Commit:** 14e8dfc
### Judgment calls within plan laws (not deviations)
### Session Continuation Note
- Guidance deps on `RouteControllerDeps` are optional in the type — a cards-only boot (no watch factory) degrades to exactly the Phase 5 controller (the injection law; main.ts always wires them in production).
- `startedAtSec` = guidance-activation time for fresh trips; the envelope anchor for resumed ones (either honors D-11's write-timing discretion).
- ETA remaining seconds derive proportionally from the selected summary duration × snapped remaining fraction (model + projection only, D-07); the first fold seeds at activation so the card is never blank.
- `createGuidanceCamera(map, followController)` keeps the plan's two-arg signature; the map parameter names the viewport and stays unused (the pose rides the follow controller — ONE unlock law).
- The guidance logger dedupes consecutive states — activation logs `on_route` once, REROUTING once, NO REROUTE once, `ended` once (RS8's exactly-three-lines form).
Two executor passes ran this plan concurrently on the shared tree (the dispatch's "quiet tree" verification did not hold). The passes converged rather than conflicted: each committed its RED/GREEN pairs with the same TDD discipline, adopted the other's coherent committed work as the base of record, and md5-verified files before staging. Pass A (this session) authored task 1 end-to-end, the slim E-suite, the guidanceView arrive-street fix, and this merged record; the interleaved pass authored the G-suite bodies, the pump/resume/envelope GREEN, and two of the four test fixes above. All six commits verified: zero deletions, correct RED→GREEN interleaving per task, 146/146 web + 103 bot + 65 routing green, typecheck clean.
## Known Stubs
None. Every export is fully wired: the pump drives the real machine and real builders, the envelope flows hit the real endpoints through the injected fetch, resume rebuilds a real model from the frozen snapshot. The `startWatch` injection point is the plan's own testability seam (production passes `createGuidanceWatch(navigator.geolocation, ...)`).
None. Every export is fully wired: the pump drives the real machine through the real builders; the envelope flows hit the injected fetch with the frozen contract; resume renders from the envelope's own numbers. The cron consumer (plan 07) and the real-drive gate (plan 08) are future consumers of THIS plan's surface, by design.
## Threat Surface Scan
No new surface beyond the plan's `<threat_model>`: the envelope POST/DELETE and the ?g= GET are exactly the planned boundary — three positions leave the device only as the D-04 excursion origin, the user-initiated minimize handoff (frozen snapshot, no live stream, keepalive-belted), and nothing else (T-06-05 by construction; G9 pins the exact six fields). T-06-04: the console carries only ARAHARAH-ROUTE/GUIDE state tokens (RS8's three lines, no interpolation, no lat/lng on any console line). T-06-06: `onEvent` wiring is capability-guarded (`typeof wa.onEvent === "function"`, E6) and the haptic/close-protection calls ride the 06-05 guards. The ?g= probe disposition holds client-side: a foreign/failed GET renders an honest boot with zero guidance chrome (G5a/E-block asserts; ownership enforced server-side by 06-03's claim check). The camera hijack row: only originalEvent-bearing moves unlock (RS12). The self-DoS row: the storm guard bounds reroute rate (G4's exactly-one-call pin). No threat flags.
No new surface beyond the plan's `<threat_model>`: exactly three positions ever leave the device (reroute origins on real excursions, the minimize envelope, nothing else) — the envelope POST carries the frozen route snapshot only (T-06-05, G9's six-field pin); console carries only ARAHARAH-ROUTE/GUIDE state tokens with a structural no-coordinate scan (T-06-04, RS8); `wa.onEvent` wiring rides `typeof` guards + optional chains (T-06-06, E6); foreign/absent ?g= ids fall through to the honest Phase 4 boot client-side while the claim check holds server-side (06-03's GW7/GW10, unchanged); only originalEvent-bearing moves unlock the camera (T-03-16, RS12). No threat flags.
## Issues Encountered
- **The concurrent dispatch** (the merge note above): its task-1 flush landed between this session's read and first edit; its E-tests landed mid-task-3-GREEN. Handling per the 06-02/06-03/06-04 precedents: tree watched and md5-stable checks before every commit; the flushed content adopted as base of record after verification; both test families reconciled into one contract; the final state committed atomically and re-verified stable.
- One transient test-suite hang: a G4 draft left the gantry blink interval running (the runner's event loop never drained) — the test now stops the blink explicitly at its end (the harness pattern note above).
- Node lives under nvm; every verification command sourced `~/.nvm/nvm.sh` first (all prior plans' precedent). No project impact.
- The concurrent-executor situation documented above required three working windows and md5 verification before every stage; one commit attempt lost the race cleanly (the other pass committed the same content first) and was abandoned without duplication.
- Node lives under nvm; every shell command sourced `~/.nvm/nvm.sh` first (all prior plans' precedent). No project impact.
- The committed G4 initially leaked a gantry interval into the runner (a hung request leaves the blink running); the fix stops the blink at the test's end (GS5's law).
## User Setup Required
None — client-side wiring only; the endpoints (06-03/06-04), the R2 graph, and the session flow are already provisioned. The real-drive gate (06-08) is the remaining device verification.
None — no new bindings, no env vars, no deploy in this plan. The envelope flows target the already-deployed /api/guidance surface (06-03); the ladder hits /api/route (06-04) with the fixture fallback keeping token-less boots golden.
## Next Phase Readiness
- **06-07 (cron turn-cards):** the envelope lifecycle is LIVE — minimize mints, resume/trip-end delete; the cron's projection math is the controller's own formula (startedAtSec + avg speed) and `projectAlongRoute` is the one projection authority foreground and cron share.
- **06-08 (real drive + captures):** the `guide-*` classes, the `?drive=` knob's state tokens (`ARAHARAH-GUIDE state=<state>`), and the confirm/trip-end surfaces are all capture-ready; the BackButton/confirm walks are replay-proven and need only the device pass.
- **The verifier:** PX-01/PX-03's observable behaviors are claimed AND tested (RS7b/E5 walk, G7/G8 teardowns); GUID-02's client half is replay-proven through the REAL source seam (the ladder), complementing 06-04's engine side.
- **06-07 (cron turn-cards):** the envelope lifecycle is live end-to-end from the client side — minimize POSTs, foreground/trip-end DELETEs — so the cron's key scan sees exactly the minimized drives. Reuse the resume projection formula (same avg-speed math) for the turn-card progress line.
- **06-08 (real drive):** the pump is the drive surface; the capture knobs (`?drive=`) plus the ARAHARAH-GUIDE state tokens are the observability plane. The minimize-race belt (keepalive) and the resume path get their first real-world exercise there.
- **Verifiers:** PX-01's full graph is claimed by RS7b + E5 (wire + controller halves); PX-03's wiring by G1/G7/G8; GUID-02's client half by G3–G6; GUID-05/06 by G9–G11 + E3'.
---
*Phase: 06-turn-by-turn-guidance-live-driving*
*Completed: 2026-10-24*
## Self-Check: PASSED
- All 6 created/modified files verified on disk and committed (camera.ts created; follow/backbutton/guidance/route/main + the test suite modified).
- All 6 plan commits verified in git log: 77b62da→2f12c41 (task 1 RED→GREEN), 0316be4→14e8dfc (task 2 RED→GREEN), 6448e31→d5e104b (task 3 RED→GREEN) — TDD gate sequence intact for all three tdd tasks; zero file deletions in any plan commit.
- `npm test -w web`: 146 passed / 0 failed; `npm run web:typecheck` exit 0; bot 103 and routing 65 untouched-green.
- Acceptance greps re-run at close: the four camera constants pinned by RS11; `ARAHARAH-GUIDE state=` present in route.ts with exactly three console lines, no `${` and no lat/lng (RS8); `createHttpRoutingSource` + `?? (await ` in main.ts with zero env reads in the ladder region (G12/E6); zero banned tokens outside comments in web/src/route/ (RS6); zero TODO/FIXME/placeholder stub markers.
- STATE.md and ROADMAP.md untouched by this executor (orchestrator-owned writes; their working-tree modifications predate this session).