# Frontend — Components

**Location:** src/lib/components/

Components are reusable UI building blocks used across multiple routes. Each is a self-contained Svelte file. They communicate with the backend via Tauri invoke() and receive live rover data via Tauri events listened to with listener().

# video.svelte + double_video.svelte — Video Components

### video.svelte

<p class="callout danger">**TODO:** still being developed, going to change</p>

<span style="white-space: pre-wrap;">The basic single-camera display component. Accepts a camera object from </span>`<span class="editor-theme-code">state.svelte.js</span>`<span style="white-space: pre-wrap;"> and renders it as an </span>`<span class="editor-theme-code"><img></span>`<span style="white-space: pre-wrap;"> tag pointing at the MJPEG stream URL. Supports two optional modes passed as the </span>`<span class="editor-theme-code">mode</span>`<span style="white-space: pre-wrap;"> prop:</span>

- **`<strong class="editor-theme-bold editor-theme-code">measure</strong>` mode**<span style="white-space: pre-wrap;"> — enables pixel-clicking for stereo measurement. The operator clicks two points across two camera feeds; the component calls </span>`<span class="editor-theme-code">invoke("request_measurement")</span>`<span style="white-space: pre-wrap;"> with the pixel coordinates from both cameras and returns the result via an </span>`<span class="editor-theme-code">onmeasurement</span>`<span style="white-space: pre-wrap;"> callback.</span>
- **`<strong class="editor-theme-bold editor-theme-code">pick</strong>` mode**<span style="white-space: pre-wrap;"> — enables a pick-up interaction for probe/rock collection. The </span>`<span class="editor-theme-code">pick()</span>`<span style="white-space: pre-wrap;"> function is a stub ready for the actual rover arm command to be wired in.</span>

When no mode is set the component is a plain passive video display.

### double\_video.svelte

Displays two camera feeds simultaneously with a picture-in-picture layout. The primary feed fills the frame; the secondary feed appears as a smaller overlay in the bottom-right corner. Clicking the overlay swaps the two feeds, making the secondary feed the primary and vice versa.

<span style="white-space: pre-wrap;">Both feeds show a </span>`<span class="editor-theme-code">⚠ SIGNAL LOST</span>`<span style="white-space: pre-wrap;"> overlay banner when their </span>`<span class="editor-theme-code">stale</span>`<span style="white-space: pre-wrap;"> flag is </span>`<span class="editor-theme-code">true</span>`<span style="white-space: pre-wrap;"> (set by the camera health listener in </span>`<span class="editor-theme-code">state.svelte.js</span>`). The secondary feed shows a smaller version of the same warning.

<span style="white-space: pre-wrap;">Props: </span>`<span class="editor-theme-code">camera1</span>`<span style="white-space: pre-wrap;">, </span>`<span class="editor-theme-code">camera2</span>`<span style="white-space: pre-wrap;"> — camera objects from </span>`<span class="editor-theme-code">state.svelte.js</span>`.

# model_scene.svelte + model_viewer.svelte + model_debug.ts — 3D Model Viewer

These three files together form the 3D model display system used on the dashboard.

### model\_scene.svelte

The outer container and lifecycle manager. Handles: delayed initialisation (100ms timer to ensure the DOM is ready before the WebGL canvas is created), WebGL context loss detection, error state with a retry button, and a resize nudge that forces the canvas to reflow correctly after mount.

<span style="white-space: pre-wrap;">Uses </span>`<span class="editor-theme-code">model_debug.ts</span>`<span style="white-space: pre-wrap;"> to persist error state across the component lifecycle using </span>`<span class="editor-theme-code">localStorage</span>`<span style="white-space: pre-wrap;"> — if the model failed to load in the previous render, the error state is restored immediately on mount to avoid a flash of broken content.</span>

### model\_viewer.svelte

<span style="white-space: pre-wrap;">The inner Three.js / Threlte scene. Loads the GLB model file by calling </span>`<span class="editor-theme-code">invoke("load_model")</span>`<span style="white-space: pre-wrap;">, which returns the raw bytes of the file. The bytes are parsed directly in the browser using </span>`<span class="editor-theme-code">GLTFLoader.parse()</span>`<span style="white-space: pre-wrap;"> — no HTTP request is made.</span>

