Files
arah2/.planning/phases/06-turn-by-turn-guidance-live-driving/06-06-SUMMARY.md
T

26 KiB
Raw Blame History

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
06-turn-by-turn-guidance-live-driving 06 web-guidance-wiring
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
phase provides
06-turn-by-turn-guidance-live-driving/02 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 provides
06-turn-by-turn-guidance-live-driving/05 the seven chrome builders, fireHaptic, engage/releaseCloseProtection, GLYPHS, the 18 strings keys, data-guidance CSS compaction — mounted, never re-styled
phase provides
06-turn-by-turn-guidance-live-driving/04 createHttpRoutingSource — the ladder's engine-a stage
phase provides
06-turn-by-turn-guidance-live-driving/03 POST/GET/DELETE /api/guidance — the frozen envelope endpoints this plan's client half calls
phase provides
05-routing-route-display/05 the route controller's injected-deps shape, the RS gate family, the walkBack total function this plan extends
phase provides
03-mini-app-shell-geolocation/03 the follow controller (ONE easing discipline + the originalEvent unlock law) and the recenter control the guidance pose rides
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
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)
added 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
created modified
web/src/guidance/camera.ts
web/src/map/follow.ts
web/src/telegram/backbutton.ts
web/src/guidance/guidance.ts
web/src/route/route.ts
web/src/main.ts
web/test/route-surface.test.mjs
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
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)
GUID-02
GUID-05
GUID-06
PX-01
PX-03
50min 2026-10-24

Phase 6 Plan 6: The Guidance Pump — Camera, BackButton Graph, 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.

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
  • 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.

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.

Task Commits

  1. task 1: guidance camera + BackButton full state graph (TDD, merged authorship)
    • 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)
    • 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)

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

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).

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
  • 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
  • Commit: d5e104b

Reconciliation edits to the co-executor's tests (not plan deviations — merged-suite repairs, documented per the merge note)

  • 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).

Judgment calls within plan laws (not deviations)

  • 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).

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, ...)).

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.

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.

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.

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.

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).