admin: reclaim the desktop header, move pause and log out to the sidebar

the header row survives only on narrow screens; on desktop its lone occupant, log out, joins pause in the sidebar footer, both full width. pause leaves the query detail page's related actions, where a global control had no business, and its hand-rolled duration dropdown becomes a react-aria menu with real keyboard navigation, dismissal and positioning.
This commit is contained in:
2026-08-29 11:23:17 +02:00
parent d5613ee718
commit 79578e1d2a
9 changed files with 164 additions and 150 deletions
+25 -18
View File
@@ -91,11 +91,14 @@ test("unpaused: duration menu pauses with the picked duration_seconds", async ()
fireEvent.click(trigger);
expect(trigger.getAttribute("aria-expanded")).toBe("true");
for (const label of ["60 seconds", "5 minutes", "30 minutes", "Indefinitely"]) {
expect(screen.getByRole("button", { name: label })).toBeTruthy();
}
expect(screen.getAllByRole("menuitem").map((item) => item.textContent)).toEqual([
"60 seconds",
"5 minutes",
"30 minutes",
"Indefinitely",
]);
fireEvent.click(screen.getByRole("button", { name: "5 minutes" }));
fireEvent.click(screen.getByRole("menuitem", { name: "5 minutes" }));
await waitFor(() => expect(postBodies).toEqual([{ paused: true, duration_seconds: 300 }]));
await screen.findByRole("button", { name: "Resume" });
@@ -105,7 +108,7 @@ test("indefinite pause sends no duration_seconds", async () => {
renderControl();
fireEvent.click(await findPauseTrigger());
fireEvent.click(screen.getByRole("button", { name: "Indefinitely" }));
fireEvent.click(screen.getByRole("menuitem", { name: "Indefinitely" }));
await waitFor(() => expect(postBodies).toEqual([{ paused: true }]));
await screen.findByRole("button", { name: "Resume" });
@@ -151,11 +154,15 @@ test("an active resolver states nothing: the button already says Pause", async (
test("escape closes the duration menu", async () => {
renderControl();
const trigger = await findPauseTrigger();
fireEvent.click(trigger);
expect(screen.getByRole("button", { name: "Indefinitely" })).toBeTruthy();
fireEvent.keyDown(trigger, { key: "Escape" });
expect(screen.queryByRole("button", { name: "Indefinitely" })).toBeNull();
fireEvent.click(await findPauseTrigger());
expect(screen.getByRole("menuitem", { name: "Indefinitely" })).toBeTruthy();
// Opening the menu moves focus into it, so Escape is pressed where the reader
// is, not on the trigger they left.
fireEvent.keyDown(screen.getByRole("menu"), { key: "Escape" });
await waitFor(() => expect(screen.queryByRole("menuitem", { name: "Indefinitely" })).toBeNull());
expect(screen.getByRole("button", { name: "Pause" }).getAttribute("aria-expanded")).toBe("false");
});
test("failed pause with 429 shows a ticking retry countdown", async () => {
@@ -168,7 +175,7 @@ test("failed pause with 429 shows a ticking retry countdown", async () => {
renderControl();
fireEvent.click(await findPauseTrigger());
fireEvent.click(screen.getByRole("button", { name: "5 minutes" }));
fireEvent.click(screen.getByRole("menuitem", { name: "5 minutes" }));
const alert = await screen.findByRole("alert");
expect(alert.textContent).toBe("Rate limited. Try again in 30s.");
@@ -187,7 +194,7 @@ test("failed pause with 503 shows the degraded message", async () => {
renderControl();
fireEvent.click(await findPauseTrigger());
fireEvent.click(screen.getByRole("button", { name: "60 seconds" }));
fireEvent.click(screen.getByRole("menuitem", { name: "60 seconds" }));
const alert = await screen.findByRole("alert");
expect(alert.textContent).toBe("The server is starting or degraded. Try again shortly.");
@@ -202,12 +209,12 @@ test("a successful pause clears the previous mutation error", async () => {
renderControl();
fireEvent.click(await findPauseTrigger());
fireEvent.click(screen.getByRole("button", { name: "5 minutes" }));
fireEvent.click(screen.getByRole("menuitem", { name: "5 minutes" }));
await screen.findByRole("alert");
postFailure = null;
fireEvent.click(screen.getByRole("button", { name: "Pause" }));
fireEvent.click(screen.getByRole("button", { name: "5 minutes" }));
fireEvent.click(screen.getByRole("menuitem", { name: "5 minutes" }));
await screen.findByRole("button", { name: "Resume" });
expect(screen.queryByRole("alert")).toBeNull();
@@ -240,7 +247,7 @@ test("a menu left open when the control withdraws does not come back open", asyn
vi.useFakeTimers({ shouldAdvanceTime: true });
renderControl();
fireEvent.click(await findPauseTrigger());
expect(screen.getByRole("button", { name: "Indefinitely" })).toBeTruthy();
expect(screen.getByRole("menuitem", { name: "Indefinitely" })).toBeTruthy();
protection = { state: "unavailable", until: null };
await vi.advanceTimersByTimeAsync(11_000);
@@ -253,7 +260,7 @@ test("a menu left open when the control withdraws does not come back open", asyn
// reader opened, and nobody opened this one.
const trigger = await vi.waitFor(() => screen.getByRole("button", { name: "Pause" }));
expect(trigger.getAttribute("aria-expanded")).toBe("false");
expect(screen.queryByRole("button", { name: "Indefinitely" })).toBeNull();
expect(screen.queryByRole("menuitem", { name: "Indefinitely" })).toBeNull();
vi.useRealTimers();
});
@@ -261,7 +268,7 @@ test("a menu open when someone else pauses does not reopen when that pause ends"
vi.useFakeTimers({ shouldAdvanceTime: true });
renderControl();
fireEvent.click(await findPauseTrigger());
expect(screen.getByRole("button", { name: "Indefinitely" })).toBeTruthy();
expect(screen.getByRole("menuitem", { name: "Indefinitely" })).toBeTruthy();
// Filtering is paused from somewhere else, and this browser learns it from
// the poll. The Resume rendering has no menu.
@@ -274,7 +281,7 @@ test("a menu open when someone else pauses does not reopen when that pause ends"
const trigger = await vi.waitFor(() => screen.getByRole("button", { name: "Pause" }));
expect(trigger.getAttribute("aria-expanded")).toBe("false");
expect(screen.queryByRole("button", { name: "Indefinitely" })).toBeNull();
expect(screen.queryByRole("menuitem", { name: "Indefinitely" })).toBeNull();
vi.useRealTimers();
});