<span style="white-space: pre-wrap;">After loading, all mesh materials are replaced with a uniform </span>`<span class="editor-theme-code">MeshStandardMaterial</span>`<span style="white-space: pre-wrap;"> in the Roboteam's purple brand colour (</span>`<span class="editor-theme-code">#5A1C74</span>`). The camera is automatically fitted to the model's bounding box so any model file will be centred and fill the view regardless of its original scale.

<span style="white-space: pre-wrap;">The model auto-rotates slowly using </span>`<span class="editor-theme-code">OrbitControls</span>`<span style="white-space: pre-wrap;"> with </span>`<span class="editor-theme-code">autoRotate</span>`<span style="white-space: pre-wrap;">. User rotation, zoom, and pan are disabled — the view is fixed. A scale animation eases the model from 0 to 1 on load using Threlte's </span>`<span class="editor-theme-code">useTask</span>`.

### model\_debug.ts

<span style="white-space: pre-wrap;">A small utility module with two functions: </span>`<span class="editor-theme-code">setLoadFailed(bool)</span>`<span style="white-space: pre-wrap;"> and </span>`<span class="editor-theme-code">wasLoadFailed(): bool</span>`<span style="white-space: pre-wrap;">. These read and write a </span>`<span class="editor-theme-code">localStorage</span>`<span style="white-space: pre-wrap;"> key to persist the model error state across component re-renders.</span>

# task_completion.svelte —  Task Completion

<span style="white-space: pre-wrap;">Displays the history of completed tasks read from the </span>`<span class="editor-theme-code">tasks/</span>`<span style="white-space: pre-wrap;"> app data directory. On mount it lists all task files and deserialises each JSON file into a </span>`<span class="editor-theme-code">Task</span>`<span style="white-space: pre-wrap;"> object.</span>

### Task list

Each task is shown as a card with its name, number, completion time, and finish timestamp. Clicking a card opens a detail modal. A delete button removes the task file and all associated sample images.

### Task detail modal

<span style="white-space: pre-wrap;">Shows full task metadata and a list of all attached </span>`<span class="editor-theme-code">Sample</span>`<span style="white-space: pre-wrap;"> objects with their location, coordinates, measurement, weight, and image paths. Image paths are clickable links that open the image viewer modal.</span>

### Image viewer modal

<span style="white-space: pre-wrap;">Loads the before and after sample images using </span>`<span class="editor-theme-code">appDataDir()</span>`<span style="white-space: pre-wrap;"> + </span>`<span class="editor-theme-code">convertFileSrc()</span>`<span style="white-space: pre-wrap;"> and displays them side by side.</span>

# map.svelte — Map

<p class="callout danger">**TODO:** We still don't have the map format, function is subject to change</p>

<span style="white-space: pre-wrap;">Displays a static map that the operator imports. The map is stored in the </span>`<span class="editor-theme-code">maps/</span>`<span style="white-space: pre-wrap;"> app data directory and loaded using Tauri's asset protocol (</span>`<span class="editor-theme-code">convertFileSrc</span>`).

### Map selection flow

<span style="white-space: pre-wrap;">On mount, the component checks the </span>`<span class="editor-theme-code">displayedMap</span>`<span style="white-space: pre-wrap;"> store. If a map is already selected (from a previous navigation within the session) it loads it directly. If not, it lists available map files and presents a selection UI. If exactly one map file exists it is auto-selected and confirmed without operator interaction.</span>

<span style="white-space: pre-wrap;">Once a map is confirmed, the full path is constructed using </span>`<span class="editor-theme-code">appDataDir()</span>`<span style="white-space: pre-wrap;"> and converted to a Tauri asset URL for display. The selected map is written to the </span>`<span class="editor-theme-code">displayedMap</span>`<span style="white-space: pre-wrap;"> store so other components (and other routes) can access it.</span>

A reload button (⟳) resets the selection and re-lists available files. Mouse coordinates over the map are tracked and displayed, laying the groundwork for click-based waypoint placement.

