trails/openspec/changes/planner-route-encoding/proposal.md
Ullrich Schäfer d933cca451
docs(openspec): propose planner-route-encoding change
Follow-up to the planner WS reconnect-loop incident. Proposes compactly
encoding the computed route in the Yjs session doc (single-source geometry
+ delta/varint polyline + run-length-encoded road metadata, dual-format
backward-compatible reads) to cut a ~305 KB / 77 km route to ~40–60 KB —
WITHOUT changing the routing-host "compute once, share via Yjs" model or
GPX export output.

Artifacts: proposal, design, specs (new planner-route-encoding
capability), tasks. Validates --strict. Ready for /opsx:apply.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 09:31:58 +02:00

36 lines
3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

## Why
The planner's Yjs session doc stores the computed route very inefficiently: a 4-waypoint / 77 km route already serializes to **~305 KB**. Three sources of bloat in `routeData` (`apps/planner/app/lib/route-data.ts`, `writeComputedRoute`):
1. **Geometry is stored twice**`geojson` (a GeoJSON LineString of every coordinate) *and* `coordinates` (the same coordinates as a plain array), both `JSON.stringify`d.
2. **Coordinates are stringified JSON floats**`[13.4051234,52.5204567]` ≈ 22 bytes/point.
3. **Seven per-coordinate road-metadata arrays** (`surfaces`, `highways`, `maxspeeds`, `smoothnesses`, `tracktypes`, `cycleways`, `bikeroutes`) stored as JSON `string[]` — in practice long runs of the same value.
This grows linearly with route length toward the 5 MB doc cap, bloats every reconnect's full-state sync, and was the underlying cause of the WS reconnect-loop outage (mitigated, not removed, by raising the frame cap in #587). It is pure encoding waste: the data is *derived*, and the routing-host model already computes it exactly once.
## What Changes
Encode the computed route compactly in `routeData`, **without changing the routing-host "compute once, share via Yjs" model** — one elected client still computes via BRouter and writes the result into the doc for everyone to read; clients never recompute.
- Store geometry **once** (single source of truth; derive the other representation at read time).
- Encode the polyline compactly (delta + varint / encoded-polyline) instead of stringified JSON floats.
- **Run-length-encode** the road-metadata arrays.
- Centralize encode/decode in `route-data.ts` so all readers are transparently unaffected.
- **Backward compatible**: the read path transparently handles both the existing JSON format (already persisted in `planner.sessions.yjs_state`) and the new encoding — no flag day, no migration downtime.
Target: **~5× smaller** (≈305 KB → ≈4060 KB for that route). `MAX_DOC_BYTES = 5 MB` stays the guard; GPX export output stays byte-compatible.
## Capabilities
### New Capabilities
- `planner-route-encoding`: how the planner's computed route (geometry + road metadata) is compactly and backward-compatibly encoded in the Yjs session document, independent of how it is computed or shared.
### Modified Capabilities
<!-- None: the routing-host compute/share behavior and route-coloring semantics are unchanged; only the on-doc encoding changes. -->
## Impact
- `apps/planner/app/lib/route-data.ts``writeComputedRoute` / read helpers (`readComputedRoute`, `readRoadMetadata`, etc.): new encode on write, dual-format decode on read.
- Readers via those helpers, unchanged at the call site: `SessionView` map rendering, elevation profile, GPX export, save-to-journal, road-type/surface coloring.
- New encoding module + tests in `@trails-cool/planner` (or `@trails-cool/gpx`/`map-core` if the codec is reusable).
- No schema change (`yjs_state` is opaque bytea); no API change; no change to routing-host election or BRouter usage.