diff --git a/starpilot/system/the_galaxy/assets/components/recordings/dashcam_routes.js b/starpilot/system/the_galaxy/assets/components/recordings/dashcam_routes.js index 4dad08354c..749697c7ea 100644 --- a/starpilot/system/the_galaxy/assets/components/recordings/dashcam_routes.js +++ b/starpilot/system/the_galaxy/assets/components/recordings/dashcam_routes.js @@ -12,6 +12,7 @@ import { groupRoutesForView, MAX_RENDERED_ROUTES, normalizeRoute, + routeMetadataErrorMessage, routeViewRenderKey, } from "/assets/components/recordings/dashcam_routes_helpers.js" @@ -738,8 +739,8 @@ async function openOverlay(route) { try { const response = await fetch(`/api/routes/${route.name}`) - if (!response.ok) throw new Error(`Route metadata request failed (${response.status})`) - const data = await response.json() + const data = await response.json().catch(() => ({})) + if (!response.ok) throw new Error(routeMetadataErrorMessage(response.status, data?.error)) segments = Array.isArray(data.segment_urls) ? data.segment_urls.filter(url => typeof url === "string") : [] const availableCameras = ["forward", "wide", "driver"].filter(camera => data.available_cameras?.includes(camera)) if (!segments.length) throw new Error("No video segments are stored for this route") @@ -884,12 +885,15 @@ export function RouteRecordings() { const view = buildRouteView(state.routes, { preservedOnly: state.showPreservedOnly, searchQuery: state.searchQuery, sortOrder: state.sortOrder }) const groups = groupRoutesForView(view.visible, state.sortOrder) const renderKey = routeViewRenderKey(view.visible, state.sortOrder, state.viewMode) + const hasActiveSearch = Boolean(state.searchQuery.trim()) return html` -
- ${view.matching.length} matching drive${view.matching.length === 1 ? "" : "s"} - ${state.loading ? html`Loading ${state.progress} of ${state.total}` : html`${state.routes.length} total local`} -
+ ${state.loading || hasActiveSearch ? html` +
+ ${hasActiveSearch ? html`${view.matching.length} matching drive${view.matching.length === 1 ? "" : "s"}` : ""} + ${state.loading ? html`Loading routes` : ""} +
+ ` : ""} ${state.error ? html`

${state.error}

` : ""} ${state.isDeletingAll ? html`

Deleting routes…

` : ""} ${!view.visible.length && state.loading ? html`

Finding local routes…

` : ""} diff --git a/starpilot/system/the_galaxy/assets/components/recordings/dashcam_routes_helpers.js b/starpilot/system/the_galaxy/assets/components/recordings/dashcam_routes_helpers.js index 2cd9ecf48f..3ea1a613e5 100644 --- a/starpilot/system/the_galaxy/assets/components/recordings/dashcam_routes_helpers.js +++ b/starpilot/system/the_galaxy/assets/components/recordings/dashcam_routes_helpers.js @@ -264,6 +264,14 @@ export function cameraVideoUrl(segmentUrl, camera, quality) { return quality ? `${url}&quality=${encodeURIComponent(quality)}` : url } +export function routeMetadataErrorMessage(status, serverError) { + if (status === 404) { + return "This route is no longer available on this device. Its local video segments may have been deleted or moved." + } + const detail = String(serverError || "").trim() + return detail || `Could not load route details (${status}).` +} + // loggerd only writes qcamera.ts alongside the road camera. export function supportsLowQuality(camera) { return camera === "forward" diff --git a/starpilot/system/the_galaxy/tests/test_dashcam_routes_helpers.py b/starpilot/system/the_galaxy/tests/test_dashcam_routes_helpers.py index af26f45690..631463f6ec 100644 --- a/starpilot/system/the_galaxy/tests/test_dashcam_routes_helpers.py +++ b/starpilot/system/the_galaxy/tests/test_dashcam_routes_helpers.py @@ -200,6 +200,17 @@ def test_route_search_input_updates_on_every_keystroke(): assert '@input="${event => { state.searchQuery = event.target.value }}"' in source +def test_route_summary_only_shows_loading_or_active_search_status(): + source = COMPONENT_PATH.read_text(encoding="utf-8") + + assert "const hasActiveSearch = Boolean(state.searchQuery.trim())" in source + assert '${state.loading || hasActiveSearch ? html`' in source + assert '${hasActiveSearch ? html`${view.matching.length} matching drive' in source + assert '${state.loading ? html`Loading routes` : ""}' in source + assert "Loading ${state.progress} of ${state.total}" not in source + assert "total local" not in source + + def test_search_indexes_the_displayed_time_for_every_route(): result = evaluate(''' const routes = [ @@ -368,6 +379,22 @@ def test_camera_video_url_carries_an_optional_quality_tier(): assert result["low"] == "/video/0000006a--9f0a7bdf9c--7?camera=forward&quality=low" +def test_route_metadata_errors_explain_missing_local_segments(): + result = evaluate(''' + return { + missing: routeMetadataErrorMessage(404, "Route not found"), + backend: routeMetadataErrorMessage(400, "Invalid route name"), + fallback: routeMetadataErrorMessage(503), + } + ''') + + assert result == { + "missing": "This route is no longer available on this device. Its local video segments may have been deleted or moved.", + "backend": "Invalid route name", + "fallback": "Could not load route details (503).", + } + + def test_only_the_road_camera_has_a_preview(): """loggerd writes qcamera.ts alongside the road camera only.""" assert evaluate('return ["forward", "wide", "driver"].map(supportsLowQuality)') == [True, False, False]