# costmap.svelte — Costmap

<p class="callout danger">**TODO:** A placeholder component that renders a "Costmap" heading. Intended to display the rover's navigation cost map (obstacle/traversability grid) received from the rover. Not yet implemented.</p>

# imu.svelte — IMU

<span style="white-space: pre-wrap;">Displays live inertial measurement unit data received from the </span>`<span class="editor-theme-code">imu-update</span>`<span style="white-space: pre-wrap;"> Tauri event.</span>

### Data displayed

**Accelerometer**<span style="white-space: pre-wrap;"> — X/Y/Z values in m/s² with a live scrolling sparkline chart showing the last 60 samples per axis. Each axis has a distinct colour (red, purple, green).</span>

**Gyroscope**<span style="white-space: pre-wrap;"> — X/Y/Z values in °/s with the same sparkline treatment.</span>

**Orientation cube**<span style="white-space: pre-wrap;"> — a CSS 3D cube whose </span>`<span class="editor-theme-code">rotateX/Y/Z</span>`<span style="white-space: pre-wrap;"> transform is driven by integrating the gyroscope values over time, giving a visual indication of the rover's pitch, roll, and yaw. Euler angles are displayed numerically next to the cube.</span>

**Compass**<span style="white-space: pre-wrap;"> — a Canvas-drawn compass rose with tick marks, cardinal labels, and a red needle pointing in the direction derived from the magnetometer X/Y values. The needle and labels adapt to light/dark colour scheme.</span>

**Status bar**<span style="white-space: pre-wrap;"> — shows calibration status (✓ Cal / ! Uncal), sensor state (Idle / Operating / Calibrating / Error), any active error code, and the current update rate in Hz.</span>

### Performance

<span style="white-space: pre-wrap;">Incoming events are batched using </span>`<span class="editor-theme-code">requestAnimationFrame</span>`<span style="white-space: pre-wrap;"> — a </span>`<span class="editor-theme-code">pending</span>`<span style="white-space: pre-wrap;"> buffer holds the latest payload and the render only runs on the next animation frame, so high-frequency updates (up to 50Hz) never block the UI thread. The Hz counter counts packets per second independently of renders.</span>

# sampling_locations.svelte + SampleField.svelte — Sampling Locations

<span style="white-space: pre-wrap;">The main data collection interface for the Science task. Manages a list of </span>`<span class="editor-theme-code">Sample</span>`<span style="white-space: pre-wrap;"> objects stored in the </span>`<span class="editor-theme-code">samples</span>`<span style="white-space: pre-wrap;"> Svelte store.</span>

### Sample card

<span style="white-space: pre-wrap;">Each sample in the list is rendered as a card with an editable location name field and a set of </span>`<span class="editor-theme-code">SampleField</span>`<span style="white-space: pre-wrap;"> sub-components. The location name field updates </span>`<span class="editor-theme-code">location_name_check</span>`<span style="white-space: pre-wrap;"> automatically as the operator types.</span>

### SampleField.svelte

<span style="white-space: pre-wrap;">A reusable row sub-component used inside each sample card. Renders a checkbox (bound to a </span>`<span class="editor-theme-code">checked</span>`<span style="white-space: pre-wrap;"> prop), a label, the current value, and a </span>`<span class="editor-theme-code">+</span>`<span style="white-space: pre-wrap;"> button that opens the relevant modal. Used for: Coordinates, Size, Weight, Image Before, Image After.</span>

### Modals

<span style="white-space: pre-wrap;">Clicking a </span>`<span class="editor-theme-code">+</span>`<span style="white-space: pre-wrap;"> button opens a modal specific to that field type:</span>

**Coordinates**<span style="white-space: pre-wrap;"> — a single button that calls </span>`<span class="editor-theme-code">invoke("request_coordinates")</span>`<span style="white-space: pre-wrap;">, receives a </span>`<span class="editor-theme-code">[lat, lon]</span>`<span style="white-space: pre-wrap;"> tuple from the rover, formats it as a string, and saves it to the sample.</span>

