diff --git a/openpilot/starpilot/galaxy/tests/test_flm.mjs b/openpilot/starpilot/galaxy/tests/test_flm.mjs
index 4e45be00c1..9763c8d8bd 100644
--- a/openpilot/starpilot/galaxy/tests/test_flm.mjs
+++ b/openpilot/starpilot/galaxy/tests/test_flm.mjs
@@ -58,13 +58,27 @@ assert.doesNotMatch(FlmPage.template, /v-html|Analyze selected|GM Trial|Choose F
assert.match(FlmPage.template, /Currently applied/)
assert.match(FlmPage.template, /PID unsupported/)
const app = readFileSync(new URL("../web/js/app.js", import.meta.url), "utf8")
-assert(app.includes("['/tuning', '/tuning/plots'].includes(route.path)"))
+assert(app.includes("['/tuning', '/tuning/plots', '/tuning/flm'].includes(route.path)"))
+const analysis = app.match(/
/)?.[0]
+assert(analysis, 'Existing analysis route and old bookmarks share one page')
+assert.match(analysis, /
{ throw error }, decodeEntities: value => value.replaceAll("&", "&") })
assert.doesNotMatch(app, /Recorded-drive analysis/)
const settings = readFileSync(new URL("../web/js/settings.js", import.meta.url), "utf8")
assert.doesNotMatch(settings, /FLMTrainingResults|View FLM training results/)
const catalog = JSON.parse(readFileSync(new URL("../web/data/catalog.json", import.meta.url), "utf8"))
assert(catalog.tools.some(tool => tool.path === "/tuning/plots"))
-assert(catalog.tools.some(tool => tool.path === "/tuning/flm"))
+assert(!catalog.tools.some(tool => tool.path === "/tuning/flm"))
+assert.equal(catalog.tools.filter(tool => tool.path.startsWith('/tuning')).length, 1)
+const plots = readFileSync(new URL('../web/js/plots.js', import.meta.url), 'utf8')
+assert.doesNotMatch(plots, /href="#\/tuning\/flm"/)
+const driving = readFileSync(new URL('../web/js/driving.js', import.meta.url), 'utf8')
+assert.match(driving, /go\('\/tuning\/plots'\)/)
+assert.doesNotMatch(driving, /\/tuning\/flm/)
+assert.match(FlmPage.template, /]*>Live steering response/)
+assert.doesNotMatch(FlmPage.template, /href="#\/tuning\/plots"/)
const route = readFileSync(new URL("../web/js/route-workspace.js", import.meta.url), "utf8")
assert.doesNotMatch(route, /Analyze driving logs/)
@@ -77,7 +91,17 @@ function instance() {
}
const originalFetch = globalThis.fetch, originalDocument = globalThis.document
try {
- globalThis.document = { hidden: false, removeEventListener() {} }
+ globalThis.document = { hidden: false, addEventListener() {}, removeEventListener() {} }
+ for (const embedded of [true, false]) {
+ const page = { ...FlmPage.data(), embedded, mode: 'local', unauthorized() {}, load() {} }
+ let plotStarts = 0
+ FlmPage.created.call(page)
+ page.feed.start = () => { plotStarts++ }
+ page.feed.stop = () => {}
+ FlmPage.mounted.call(page)
+ assert.equal(plotStarts, embedded ? 0 : 1, 'Embedded results reuse the existing live plots')
+ FlmPage.beforeUnmount.call(page)
+ }
const calls = []
globalThis.fetch = async (url, options) => { calls.push({ url, options }); return { ok: true, json: async () => value } }
const vm = instance(); await vm.load()
diff --git a/openpilot/starpilot/galaxy/tests/test_preview_contract.py b/openpilot/starpilot/galaxy/tests/test_preview_contract.py
index 30517b5bc0..31983a0fc4 100644
--- a/openpilot/starpilot/galaxy/tests/test_preview_contract.py
+++ b/openpilot/starpilot/galaxy/tests/test_preview_contract.py
@@ -19,7 +19,7 @@ class PreviewContractTest(unittest.TestCase):
self.assertEqual([tool["path"] for tool in catalog["tools"] if tool["availability"] == "partial-preview"], ["/logs"])
self.assertEqual([tool["path"] for tool in catalog["tools"] if tool["availability"] == "local-only"],
["/bluetooth", "/cameras", "/device-preferences", "/galaxy", "/manage_models", "/model_laboratory",
- "/navigation", "/system", "/theme_maker", "/tuning/plots", "/tuning/flm", "/vehicle", "/android-auto"])
+ "/navigation", "/system", "/theme_maker", "/tuning/plots", "/vehicle", "/android-auto"])
self.assertTrue(all(tool["availability"] in {"unavailable", "partial-preview", "local-only"} for tool in catalog["tools"]))
self.assertTrue(all(tool["name"] and tool["description"] and tool["icon"] for tool in catalog["tools"]))
diff --git a/openpilot/starpilot/galaxy/web/data/catalog.json b/openpilot/starpilot/galaxy/web/data/catalog.json
index bf6e30d767..0b139e6430 100644
--- a/openpilot/starpilot/galaxy/web/data/catalog.json
+++ b/openpilot/starpilot/galaxy/web/data/catalog.json
@@ -75,14 +75,7 @@
"name": "Plots & Analysis",
"path": "/tuning/plots",
"icon": "bi-sign-turn-right",
- "description": "Live steering and acceleration plots",
- "availability": "local-only"
- },
- {
- "name": "Auto Learn Results",
- "path": "/tuning/flm",
- "icon": "bi-sliders",
- "description": "StarPilot learned steering corrections and fitting progress",
+ "description": "Live control plots and StarPilot Auto Learn results",
"availability": "local-only"
},
{
@@ -96,7 +89,7 @@
"name": "Android Auto",
"path": "/android-auto",
"icon": "bi-phone",
- "description": "Show StarPilot on your car’s screen",
+ "description": "Show StarPilot on your car\u2019s screen",
"availability": "local-only",
"visibility": "authenticated"
}
diff --git a/openpilot/starpilot/galaxy/web/js/app.js b/openpilot/starpilot/galaxy/web/js/app.js
index 0698e53abf..3b53628a83 100644
--- a/openpilot/starpilot/galaxy/web/js/app.js
+++ b/openpilot/starpilot/galaxy/web/js/app.js
@@ -250,8 +250,10 @@ createApp({
-
-
+
This capability is unavailable in this build. No operation was attempted.
diff --git a/openpilot/starpilot/galaxy/web/js/driving.js b/openpilot/starpilot/galaxy/web/js/driving.js
index 950225d25b..74802abadb 100644
--- a/openpilot/starpilot/galaxy/web/js/driving.js
+++ b/openpilot/starpilot/galaxy/web/js/driving.js
@@ -51,7 +51,7 @@ export const DrivingPage = {
+ Auto Learn results
`,
}
diff --git a/openpilot/starpilot/galaxy/web/js/flm.js b/openpilot/starpilot/galaxy/web/js/flm.js
index 9c5b79536c..4c5a9f1b5f 100644
--- a/openpilot/starpilot/galaxy/web/js/flm.js
+++ b/openpilot/starpilot/galaxy/web/js/flm.js
@@ -8,10 +8,11 @@ import { validTrainingResults, trainingSummary, readable, detailText, surfaceRow
export const FlmPage = {
name: "FlmPage",
components: { GxState, GxNotice, PlotGraph },
- props: { mode: { type: String, required: true }, unauthorized: { type: Function, required: true } },
+ props: { mode: { type: String, required: true }, unauthorized: { type: Function, required: true }, embedded: { type: Boolean, default: false } },
data: () => ({ report: null, error: "", loading: false, request: null, receivedAt: 0, now: 0,
status: "idle", data: null, history: [], plotError: "", showPlots: true }),
created() {
+ if (this.embedded) this.showPlots = false
this.feed = new PlotsFeed({ publish: update => {
const { error, ...values } = update
Object.assign(this.$data, values, { plotError: error })
@@ -26,7 +27,7 @@ export const FlmPage = {
else { this.load(); if (this.showPlots && this.mode === "local") this.feed.start() }
}
document.addEventListener("visibilitychange", this.visibility)
- if (this.mode === "local") { this.poller.start(); this.load(); if (!document.hidden) this.feed.start() }
+ if (this.mode === "local") { this.poller.start(); this.load(); if (this.showPlots && !document.hidden) this.feed.start() }
},
beforeUnmount() {
this.activePage = false; clearTimeout(this.freshnessTimer); this.poller?.stop(); this.request?.abort(); this.request = null; this.feed.stop()
@@ -79,9 +80,9 @@ export const FlmPage = {
},
},
template: `
-