Mission planning: Add mission library#2654
Conversation
Automated PR Review (Claude)0. SummaryVerdict: MINOR SUGGESTIONS Minor items to address: 1.1, 1.2, 1.3, 2.1, 6.1, 6.2, 6.3 This PR adds a mission library feature to the mission planning view, allowing users to save, organize, reload, import/export, and reposition missions. It introduces a 1. Correctness & Implementation Bugs1.1 ( 1.2 ( 1.3 ( 2. AGENTS.md Adherence2.1 (
Per AGENTS.md: "No new comments unless explaining 'why', never 'what'." 2.2 ( 2.3 ( 2.4 ( 3. Security3.1 No obfuscated or intentionally unreadable code found. 3.2 The 3.3 No hidden Unicode, zero-width characters, RTL overrides, or homoglyph attacks detected. 3.4 No unexpected network calls. The only external URL constructed is the Google Earth link ( 3.5 No changes to build scripts, CI workflows, Dockerfiles, or Electron main-process code. 3.6 No new use of 3.7 No new dependencies added. 3.8 No other suspicious patterns detected. 4. Performance4.1 ( 4.2 The placement preview rebuild is properly coalesced via 4.3 Map event listeners ( 5. UI / UX5.1 ( 5.2 ( 5.3 The endpoint markers use 6. Code Quality & Style6.1 ( 6.2 ( 6.3 ( 6.4 ( 7. TestsNo tests are added or modified in this PR. The mission library logic (thumbnail generation, 8. Documentation8.1 ( 8.2 In-code JSDoc is thorough across all new public functions, types, and interfaces. Good. 9. Nitpicks / Optional9.1 ( 9.2 ( 9.3 ( 9.4 ( Generated by Claude. This is advisory; a human reviewer must still approve. |
98fa8d9 to
69bf1be
Compare
Tame the default Leaflet wheel/pinch zoom so a single mouse-wheel notch (or pinch step) advances exactly one zoom level instead of jumping two, which felt jumpy and easy to overshoot on the planning map and on the general map widget. Made-with: Cursor
Add SavedMission (CockpitMission plus library metadata: id, name, description, vehicle type, timestamps, thumbnail, estimates snapshot) and MissionEstimatesSnapshot (pre-formatted strings captured at save time so the library can render summaries without recomputing against the user's currently planned mission). Made-with: Cursor
Add helpers used by the mission library: - generateMissionThumbnail: builds a base64 SVG preview of a mission (waypoints path, survey polygons, fallback "No path" label) with the first/last waypoints rendered in orange and 25% larger so the endpoints stand out at a glance on the library cards. - computeMissionLocation: returns the centroid of a mission's waypoints/surveys, falling back to the saved map center when empty. - isSavedMission: type guard used when ingesting library entries from imported files or persisted storage. Made-with: Cursor
Add `missionLibraryVisibility` state and matching `isMissionLibraryVisible` getter so the mission planning view can open and close the library modal through the same shared interface store used for the rest of the app's modals (config, video library, etc.). Made-with: Cursor
Add `plannedVehicleType` (persisted via useBlueOsStorage as `cockpit-planned-vehicle-type`) so users can pick the vehicle type to plan for when no vehicle is connected, and `effectiveVehicleType` that prefers the connected vehicle's reported type and falls back to the planned one. This unblocks vehicle-type-specific planning features (mission estimates, vehicle-specific UI cues) while planning offline. Made-with: Cursor
…tures Switch the mission estimates composable and MissionEstimates panel to read from missionStore.effectiveVehicleType so they keep working when the planner is used offline. Add a "Planning for" selector to the mission planning sidebar (visible only while no vehicle is connected) that exposes Surface Boat, Submarine, UAV, and Ground Rover as the supported categories the user can plan against. Made-with: Cursor
Add the persisted `savedMissions` collection (under `cockpit-mission-library`, synced to BlueOS when available) plus the helpers used by the library UI: - saveMissionToLibrary: creates a new entry or updates an existing one in place by id, baking a thumbnail and the metadata (name, description, vehicle type, estimates snapshot, timestamps) at save time so the library can render summaries cheaply. - deleteSavedMission: removes an entry by id. - getSavedMissionLocation: thin wrapper over the libs helper for consumers that already hold the store. Made-with: Cursor
Add MissionLibraryModal: the full library UI mounted by the mission planning view. Renders saved missions as a grid of preview cards (thumbnail, name, date, vehicle-type and stats pills with a frosted- glass background for readability over the mission preview), supports saving the current mission via a dedicated dialog (native inputs with explicit labels, Enter-to-save, name and description fields), and opens a detail dialog with full estimates, vehicle-type tag, and an "Open in Google Earth" link. Also exposes an `openSaveOnMount` prop so callers can land users directly on the save form. Made-with: Cursor
Replace the separate "Save mission to file" / "Load mission from file" toolbar buttons with a single bookshelf entry that opens the new MissionLibraryModal, and drop the now-unused saveMissionToFile / loadMissionFromFile helpers (and their `format` / `saveAs` imports). Wire the modal to the current planner state (snapshot of waypoints/surveys, mission estimates, effective vehicle type) and handle library loads via a placement-choice dialog so the user is asked where the mission should land before it touches the current plan. The "Keep original location" path replays the saved settings via loadDraftMission; the repositioning path arrives in a follow-up commit. Made-with: Cursor
Extend the library load flow with a "Reposition on map" option that
drops the saved mission onto the planning map at 1:1 scale and lets
the user drag, scale (independent X/Y, hold Shift for uniform), and
rotate it via on-map handles plus numeric overlays before committing.
A green check confirms the placement, a red trash cancels, and a
restore button resets scale and rotation to defaults.
Also add the segment-insert third option to the radial menu shown when
hovering between two existing waypoints ("Insert mission from library
here") and the routing in finalizeMissionPlacement /
appendMissionToPlanning / insertMissionIntoSegment so loading a library
mission either:
- splices into the requested segment (when the load came from the
radial menu), preserving waypoint ids consistently between the
top-level and survey-internal lists so editing the survey later
doesn't leave an orphan polygon on the map;
- appends to the current planning when the planner already has work
in progress (so a second library load doesn't wipe the first); or
- replays the saved settings when the planner is empty.
Made-with: Cursor
Replace the single "Mission library" entry plan with a hover submenu in the map context menu that exposes "Save mission to library" (only enabled when there's something to save) and "Add mission from library". The save entry opens the library modal directly on the save form via a new `openSaveOnMount` prop / `missionLibraryOpenSaveOnMount` flag, while the add entry reuses the existing toolbar entry point. Both segment-insert intent and save-on-mount intent are now cleared on every fresh open of the library so a plain toolbar open never inherits stale flags from a previous interaction. Made-with: Cursor
Render the first and last waypoints of the current mission with the new `.endpoint-marker` style (orange fill via `#ff9800`, scaled up to 1.25x via transform around the center) so it's visually obvious which waypoints are the mission's start and end. The `endpoint-marker` class is added through `createWaypointMarkerHtml`'s new `isEndpoint` flag, which is computed via the small `isEndpointWaypoint(id)` helper and threaded through every place that builds or refreshes a waypoint icon (updateWaypointMarkers, both marker-creation paths, applySelectedWaypointMarkerVisual, and the click-clear handler) so the highlight stays consistent across selection, drag, zoom, and survey entry/exit re-renders. Made-with: Cursor
Make "Add waypoint here" from the map context menu act relative to the closer endpoint when the cursor isn't on the existing path: if the click is closer to the first waypoint, the new waypoint is prepended instead of appended; otherwise it falls back to the previous append-to-end behavior. Segment-proximity insertion (splitting the path between two waypoints) still wins when the cursor is on or near a segment. Done by adding an optional `insertIndex` to addWaypoint (splice when provided, push otherwise) and a small `getEndpointInsertIndexForLatLng` helper that picks 0 vs. undefined based on which endpoint is nearer. Made-with: Cursor
…oser endpoint Extend the existing endpoint-bias from "add waypoint" to the other context-menu add operations so right-clicking near the start of an existing mission adds the new survey, simple path, or library mission at the start of the planning instead of always appending. Made-with: Cursor
69bf1be to
0174b54
Compare
Mission Library:
Screenshare.-.2026-04-29.11_56_26.AM.mp4
Bidirectional element addition:
Screenshare.-.2026-04-29.1_22_22.PM.mp4