**Measurement**<span style="white-space: pre-wrap;"> — shows two camera feeds (arm + front) in </span>`<span class="editor-theme-code">measure</span>`<span style="white-space: pre-wrap;"> mode. The operator clicks two corresponding points on the two feeds to trigger a stereo measurement via </span>`<span class="editor-theme-code">invoke("request_measurement")</span>`. The returned value is saved to the sample.

**Weight**<span style="white-space: pre-wrap;"> — a single button that calls </span>`<span class="editor-theme-code">invoke("request_weight")</span>`<span style="white-space: pre-wrap;"> and saves the returned gram value to the sample.</span>

**Image Before / Image After**<span style="white-space: pre-wrap;"> — shows all three camera feeds. Clicking any feed calls </span>`<span class="editor-theme-code">invoke("save_snapshot")</span>`<span style="white-space: pre-wrap;"> which captures a JPEG frame from that stream and saves it to the </span>`<span class="editor-theme-code">images/</span>`<span style="white-space: pre-wrap;"> directory. The filename is </span>`<span class="editor-theme-code">{sample.label}_{before|after}</span>`.

### Pick up Rock

<p class="callout warning">This section is work in progress</p>

<span style="white-space: pre-wrap;">A separate overlay accessible from a button at the bottom of the component. Shows all three camera feeds in </span>`<span class="editor-theme-code">pick</span>`<span style="white-space: pre-wrap;"> mode. The </span>`<span class="editor-theme-code">pick()</span>`<span style="white-space: pre-wrap;"> function is currently a stub for the rover arm pick-up command.</span>

# interest_locations.svelte — Interest Locations

<p class="callout danger">**TODO:** A placeholder component that renders a "Locations of Interest" heading. Intended to display GPS-tagged points of interest identified during the probing task. Not yet implemented.</p>

# navigation_plan.svelte — Navigation Plan

<span style="white-space: pre-wrap;">A drag-and-drop ordered list of navigation waypoints using </span>`<span class="editor-theme-code">svelte-dnd-action</span>`<span style="white-space: pre-wrap;">. Reads from and writes to the </span>`<span class="editor-theme-code">waypoints</span>`<span style="white-space: pre-wrap;">, </span>`<span class="editor-theme-code">startPoint</span>`<span style="white-space: pre-wrap;">, and </span>`<span class="editor-theme-code">endPoint</span>`<span style="white-space: pre-wrap;"> stores in </span>`<span class="editor-theme-code">stores/map.ts</span>`.

<span style="white-space: pre-wrap;">The list always starts with a fixed </span>**Starting Point**<span style="white-space: pre-wrap;"> card and ends with a fixed </span>**End Point**<span style="white-space: pre-wrap;"> card. Between them the operator can add intermediate waypoints and reorder them by dragging. Each waypoint has a delete button.</span>

<span style="white-space: pre-wrap;">The </span>**Add Map File**<span style="white-space: pre-wrap;"> button opens a native file picker (filtered to JSON, GeoJSON, TXT, JPEG) and calls </span>`<span class="editor-theme-code">invoke("import_map_file")</span>`<span style="white-space: pre-wrap;"> to copy the selected file into the app's </span>`<span class="editor-theme-code">maps/</span>`<span style="white-space: pre-wrap;"> directory, where the map component can then find it.</span>

<p class="callout danger"><span style="white-space: pre-wrap;">The </span>**Plan Route**<span style="white-space: pre-wrap;"> button is a stub ready to be connected to actual route planning logic.</span></p>

# probes.svelte — Probes

<span style="white-space: pre-wrap;">Displays the list of probes from the </span>`<span class="editor-theme-code">probes</span>`<span style="white-space: pre-wrap;"> store. Currently each probe renders as a basic card. A "Pick up probe" button opens an overlay showing all three camera feeds in </span>`<span class="editor-theme-code">pick</span>`<span style="white-space: pre-wrap;"> mode, using the same pattern as the pick-up overlay in </span>`<span class="editor-theme-code">sampling_locations.svelte</span>`<span style="white-space: pre-wrap;">. </span>

<p class="callout danger">**TODO:** <span style="white-space: pre-wrap;">The </span>`<span class="editor-theme-code">pick()</span>`<span style="white-space: pre-wrap;"> function is a stub.</span></p>

