diff --git a/starpilot/system/the_galaxy/assets/mobile/js/components/TroubleshootPanel.js b/starpilot/system/the_galaxy/assets/mobile/js/components/TroubleshootPanel.js
index 2a70119ef2..8b69ccf5c2 100644
--- a/starpilot/system/the_galaxy/assets/mobile/js/components/TroubleshootPanel.js
+++ b/starpilot/system/the_galaxy/assets/mobile/js/components/TroubleshootPanel.js
@@ -6,6 +6,7 @@ function formatValue(value) {
if (typeof value === "boolean") return value ? "On" : "Off"
if (typeof value === "number") return Number.isInteger(value) ? String(value) : String(Number(value.toFixed(4)))
if (value === null || value === undefined) return "n/a"
+ if (typeof value === "object") return JSON.stringify(value)
const text = String(value).trim()
return text || "(empty)"
}
@@ -18,6 +19,7 @@ function formatLearnedValue(value) {
function valuesMatch(left, right) {
if (left === right) return true
+ if (left && right && typeof left === "object" && typeof right === "object") return JSON.stringify(left) === JSON.stringify(right)
if ((left === null || left === undefined) && (right === null || right === undefined)) return true
if (typeof left === "number" && typeof right === "number") return Math.abs(left - right) < 1e-9
const lt = String(left ?? "").trim()
@@ -39,6 +41,7 @@ export const TroubleshootPanel = {
busySection: "",
error: "",
onlyNonDefault: false,
+ search: "",
vehicleStatus: { available: false, summary: "", summarySeverity: "neutral", items: [] },
snapshot: [],
sections: [],
@@ -50,8 +53,7 @@ export const TroubleshootPanel = {
return this.sections.reduce((count, s) => count + (Array.isArray(s.items) ? s.items.filter((i) => !valuesMatch(i?.value, i?.defaultValue)).length : 0), 0)
},
visibleSections() {
- if (!this.onlyNonDefault) return this.sections
- return this.sections.filter((s) => (Array.isArray(s.items) ? s.items.filter((i) => !valuesMatch(i?.value, i?.defaultValue)).length : 0) > 0)
+ return this.sections.filter((section) => this.itemsVisible(section).length > 0)
},
},
mounted() { this.load() },
@@ -61,7 +63,9 @@ export const TroubleshootPanel = {
isChanged(item) { return !valuesMatch(item?.value, item?.defaultValue) },
itemsVisible(section) {
const items = Array.isArray(section?.items) ? section.items : []
- return this.onlyNonDefault ? items.filter((i) => !valuesMatch(i?.value, i?.defaultValue)) : items
+ const query = this.search.trim().toLowerCase()
+ return items.filter((i) => (!this.onlyNonDefault || !valuesMatch(i?.value, i?.defaultValue)) &&
+ (!query || `${section.title} ${i.key} ${i.label} ${formatValue(i.value)}`.toLowerCase().includes(query)))
},
severity(sev) {
const s = String(sev || "neutral").toLowerCase()
@@ -167,6 +171,7 @@ export const TroubleshootPanel = {
+
Onroad: {{ isOnroad ? 'Yes' : 'No' }}
Changed Settings: {{ countNonDefault }}
diff --git a/starpilot/system/the_galaxy/tests/test_diagnostic_registry.py b/starpilot/system/the_galaxy/tests/test_diagnostic_registry.py
new file mode 100644
index 0000000000..00e47a2740
--- /dev/null
+++ b/starpilot/system/the_galaxy/tests/test_diagnostic_registry.py
@@ -0,0 +1,35 @@
+from types import SimpleNamespace
+from test_dashboard_stats import _load_server_module, FakeParams
+
+
+def test_registry_sections_are_loggable_supported_and_read_only(monkeypatch):
+ s = _load_server_module()
+ monkeypatch.setattr(s, "params", FakeParams({"IsOnroad": False}))
+ monkeypatch.setattr(s, "_TROUBLESHOOT_SECTION_DEFINITIONS", [])
+ monkeypatch.setattr(s, "_get_param_type_info", lambda: (set(), {}))
+ monkeypatch.setattr(s, "_get_default_param_values", lambda: {})
+ monkeypatch.setattr(s, "_get_troubleshoot_learned_values", lambda: {})
+ monkeypatch.setattr(s, "_get_layout_param_metadata", lambda: {"Parent": {"label": "Parent menu"}})
+ monkeypatch.setattr(s, "_get_hardware_snapshot_items", lambda: [])
+ monkeypatch.setattr(s, "_build_vehicle_fault_status", lambda: {"summary": "preserved"})
+ monkeypatch.setattr(s, "_get_safety_snapshot_text", lambda: "stock safety")
+ monkeypatch.setattr(s, "_get_fingerprint_snapshot_text", lambda: "fixture")
+ monkeypatch.setattr(s.utilities, "get_current_lan_ip", lambda: "127.0.0.1")
+ monkeypatch.setattr(s, "_safe_params_get", lambda *args, **kwargs: "")
+ keys = ["NewSetting", "Secret", "RivianAngle", "TeslaWakeOnCAN", "ControllerActionSlots"]
+ monkeypatch.setattr(s, "starpilot_default_params", [(key, None, None, None) for key in keys])
+ monkeypatch.setattr(s, "_params_raw", SimpleNamespace(get_key_flag=lambda key: s.ParamKeyFlag.DONT_LOG if key == "Secret" else 0))
+ monkeypatch.setattr(s, "_get_has_rivian_angle_harness", lambda: False)
+ monkeypatch.setattr(s, "supports_tesla_can_wake", lambda params: False)
+ monkeypatch.setattr(s, "load_settings_catalog", lambda: [{"name": "Category", "params": [
+ {"key": "NewSetting", "parent_key": "Parent"}, {"key": "NewSetting"},
+ {"key": "Secret"}, {"key": "Missing"}, {"key": "TeslaWakeOnCAN"},
+ {"key": "RivianAngle", "requires_capability": "HasRivianAngleHarness"}]}])
+ monkeypatch.setattr(s, "_build_troubleshoot_section_payload", lambda d, *args: {**d, "items": [{"key": k} for k in d["keys"]], "resettable": True})
+ result = s._build_troubleshoot_payload()
+ assert result["vehicleStatus"]["summary"] == "preserved"
+ assert result["snapshot"][0]["value"] == "stock safety"
+ sections = result["sections"]
+ assert [k for section in sections for k in section["keys"]] == ["ControllerActionSlots", "NewSetting"]
+ assert sections[1]["title"] == "Category › Parent menu"
+ assert all(section["resettable"] is False for section in sections)
diff --git a/starpilot/system/the_galaxy/the_galaxy.py b/starpilot/system/the_galaxy/the_galaxy.py
index b6a74a1eda..02f5d1e010 100644
--- a/starpilot/system/the_galaxy/the_galaxy.py
+++ b/starpilot/system/the_galaxy/the_galaxy.py
@@ -1957,22 +1957,22 @@ _RUNTIME_DEFAULT_ZERO_OK_KEYS = {
_TROUBLESHOOT_SECTION_DEFINITIONS = [
{
"id": "personality_settings",
- "title": "Personality Profile Settings",
+ "title": "Longitudinal (Speed & Following) › Driving Personalities",
"keys": _TROUBLESHOOT_PERSONALITY_KEYS,
},
{
"id": "cem_settings",
- "title": "CEM Settings",
+ "title": "Longitudinal (Speed & Following) › Longitudinal control mode",
"keys": _TROUBLESHOOT_CEM_KEYS,
},
{
"id": "advanced_lateral_tuning",
- "title": "Advanced Lateral Tuning",
+ "title": "Lateral (Steering) › Advanced Lateral Tuning",
"keys": _TROUBLESHOOT_ADVANCED_LATERAL_KEYS,
},
{
"id": "advanced_longitudinal_tuning",
- "title": "Advanced Longitudinal Tuning",
+ "title": "Longitudinal (Speed & Following) › Advanced Longitudinal Tuning",
"keys": _TROUBLESHOOT_ADVANCED_LONGITUDINAL_KEYS,
},
]
@@ -4514,6 +4514,46 @@ def _build_troubleshoot_payload():
for section_definition in _TROUBLESHOOT_SECTION_DEFINITIONS
]
+ # Use the same category and parent metadata as Settings, rather than a second short list.
+ shown = {item['key'] for section in sections for item in section['items']}
+ registered = {key for key, *_ in starpilot_default_params}
+ for category in load_settings_catalog() or []:
+ groups = {}
+ for entry in category.get('params', []):
+ key = entry.get('key')
+ if key in shown or key not in registered or key.startswith('LaneCentering') or key == 'LaneCenterOffset':
+ continue
+ if (entry.get("requires_capability") == "HasRivianAngleHarness" and not _get_has_rivian_angle_harness()):
+ continue
+ if key == "TeslaWakeOnCAN" and not supports_tesla_can_wake(params):
+ continue
+ if _params_raw.get_key_flag(key) & ParamKeyFlag.DONT_LOG:
+ continue
+ parent = entry.get('parent_key')
+ title = category['name']
+ if parent:
+ title += ' › ' + str(layout_metadata.get(parent, {}).get('label', parent))
+ groups.setdefault(title, []).append(key)
+ shown.add(key)
+ for title, keys in groups.items():
+ section = _build_troubleshoot_section_payload({'id': 'catalog_' + keys[0], 'title': title, 'keys': keys},
+ value_types, default_values, layout_metadata, learned_values)
+ section['resettable'] = False
+ sections.append(section)
+ for title, keys in [
+ ('Bluetooth Controllers', ['BluetoothEnabled', 'BluetoothDisconnectControllersOffroad', 'WheelControlsEnabled', 'ControllerActionSlots', 'WheelControlMappings']),
+ ('Longitudinal (Speed & Following) › Longitudinal control mode', ['ExperimentalMode', 'ConditionalExperimental', 'ConditionalChill', 'LongitudinalPersonality']),
+ ('Model Manager', ['Model', 'ActiveBigModel', 'ActiveSmallModel', 'ModelSortMode', 'UserFavorites'])]:
+ keys = [key for key in keys if key in registered and key not in shown
+ and not (_params_raw.get_key_flag(key) & ParamKeyFlag.DONT_LOG)]
+ if keys:
+ section = _build_troubleshoot_section_payload({'id': 'extra_' + keys[0], 'title': title, 'keys': keys},
+ value_types, default_values, layout_metadata, learned_values)
+ section['resettable'] = False
+ sections.append(section)
+ shown.update(keys)
+ sections.sort(key=lambda section: section["title"])
+
return _sanitize_json_value({
"vehicleStatus": _build_vehicle_fault_status(),
"snapshot": snapshot_items,