trails/openspec/changes/archive/2026-07-15-planner-route-encoding/proposal.md
Ullrich Schäfer 6fcd1e21cd
chore(openspec): archive planner-route-encoding
All 12 tasks complete (shipped in #590 codec + #591 wiring). Archive via
`openspec archive`:
- Creates openspec/specs/planner-route-encoding/spec.md — new capability
  (5 requirements: compact single-source geometry, RLE road metadata,
  backward-compatible reads, preserved compute-once model, bounded doc
  size). Purpose filled in (CLI leaves TBD).
- Moves the change to openspec/changes/archive/2026-07-15-planner-route-encoding/.

Spec passes `openspec validate --strict`.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 17:13:19 +02:00

3 KiB
Raw Blame History

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 twicegeojson (a GeoJSON LineString of every coordinate) and coordinates (the same coordinates as a plain array), both JSON.stringifyd.
  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

Impact

  • apps/planner/app/lib/route-data.tswriteComputedRoute / 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.