# maintenance_tasks.svelte — Maintenance Panel

<p class="callout danger">**TODO:** WIP</p>

# map.svelte + navigation_plan.svelte + interest_locations.svelte — Map & Navigation Components

### map.svelte

<span style="white-space: pre-wrap;">The core map display component. Loads a map file from </span>`<span class="editor-theme-code"><appDataDir>/maps/</span>`<span style="white-space: pre-wrap;">, renders it in a letterboxed </span>`<span class="editor-theme-code"><img></span>`<span style="white-space: pre-wrap;"> element, and overlays interactive pins on top. Accepts a </span>`<span class="editor-theme-code">mode</span>`<span style="white-space: pre-wrap;"> prop that controls what happens when the operator clicks the map.</span>

<table id="bkmrk-modeclick-behaviourp"><colgroup><col></col><col style="width: 223px;"></col><col></col></colgroup><tbody><tr><th>Mode

</th><th>Click behaviour

</th><th>Pins shown

</th></tr><tr><td>`<span class="editor-theme-code">navigation</span>`

</td><td><span style="white-space: pre-wrap;">Adds a </span>`<span class="editor-theme-code">PinnedCoord</span>`<span style="white-space: pre-wrap;"> to the </span>

`<span class="editor-theme-code">pinnedCoords</span>`<span style="white-space: pre-wrap;"> store</span>

</td><td>Unassigned pins + start/waypoint/end markers

</td></tr><tr><td>`<span class="editor-theme-code">science</span>`

</td><td><span style="white-space: pre-wrap;">Adds an </span>`<span class="editor-theme-code">InterestLocation</span>`<span style="white-space: pre-wrap;"> to </span>`<span class="editor-theme-code">scienceLocations</span>`

</td><td>Science location pins

</td></tr><tr><td>`<span class="editor-theme-code">probing</span>`

</td><td><span style="white-space: pre-wrap;">Adds an </span>`<span class="editor-theme-code">InterestLocation</span>`<span style="white-space: pre-wrap;"> to </span>`<span class="editor-theme-code">probingLocations</span>`

</td><td>Probing location pins

</td></tr></tbody></table>

***Map loading***<span style="white-space: pre-wrap;"> — On mount, if </span>`<span class="editor-theme-code">displayedMap</span>`<span style="white-space: pre-wrap;"> store already holds a filename it is opened immediately; otherwise the component calls </span>`<span class="editor-theme-code">list_task_files("maps")</span>`<span style="white-space: pre-wrap;"> and shows a selection modal. If only one map file exists it is auto-selected. The reload button (⟳) clears all state and returns to the selection modal.</span>

3D formats (`<span class="editor-theme-code">.obj</span>`<span style="white-space: pre-wrap;">, </span>`<span class="editor-theme-code">.las</span>`<span style="white-space: pre-wrap;">, </span>`<span class="editor-theme-code">.laz</span>`<span style="white-space: pre-wrap;">, </span>`<span class="editor-theme-code">.e57</span>`<span style="white-space: pre-wrap;">) trigger a </span>`<span class="editor-theme-code">render_map</span>`<span style="white-space: pre-wrap;"> invoke before display, showing a spinner while the backend works. Plain image formats are loaded directly via </span>`<span class="editor-theme-code">convertFileSrc</span>`<span style="white-space: pre-wrap;">. After rendering, the </span>`<span class="editor-theme-code">_preview.png</span>`<span style="white-space: pre-wrap;"> path is used for all subsequent display.</span>

