9.2 KiB
Phase 5: Routing + Route Display - Pattern Map
Mapped: 2026-10-24 Files analyzed: 24 new/modified surfaces (grouped into 16 rows below) Analogs found: 15 / 16 rows matched (1 genuinely new: the polyline6 codec — pure algorithm, RESEARCH is the spec)
Source files for this map: 05-CONTEXT.md (D-01..D-12), 05-RESEARCH.md, 05-UI-SPEC.md (binding). The controlling analogs are the
search/workspace (Phase 4) — interface + ladder + loader + corpus gate — and the Phase 3 shell surfaces (map/,ui/cards.ts,telegram/backbutton.ts).
File Classification
| New/Modified File | Role | Data Flow | Closest Analog | Match Quality |
|---|---|---|---|---|
routing/ workspace pkg (package.json, tsconfig.json) |
package config | — | search/package.json / search/tsconfig.json (Phase 4, shipped) |
exact |
routing/src/types.ts (RoutingSource + RouteResult + day-one model fields) |
model (types) | — | search/src/types.ts |
exact |
routing/src/polyline.ts (polyline6 codec) |
utility | transform (pure) | none — new algorithm; 05-RESEARCH Finding 1 is the spec (round-trip law makes it self-verifying) | none (algorithm) |
routing/src/fixture-source.ts (backend #1) |
service | request-response (in-memory match) | search/src/source.ts + search/src/gazetteer.ts (lazy LAW 1/2/3) |
exact |
routing/src/index.ts (public API) |
package surface | — | search/src/index.ts (named exports, subpath-with-extension import law) |
exact |
routing/test/runner.mjs + harness.mjs + *.test.mjs |
test | — | search/test/* (same harness shape) |
exact |
routing/src/data/route-fixtures.json (committed fixtures) |
data | — | search/src/data/kl-gazetteer.json (committed dataset, ODbL header object) |
exact |
routing/test/fixtures/golden-pairs.json (pinned corpus) |
data (test fixture) | — | search/test/fixtures/golden-corpus.json |
exact |
routing/scripts/fetch-route-fixtures.mjs |
script (repo tool, network) | file-I/O + network | search/scripts/overpass-topup.mjs (UA mandatory, soft-exit, never a runtime dep) |
exact |
routing/scripts/route-corpus.mjs + routing/test/corpus.test.mjs |
script + Nyquist gate | batch | search/scripts/gazetteer-corpus.mjs + search/test/corpus.test.mjs (one measurement, two reporters) |
exact |
bot/src/kv/guidance-sessions.ts (envelope TYPES ONLY) |
model (types) | — | bot/src/kv/sessions.ts (SessionClaim "the entire persisted claim surface — a 5th property means drift") |
role-match (types-only, no KV calls) |
web/src/route/route-layers.ts (GeoJSON sources + 4 layers) |
service (map painting) | addSource/addLayer/setData | web/src/map/map.ts building-3d block (addLayer inside map.on("load"), reuses style-owned values, never re-sets style) |
exact |
web/src/route/route-markers.ts (origin ring + destination pin + name chip) |
view (DOM markers) | Marker DOM | web/src/map/marker.ts (createPositionMarker DOM-marker pattern, .pos-* chrome in shell.css) |
exact |
web/src/route/route-card.ts (route card + chips + pending card) |
view (card builders) | DOM | web/src/ui/cards.ts (mountHintedCard) + web/src/search/destination-card.ts (createElement + textContent ONLY, strings-module copy) |
exact |
web/src/route/route.ts (controller: consult, selection swap, camera) |
controller | event/state | web/src/map/follow.ts (constants + controller closure; CAMERA_PADDING_PX/RECENTER_DURATION_MS precedent) |
role-match |
web/src/main.ts (mod) + web/src/telegram/backbutton.ts (mod: two-state walk) |
controller wiring | — | themselves (Phase 3/4; overlayPresent closure becomes the route-state predicate) |
— |
web/src/shell.css (mod: chip/card-body/marker-chrome rules) |
style | — | itself (token-only law; machined-geometry exception list) | — |
web/src/i18n/strings.ts (mod: 10 route keys) |
config/i18n | — | itself (COPY1..COPY6 gate) | exact |
web/index.html (mod: ?fixpair= knob) |
config (harness page) | — | itself (?dest= profile block, 04-04) |
exact |
web/src/mock/route-profiles.ts (harness-only pair-origin lookup) |
test harness | — | web/src/mock/telegram-mock.ts (harness-only module, BA5-excluded) |
exact |
web/scripts/capture.mjs (mod: 5 route legs + ROUTE signal) |
script | batch | itself (SIGNALS array + captureLeg) | exact |
web/test/build-artifact.test.mjs (mod: BA9) |
test (gate) | — | itself (BA1..BA8) | exact |
web/test/route-surface.test.mjs (new structural suite) |
test | — | web/test/destination-preload.test.mjs (DP-gate style) |
exact |
Pattern Assignments
routing/src/types.ts (model, types-only)
Analog: search/src/types.ts — same header-contract comment style, same "the seam every backend implements" law, same empty-value-is-load-bearing semantics ([] → null).
// search/src/types.ts:57-63 — the shape being mirrored:
export interface SearchSource {
readonly name: string;
search(q: SearchQuery): Promise<SearchHit[]>; // EMPTY array = honest refusal
byId(id: string): GazetteerEntry | null;
}
// becomes: route(req): Promise<RouteResult | null> — null = router pending (D-03)
routing/src/fixture-source.ts (service)
Analog: search/src/gazetteer.ts LAW 1/2/3 verbatim — lazy memoized index (never module scope), static JSON import (with { type: "json" }), zero logging/storage. The matcher adds one pure function the gazetteer never needed: a haversine distance with a 200 m tolerance constant (05-RESEARCH Finding 3).
routing/scripts/fetch-route-fixtures.mjs (repo tool)
Analog: search/scripts/overpass-topup.mjs — descriptive User-Agent mandatory, inter-request delay, per-item soft diagnostics, writes a committed JSON dataset, imported by NOTHING at runtime. The header contract states the D-12 boundary (fixed public coordinates, maintainer machine, no runtime dependency).
routing/test/corpus.test.mjs (Nyquist gate)
Analog: search/test/corpus.test.mjs — "the suite re-runs the corpus runner's measurements IN-PROCESS (never spawns the script), so npm run routing:test fails whenever the corpus regresses." One measurement core exported from the script, two reporters.
web/src/route/route-layers.ts (map painting)
Analog: web/src/map/map.ts:43-67 — the building-3d block is the only existing addLayer precedent: inside map.on("load"), no beforeId, paint values copied from style-owned constants ("zero invented colors", DSGN-01). UI-SPEC extends it: 4 layers added AFTER the building-3d block, bottom→top route-alt-casing → route-alt-line → route-casing → route-line; sources added empty, filled via setData (data, not style — never a style re-set).
// web/src/map/map.ts:43 — the exact insertion point (AFTER this block, same handler):
map.on("load", () => { /* building-3d … console.log("RAMS-BASEMAP-READY …") */ });
web/src/route/route-card.ts (card builders)
Analog: web/src/search/destination-card.ts — createElement + textContent ONLY (an id/name-derived string never reaches innerHTML), composed from theme classes, every string from the strings module (COPY5). Chips add the .toggle-option focus precedent (3px orange bracket) and the <button type="button"> law. Per the UI-SPEC post-approval addendum: NO per-card padding overrides — compose from the existing .card > * padding rules (shell.css:74-80).
web/src/route/route.ts (controller)
Analog: web/src/map/follow.ts — named constants beside the existing ones (ROUTE_CAMERA_PADDING = { top: 96, bottom: 320, left: 64, right: 64 }, ROUTE_FIT_DURATION_MS = 400), a closure controller, no state outside memory (D-07).
bot/src/kv/guidance-sessions.ts (envelope types)
Analog: bot/src/kv/sessions.ts SessionClaim — "the entire persisted claim surface — a 5th property means drift" — but TYPES-ONLY per D-06: no KvLike, no .put(, no wrangler namespace; the TTL exists as a documented constant (GUIDANCE_SESSION_TTL_SECONDS = 7200) awaiting its Phase 6 consumer. The grep gate (bot test) pins "types-only" mechanically.
web/src/telegram/backbutton.ts (two-state walk)
Analog: itself — Phase 3's total-function wire (overlayPresent() => false → close()); the predicate becomes "route or pending state active" and the pushed-state click walks back via the SAME total function SEARCH AGAIN invokes (UI-SPEC §BackButton Contract). The handler and predicate ship as one change.
Data Flow Notes
- Origin never persists: the request
{origin, destinationId}is constructed in main.ts's closure from the live fix + the Phase 4 memory-only destination, passed by argument, and dropped with the frame (D-07/GEO-03 law — the route module takes no storage, keeps no module-scope origin). - Fixtures are public data:
route-fixtures.jsonenters the bundle by static import exactly likekl-gazetteer.json(build-time inlining, BA7-style parity gate idea carries to BA9's budget measurement). - Signals are state tokens:
ARAHARAH-ROUTE state=drawn|pending— never coordinates (D-15 hygiene, theARAHARAH-DEST state=precedent).
Pattern map: 2026-10-24 — pattern-mapper role run inline (no subagent spawn capability; sanctioned fallback).