## Context The Planner stores waypoints as a Yjs `Y.Array>` where each Y.Map holds `lat`, `lon`, and optionally `name`. Routes are computed segment-by-segment between consecutive waypoints via BRouter, producing an `EnrichedRoute` with `coordinates`, `segmentBoundaries`, `totalLength`, `totalAscend`, and `totalTime`. The visual-redesign change already defines the UI treatment for multi-day routes (sidebar day breakdown, elevation chart dividers, map day labels) but explicitly defers the data model and logic. This design covers the data model, computation, and integration decisions. ## Decisions ### D1: Day-break waypoints via `overnight` flag A waypoint becomes a day boundary by setting `overnight: true` on its Y.Map. The first waypoint of the route is the implicit start of Day 1, the last waypoint is the implicit end of the final day, and every waypoint with `overnight: true` marks the end of one day and start of the next. ``` Waypoints: [Berlin, Zossen, Dessau(overnight), Halle, Erfurt] |------- Day 1 -------||------- Day 2 ------| ``` This is the simplest possible model: a single boolean on existing data. No new Yjs types, no separate array, no ordering concerns. It composes naturally with waypoint reordering, insertion, and deletion -- if an overnight waypoint is removed, the two days merge automatically. ### D2: Day computation as a pure utility A `computeDays()` function takes the waypoints array and the `EnrichedRoute` and returns an array of day objects: ```typescript interface DayStage { dayNumber: number; startWaypointIndex: number; endWaypointIndex: number; startName: string; endName: string; distance: number; // meters ascent: number; // meters descent: number; // meters estimatedTime: number; // seconds coordStartIndex: number; coordEndIndex: number; } function computeDays( waypoints: Array<{ lat: number; lon: number; name?: string; overnight?: boolean }>, route: EnrichedRoute, ): DayStage[]; ``` The function walks `segmentBoundaries` to map waypoint indices to coordinate ranges, then accumulates distance and elevation per day by iterating coordinates within each range. If no waypoints have `overnight: true`, it returns a single day covering the entire route. This is a pure function with no Yjs dependency -- it takes plain data and returns plain data. This makes it easy to test and reuse. ### D3: Yjs state -- minimal addition The only change to Yjs state is adding an `overnight` key to waypoint Y.Maps: ```typescript // Setting overnight on a waypoint const waypointMap = yjs.waypoints.get(index); waypointMap.set("overnight", true); // Clearing overnight waypointMap.delete("overnight"); ``` No new Y.Array or Y.Map types are introduced. The `overnight` key is optional -- existing waypoints without it are treated as regular (non-overnight) waypoints. This is fully backwards-compatible: sessions created before this feature work identically, and clients that don't understand `overnight` simply ignore it. The existing crash-recovery logic (periodic localStorage save of Y.Doc state) preserves overnight flags automatically since they are part of the Y.Doc. ### D4: Sidebar day breakdown The `WaypointSidebar` component gains a day-grouped view when any waypoint has `overnight: true`. The layout follows visual-redesign D4: ``` ACTIVE ROUTE Berlin -> Erfurt 343 km ^868m 2 days DAY 1 - Berlin -> Dessau [v] ^340m 120 km ~4h 30m 1. Berlin Alexanderplatz 2. Zossen 3. Juterbog 4. Dessau [OVERNIGHT] > DAY 2 - Dessau -> Erfurt 223 km [>] (collapsed days show summary only) ``` **Behavior:** - Day 1 is expanded by default; other days are collapsed - Clicking a day header toggles expand/collapse - Per-day stats (distance, ascent, estimated time) shown in day header - Overnight waypoints display an amber badge - Waypoints within each day are numbered sequentially (1, 2, 3... restarting per day would be confusing -- use global numbering) - When no waypoints have `overnight: true`, the sidebar shows the flat list as it does today (no "Day 1" wrapper for single-day routes) ### D5: Elevation chart day dividers The `ElevationChart` canvas drawing is extended to render day boundaries as vertical dashed lines. For each day boundary (overnight waypoint), the corresponding distance along the route is computed from `segmentBoundaries` and `coordinates`, and a dashed vertical line is drawn at that x-position. ``` Day 1 Day 2 Day 3 ___/\__/\___ : __/\_____ : __/\___ / \ : / \ : / \ /_______________\:/___________\:/________\ 0 km 120 km 120 km 250 km 250 km 343 km ``` Each divider has a "Day N" label at the top of the chart area. The dashed line uses a muted color (`--text-lo` / `#9A9484`) to avoid competing with the elevation profile. This follows visual-redesign D6. ### D6: Map day labels White pill-shaped labels are placed on the route at each day boundary, showing "Day 1 . 120 km". These use Leaflet DivIcon markers positioned at the coordinate of the overnight waypoint. Styling follows visual-redesign D5: - White background with subtle shadow (`--shadow-sm`) - Text in `--text-hi` with distance in `--font-mono` - Positioned slightly offset from the route line to avoid overlap with the route itself Day labels are only shown when there are 2+ days. They update reactively when overnight flags change (same Yjs observe pattern as existing markers). ### D7: GPX export with day-break metadata Day-break waypoints are exported with a `overnight` element inside the `` tag. This is valid GPX 1.1 (the `` element is a standard child of ``). ```xml Dessau overnight ``` Additionally, the track can optionally be split into multiple `` elements (one per day), each with a `` like "Day 1: Berlin - Dessau". This gives GPS devices and other tools a natural per-day breakdown. The single-track export remains the default; multi-track is an option in the export dialog. On GPX import (future), the parser should recognize `overnight` waypoints and restore the overnight flags. This is not in scope for this change but the format is designed to support it. ### D8: Overnight toggle UX Two interaction paths to toggle a waypoint as overnight: 1. **Sidebar**: Each waypoint row in the sidebar gains an overnight toggle button (crescent moon icon). Clicking it sets/clears `overnight` on the waypoint's Y.Map. The button uses amber styling (`--stop`, `--stop-bg`) when active. 2. **Map context menu**: Right-clicking (long-press on mobile) a waypoint marker on the map shows a context menu with "Mark as overnight stop" / "Remove overnight stop". This reuses the same Y.Map mutation. Visual feedback follows visual-redesign tokens: - Overnight waypoint markers on the map use amber-brown (`--stop`: `#8B6D3A`) instead of the default olive (`--accent`: `#4A6B40`) - Sidebar overnight waypoints have a subtle amber background (`--stop-bg`) - The "OVERNIGHT" badge uses `--stop` text on `--stop-bg` background with `--stop-border` border ## Risks / Trade-offs - **Segment boundary alignment**: The day computation relies on `segmentBoundaries` from `EnrichedRoute` to map waypoint indices to coordinate ranges. If the segment merge logic changes, day computation breaks. Mitigate with thorough unit tests on `computeDays`. - **Large routes**: A route with 50+ waypoints and many overnight stops could make the sidebar unwieldy. Collapsible sections mitigate this. We can add virtual scrolling later if needed. - **Yjs backwards compatibility**: Adding `overnight` to Y.Maps is safe, but older clients that don't understand it will silently ignore overnight flags. In a collaborative session, one user could see day breakdown while another does not. This is acceptable for now since all clients will be updated together. - **GPX round-trip**: The `overnight` convention is not a standard GPX extension namespace. Other tools will ignore it, which is fine. The data is not lost, just not interpreted.