# 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`). ```typescript // search/src/types.ts:57-63 — the shape being mirrored: export interface SearchSource { readonly name: string; search(q: SearchQuery): Promise; // EMPTY array = honest refusal byId(id: string): GazetteerEntry | null; } // becomes: route(req): Promise — 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). ```typescript // 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 `