Appearance
Use Case 025: Control Points Persistence & Orphaned Cues
1. Objective
To stabilize the route planning experience by separating the "Skeleton" (user-defined control points/clicks) from the "Meat" (high-resolution track geometry), ensuring long-term track editability. Additionally, to implement a "Pro-level" UX for track modifications where absolute POIs remain stationary, while track-dependent Cue Points intelligently snap to micro-shifts or visually warn the user (Orphan State) upon macro-shifts.
Target Users: Advanced route planners who design complex routes and later return to tweak specific segments without destroying the original routing intent.
2. Core Features & Acceptance Criteria
Block 1: "Skeleton and Meat" (Routing Control Points)
- Persistence: Start, Finish, and all intermediate control points MUST be explicitly serialized and saved to the database alongside the track's GeoJSON geometry.
- Visibility by State:
- Edit Mode: Control points are visible as white markers. The user can drag, delete, or add them.
- Read-Only Mode: Control points are completely hidden. Only the track line, Start/Finish, and POIs/Cues are visible.
- GPX Export Constraint: Control points MUST NOT be exported as
<wpt>tags in GPX files to prevent polluting the bike computer screen. Only the final track<trk>is exported. - Edit Restoration: When a saved track is opened in Edit mode, dragging a point only recalculates the adjacent segments, preserving the rest of the complex geometry.
Block 2: POI and Cue Point Physics
- Class A: Free POIs (Absolute Coordinates)
- Definition: Springs, cafes, campsites.
- Behavior: Bound strictly to the Earth (GPS Lat/Lng).
- Interaction: When the track is dragged away, the POI remains in its original position.
- Class B: Track Cues (Relative Context)
- Definition: Dangers, turns, fords (meaningful only when riding the track).
- Micro-Shift (Auto-Snap): If the track is dragged and the new line is < 30 meters from the Cue, the Cue automatically snaps to the nearest point on the new line.
- Macro-Shift (Orphan State): If the new line moves significantly (> 30 meters), the Cue is "Orphaned".
- Visual State: Turns semi-transparent (Ghost state), gets a red dashed border, and displays a broken chain icon (🔗).
- UX Alert: Triggers a micro-alert near the UI: "⚠️ You have 1 cue off route! [Delete] / [Keep]".
- Resolution: User can click to delete it, or manually drag it back onto the new track line.
3. Tech Stack Preferences & Constraints
- State Management:
plannerStore(Zustand) must be updated to managecontrolPointsalongsidegeojson.zundohistory must track control points. - Backend Schema: Update the database and
api-libsmodels to support acontrolPointsarray inside the Route entity. - Spatial Calculations: Use
turf.pointToLineDistanceandturf.nearestPointOnLineto handle the threshold logic (< 30m) for Cue snapping.
4. Known Boundaries
- NEVER auto-delete an Orphaned Cue. The system must ask the user or wait for manual deletion.
- NEVER export control points as
<wpt>in GPX. - ALWAYS decouple visual geometry (thousands of coordinates) from logical anchors (5-10 control points) during the save/load cycle.
- ALWAYS check for orphaned cues silently in the background right after
buildRoutesucceeds.
5. Implementation Roadmap / Commands
- Database / Schema: Extend
Routemodel in API/DB to includewaypointsJSON. Update/routesPOST/PUT endpoints. - Frontend Store: Update
trackService.tsto push/pullwaypoints. RefactorhandleOpenTrackto restore waypoints intoplannerStore. - Visibility Logic: Hook
useWaypointMarkersintoisReadonlymap state to toggle visibility. - GPX Exporter: Verify
exportToGpxstrictly filters out routing control points. - Orphan Logic: In
useBuildRoute(or a dedicated observer), calculate the distance of all Cues to the new line. DispatchsnapCueormarkCueOrphanedactions. - Orphan UI: Create the
Ghoststyling and thebroken chainicon in MapLibre HTML markers. Add the floating alert toast.