42 KiB
phase, slug, status, shadcn_initialized, preset, created
| phase | slug | status | shadcn_initialized | preset | created |
|---|---|---|---|---|---|
| 6 | turn-by-turn-guidance-live-driving | draft | false | none | 2026-10-24 |
Phase 6 — UI Design Contract (Turn-by-Turn Guidance, Live Driving)
Visual and interaction contract for the guidance experience. Generated by gsd-ui-researcher, verified by gsd-ui-checker.
Source posture: the design system is IN USE —
theme/dist/rams.csstokens and the live shell (strip, card stack, recenter control, gantry, destination card, route card + chips) shipped in Phases 3–5 and passed their UI-SPECs + device gates. This contract specifies extension of that shell into the guidance cockpit: the top maneuver banner, the compacted location register, the collapsible maneuver list, the guidance card (the route card evolving), the abandon-route confirm card, trip-end messaging, the locked-follow camera, and the chat turn-card (a Telegram message, not app chrome). It invents no new token values, no new colors, no roundedness, no shadows. Binding upstream: 06-CONTEXT.md D-01..D-16 (layout, ETA deadband, haptics, PX-03/PX-01, camera, glyphs, honest copy, trip-end deletion), 05-UI-SPEC.md (the route shell this extends — ITS CONVENTIONS BIND), 03-UI-SPEC.md (strip/card/control/copy/a11y law), telemetry_rams/DESIGN.md §Navigation Heads-Up Display (the banner's component spec), DESIGN.md palette semantics (orange = navigation/primary, red = hazards/errors only). Theactive_route_guidancemockup is layout reference only; its lane matrix, incident band, speedometer deck, report/audio buttons, backdrop-blur header, and rounded puck are scrubbed residue (deferred features or existing-law violations — recorded in Fidelity Artifact).Map-law restated: the MapLibre style is never re-set; additions only (
addLayer/addSource/setData). The route line stack from Phase 5 is guidance's route rendering — unchanged paint (orange 5px on black 9px casing). No route-line progress split this phase: the puck, the progress bar, and the camera carry progress (recorded call — less, but better).
Design System
| Property | Value |
|---|---|
| Tool | none — shadcn gate N/A (TypeScript + Vite, no UI framework; unchanged from Phase 3) |
| Preset | not applicable |
| Component library | @araharah/theme — theme/dist/rams.css (already imported via shell.css; nothing new to wire) |
| Icon library | inline SVG — 2px uniform stroke, square linecaps, orthogonal/45° baselines (app chrome); PLUS the phase's one new committed asset: the 8-arrow maneuver glyph set — hand-drawn 32×32 SVGs at 3px stroke (the HUD spec's weight), rendered as inline SVG in-app and rasterized once to 256×256 PNGs (maintainer build-glyphs.mjs, sharp) for chat turn-cards. One geometry, two renderings; the committed SVGs are the single source |
| Font | self-hosted Geist + Space Grotesk via theme.css @font-face (unchanged) |
| Basemap | theme/basemap/liberty-rams.json — Rams-constant under both Telegram themes (unchanged) |
Consumed component classes (all pre-existing; none restyled locally): .card, .card-header, .card-ref, .btn-primary, .btn-secondary, .text-headline-md, .text-body-md, .text-body-sm, .text-label-sm, .text-telemetry-num — plus the Phase 3–5 shell classes (.strip, .recenter, .route-stack, .card-stack).
New CSS for this phase (banner, list, guidance-card rows, progress bar) lives in shell.css and references var(--color-*) / var(--spacing-*) / var(--type-*) only. The machined-geometry literal list grows by exactly: banner arrow plate 48×48 + glyph 32×32, banner min-height 96px, compact strip min-height 24px, list row min-height 48px + row glyph 24×24, next-row lamp 8px, list panel max-height 480px, progress bar 4px, the 64px control-clearance composite (reused), guidance camera paddings {top 320, bottom 64, left/right 64} (map-space px) — all 4-multiples, 1px/2px rules, machined glyph geometry, and map-space paint arithmetic — the established exception classes.
Shell Layout — Register Bands (guidance occupation, D-06)
The Phase 3 partition is unchanged. During guidance the registers re-populate:
| Band | Content during guidance | Geometry |
|---|---|---|
| Top register | Maneuver banner (new, D-06) — distance + arrow + street, matte-black chassis; the toggle for the maneuver list | Below safe-top + space-sm; min-height 96px; full-bleed |
| Top register, compacted | Location strip (compacted) — same lamp + state word, telemetry cluster hidden, min-height 24px | Directly beneath the banner, separated by a 1px chassis-subtle rule; one continuous black chassis with the banner |
| Center register | Rams basemap + position marker + Phase 5 route stack; maneuver list panel (only while expanded, overlays the map — no dim layer, boot-card precedent); boot/denied/pending cards unchanged when guidance is not active | Map stays the dominant surface |
| Bottom register | Guidance card (the route card evolved — D-06); abandon confirm card (bottom placement — thumb-reach call, D-09); signal-lost card (existing, transient); recenter control bottom-right (returns on pan-unlock, Phase 3 semantics); attribution chip bottom-left | Card bottom offset calc(var(--safe-bottom) + 64px) — the Phase 5 composite, unchanged |
- Strip compaction is layout-only. The strip's state machine, lamp colors, and word labels are byte-identical (Phase 3 table stands;
strip.tslogic untouched). Guidance adds adata-guidanceattribute on the shell root: padding-block dropsspace-sm→space-xs, min-height 32 → 24px, and the right telemetry cluster (±M/FIX Ns AGO) hides — accuracy/fix-age are not glance data at speed. The strip never hides degradation silently: staleness and denial still announce via red lamp + word, unchanged. Compacted registration is honest registration. - No new floating controls. The recenter control keeps its plate, notch states, and hit target exactly; during guidance its
followingstate means locked-follow (D-10) — the notch semantics read identically to the user. - Banner placement is the strip's own band + the space above it: the chassis grows upward, the map viewport shrinks correspondingly (camera arithmetic below compensates).
Spacing Scale
Bound to tokens (--spacing-*), consumption not invention. Phase 3/5 tables stand; Phase 6 adds:
| Element | Number | Token source |
|---|---|---|
| Banner padding | space-sm space-md (8/16) |
tokens |
| Banner min-height | 96px (register 12 + distance 32 + street 26 + 2×space-xs gaps + 2×space-sm padding ≈ 94 → 96 = 12×8) |
tokens + spec geometry |
| Banner arrow plate | 48×48px (32px glyph + 8px breathing per side), surface-dark field on the black chassis (mockup nesting precedent, token --color-surface-dark) |
machined geometry |
| Banner arrow glyph | 32×32 viewBox, 3px stroke, square linecaps | telemetry_rams HUD spec |
| Compact strip min-height | 24px, padding-block space-xs |
tokens + spec geometry |
| Banner→strip separator | 1px solid chassis-subtle |
machined rule |
| List panel | card geometry: calc(100% - 2×margin), max-width 320px, centered; max-height 480px (scrolls); padding space-sm bottom |
tokens + spec geometry |
| List row | min-height 48px, padding space-sm space-md, glyph 24×24, 1px matte-black row separators (dark: inverse-on-surface) |
tokens + machined rule |
| Next-row lamp | 8×8px signal-orange square at the row's left edge (checkbox geometry precedent) |
spec geometry |
| Guidance card | bottom offset 64px composite (Phase 5, reused); telemetry row + progress bar margins space-xs; progress bar height 4px, width = content width (inset space-md) |
tokens + machined geometry |
| Confirm card | bottom register, same 64px composite (thumb-reach — see A11y) | Phase 5 composite |
| Guidance camera padding | { top: 320, bottom: 64, left: 64, right: 64 } — map-space px (MapLibre padding, not CSS; 8-multiples; T − B = 256 ≈ 0.30 × 844 reference viewport → center at 65% down-screen, D-10) |
spec geometry |
Exceptions: none beyond the established classes (44px hit targets via existing slop patterns; 4-multiples; map-space arithmetic).
Typography
The Phase 3 scale stands — zero new sizes, zero new weights. telemetry-num gains its second consumer (the banner distance).
| Role | Token | Spec | Usage in Phase 6 |
|---|---|---|---|
| Banner distance | --type-telemetry-num |
28px Space Grotesk 700 / 32px, tracking −0.03em | Distance-to-maneuver number (250 + M) — the glance datum |
| Banner street | --type-headline-md |
20px Space Grotesk 600 / 26px, tracking −0.02em | Street/designation line, uppercase (HUD spec: "Maneuver Typography: headline-md") |
| Banner register | --type-label-sm |
10px Geist 700 / 12px, tracking +0.08em, uppercase | GUIDE // NEXT register line; LIST/CLOSE toggle word |
| List row street | --type-body-md |
14px Geist 400 / 20px | Row street names |
| List row distance | --type-label-sm |
10px Geist 700 / 12px, uppercase | Per-row remaining distance (1.2 KM) |
| Guidance-card time | --type-telemetry-num |
(as above) | Minutes remaining — the route card's time slot, evolved |
| Guidance-card units/ETA | --type-label-sm |
(as above) | MIN, {km} KM, ETA 18:42 |
| Card state line | --type-body-sm |
12px Geist 400 / 16px | Degradation copy (no-reroute), confirm-card body |
| Confirm headline | --type-headline-md |
(as above) | Abandon route? |
| CTA labels | --type-label-lg (inside .btn-*) |
14px Space Grotesk 700 / 18px, uppercase | END TRIP, YES, ABANDON, CONTINUE |
Unit formats are instrument constants, code-composed (Phase 3/5 precedent): distance {n} M under 1000 (integer), {n.n} KM at/over (one decimal), ETA clock 24-hour HH:MM, minutes {n} MIN. Units unchanged under ms. Body line-height ~1.43, heading 1.3 — token ratios verbatim.
Color
60 / 30 / 10 split unchanged. The banner's arrow joins signal-orange's reserved-for list — navigation semantics, the Phase 1 reservation's natural fulfillment. The reserved-for list is restated and extended:
| Role | Value | Usage in Phase 6 |
|---|---|---|
| Dominant (60%) | Rams basemap grayscale + shell chrome (unchanged) | The world stays grayscale; guidance ink reads against it |
| Secondary (30%) | Instrument surfaces (unchanged) | Guidance card + list panel + confirm/trip-end cards: light soft-beige / dark inverse-surface via theme slots; banner + compact strip chassis matte-black (Rams-constant, strip precedent) |
| Accent (10%) | signal-orange #EB8008 |
Reserved-for list (exact, Phase 6 — supersedes the Phase 5 list where guidance surfaces are concerned): every Phase 5 reservation (route line, heading cone, follow notch, focus brackets, lamps, primary CTA fill, press inverts); PLUS the banner maneuver arrow (32×32, 3px stroke — HUD spec); the machine-blink gantry square in REROUTING/RESUMING states (boot-card precedent); the next-maneuver row lamp + row glyph in the list; the progress-bar fill. Orange appears at most once per band, always meaning navigation |
| Destructive | functional-red #ED3F1C |
Unchanged and NOT extended: denied/signal-lost lamps only. Abandoning a route is a deliberate user action, not an error — the confirm card renders in standard card ink; the REROUTING and no-reroute states are deferrals, never failures — no red anywhere in guidance chrome |
| Operational | olive-green #736B1E |
LIVE lamp only (unchanged) |
Banner ink hierarchy (chassis discipline — three inks + one accent): register line chassis-subtle (≈9:1 on black), street soft-beige (≈11.6:1 — the HUD spec's ink), distance pure-white (≈17:1 — maximum glance contrast), arrow + state square signal-orange. The HUD spec pins the arrow orange and the street soft-beige; the distance reads white (mockup precedent: 250 M in pure-white) so the accent stays singular per band.
Turn-card PNG colors (D-12): stroke signal-orange #EB8008 on a matte-black #1A1A1A full-bleed plate — the banner's chat sibling; hex values copied verbatim from tokens.css as build constants (DSGN-01 precedent; the glyph script's source SVGs carry the literals).
- Opacity 1.0 everywhere — physical planarity law. The one sanctioned translucency remains the Phase 3 accuracy circle (data grounding).
- Past-maneuver rows render in
outlineink (#897363, an existing token) — muted history, not hidden (the list is honest about where you have been).
Maneuver Banner (D-06 — the top register's guidance occupant)
Full-bleed matte-black chassis under the safe inset. The whole banner is the list toggle (<button>, full-width, hit height = banner height ≥ 44px).
Structure (single row, top→bottom within the band):
| Slot | Class / geometry | Content |
|---|---|---|
| Register line | .text-label-sm, chassis-subtle ink, gap space-xs |
GUIDE // NEXT — or the state word per the state table below (instrument vocabulary, en under both locales, composed in code from the GUIDE base key) |
| Arrow plate | 48×48px surface-dark field |
32×32 inline SVG maneuver glyph, signal-orange, 3px stroke, square linecaps (the committed glyph set, in-app rendering) |
| Distance | .text-telemetry-num pure-white + unit in .text-label-sm |
250 M / 1.2 KM — remaining distance to the maneuver, from cumulativeDistanceM + snapped progress (never raw fix-to-point distance, D-07); unknown during REROUTING/RESUMING = — (strip's unknown-value glyph) |
| Street line | .text-headline-md, soft-beige ink, uppercase, single line, ellipsis overflow (the list carries the full name) |
Street/designation verbatim from the route step name (data, not copy — destination-name rule); unnamed segment renders the streetUnnamed fallback copy |
| Toggle word | .text-label-sm, chassis-subtle ink, right edge |
LIST when collapsed / CLOSE when expanded (real-word affordance — no chevron decoration); aria-expanded carries the state |
- Recalculating-calm state (D-04): register →
GUIDE // REROUTING; street slot → the reroute copy (calm, no blame, no apology); distance →—; the 8px orange gantry square machine-blinks beside the copy (boot-card precedent: JS-timed two-state toggle ~500ms — an indicator duty cycle, not CSS animation). The arrow plate holds the last glyph — nothing spins, nothing blames. - States are instant hard swaps (no transitions — the shell law). The banner never blanks mid-swap: every state renders all five slots.
Maneuver List (D-06 — collapsible, collapsed default)
A .card panel in the center register (card geometry: max-width 320px, centered, calc(100% - 2×margin)), rendered above the map and below the banner while expanded; max-height 480px, inner scroll. No dim layer, no modal chrome — the boot-card precedent: the map stays visible and calm behind it.
- Toggle: the banner (above). Collapsed by default — guidance's default state is banner + card, the map dominant.
- Tap-out collapses: a full-viewport transparent layer beneath the panel (no dim —
background: transparent) catches any map tap and collapses the list. Rams-clean, no overlay glass. - Row anatomy (top→bottom, trip order): glyph 24×24 (same committed set, 2px stroke rendering at this size — the icon family's uniform-weight rule) · street
body-md(verbatim, ellipsis, full name in the row'stitle) · distancelabel-smright-aligned ({n} M/{n.n} KMremaining to that maneuver). - Row states: NEXT (the banner's maneuver) = orange glyph + 8px orange square lamp at the row's left edge + on-surface ink; FUTURE =
matte-blackglyph + on-surface ink (dark theme:inverse-on-surface); PAST =outlineglyph +outlineink. - Rows are real text (readable as-is, no aria required); glyphs are
aria-hidden; the panel isrole="region"witharia-label=ariaManeuverList; the banner button carriesaria-expanded+aria-controls. - Recalculating refresh (D-04): on
ON_ROUTEafter a reroute, the list re-renders from the new route's steps — instant swap, scroll resets to top (the NEXT row is the first visible row).
Guidance Card (D-06 — the route card evolved, bottom register)
The Phase 5 route card's slots evolve in place — same card class, same bottom offset (64px composite), same builder discipline (createElement + textContent only; strings module only; no per-card padding overrides — the 05 addendum's .card child rules carry the rhythm).
Structure (top→bottom):
| Slot | Class | Content |
|---|---|---|
| Register | .card-header › .card-ref |
GUIDE — the route card's ROUTE register, evolved (instrument vocabulary, en both locales) |
| Headline | .text-headline-md |
Destination name, verbatim — the continuity anchor holds |
| Telemetry row | flex, gap space-xs |
Minutes remaining in .text-telemetry-num + MIN in label-sm (the route card's time slot, now live and deadband-smoothed, D-07) · {km} KM in label-sm · ETA 18:42 in label-sm (clock = now + smoothed minutes, updated on the same deadband) |
| Progress bar | 4px, no radius | Track = chassis-subtle with a 1px matte-black border (checkbox geometry — distinguishable without color); fill = signal-orange, width = snapped progress %, set instantly per accepted fix (no transition). role="progressbar" + aria-valuemin/max/now + localized aria-label (ariaTripProgress) |
| State line | .text-body-sm |
ABSENT in the normal state (less, but better). Present only in the no-reroute degradation: the honest copy below |
| Actions | .card-actions |
One .btn-secondary: END TRIP → opens the abandon confirm card (the D-09 surface — the same confirm the BackButton and native-close paths reach). No .btn-primary on the guidance card: the drive's forward action is driving; orange CTA fill appears nowhere in guidance chrome except the reserved accents above |
Height budget: ≤ 200px (register + headline + telemetry + bar + one action; the state line is the only conditional row). The camera bottom padding (64) + this budget keeps the puck's corridor clear, same arithmetic family as Phase 5.
Abandon-Route Confirm Card (D-09 / PX-03)
One confirm surface for ALL guidance exits: the guidance card's END TRIP, the BackButton from guidance, and the defense-in-depth native closing confirmation (the Rams card is the designed surface; the native dialog is the net behind it — research Open Q 2 recommendation, planner wires it). Standard card anatomy, bottom register (thumb-reach call — confirmations live where one-handed thumbs are; the banner is glance-only, never a decision surface).
| Element | en | ms (Bahasa Melayu) |
|---|---|---|
| Register | GUIDE // CONFIRM |
GUIDE // CONFIRM (instrument vocabulary) |
| Headline | Abandon route? |
Tinggalkan laluan? |
| Body | Guidance stops and this trip's record is deleted. Nothing is stored. |
Panduan berhenti dan rekod perjalanan ini dipadam. Tiada apa-apa disimpan. |
Primary CTA (.btn-primary) |
YES, ABANDON |
YA, TINGGALKAN |
Secondary CTA (.btn-secondary) |
CONTINUE |
TERUSKAN |
- The body's deletion clause is the brand speaking (PRIV-01: the privacy promise stated once, clinically, at the exact moment it executes — D-14).
YES, ABANDONis.btn-primary(the working forward path — Phase 3 denied-card mapping precedent). Not red: abandoning is deliberate, not a failure; orange = primary action semantics; the confirm card carries no red.- While the confirm card is open, a BackButton click means CONTINUE (dismiss, stay in guidance — the no-dead-ends law applied to the confirm layer).
- Both CTAs are full component-class buttons ≥ 44px effective height;
:focus-visiblebrackets are built into.btn-*(Phase 3).
Trip-Ended Card (D-14 — PRIV-01's in-app half, visible)
Renders in the center register the instant the trip ends — arrival (snapped progress ≥ ARRIVAL_THRESHOLD_M) or abandon (confirm path). Both paths: guidance chrome (banner, list, guidance card) clears instantly, the envelope is DELETEd server-side (the KV record gone — the TTL law's promise executed actively), the watch stops (the trip is over — privacy-minimal), and the camera holds its last pose.
| Element | en | ms (Bahasa Melayu) |
|---|---|---|
| Register | GUIDE // END |
GUIDE // END (instrument vocabulary) |
| Headline | Trip ended |
Perjalanan tamat |
| Body | Route guidance stopped and the trip record is deleted — nothing about this drive remains. |
Panduan laluan dihentikan dan rekod perjalanan dipadam — tiada apa-apa tentang pemanduan ini kekal. |
- ONE card for arrival and abandon — both delete, one message (less, but better).
- The card persists until the next action (no silent auto-dismiss — honesty law); the BackButton is visible and walks to the search root; a new destination resolution replaces it through the Phase 4/5 chain.
Turn-Card Chat Message (D-12 — the minimized handoff's surface)
A Telegram chat message pushed by the Worker cron (D-11): sendPhoto with the maneuver glyph PNG + an HTML caption + one inline button. It is chat content, not app chrome — Telegram renders the text; the spec pins the composition. Owned strings live in the bot strings module (bot/src/i18n/strings.ts pattern; the bot copy gate asserts them), not the web module.
The 8-glyph set (committed once, reused everywhere)
| # | Kind | Maneuver meaning | Glyph geometry (32×32 grid, 3px stroke, square caps, orthogonal/45° only) |
|---|---|---|---|
| 1 | depart |
Trip start onto the first road | 4×4 filled square (origin-ring echo) + vertical shaft up |
| 2 | straight |
Continue ahead | Vertical shaft + orthogonal arrowhead |
| 3 | left |
Turn left | Up shaft + 90° left head (the mockup's turn glyph family) |
| 4 | right |
Turn right | Mirror of left |
| 5 | slight-left |
Keep left | 45° up-left shaft + head |
| 6 | slight-right |
Keep right | Mirror of slight-left |
| 7 | uturn |
U-turn (routed for real in v1 — D-01 defers turn restrictions, so the graph WILL produce them; the glyph is honest about it) | Up shaft, orthogonal 90° left, down shaft — a machined staple, no arc (orthogonal law) |
| 8 | arrive |
Arrival | Vertical shaft ending on the 12×12 filled square + tail — the Phase 5 destination-pin glyph, promoted |
Production: hand SVGs (source of truth, committed) → build-glyphs.mjs (sharp) → 256×256 PNGs committed beside the bot's static assets, served from the Worker's static handler (sendPhoto fetches the public URL — research-verified). Stroke #EB8008 on a full-bleed #1A1A1A plate, 256×256 output (chat renders it ~48–96px; the oversize keeps edges crisp). In-app, the SAME paths render as inline SVG at 32×32 (banner) / 24×24 (list).
Caption layout (HTML parse mode, ≤1024 chars — verified budget; ~200 used)
GUIDE // T-07
<b>250 M — TURN RIGHT ONTO JALAN SULTAN ISMAIL</b>
ETA 18:42 · 7.8 KM LEFT
Best-effort projection — updates while the app is minimized.
| Line | Source | Rules |
|---|---|---|
| Register | GUIDE // T-{nn} — instrument constant, code-composed (nn = zero-padded maneuver index) |
en both locales; plaintext |
| Maneuver line | {dist} — {action} onto {street} — the 8 localized action verbs below; street verbatim from the step name |
<b> wrapped, uppercase (instrument voice); streetUnnamed fallback |
| Telemetry line | ETA {time} · {km} KM LEFT template — projected ETA/remaining from the envelope snapshot (the cron's projection, D-11) |
plaintext; formats identical to app constants |
| Honesty line (D-13) | fixed copy | plaintext, never promises live tracking; the 8 verbs, OPEN button label, and both templates are localized en/ms |
Localized action verbs (the caption's {action}): DEPART/MULA · CONTINUE ON/TERUS KE · TURN LEFT ONTO/BELOK KIRI KE · TURN RIGHT ONTO/BELOK KANAN KE · KEEP LEFT ONTO/SEDAKIT KIRI KE · KEEP RIGHT ONTO/SEDAKIT KANAN KE · MAKE A U-TURN ONTO/BUAT PUSARAN KE · ARRIVE AT/TIBA DI.
Inline button: one button, OPEN GUIDANCE / BUKA PANDUAN, carrying the Mini App direct-link with the guidance id (research Open Q 1: startapp/?g= knob — planner picks the exact name) so a killed webview resumes from the envelope. reply_markup rides the same sendPhoto call.
Push cadence (UX-visible half of D-11/D-13): a card pushes when the projected next maneuver changes (or every N minutes — discretion); cron pushes pause while the app is foregrounded (structural: the envelope is DELETEd on resume, re-POSTed at minimize). Cron lag and Telegram delivery variance are documented behaviors — the copy says "best-effort", the mechanism means it.
Camera Behavior (D-10)
| Event | Camera action | Parameters |
|---|---|---|
| Guidance starts | Snap to locked-follow | easeTo snapped position: padding { top: 320, bottom: 64, left: 64, right: 64 } (center ≈65% down-screen), pitch: 35 (the standing pitch — never changed by guidance), zoom: 16 (named constant GUIDANCE_ZOOM — urban detail; replay-tunable), duration 400ms |
| Per accepted fix (≥10 m / ≥5 s, D-15) | Ease along the snapped line | easeTo the snapped progress point (D-05: one authority — the camera rides the same line the banner reads, never the raw fix), duration 400ms (GUIDANCE_EASE_MS — the RECENTER_DURATION_MS family, one easing discipline) |
| User pans/zooms | Existing movestart originalEvent rule → follow unlocks; the recenter control's notch clears (Phase 3 free state returns) |
The route line, banner, and card are untouched — unlock is camera-only |
| Recenter tap while unlocked | Re-lock locked-follow: easeTo snapped point, guidance padding, notch returns |
Phase 3 control semantics, guidance parameters |
| Reroute snaps in (D-04) | No camera jump — the new route's snapped line simply continues under the puck | Instant setData (map motion settles naturally on the next per-fix ease) |
| Trip ends | Camera holds its last pose | No move; the watch has stopped |
- Bearing stays north-up (recorded call): guidance never rotates the map — Phase 3/5 never touched bearing; the heading cone already communicates direction; a rotating map contradicts the calm-instrument posture. Pitch stays 35 per D-10 and the standing law.
- Camera moves are map-viewport behavior — permitted motion; the chrome never animates (Phase 3 law stands).
State Coexistence — Guidance States (D-03/D-04/D-14)
One guidance surface set; states swap instantly. The Phase 5 coexistence table stands beneath this one (boot/pending/destination states are unchanged when guidance is not active).
| State | Banner | Guidance card | List | Map/camera | Strip |
|---|---|---|---|---|---|
| Guidance active (ON_ROUTE) | GUIDE // NEXT, glyph + distance + street |
normal (no state line) | collapsed (toggle opens) | locked-follow, route stack as Phase 5 | compacted, lamp/word unchanged |
| OFF_ROUTE (grace window, pre-reroute) | unchanged — still GUIDE // NEXT (no premature signal; the machine acts, not warns) |
unchanged | unchanged | locked-follow | unchanged |
| REROUTING (D-04 calm state) | GUIDE // REROUTING, street slot → reroute copy, distance —, gantry blink |
unchanged (last honest numbers hold) | unchanged (stale-but-labeled) | locked-follow on old line until the new line snaps | unchanged |
| ON_ROUTE after reroute | GUIDE // NEXT with the new maneuver; list re-rendered |
ETA/remaining continue from the new model (EMA state persists — no whiplash, D-07) | re-rendered, scrolled to top | same line-style paint, new geometry (setData) |
unchanged |
| Router-pending degradation (graph absent, D-03 — excursion while reroute impossible) | GUIDE // NO REROUTE, street slot → degradation copy, distance — |
state line carries the longer honest copy; card otherwise unchanged | unchanged | route line stays drawn (fixture/engine route is real); locked-follow continues | unchanged. No red — a designed deferral, not an error (router-pending precedent) |
| Resuming (re-open before first fix, D-11/D-06) | GUIDE // RESUMING, distance from envelope projection, gantry blink |
projected numbers (labeled honest by the banner state) | collapsed | locked-follow at projected position | unchanged |
| First fix after resume | GUIDE // NEXT — live numbers |
live numbers | — | snapped progress corrected | unchanged |
| Arrived / abandoned (D-14) | cleared instantly | → trip-ended card (center) | cleared | camera holds; watch stops | returns to full-height live strip |
Minimize (webview deactivated) |
— | — | — | — | envelope POSTed (best-effort, D-13); cron turn-cards begin in chat |
Guidance never starts without a route: a router-pending route state keeps the Phase 5 pending card and binds NO PX-03 protections (nothing to abandon — swipe/close stays native, BackButton shows for the pending state per Phase 5).
Copywriting Contract (copy tone: clinical, honest — D-01/D-13; en + ms; web keys join web/src/i18n/strings.ts, bot keys join the bot strings module; never inline — the copy gates)
New ShellStrings keys (web side; registers and unit formats are instrument constants in both locales):
| Key | en | ms |
|---|---|---|
guideRegister |
GUIDE |
GUIDE |
guideStreetUnnamed |
Unnamed road |
Jalan tanpa nama |
guideReroutingLine |
Finding a new route from where you are. |
Mencari laluan baharu dari kedudukan anda. |
guideResumingLine |
Resuming from your last known progress. |
Sambung semula dari kemajuan terakhir anda. |
guideNoRerouteLine |
Rerouting unavailable on this server. |
Pengiraan semula tiada pada pelayan ini. |
guideNoRerouteBody |
No road graph here — follow the drawn route; recalculation is unavailable. |
Tiada graf jalan di sini — ikut laluan yang dilukis; pengiraan semula tidak tersedia. |
ctaBannerList |
LIST |
SENARAI |
ctaBannerClose |
CLOSE |
TUTUP |
ctaEndTrip |
END TRIP |
TAMAT PERJALANAN |
guideConfirmHeadline |
Abandon route? |
Tinggalkan laluan? |
guideConfirmBody |
(confirm table above) | (confirm table above) |
ctaConfirmAbandon |
YES, ABANDON |
YA, TINGGALKAN |
ctaConfirmContinue |
CONTINUE |
TERUSKAN |
guideEndHeadline |
Trip ended |
Perjalanan tamat |
guideEndBody |
(trip-end table above) | (trip-end table above) |
ariaManeuverList |
Full maneuver list |
Senarai penuh manuver |
ariaBannerToggle |
Next maneuver |
Manuver seterusnya |
ariaTripProgress |
Trip progress |
Kemajuan perjalanan |
Bot-side keys (turn-cards): cardAction{Depart,Straight,Left,Right,SlightLeft,SlightRight,Uturn,Arrive} (verbs above), cardEtaTemplate ETA {time} · {km} KM LEFT / ETA {masa} · {km} KM LAGI, cardHonesty Best-effort projection — updates while the app is minimized. / Unjuran cuba terbaik — dikemas kini semasa aplikasi diminimumkan., cardOpenButton OPEN GUIDANCE / BUKA PANDUAN. Register lines (GUIDE // NEXT, GUIDE // T-07, …) are instrument constants composed in code.
- Primary CTA:
YES, ABANDONonly (the confirm card). Orange CTA fill is otherwise absent from guidance chrome. - Empty state: not applicable — guidance renders only over a live route; the pre-guidance empty states are the Phase 3/4/5 surfaces (unchanged).
- Error state: the no-reroute degradation IS the honest error-adjacent state (graph absent, D-03) — standard ink, no red, never blank. A reroute request failure renders the same state (fail-honest, banner stays calm).
- Destructive actions: exactly one — abandoning the trip. It is guarded by the confirm card on every path (card button, BackButton, native-close net). Both exit paths execute the D-14 deletion and show the trip-end card.
- Honesty law (D-13) applied: the turn-card never says "live", "real-time", or "guaranteed" — "best-effort projection" is the strongest claim the mechanism supports; the resuming banner labels projected numbers as resuming until a live fix lands.
Copy gate: the strings-module tests assert exactly these keys in en + ms (web + bot modules); no guidance copy inline in web/src/guidance/*, shell.css, or the cron handler.
Transitions & Micro-Behaviors (inside Rams rules)
- No CSS
transition/animationanywhere in chrome — unchanged law. Banner states, list expand/collapse, card swaps: instant hard swaps (no height animation on the list). - Machine-blink gantry (boot-card precedent) is the only loading indicator: REROUTING and RESUMING states blink the 8px orange square at ~500ms via JS timer — two visible states, an indicator duty cycle, never an easing pulse.
- Progress bar: width set per accepted fix, instant, no transition. A bar that glides is a chart; a bar that steps is an instrument.
- Camera easing (400ms
easeToper fix) is map-viewport motion — permitted; chrome never moves. - Turn-card arrival in chat: Telegram's own message rendering — no app-side animation exists or is wanted.
- No blur, no shadow, no glass, no pulse on the route line. A driven route is static geometry; only state changes it.
Close Protection (PX-03 — the three layers, wired on guidance start, unwound at trip end)
| Layer | API (version guard) | Wiring |
|---|---|---|
| Body-swipe minimize off | disableVerticalSwipes() (Bot API 7.7+) |
On guidance start; enableVerticalSwipes() at trip end; capability-guarded ?. chains per the contract style |
| Native close confirm (defense-in-depth — the header swipe cannot be disabled, official docs) | enableClosingConfirmation() (6.2+) |
On guidance start; disabled at trip end. The Rams confirm card remains the designed surface; the native dialog is the net (research recommendation) |
| In-app exits | the abandon confirm card | END TRIP, BackButton-from-guidance, and any post-native-confirm return path land on the same card |
Safe areas: the banner sits at safe-top + space-sm (the strip's existing --safe-top var); the guidance/confirm cards clear safe-bottom via the 64px composite; insets resolve via the Phase 3 resolveSafeInsets union (env ∩ Telegram 8.0+, max per edge) — nothing interactive in the home-indicator zone; the basemap extends under bars.
BackButton Contract (PX-01 — the full state graph, never dead-ends)
The Phase 5 wire (wireBackButton(wa, hasOverlay, walkBack) — predicate + handler as one change) extends to a context-aware walkBack:
| App state | BackButton | Click behavior |
|---|---|---|
| Boot stages / destination card (root) | hidden (unchanged) | — |
| Route state | show(); click → walk back |
Phase 5 walk: clears route layers, card swaps to destination card, refollow, hide() |
| Guidance state | show(); click → opens the abandon confirm card (bottom register) |
Guidance is a pushed state — never torn down silently. YES, ABANDON → guidance teardown (chrome clears, envelope DELETE, watch stops) → route card state (the route still exists in memory). CONTINUE → card dismisses, guidance continues |
| Route state (post-abandon) | show(); click → walk back |
The Phase 5 route walk → destination card (search root), hide() |
| Trip-ended card | show(); click → walk to search root |
Card clears, hide() — the graph's exit ramp |
| Confirm card open + BackButton click | (guidance still active) | Means CONTINUE — dismiss the card, stay in guidance (no stacked dialogs) |
The full walk — search → routes → guidance → (confirm) → routes → search — presses Back at most once per state; the button is visible exactly when a pushed state exists; its handler is never a no-op; close() fires only at the true root (Telegram semantics). No dead ends.
Haptics (D-08 — the eyes-free channel)
| Event | Cue | Trigger point | Guard |
|---|---|---|---|
| Maneuver approach | HapticFeedback.selectionChanged() |
Snapped progress within 300 m of the next maneuver (named constant APPROACH_HAPTIC_M — replay-tunable); fires once per maneuver |
Bot API 6.1+ / ?. chain — graceful no-op |
| Maneuver point | HapticFeedback.impactOccurred("medium") |
Snapped progress crosses the step boundary (once, edge-triggered) | as above |
| Arrival (discretion extension of the same mechanism) | HapticFeedback.notificationOccurred("success") |
ARRIVAL_THRESHOLD_M crossed — the trip-end card's tactile twin for eyes-on-road |
as above |
No haptic on reroute (the banner stays calm — a haptic would announce blame), none on confirm dialogs (visual + thumb is enough). Every cue is once-per-edge, replay-tested with the state machine.
Accessibility & Device Notes
- Contrast (computed, WCAG):
pure-whiteonmatte-black≈ 17:1 (distance — the glance datum, AAA);soft-beigeonmatte-black≈ 11.6:1 (street, AAA);chassis-subtleonmatte-black≈ 8.9:1 (register line, AAA); card inkmatte-blackonsoft-beige≈ 11.6:1 (AAA);signal-orangeused only for graphics/fills beside ≥ AA text (arrow, lamps, bar fill — non-text UI elements ≥ 3:1 against their fields, reinforced by the bar's 1px border). - Thumb-reach: destructive confirm at the bottom register (one-handed reach); banner interactions are glance + whole-band tap (no precision targets at the top); the recenter control keeps its ≥44px hit target.
- Hit targets: banner toggle = full band; list rows are non-interactive text (the list is a readout, not a menu — less, but better); card buttons are component-class.
- Screen readers: banner = one toggle button (
aria-expanded,aria-controls,aria-label); list = labelled region of real text rows; progress bar =progressbarwith valuetext; glyphs/lampsaria-hidden; state is never carried by color alone (register words, gantry, card state line are text). - Haptics degrade silently (D-08); safe-area layout degrades to pure
env()on old clients (Phase 3 law); every Telegram call is capability-guarded per the contract style (research Pitfall 5).
Capture Profiles (extends web/scripts/capture.mjs + the Mock-Telegram harness)
New harness knob and legs; all existing legs unchanged:
| Artifact | Profile | Asserts |
|---|---|---|
Harness knob ?drive=<fixture> |
the mock's Telegram fix source replays a committed drive fixture (the D-16 replay JSON streams — corridor follow / off-route excursion / missed-turn excursion) through the live shell; pairs with ?dest= + ?fixpair= carriers |
ARAHARAH-GUIDE state=<state> signals (state tokens only — no coordinates in console, D-15 hygiene) |
shell-guidance-active.png |
mock=android&dest=<golden-id>&fixpair=<pair>&drive=<corridor>, light theme |
banner (GUIDE // NEXT, arrow, distance, street) + compact strip + guidance card + progress bar + locked camera pose |
shell-guidance-list.png |
same, after a scripted banner tap | list panel expanded over the map, NEXT row lamp, collapsed-by-default proof = the active leg's complement |
shell-guidance-rerouting.png |
drive=<missed-turn> captured inside the REROUTING window |
calm banner state (reroute copy, — distance, gantry), NO red anywhere |
shell-guidance-confirm.png |
scripted END TRIP tap |
confirm card in the bottom register, both CTAs, no red |
shell-guidance-tripend.png |
scripted YES, ABANDON |
trip-ended card (center), guidance chrome cleared |
shell-guidance-dark.png |
active profile, scheme=dark |
PX-02: chrome re-slots, banner/chassis Rams-constant |
guidance-gallery-compare.png |
DSGN-02 side-by-side vs active_route_guidance/screen.png |
fidelity artifact for the phase gallery |
Turn-card captions are asserted by the bot-side suite + copy gate (bot/test/guidance-cron.test.mjs — caption composition, verb table, ≤1024 budget), not by browser capture.
Bundle-budget note (planner-facing): guidance chrome is DOM builders + the inline glyph paths — inside the ≤ ~200KB gzipped BA gate. The glyph PNGs ride the bot's static assets (never the web bundle); sharp is a maintainer devDependency only (research-verified).
Fidelity Artifact (DSGN-02 obligation)
Phase exit extends the gallery pattern: the seven guidance captures above beside active_route_guidance/screen.png, reviewed for fidelity before phase close. Where this document, the mockup markup, and tokens.css disagree: tokens.css wins, the mockup loses, this spec records the call. Recorded calls:
- Mockup's lane-assignment matrix, incident telemetry band, speedometer + limit medallion, telemetry gauge deck → deferred features (incidents v2, speed limits v1.x, REQUIREMENTS deferred table) — scrubbed, nothing placeholder-shaped.
- Mockup's REROUTE VIA TELEGRAM BOT / REPORT HAZARD / AUDIO buttons → no such actions exist in v1 scope; guidance's only action is END TRIP.
- Mockup's backdrop-blur header + rounded puck/medallions → scrubbed (existing law: no blur, zero radius — the Phase 3 machined marker stands).
- Mockup's arrow glyph at stroke 4 / 40×40 grid → HUD spec's 32×32 / 3px wins (the committed set's geometry).
- Mockup's 56px (
w-14) arrow plate → 48×48 (32 glyph + 2×8 breathing; 12×8 grid). - Mockup's orange distance number →
pure-whitewins (accent-singular-per-band; the arrow carries the orange).
Registry Safety
| Registry | Blocks Used | Safety Gate |
|---|---|---|
| shadcn official | none (not initialized; gate N/A for the no-framework stack) | not required |
| third-party | none — glyph PNGs are committed artifacts built from committed hand SVGs via a maintainer sharp script (no registry, no CDN, no runtime dependency); map tiles remain an OpenFreeMap runtime data fetch, not a code dependency | not required |
Checker Sign-Off
- Dimension 1 Copywriting: PASS
- Dimension 2 Visuals: PASS
- Dimension 3 Color: PASS
- Dimension 4 Typography: PASS
- Dimension 5 Spacing: PASS
- Dimension 6 Registry Safety: PASS
Approval: pending
Phase: 06-turn-by-turn-guidance-live-driving Contract drafted: 2026-10-24 by gsd-ui-researcher Consumes: theme tokens (binding), liberty-rams.json layer order, 06-CONTEXT.md D-01..D-16, 05-UI-SPEC.md (binding conventions), 03-UI-SPEC.md (binding conventions), telemetry_rams/DESIGN.md §Navigation Heads-Up Display, DESIGN.md palette semantics, Phase 5 frozen GuidanceSessionEnvelope