***Coordinate geometry*** <span style="white-space: pre-wrap;">— </span>`<span class="editor-theme-code">getRenderedRect()</span>`<span style="white-space: pre-wrap;"> computes the actual rendered rectangle of the image inside its element (accounting for letterboxing / </span>`<span class="editor-theme-code">object-fit</span>`<span style="white-space: pre-wrap;"> behaviour). </span>`<span class="editor-theme-code">eventToImgPixel()</span>`<span style="white-space: pre-wrap;"> converts a raw </span>`<span class="editor-theme-code">MouseEvent</span>`<span style="white-space: pre-wrap;"> into a pixel coordinate within the PNG, with Y flipped so the origin is bottom-left. </span>`<span class="editor-theme-code">worldToCSSPos()</span>`<span style="white-space: pre-wrap;"> is the inverse — takes a world-space </span>`<span class="editor-theme-code">(x, y)</span>`<span style="white-space: pre-wrap;"> in metres and returns a </span>`<span class="editor-theme-code">left/top</span>`<span style="white-space: pre-wrap;"> percentage suitable for absolute positioning an overlay element on top of the image. Both functions short-circuit to </span>`<span class="editor-theme-code">null</span>`<span style="white-space: pre-wrap;"> when </span>`<span class="editor-theme-code">mapMeta</span>`<span style="white-space: pre-wrap;"> is unavailable.</span>

***Mouse interaction***<span style="white-space: pre-wrap;"> — </span>`<span class="editor-theme-code">onMouseMove</span>`<span style="white-space: pre-wrap;"> calls </span>`<span class="editor-theme-code">eventToImgPixel</span>`<span style="white-space: pre-wrap;"> and then invokes </span>`<span class="editor-theme-code">pixel_to_world</span>`<span style="white-space: pre-wrap;"> to display a live coordinate overlay in the bottom-left corner (pixel position + world metres + "Click to pin" hint). The overlay disappears when the cursor leaves the image.</span>

***GPS marker***<span style="white-space: pre-wrap;"> — Listens for </span>`<span class="editor-theme-code">gps-update</span>`<span style="white-space: pre-wrap;"> Tauri events on mount. The payload's </span>`<span class="editor-theme-code">longitude</span>`/`<span class="editor-theme-code">latitude</span>`<span style="white-space: pre-wrap;"> fields are reused directly as map-space X/Y metres. When a valid GPS position is in the store, a directional arrow marker (</span>`<span class="editor-theme-code">▶</span>`<span style="white-space: pre-wrap;">) is rendered at the corresponding map position, rotated by </span>`<span class="editor-theme-code">heading</span>`<span style="white-space: pre-wrap;"> via a CSS custom property </span>`<span class="editor-theme-code">--heading</span>`.

***Pinned coordinate list*** <span style="white-space: pre-wrap;">— In </span>`<span class="editor-theme-code">navigation</span>`<span style="white-space: pre-wrap;"> mode, a sidebar panel lists all </span>`<span class="editor-theme-code">pinnedCoords</span>`<span style="white-space: pre-wrap;"> with copy-to-clipboard (📋) and remove (✕) buttons. Hovering a row highlights the corresponding pin on the map, and vice versa, via </span>`<span class="editor-theme-code">hoveredPinId</span>`.

### navigation\_plan.svelte

<span style="white-space: pre-wrap;">A sidebar panel for building a navigation route from map-pinned coordinates. Displays a structured plan of start point → ordered waypoints → end point, and surfaces any </span>`<span class="editor-theme-code">pinnedCoords</span>`<span style="white-space: pre-wrap;"> that haven't yet been assigned a role.</span>

***Route structure***<span style="white-space: pre-wrap;"> — The plan always shows three sections in order: a Start card, a draggable Waypoints list, and an End card. Unset slots show "Not set". Hovering any card cross-highlights its corresponding pin on the map via the </span>`<span class="editor-theme-code">hoveredNavId</span>`<span style="white-space: pre-wrap;"> store (shared with </span>`<span class="editor-theme-code">map.svelte</span>`).

***Waypoint reordering*** <span style="white-space: pre-wrap;">— The waypoint list uses </span>`<span class="editor-theme-code">svelte-dnd-action</span>`<span style="white-space: pre-wrap;"> (</span>`<span class="editor-theme-code">dndzone</span>`<span style="white-space: pre-wrap;">) for drag-and-drop reordering. Both </span>`<span class="editor-theme-code">consider</span>`<span style="white-space: pre-wrap;"> and </span>`<span class="editor-theme-code">finalize</span>`<span style="white-space: pre-wrap;"> events write the new order directly to the </span>`<span class="editor-theme-code">waypoints</span>`<span style="white-space: pre-wrap;"> store.</span>

