[fix](trx-frontend-http): stop Tools lighting up on every refresh
CI / lint (push) Successful in 2m17s
CI / test (push) Successful in 8m21s
CI / frontend (push) Failing after 30s
CI / reuse (push) Successful in 3s

navigateToTab marked the Tools button by asking whether the destination
tab was displayed, which is the right question at the wrong moment: the
first route navigation runs while the card is still behind the loading
state, where every tab computes to display:none.  Refreshing or deep
linking to any page therefore lit Tools alongside the real destination,
and nothing re-evaluated it once the page appeared.

Membership of the Tools menu answers the same question without needing
anything laid out, and still reads the grouping ui-core installs rather
than a second copy of it.

The smoke fixture now serves the SPA shell for route paths the way the
server's per-tab index handlers do, so a deep link no longer 404s and
the case is testable at all; two of them are asserted.

Signed-off-by: Stan Grams <sjg@haxx.space>
This commit is contained in:
sjg
2026-08-03 20:51:04 +02:00
parent 44721b579c
commit b2fbcb318d
3 changed files with 35 additions and 5 deletions
@@ -124,7 +124,9 @@ const contentTypes = new Map([
]);
function assetPath(urlPath) {
if (urlPath === "/") return path.join(webDir, "index.html");
// Every tab route has its own index handler on the server (see api/assets.rs),
// so a deep link or a refresh serves the SPA shell, not a 404.
if (!path.extname(urlPath)) return path.join(webDir, "index.html");
if (urlPath.startsWith("/vendor/")) return path.join(webDir, urlPath);
const generated = path.join(generatedDir, path.basename(urlPath));
if (urlPath.endsWith(".js")) return generated;
@@ -265,6 +267,24 @@ try {
assert.equal(new URL(page.url()).pathname, "/");
assert.deepEqual(runtimeErrors, []);
// Refreshing or deep-linking must mark the destination, not Tools. The first
// route navigation runs while the card is still behind the loading state, so
// a test that asked whether the tab was displayed saw "none" for every tab
// and lit Tools up on every refresh of every page.
for (const [route, tab, toolsLit] of [["/map", "map", false], ["/about", "about", true]]) {
await page.goto(`http://127.0.0.1:${address.port}${route}`, { waitUntil: "domcontentloaded" });
await page.locator(`#tab-${tab}`).waitFor({ state: "visible" });
const marked = await page.evaluate(() => ({
actives: [...document.querySelectorAll(".tab-bar .tab.active")].map((t) => t.dataset.tab || t.id),
tools: document.getElementById("mobile-more-btn").classList.contains("active"),
}));
assert.ok(marked.actives.includes(tab), `${route} marks ${JSON.stringify(marked.actives)}`);
assert.equal(marked.tools, toolsLit, `${route}: Tools active is ${marked.tools}`);
}
await page.goto(`http://127.0.0.1:${address.port}/`, { waitUntil: "domcontentloaded" });
await page.locator("#tab-main").waitFor({ state: "visible" });
assert.deepEqual(runtimeErrors, []);
// --- Layout regressions -------------------------------------------------
// Every fault below shipped at some point while the rest of this file
// passed, because nothing here looked at geometry: a header whose height