From 2878d13c3d627e8634d3ab2431037e82ee2914c8 Mon Sep 17 00:00:00 2001 From: firestar5683 <168790843+firestar5683@users.noreply.github.com> Date: Mon, 14 Sep 2026 14:11:41 -0500 Subject: [PATCH] nav --- .../navigation/navigation_destination.js | 37 +++++++++++++------ .../components/NavigationDestinationPanel.js | 14 ++++++- .../the_galaxy/tests/test_dashboard_stats.py | 1 + .../tests/test_navigation_params.py | 27 ++++++++++++++ .../the_galaxy/tests/test_ui_vue_frontend.py | 15 +++++++- starpilot/system/the_galaxy/the_galaxy.py | 7 +++- 6 files changed, 85 insertions(+), 16 deletions(-) diff --git a/starpilot/system/the_galaxy/assets/components/navigation/navigation_destination.js b/starpilot/system/the_galaxy/assets/components/navigation/navigation_destination.js index b9f2516f76..543dde6c9d 100644 --- a/starpilot/system/the_galaxy/assets/components/navigation/navigation_destination.js +++ b/starpilot/system/the_galaxy/assets/components/navigation/navigation_destination.js @@ -790,7 +790,7 @@ export function NavDestination() {
Mapbox Keys Required
-

You must set both your public and secret Mapbox keys before using navigation features.

+

The public key powers destination search and the map. The secret key lets your comma calculate the on-device route and provide navigation turn desires. Add both keys before starting navigation.

Go to "Manage Keys"
@@ -926,7 +926,7 @@ function NavigationDestination({ isFavorited, favoriteRoutes = [], steps = [] -}) { + }) { async function cancelNavigation() { showSnackbar("Navigation cancelled..."); removeRouteFromMap(map); @@ -938,19 +938,32 @@ function NavigationDestination({ await fetch("/api/navigation", { method: "DELETE" }); } async function confirmDestination() { + let response; + let result = {}; + try { + response = await fetch("/api/navigation", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + name, + longitude: destinationCoordinates[0], + latitude: destinationCoordinates[1], + routeId, + }) + }); + result = await response.json().catch(() => ({})); + } catch { + showSnackbar("Could not reach the comma to start navigation.", "error"); + return; + } + if (!response.ok) { + showSnackbar(result.message || "Failed to start navigation.", "error"); + return; + } + onConfirm?.(); showSnackbar("Navigation set!"); localStorage.setItem("activeRouteId", routeId); - await fetch("/api/navigation", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - name, - longitude: destinationCoordinates[0], - latitude: destinationCoordinates[1], - routeId, - }) - }); await loadFavorites(); const searchInputEl = document.getElementById("search-field"); if (searchInputEl) searchInputEl.value = ""; diff --git a/starpilot/system/the_galaxy/assets/mobile/js/components/NavigationDestinationPanel.js b/starpilot/system/the_galaxy/assets/mobile/js/components/NavigationDestinationPanel.js index a40e32ba0d..b37afc278d 100644 --- a/starpilot/system/the_galaxy/assets/mobile/js/components/NavigationDestinationPanel.js +++ b/starpilot/system/the_galaxy/assets/mobile/js/components/NavigationDestinationPanel.js @@ -97,6 +97,7 @@ export const NavigationDestinationPanel = { navigationStarted: false, isMetric: false, mapboxPublic: "", + mapboxSecret: "", language: "", lastPosition: null, map: null, @@ -110,6 +111,7 @@ export const NavigationDestinationPanel = { }, computed: { hasMapbox() { return !!this.mapboxPublic }, + hasRoutingKey() { return !!this.mapboxSecret }, recentPlaces() { const seen = new Set() return [...this.favorites, ...this.recentDestinations].filter((place) => { @@ -157,6 +159,7 @@ export const NavigationDestinationPanel = { api.getNavigationFavorites().catch(() => ({ favorites: [] })), ]) this.mapboxPublic = String(nav?.mapboxPublic || "").trim() + this.mapboxSecret = String(nav?.mapboxSecret || "").trim() this.language = String(nav?.language || "").trim() this.isMetric = !!nav?.isMetric this.lastPosition = coordinates(nav?.lastPosition) @@ -284,6 +287,10 @@ export const NavigationDestinationPanel = { showSnackbar("Add a Mapbox public key in App Keys first.", "error") return } + if (!this.hasRoutingKey) { + showSnackbar("Add a Mapbox secret key in App Keys first. It is required for the comma to calculate the on-device route and provide navigation turn desires.", "error") + return + } this.loadingRoute = true try { this.destination = place || await this.resolveQuery() @@ -419,11 +426,14 @@ export const NavigationDestinationPanel = {
+
+ The map and destination search only use your public Mapbox key. Add a secret Mapbox key in App Keys before starting navigation so the comma can calculate the on-device route and provide turn desires. +
diff --git a/starpilot/system/the_galaxy/tests/test_dashboard_stats.py b/starpilot/system/the_galaxy/tests/test_dashboard_stats.py index b03d1809f8..b7bf0339ee 100644 --- a/starpilot/system/the_galaxy/tests/test_dashboard_stats.py +++ b/starpilot/system/the_galaxy/tests/test_dashboard_stats.py @@ -393,6 +393,7 @@ def _install_server_import_stubs(): sys.modules["openpilot.starpilot.navigation.destination_store"] = _simple_module( "openpilot.starpilot.navigation.destination_store", normalize_destination_payload=lambda payload: payload, + routing_configured=lambda params: bool(str(params.get("MapboxSecretKey") or "").strip()), update_recent_destinations=lambda *args, **kwargs: [], ) sys.modules["openpilot.starpilot.system.the_galaxy.factory_reset"] = _simple_module( diff --git a/starpilot/system/the_galaxy/tests/test_navigation_params.py b/starpilot/system/the_galaxy/tests/test_navigation_params.py index 64eb872d97..b81a830a7d 100644 --- a/starpilot/system/the_galaxy/tests/test_navigation_params.py +++ b/starpilot/system/the_galaxy/tests/test_navigation_params.py @@ -462,6 +462,33 @@ def test_navigation_last_position_rejects_stale_persisted_fix(monkeypatch): assert the_galaxy._get_navigation_last_position() is None +def test_navigation_api_rejects_destination_without_secret_key(monkeypatch): + client, fake_params = _params_client(monkeypatch, {"MapboxPublicKey": "public"}, "tici") + + response = client.post("/api/navigation", json={ + "name": "Work", + "latitude": 41.0, + "longitude": -87.0, + }) + + assert response.status_code == 400 + assert "secret key" in response.get_json()["message"] + assert fake_params.get("NavDestination") is None + + +def test_navigation_api_accepts_destination_with_secret_key(monkeypatch): + client, fake_params = _params_client(monkeypatch, {"MapboxSecretKey": "secret"}, "tici") + + response = client.post("/api/navigation", json={ + "name": "Work", + "latitude": 41.0, + "longitude": -87.0, + }) + + assert response.status_code == 200 + assert json.loads(fake_params.get("NavDestination"))["name"] == "Work" + + def test_save_longitudinal_maneuver_status_writes_json_param_as_dict(monkeypatch): fake_params = WritableFakeParams() monkeypatch.setattr(the_galaxy, "params", fake_params) diff --git a/starpilot/system/the_galaxy/tests/test_ui_vue_frontend.py b/starpilot/system/the_galaxy/tests/test_ui_vue_frontend.py index fb0afce7c8..03a5b686e5 100644 --- a/starpilot/system/the_galaxy/tests/test_ui_vue_frontend.py +++ b/starpilot/system/the_galaxy/tests/test_ui_vue_frontend.py @@ -504,7 +504,6 @@ def test_ui_all_remaining_classic_tools_native_no_embed(): assert "ref=\"map\"" in destination and "setNavigation(this.destination)" in destination assert destination.count("methods: {") == 1 and "secondaryLabel," in destination assert _read("js/components/LateralTuningPanel.js") - # Shared API surface added for the second batch of ported pages. for method in ["selectTestingGround", "getSentryStatus", "getSentryEvents", "deleteSentryEvent", "sentryPushSubscribe", @@ -536,6 +535,20 @@ def test_ui_all_remaining_classic_tools_native_no_embed(): assert lateral.index('>Saved Tunes') < lateral.index('>Local Routes') +def test_navigation_requires_secret_key_before_starting_on_device_route(): + destination = _read("js/components/NavigationDestinationPanel.js") + classic_destination = (REPO_ROOT / "starpilot/system/the_galaxy/assets/components/navigation/navigation_destination.js").read_text(encoding="utf-8") + + assert 'mapboxSecret: ""' in destination + assert "hasRoutingKey()" in destination + assert "!query.trim() || !hasRoutingKey" in destination + assert "loadingRoute || !hasRoutingKey" in destination + assert "required for the comma to calculate the on-device route" in destination + assert "secret key lets your comma calculate the on-device route" in classic_destination + assert "if (!response.ok)" in classic_destination + assert 'result.message || "Failed to start navigation."' in classic_destination + + def test_ui_cameras_hub_vasm_and_pip_native_no_embed(): app = _read("js/app.js") store = _read("js/store.js") diff --git a/starpilot/system/the_galaxy/the_galaxy.py b/starpilot/system/the_galaxy/the_galaxy.py index cef799387e..cc152913da 100644 --- a/starpilot/system/the_galaxy/the_galaxy.py +++ b/starpilot/system/the_galaxy/the_galaxy.py @@ -168,7 +168,7 @@ from openpilot.starpilot.common.testing_grounds import ( TESTING_GROUNDS_SLOT_DEFINITIONS as SHARED_TESTING_GROUNDS_SLOT_DEFINITIONS, TESTING_GROUNDS_STATE_PATH as SHARED_TESTING_GROUNDS_STATE_PATH, ) -from openpilot.starpilot.navigation.destination_store import normalize_destination_payload, update_recent_destinations +from openpilot.starpilot.navigation.destination_store import normalize_destination_payload, routing_configured, update_recent_destinations from openpilot.starpilot.system.the_galaxy.factory_reset import remove_path as _run_factory_reset_delete from openpilot.starpilot.system.the_galaxy import flm_workspace, utilities from openpilot.starpilot.system.the_galaxy.update_recovery import inspect_interrupted_update, public_recovery_status, recover_interrupted_update @@ -5760,6 +5760,11 @@ def setup(app): @app.route("/api/navigation", methods=["POST"]) def set_navigation(): + if not routing_configured(params): + return { + "message": "A Mapbox secret key is required to calculate the on-device route and provide navigation turn desires. Add it in App Keys first." + }, 400 + destination = normalize_destination_payload(request.json) if destination is None: return {"message": "Invalid destination payload"}, 400