- consult filters degenerate options (geometry.points < 2) into the pending-card class
- reroute ladder: no drawable option = the honest reroute-failed degradation, never a half-drawn snap-in
- GREEN for FM4; web suite 165/165, typecheck clean
- route.ts: requestGuidanceFullscreen at BOTH guidance-start edges (activateGuidance + resume) directly after engageCloseProtection — synchronous, best-effort, return ignored
- route.ts: exitGuidanceFullscreen at BOTH teardown edges (teardownGuidance + stopGuidanceArtifacts) directly after releaseCloseProtection
- route.ts: watchFullscreenEvents registered exactly ONCE at the guidance-start edge; the callback is a deliberate no-op (observability only, D-14)
- main.ts: watchSafeAreaChanges(getWebApp()) called at the boot site directly after applySafeArea(resolveSafeInsets(getWebApp())) — insets re-resolve at runtime, not only once at boot (T-07-06b)
- no new logging; the route module's state-token console law unchanged
- telegram-mock gains requestFullscreen/exitFullscreen/lockOrientation/unlockOrientation + __fullscreen* state fields in the SAME change as the contract (contract + fake + test together)
- FS1-FS7: both-halves guard vectors, the throwing-platform path (FS4), the exit mirror (FS5), the event categories (FS6), the old-client fallback (FS7, SC2.e)
- FS8: safeAreaChanged re-resolution through the union authority, with a minimal document stub restored in finally
- FS9: comment-stripped source-scan ban on the DOM-fullscreen/orientation-lock escape hatches (T-07-10) + the guard-is-not-optional assertion
- suite 157 passed, 0 failed (148 baseline + 9); mutation check: neutering the 8.0 guard fails FS2/FS5
- new watchSafeAreaChanges(wa): registers handlers for both Telegram inset events, each re-applying through the SAME union authority (resolveSafeInsets -> applySafeArea) — never a duplicated computation
- each handler wrapped in try/catch so a throwing client event cannot break the boot (T-07-06b)
- resolveSafeInsets/applySafeArea behaviour unchanged; nothing written to storage
- TelegramWebAppLike gains requestFullscreen/exitFullscreen/isFullscreen/lockOrientation/unlockOrientation as OPTIONAL members only (no existing member reshaped)
- new web/src/telegram/fullscreen.ts: requestGuidanceFullscreen/exitGuidanceFullscreen/watchFullscreenEvents behind the BOTH-halves guard (method presence AND isVersionAtLeast("8.0"))
- every platform call in try/catch — a refused request is a supported degradation, never an error surface (T-07-06)
- no DOM Fullscreen API, no generic orientation lock, zero logging (T-07-10, T-07-09)
- during guidance the pump consumed fixes without refreshing the chain's
surfaces: the puck froze at the boot fix while the camera rode the
snapped line (the UI-SPEC's recorded call: 'the puck ... carr[ies]
progress'), and lastFix stayed at boot so the strip's 45s staleness
law falsely flipped SIGNAL LOST mid-drive and re-ran the chain
- one injected dep (updatePuck) composes both in main.ts: lastFix = fix,
marker.update (the RAW fix — the camera rides the SNAPPED foot, the
two-authorities-one-line law), marker.setStale(false); the camera is
NOT touched (the guidance camera owns the viewport — the guard
handleLive already keeps)
- G7c pins the dep call per accepted fix + the main.ts source-scan for
the marker composition (the RS2 boot-wiring shape)
- found while building the real-drive gate: any drive past 45s would
have hit both defects on the road
- web 148/148, typecheck clean; captures regenerated 20/20
- ?drive=<name> replaces navigator.geolocation with a replay source
streaming a committed drive fixture (web/test/fixtures/drives/*.json)
through the LIVE shell: the real pump arms the real
createGuidanceWatch against it — no test-only bypass of validation
- the replay clock starts lazily at the first watchPosition arm (the
route draw — nothing emitted before a consumer exists) and plays
REAL-TIME: the machine's grace/storm clocks read wall time
- window.__ARAHARAH_DRIVE = { pause(), resume(), name }: pause freezes
emission mid-stream (the capture legs' freeze for scripted taps and
the REROUTING window), resume continues; the machine's wall clock
keeps running across a pause (documented harness semantics)
- unknown drive name -> one honest token (ARAHARAH-DRIVE
state=unknown) + the default geolocation stays — never a crash,
never arbitrary data (T-06-08 Tampering mitigation); slug guard on
the name, defensive fixture shape check
- import.meta.glob pattern is ROOT-ABSOLUTE (/test/fixtures/drives/*.json
— Vite 8's importGlob matches no relative pattern escaping src/,
verified by the dev-server probe); fixtures inline into the harness
bundle only — production tree-shake verified clean in BOTH directions
- BA5 posture held: production scan clean, harness bundle carries the
replay; 147/147 web tests, typecheck clean
- a fix arriving while the abandon confirm is open rebuilt the guidance
card over it (the ONE PX-03 exit guard evaporating mid-decision); the
pump now re-renders banner/list only and the confirm surface survives
until CONTINUE or YES, ABANDON
- G7b pins it: confirm mounts -> live fix -> confirm still shown,
guidancePhase=confirm holds, YES ABANDON still tears down
- found while building 06-08's scripted END TRIP capture leg; the real
drive's step 6 (END TRIP on a live road) would have hit this within one
watch fix
- route.ts: onDeactivated POSTs the FROZEN six-field snapshot with
keepalive (the minimize race's belt) and stores the server-minted id;
onActivated DELETEs (the cron pauses — structural) and RETAINS the id;
teardown's deleteEnvelope completes the idempotent pair; a live
envelope is never double-minted; best-effort honesty throughout (D-13)
- resume(trip): the controller computes the projection from
startedAtSec + avg speed (the cron's own math, one formula), renders
GUIDE // RESUMING + gantry + projected card, draws the rebuilt line,
arms the watch, DELETEs immediately (a resumed boot is foregrounded),
and seeds the session mid-route on the first fix through the machine's
own resuming path — synthesized terminal marks keep the arrive mark at
the line's END (a steps-less envelope model would instantly arrive)
- backAction(): the PX-01 resolver's single authority — driving →
confirm, confirm → CONTINUE, ended/route → the walk, root → close
- guidance.ts: resumingView keeps the progress math in guidance/ (RS6)
- main.ts: the COMPOSED LADDER (engine-a first, fixture fallback —
code-constructed, zero env reads), the ?g= UUID-validated reader with
the defensive envelope validation (fail-honest → honest boot), the
capability-guarded activated/deactivated wiring, the recenter
re-lock at the guidance pose, the chain-fix camera skip while driving,
and the no-double-consult resume guard
- merged suite: G9-G12 + the E-family (co-executor tests reconciled:
pre-gate probe, builder-format asserts, terminal-mark fixture)
— 146 passed, typecheck clean
- route.ts grows the pump beside the Phase 5 graph: the injected watch arms
with the route draw; the first fix opens the 06-02 session; the start
gate (>= GUIDANCE_START_ALONG_M along) activates the cockpit — PX-03
close protection, [data-guidance=active], banner + card + list, camera
eased to the SNAPPED line, state=on_route token
- effects dispatch: haptics via fireHaptic, reroute-requested through the
injected RoutingSource (fail-honest catch; success = setData snap-in with
NO camera jump + list re-render; failure = calm NO REROUTE + card state
line); arrival -> the D-14 teardown (watch stop, protection release,
chrome clear, trip-end card center)
- END TRIP/BackButton -> the ONE confirm card; YES ABANDON = same teardown;
CONTINUE dismisses with guidance alive; guidancePhase exposes
none/driving/confirm/ended for the PX-01 resolver; walkBack refuses
while the confirm owns the exit and vacates the route stack on the walk
- guidanceView (guidance.ts): banner/card/list view data composed beside
the machine's own maneuver marks — route/ imports and calls it, never
re-derives (the RS6 re-pin's boundary; zero banned tokens in route/)
- G1-G8 green; RS6/RS8 re-pins green; 137 passed, typecheck clean
- guidance/camera.ts: GUIDANCE_CAMERA_PADDING {320,64,64,64} / ZOOM 16 /
EASE_MS 400 / PITCH 35; easeToSnapped rides the follow controller (ONE
easing discipline) and never passes bearing — north-up, recorded call;
onRerouteApplied is the documented camera no-op (setData lives in the
route layer)
- map/follow.ts: follow(point, opts?) pose overrides spread over the
Phase 3/5 defaults — no-opts calls stay byte-identical (RS13); the
re-lock announces the mode (the recenter notch's return signal, RS12);
the originalEvent unlock law untouched
- telegram/backbutton.ts: BackAction resolver ({hidden|close|callback})
consulted per click + wire.sync() visibility re-apply (visible iff
actionable); the legacy (hasOverlay, walkBack) shape composes internally
so Phase 5 call sites and RS7 stay valid; close() only at the true root
- buildGuidanceCard: GUIDE register, verbatim headline, live telemetry row (MIN / {km} KM / ETA), 4px progressbar with full aria value set + instant inline width, no-reroute state line only when degrading, ONE btn-secondary END TRIP
- buildConfirmCard: GUIDE // CONFIRM + exact en/ms privacy copy, btn-primary YES ABANDON + btn-secondary CONTINUE, guide-confirm bottom-register hook — the ONE primary CTA in guidance chrome
- buildTripEndCard: GUIDE // END, exact deletion copy, no actions
- shell.css guidance block: banner chassis + 1px separator, CSS-only strip compaction under [data-guidance], list panel + row states + lamp, progress bar — token vars only, zero motion/depth/radius/hex (theme audit clean)
- 8 hand-authored 32x32 SVGs per the UI-SPEC geometry table (3px stroke, square caps, orthogonal/45° only; uturn = machined staple, no arc)
- glyphs.ts: GLYPH_KINDS/GLYPHS via Vite ?raw (committed files are the single source) + svgHost — the ONE sanctioned innerHTML site, a static import value
- strings.ts: 18 ShellStrings keys appended in en + ms, values verbatim from the 06-UI-SPEC copywriting contract
- copy-gate: COPY1 floor 40→58 + COPY8 pins all 18 guidance keys verbatim in both locales
- shouldAcceptFix: accept when first fix, >=10 m moved, or >=5 s elapsed
(whichever first) — the pure accept rule over haversineM
- createGuidanceWatch(geo, onFix): high-accuracy watchPosition
(maximumAge: 0), six-field Fix normalization (null accuracy/heading,
source webview), rejected fixes die in the wrapper, idempotent stop()
clearing the watcher id (T-06-05b's privacy-minimal trip end)
- replay suite T1-T3 + R12b: truth table, recorded-options structural
gate, feed-only-accepted proof, stop idempotence, module purity scan
- route-profiles.ts: HARNESS-ONLY module — sentinel ROUTE_PROFILE_HARNESS_ONLY
+ getRouteProfileOrigin wrapping getPairOrigin; D-12 header (pinned PUBLIC
origin only, no coordinate params) + BA5 exclusion note
- index.html: ?fixpair=<pair-id> knob resolves the pair's pinned public
origin as the mock's telegramFix (accuracy 12, heading null); unknown
pairId keeps the default fix; knob documented in the profile comment
- [Rule 1 - Bug] main.ts never passed bootMap's opts.onLoad — route sources
never existed and setRouteDrawn's setData silently no-op'd (cards rendered,
the line never painted; invisible to the RS fakes). Wiring added + RS2
boot-wiring gate pins it
- [Rule 1 - Bug] consult raced the map load: harness session stubs resolve
the location chain before WebGL/style init, so draw landed on missing
sources and the post-load empty install discarded it (probe: ROUTE token
fired before RAMS-BASEMAP-READY). Consult now awaits mapLayersReady,
resolved in the load handler after installRouteLayers (never wedges —
finally-resolved; cards-only boots resolve immediately)
- Capture run: CAPTURE OK — 13/13; route line verified painted, alt swap
renders ROUTE 2/2 +1 MIN VS ROUTE 1, pending card honest, dark PX-02
- npm test -w web: 76 passed (new RS2 boot-wiring gate included)
- homescreen.ts: wireHomeScreen behind the BOTH-halves Bot API 8.0
guard; 'added' hides (no nag), 'unknown' (always iOS) shows the
neutral prompt — indeterminate, never an error; homeScreenAdded
hides; a refused addToHomeScreen is caught, the affordance stays;
.btn-secondary in the existing strip — no new furniture; NO
beforeinstallprompt/manifest/service worker (T-04-38)
- main.ts: locale derivation moved above the bindings block; stage 13
reads BOTH destination carriers, seeds the memory-only holder, builds
the destination/unavailable surface (null when no id — Phase 3 boot
byte-identical, D-12); the live register shows the destination
surface; the ARAHARAH-DEST state-token signal fires unconditionally
- index.html: ?dest=<id> harness profile threaded to the mock's
startParam; the block stays inside __ARAHARAH_HARNESS__
- HS1-HS9 green (54/54), web:typecheck green
- resolve-destination.ts: reads ?dest= (URL, primary — the inline
web_app-button carrier) then initData's start_param (the signed
string, secondary — the direct-link/home-screen carrier), ONE
slug validation, dest_ prefix stripped; resolves against the
webview's own build-time @araharah/search copy; honest null for
unknown ids; module-scope memory-only holder (GEO-03/D-07)
- destination-card.ts: the confirmation + honest-unavailable cards,
createElement/textContent only, existing theme classes (T-04-32)
- webapp.ts: TelegramWebAppLike gains startParam, addToHomeScreen?,
checkHomeScreenStatus? + HomeScreenStatus (Bot API 8.0+ members)
- telegram-mock.ts: satisfies the extended contract in the same
change (startParam initData wiring, homeScreenStatus, __homeScreenAdded)
- strings.ts: 5 new copy fields in en+ms; web depends on @araharah/search
- DP1-DP12 green (45/45 web suite), web:typecheck green
- theme.ts: colorScheme → data-tg-theme on <html>; header_bg_color 0-1 luminance fallback
(T-03-11 — Telegram hex is a signal, never a consumed CSS value); applyTheme's ONLY
statement is the attribute set (Pitfall 7 / D-10, greppable invariant)
- backbutton.ts: hide() at init (one shell state), onClick is total — close() at the root,
no dead-ends (UI-SPEC §BackButton)
- safe-area.ts: env(safe-area-inset-*) probe ∪ WebApp.safeAreaInset, per-edge max,
version-gated at 8.0, applied as --safe-* custom properties on :root
- webapp.ts + backbutton/safe-area never read node_modules types;
@types/telegram-web-app stays dropped (web/package.json untouched)