From c28ca7aab58b67a39b484899bc07b9c848666f6f Mon Sep 17 00:00:00 2001 From: iris Date: Mon, 26 Oct 2026 22:48:45 +0100 Subject: [PATCH] swarm-ui: the moved tabs' redirects come from one table, with tests The four tab moves each hand-wrote a redirect route. They are one table now, MOVED_ROUTES, and movedHref says where an old address goes with its query kept; /builds?run= still lands on the build's card. The builds page's own redirect helper is gone. --- frontend/packages/swarm-ui/src/App.tsx | 29 +++++++------------ .../swarm-ui/src/pages/builds/builds-types.ts | 5 ---- .../packages/swarm-ui/src/shell/routes.ts | 19 ++++++++++++ .../swarm-ui/test/builds-types.test.mjs | 11 +------ .../packages/swarm-ui/test/routes.test.mjs | 29 ++++++++++++++++++- 5 files changed, 58 insertions(+), 35 deletions(-) diff --git a/frontend/packages/swarm-ui/src/App.tsx b/frontend/packages/swarm-ui/src/App.tsx index 891678b3..4afa9d16 100644 --- a/frontend/packages/swarm-ui/src/App.tsx +++ b/frontend/packages/swarm-ui/src/App.tsx @@ -1,7 +1,7 @@ // Root shell component — routing only. Each route's content is its own // page component under `./pages/`; this file just maps paths to them. import { Redirect, Route, Router, Switch, useSearch } from "wouter-preact"; -import { jobsHref } from "./pages/builds/builds-types.js"; +import { MOVED_ROUTES, movedHref } from "./shell/routes.js"; import { Shell } from "./shell/Shell.js"; import { useGuardedLocation } from "./shell/useGuardedLocation.js"; import { AgentsPage } from "./pages/agents/AgentsPage.js"; @@ -16,11 +16,10 @@ import { ReportsPage } from "./pages/reports/ReportsPage.js"; import { StartPage } from "./pages/StartPage.js"; import { Panel } from "./ui/panel/Panel.js"; -// The builds page moved onto the jobs tab. Links made before that, such as the -// Details link on a swarm-builder commit status, land here and go on to the -// same build's card, query kept. -function BuildsMoved() { - return ; +// An address from before the tabs were folded together: replace it with the +// tab that holds the page now, query kept. +function MovedTo({ from }: { from: string }) { + return ; } function NotFound() { @@ -38,9 +37,11 @@ export function App() { - - - + {Object.keys(MOVED_ROUTES).map((from) => ( + + + + ))} - - - - - - - - - - diff --git a/frontend/packages/swarm-ui/src/pages/builds/builds-types.ts b/frontend/packages/swarm-ui/src/pages/builds/builds-types.ts index 6e0f8a79..322ba6a5 100644 --- a/frontend/packages/swarm-ui/src/pages/builds/builds-types.ts +++ b/frontend/packages/swarm-ui/src/pages/builds/builds-types.ts @@ -37,11 +37,6 @@ export const OUTCOME_TONE: Record = { error: "warning", }; -/** Where an old `/builds` link goes: the jobs page, keeping its query. */ -export function jobsHref(search: string): string { - return search === "" ? "/jobs" : `/jobs?${search}`; -} - /** The run a `?run=` query names, or `null` when it names none. */ export function wantedRun(search: string): string | null { const id = new URLSearchParams(search).get("run"); diff --git a/frontend/packages/swarm-ui/src/shell/routes.ts b/frontend/packages/swarm-ui/src/shell/routes.ts index 57d17296..07238feb 100644 --- a/frontend/packages/swarm-ui/src/shell/routes.ts +++ b/frontend/packages/swarm-ui/src/shell/routes.ts @@ -27,3 +27,22 @@ export function visibleNavItems( if (!hideDeveloper) return items; return items.filter((item) => !DEVELOPER_ROUTES.includes(item.href)); } + +// Tabs that were folded into another one. Each old address redirects to its +// new tab, so a bookmark or a link made before the move still lands. The +// builds page's `?run=` query rides along, which a swarm-builder commit +// status link relies on. +export const MOVED_ROUTES: Readonly> = { + "/alerts": "/reports", + "/issues": "/reports", + "/builds": "/jobs", + "/review": "/jobs", + "/schedules": "/messages", +}; + +/** Where an old address goes, keeping its query; `null` for any other path. */ +export function movedHref(path: string, search: string): string | null { + const to = MOVED_ROUTES[path]; + if (to === undefined) return null; + return search === "" ? to : `${to}?${search}`; +} diff --git a/frontend/packages/swarm-ui/test/builds-types.test.mjs b/frontend/packages/swarm-ui/test/builds-types.test.mjs index d4210fa2..2cba6d47 100644 --- a/frontend/packages/swarm-ui/test/builds-types.test.mjs +++ b/frontend/packages/swarm-ui/test/builds-types.test.mjs @@ -3,11 +3,7 @@ // stripping, so the `.ts` module is imported as it is. import assert from "node:assert/strict"; import { test } from "node:test"; -import { - bodyText, - jobsHref, - wantedRun, -} from "../src/pages/builds/builds-types.ts"; +import { bodyText, wantedRun } from "../src/pages/builds/builds-types.ts"; const base = { id: "atlas-abc-1", @@ -53,8 +49,3 @@ test("a run with no text for its outcome shows no body", () => { assert.equal(bodyText({ ...base, outcome: "failed" }), null); assert.equal(bodyText({ ...base, outcome: "built", error_tail: "x" }), null); }); - -test("an old builds link goes to the jobs page and keeps its query", () => { - assert.equal(jobsHref("run=atlas-abc-1"), "/jobs?run=atlas-abc-1"); - assert.equal(jobsHref(""), "/jobs"); -}); diff --git a/frontend/packages/swarm-ui/test/routes.test.mjs b/frontend/packages/swarm-ui/test/routes.test.mjs index a77b80b5..f33fa1a4 100644 --- a/frontend/packages/swarm-ui/test/routes.test.mjs +++ b/frontend/packages/swarm-ui/test/routes.test.mjs @@ -1,7 +1,12 @@ // The shell's per-route layout: which paths are full-bleed. Run by `npm test`. import assert from "node:assert/strict"; import { test } from "node:test"; -import { isFullBleedRoute, visibleNavItems } from "../src/shell/routes.ts"; +import { + MOVED_ROUTES, + isFullBleedRoute, + movedHref, + visibleNavItems, +} from "../src/shell/routes.ts"; test("the start page is full-bleed", () => { assert.equal(isFullBleedRoute("/"), true); @@ -42,3 +47,25 @@ test("hide developer details takes the components tab out of the nav", () => { test("with it off the nav is whole again", () => { assert.deepEqual(visibleNavItems(NAV, false), NAV); }); + +test("an old address goes to the tab that holds its page, query kept", () => { + assert.equal(movedHref("/alerts", ""), "/reports"); + assert.equal(movedHref("/issues", ""), "/reports"); + assert.equal(movedHref("/review", ""), "/jobs"); + assert.equal(movedHref("/schedules", ""), "/messages"); + assert.equal( + movedHref("/builds", "run=atlas-abc-1"), + "/jobs?run=atlas-abc-1", + ); +}); + +test("a current tab is not moved", () => { + assert.equal(movedHref("/jobs", ""), null); + assert.equal(movedHref("/agents", "x=1"), null); +}); + +test("no old address leads to another old address", () => { + for (const to of Object.values(MOVED_ROUTES)) { + assert.equal(to in MOVED_ROUTES, false, to); + } +});