diff --git a/openspec/changes/planner-features/.openspec.yaml b/openspec/changes/archive/2026-03-26-planner-features/.openspec.yaml similarity index 100% rename from openspec/changes/planner-features/.openspec.yaml rename to openspec/changes/archive/2026-03-26-planner-features/.openspec.yaml diff --git a/openspec/changes/planner-features/design.md b/openspec/changes/archive/2026-03-26-planner-features/design.md similarity index 100% rename from openspec/changes/planner-features/design.md rename to openspec/changes/archive/2026-03-26-planner-features/design.md diff --git a/openspec/changes/planner-features/proposal.md b/openspec/changes/archive/2026-03-26-planner-features/proposal.md similarity index 100% rename from openspec/changes/planner-features/proposal.md rename to openspec/changes/archive/2026-03-26-planner-features/proposal.md diff --git a/openspec/changes/planner-features/specs/brouter-integration/spec.md b/openspec/changes/archive/2026-03-26-planner-features/specs/brouter-integration/spec.md similarity index 100% rename from openspec/changes/planner-features/specs/brouter-integration/spec.md rename to openspec/changes/archive/2026-03-26-planner-features/specs/brouter-integration/spec.md diff --git a/openspec/changes/planner-features/specs/crash-recovery/spec.md b/openspec/changes/archive/2026-03-26-planner-features/specs/crash-recovery/spec.md similarity index 100% rename from openspec/changes/planner-features/specs/crash-recovery/spec.md rename to openspec/changes/archive/2026-03-26-planner-features/specs/crash-recovery/spec.md diff --git a/openspec/changes/planner-features/specs/map-display/spec.md b/openspec/changes/archive/2026-03-26-planner-features/specs/map-display/spec.md similarity index 100% rename from openspec/changes/planner-features/specs/map-display/spec.md rename to openspec/changes/archive/2026-03-26-planner-features/specs/map-display/spec.md diff --git a/openspec/changes/planner-features/specs/no-go-areas/spec.md b/openspec/changes/archive/2026-03-26-planner-features/specs/no-go-areas/spec.md similarity index 100% rename from openspec/changes/planner-features/specs/no-go-areas/spec.md rename to openspec/changes/archive/2026-03-26-planner-features/specs/no-go-areas/spec.md diff --git a/openspec/changes/planner-features/specs/planner-session/spec.md b/openspec/changes/archive/2026-03-26-planner-features/specs/planner-session/spec.md similarity index 100% rename from openspec/changes/planner-features/specs/planner-session/spec.md rename to openspec/changes/archive/2026-03-26-planner-features/specs/planner-session/spec.md diff --git a/openspec/changes/planner-features/specs/rate-limiting/spec.md b/openspec/changes/archive/2026-03-26-planner-features/specs/rate-limiting/spec.md similarity index 100% rename from openspec/changes/planner-features/specs/rate-limiting/spec.md rename to openspec/changes/archive/2026-03-26-planner-features/specs/rate-limiting/spec.md diff --git a/openspec/changes/planner-features/specs/session-notes/spec.md b/openspec/changes/archive/2026-03-26-planner-features/specs/session-notes/spec.md similarity index 100% rename from openspec/changes/planner-features/specs/session-notes/spec.md rename to openspec/changes/archive/2026-03-26-planner-features/specs/session-notes/spec.md diff --git a/openspec/changes/planner-features/tasks.md b/openspec/changes/archive/2026-03-26-planner-features/tasks.md similarity index 100% rename from openspec/changes/planner-features/tasks.md rename to openspec/changes/archive/2026-03-26-planner-features/tasks.md diff --git a/openspec/changes/planner-route-interactions/.openspec.yaml b/openspec/changes/archive/2026-03-26-planner-route-interactions/.openspec.yaml similarity index 100% rename from openspec/changes/planner-route-interactions/.openspec.yaml rename to openspec/changes/archive/2026-03-26-planner-route-interactions/.openspec.yaml diff --git a/openspec/changes/archive/2026-03-26-planner-route-interactions/design.md b/openspec/changes/archive/2026-03-26-planner-route-interactions/design.md new file mode 100644 index 0000000..794130a --- /dev/null +++ b/openspec/changes/archive/2026-03-26-planner-route-interactions/design.md @@ -0,0 +1,127 @@ +## Context + +The Planner currently renders routes as a single-color `L.Polyline` with no +interactivity — users can't click on the route or drag it. Waypoints can only +be appended (click on map) or reordered in the sidebar. BRouter returns +per-point elevation in coordinates (`[lon, lat, ele]`) and can return surface +tags, but `mergeGeoJsonSegments` currently discards all per-point data and +only keeps track-level totals. + +bikerouter.de and komoot both support click-to-split and drag-to-reshape as +primary editing interactions. These are the most natural way to refine a route +after the initial waypoints are placed. + +## Goals / Non-Goals + +**Goals:** +- Hover over the route to see a transient ghost marker at the nearest route point +- Click or drag the ghost marker to insert a waypoint (split / reshape) +- Color the route by elevation gradient (green→yellow→red) or surface type +- Sync elevation chart colors with route coloring in elevation mode +- Preserve per-point data from BRouter through the merge pipeline +- All interactions synced via Yjs (collaborative) + +**Non-Goals:** +- Undo/redo system (future change) +- Route alternatives (show multiple options) +- Custom color gradient configuration +- Surface type legend or detailed surface info panel +- Offline route coloring (requires BRouter data) + +## Decisions + +### D1: Ghost marker interaction (inspired by brouter-web) + +Following brouter-web's `L.Routing.Edit` pattern: a **single persistent +draggable Marker** (`RouteInteraction` component) that appears when the cursor +is near the route and can be clicked or dragged to insert a waypoint. + +Key techniques from brouter-web: +- **Distance-based snap**: Listen on `map.mousemove`, compute pixel distance + to nearest route coordinate (15px tolerance). No reliance on SVG polyline + mouseout events (which cause flickering). +- **Single reusable marker**: Created once via `L.marker({ draggable: true })`, + shown/hidden with `addTo(map)`/`remove()`. Not destroyed/recreated on each + hover. +- **Dragging guard**: `draggingRef` flag freezes snap updates during drag and + prevents hide-on-mouseout. +- **Leaflet's built-in drag**: `draggable: true` on the Marker uses Leaflet's + `L.Draggable` which automatically calls `L.DomUtil.disableTextSelection()` + during drag and re-enables on dragend. +- **Trailer lines**: Two persistent dashed `L.Polyline` instances shown during + drag, connecting the ghost to adjacent waypoints for visual feedback. + +**Alternative considered and rejected**: Fixed midpoint CircleMarkers between +waypoints. Caused flickering (render loop between marker and polyline events), +required zoom threshold to avoid clutter, and `CircleMarker` SVG rendering +was unreliable in react-leaflet after programmatic zoom. + +**Alternative considered and rejected**: Invisible wide polyline for click +detection. Playwright couldn't trigger mousemove events on SVG paths with +opacity 0. The map-level listener with distance-based snap is more reliable. + +### D2: Per-point data preservation in BRouter response + +BRouter GeoJSON coordinates are `[lon, lat, ele]`. For surface data, BRouter's +`tiledesc=true` parameter includes per-point `WayTags` in the +`properties.messages` array (e.g., `surface=asphalt highway=primary`). + +`mergeGeoJsonSegments` now: +1. Preserves full 3D coordinates `[lon, lat, ele]` +2. Tracks segment boundary indices (where each waypoint segment starts) +3. Extracts surface type per point from `WayTags` via regex + +The `EnrichedRoute` interface: + +```typescript +interface EnrichedRoute { + coordinates: [number, number, number][]; // [lon, lat, ele] + segmentBoundaries: number[]; // coordinate index where each segment starts + surfaces: string[]; // surface type per point (from WayTags) + totalLength: number; + totalAscend: number; + totalTime: number; + geojson: GeoJsonCollection; // backwards compat +} +``` + +All enriched data is stored in `routeData` Y.Map (as JSON strings) so all +participants receive it. + +### D3: Colored route rendering with segmented polylines + +`ColoredRoute` component renders multiple `L.Polyline` segments colored by +per-point data. Three modes toggled via a `