fix(activity): an old audit/log link opens its tab, times read relative, one name for admin actions

Seen in the browser, not in the page's own tests: the navigation provider rewrites a retired page id
to `activity` before the page's chunk reads the URL, so `?page=audit` landed on the Runs tab. The
retired-page map now carries the params that open the successor's part (`at`), and the provider
writes them as it rewrites the page — on load and on Back alike.

A list time is relative with the exact second on hover (the DESIGN copy rule); a run moved over from
the audit log, whose start and end are one instant, shows no length instead of an invented "1 sec".
The audit log is called "admin actions" everywhere it is named (the clear button and its confirm, the
Configuration group, the cleanup job, which also prunes the run history its description left out).
This commit is contained in:
2026-10-18 04:00:38 +02:00
parent fe6308d844
commit c288ba147b
18 changed files with 133 additions and 92 deletions
+18 -10
View File
@@ -141,22 +141,30 @@ function isPage(p: string | null | undefined): p is Page {
return !!p && (PAGES as readonly string[]).includes(p);
}
// A page this build no longer has, and the page that took its place. A bookmark, the remembered last
// page and an old history entry can still name one; all three resolve through `resolvePage`, so a
// retired page lands where its content went instead of on the feed.
const RETIRED: Readonly<Record<string, Page>> = {
// A page this build no longer has, the page that took its place, and the params that open its part
// of that page. A bookmark, the remembered last page and an old history entry can still name one; all
// three resolve through `resolvePage`, so a retired page lands where its content went instead of on
// the feed, and the navigation provider writes `params` into the address bar as it rewrites the page.
const RETIRED: Readonly<Record<string, { page: Page; params?: Record<string, string> }>> = {
// The download queue became the tray; what it downloaded lives in Files.
downloads: "files",
// The audit log and the server log became tabs of the activity page (activityUrlState opens the
// right tab from the old page id).
audit: "activity",
logs: "activity",
downloads: { page: "files" },
// The audit log and the server log became tabs of the activity page (its `at` key).
audit: { page: "activity", params: { at: "admin" } },
logs: { page: "activity", params: { at: "logs" } },
};
const retired = (p: string | null | undefined) =>
p && Object.prototype.hasOwnProperty.call(RETIRED, p) ? RETIRED[p]! : null;
/** A page id as stored or linked: a live page, a retired page's successor, else null. */
export function resolvePage(p: string | null | undefined): Page | null {
if (isPage(p)) return p;
return p && Object.prototype.hasOwnProperty.call(RETIRED, p) ? RETIRED[p]! : null;
return retired(p)?.page ?? null;
}
/** The params that open a retired page's part of its successor (none for a live page). */
export function retiredParams(p: string | null | undefined): Record<string, string> {
return isPage(p) ? {} : (retired(p)?.params ?? {});
}
export function readPage(): Page {