***Promoting pinned coords*** <span style="white-space: pre-wrap;">— Each </span>`<span class="editor-theme-code">PinnedCoord</span>`<span style="white-space: pre-wrap;"> from the map appears in a "Pinned from map" section at the bottom of the list. Three buttons let the operator promote a pin to Start, add it as a new Waypoint, or set it as End. In all cases the pin is removed from </span>`<span class="editor-theme-code">pinnedCoords</span>`<span style="white-space: pre-wrap;"> after promotion.</span>

***Destructive actions***<span style="white-space: pre-wrap;"> — Removing a waypoint and clearing the entire plan both trigger a native </span>`<span class="editor-theme-code">confirm()</span>`<span style="white-space: pre-wrap;"> dialog before proceeding.</span>

***Map import***<span style="white-space: pre-wrap;"> — The "+ Add Map File" button opens a file picker (via </span>`<span class="editor-theme-code">@tauri-apps/plugin-dialog</span>`<span style="white-space: pre-wrap;">) filtered to </span>`<span class="editor-theme-code">.json</span>`<span style="white-space: pre-wrap;">, </span>`<span class="editor-theme-code">.geojson</span>`<span style="white-space: pre-wrap;">, </span>`<span class="editor-theme-code">.txt</span>`<span style="white-space: pre-wrap;">, </span>`<span class="editor-theme-code">.jpeg</span>`<span style="white-space: pre-wrap;">, </span>`<span class="editor-theme-code">.obj</span>`<span style="white-space: pre-wrap;">, </span>`<span class="editor-theme-code">.las</span>`<span style="white-space: pre-wrap;">, </span>`<span class="editor-theme-code">.laz</span>`<span style="white-space: pre-wrap;">, </span>`<span class="editor-theme-code">.e57</span>`<span style="white-space: pre-wrap;">, then calls </span>`<span class="editor-theme-code">invoke("import_map_file")</span>`<span style="white-space: pre-wrap;"> to copy the chosen file into the app's maps directory. The "▶︎ Plan Route" button is present but not yet wired to a backend command.</span>

### interest\_locations.svelte

A generic, reusable sidebar list for named locations of interest. Used by both the Science and Probing task panels, which pass in their respective stores (`<span class="editor-theme-code">scienceLocations</span>`<span style="white-space: pre-wrap;"> / </span>`<span class="editor-theme-code">probingLocations</span>`<span style="white-space: pre-wrap;"> and the matching hovered-id store) as props.</span>

<span style="white-space: pre-wrap;">Props: </span>`<span class="editor-theme-code">locations</span>`<span style="white-space: pre-wrap;"> — a </span>`<span class="editor-theme-code">Writable<InterestLocation[]></span>`<span style="white-space: pre-wrap;"> store; </span>`<span class="editor-theme-code">hoveredId</span>`<span style="white-space: pre-wrap;"> — a </span>`<span class="editor-theme-code">Writable<string | null></span>`<span style="white-space: pre-wrap;"> store shared with </span>`<span class="editor-theme-code">map.svelte</span>`<span style="white-space: pre-wrap;"> for cross-highlighting.</span>

<span style="white-space: pre-wrap;">Each location row shows its auto-generated name and its </span>`<span class="editor-theme-code">(x, y)</span>`<span style="white-space: pre-wrap;"> coordinates in metres. Hovering a row sets </span>`<span class="editor-theme-code">hoveredId</span>`, which causes the corresponding pin on the map to highlight. Two actions are available per row:

- **Rename (✏️)**<span style="white-space: pre-wrap;"> — Switches the name label to an inline </span>`<span class="editor-theme-code"><input></span>`. The edit is committed on blur or Enter; if the input is left empty the original name is kept. Only one location can be in edit mode at a time (`<span class="editor-theme-code">editingId</span>`<span style="white-space: pre-wrap;"> state).</span>
- **Remove (✕)**<span style="white-space: pre-wrap;"> — Removes the location from the store immediately with no confirmation.</span>

When the list is empty a hint instructs the operator to click the map to add a location.