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() {
@@ -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.
+
-
+
Searching...
@@ -453,7 +463,7 @@ export const NavigationDestinationPanel = {
Cancel Navigation
- {{ loadingRoute ? 'Calculating...' : 'Start Navigation' }}
+ {{ loadingRoute ? 'Calculating...' : 'Start Navigation' }}
{{ isFavorite ? 'Unfavorite' : 'Favorite' }}
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