feat(shell): collapsible sidebar, account menu, and phone drawer
CI / verify (pull_request) Failing after 57s
CI / infrastructure-smoke (pull_request) Skipped
CI / migration-smoke (pull_request) Skipped
CI / package-release (pull_request) Skipped
CI / package-publication (pull_request) Skipped
CI / image-build (pull_request) Skipped
CI / deploy-smoke (same-origin) (pull_request) Skipped
CI / deploy-smoke (split) (pull_request) Skipped
CI / verify (pull_request) Failing after 57s
CI / infrastructure-smoke (pull_request) Skipped
CI / migration-smoke (pull_request) Skipped
CI / package-release (pull_request) Skipped
CI / package-publication (pull_request) Skipped
CI / image-build (pull_request) Skipped
CI / deploy-smoke (same-origin) (pull_request) Skipped
CI / deploy-smoke (split) (pull_request) Skipped
The Dashboard's shell now follows DESIGN.md §4 (ADR 0058): - A collapsible left sidebar replaces the header-and-column layout. It is icons and labels when expanded and icons only when collapsed; each collapsed item keeps its name, visually hidden, and shows a tooltip on hover and on keyboard focus (WCAG 2.2 §1.4.13). The Overview comes first, then the server's groups and modules. There is no top bar. - The expanded/collapsed choice is remembered in this browser's localStorage, behind a SidebarPreference interface that a synced store can replace later. - An account menu at the bottom of the sidebar follows the WAI-ARIA menu button pattern and offers only Sign out, which now lives there rather than in the header. - On phones (below 48em) the same navigation opens in a modal drawer: the page behind it is inert and focus is trapped inside it. Escape, its close button, and the backdrop close it and return focus to the menu button. Choosing a page closes it and focuses the page. Changing layout keeps the route, the desktop mode, and the mounted module. - Lucide (lucide-react 1.48.0, shell only) is Ekklesia's icon vocabulary. Every module shows one generic icon until manifest v9 lets modules declare their own; there is no per-module icon map. - The identity slot is a neutral placeholder with no inputs, waiting for branding (#101). The tooltip, menu, and drawer stay shell-local until a second consumer needs them (ADR 0039). The deploy smoke now signs out through the account menu, and drives the drawer at phone size in a real browser. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
f3b7e19651
commit
af31724cb4
@@ -191,8 +191,10 @@ check(
|
||||
const moduleButton =
|
||||
'document.querySelector(\'[data-testid^="select-"]:not([data-testid="select-overview"])\')';
|
||||
const hasModule = await waitFor(moduleButton, "a module in the Dashboard navigation");
|
||||
let openedModuleId;
|
||||
if (hasModule) {
|
||||
const moduleId = await evaluate(`${moduleButton}.dataset.testid.slice("select-".length)`);
|
||||
openedModuleId = moduleId;
|
||||
await evaluate(`${moduleButton}.click()`);
|
||||
const mounted = await waitFor(
|
||||
`${selector("module-container")}?.childElementCount > 0 && !${selector("module-loading")} && !${selector("module-error")}`,
|
||||
@@ -282,9 +284,74 @@ if (displayPath === null) {
|
||||
);
|
||||
}
|
||||
|
||||
// --- Sign out on the Dashboard -------------------------------------------------------------------
|
||||
// --- The phone layout: the same navigation, in a drawer (ADR 0058) ------------------------------
|
||||
await send("Emulation.setDeviceMetricsOverride", {
|
||||
deviceScaleFactor: 2,
|
||||
height: 844,
|
||||
mobile: true,
|
||||
width: 390,
|
||||
});
|
||||
await navigate(`${dashboardOrigin}/`);
|
||||
await waitFor(selector("host-logout"), "the sign-out control");
|
||||
check(
|
||||
await waitFor(
|
||||
`${selector("mobile-navigation-trigger")} && !${selector("host-sidebar")}`,
|
||||
"the phone navigation trigger",
|
||||
),
|
||||
"a phone-sized window shows the navigation trigger instead of the sidebar",
|
||||
);
|
||||
await evaluate(`${selector("mobile-navigation-trigger")}.click()`);
|
||||
check(
|
||||
await waitFor(
|
||||
`document.activeElement === ${selector("navigation-drawer-close")} && ${selector("host-main-region")}.inert`,
|
||||
"focus inside the opened drawer",
|
||||
),
|
||||
"the drawer opens as a modal, with focus inside it and the page inert",
|
||||
);
|
||||
await evaluate(
|
||||
`document.activeElement.dispatchEvent(new KeyboardEvent("keydown", { bubbles: true, key: "Escape" }))`,
|
||||
);
|
||||
check(
|
||||
await waitFor(
|
||||
`!${selector("navigation-drawer")} && document.activeElement === ${selector("mobile-navigation-trigger")}`,
|
||||
"the drawer to close on Escape",
|
||||
),
|
||||
"Escape closes the drawer and returns focus to its trigger",
|
||||
);
|
||||
if (openedModuleId) {
|
||||
await evaluate(`${selector("mobile-navigation-trigger")}.click()`);
|
||||
await waitFor(selector(`select-${openedModuleId}`), "the module in the drawer");
|
||||
await evaluate(`${selector(`select-${openedModuleId}`)}.click()`);
|
||||
check(
|
||||
await waitFor(
|
||||
`location.pathname === ${JSON.stringify(`/${openedModuleId}`)} && !${selector("navigation-drawer")} && ${selector("module-container")}?.childElementCount > 0 && !${selector("module-error")}`,
|
||||
"the module chosen from the drawer to open",
|
||||
),
|
||||
`choosing ${openedModuleId} in the drawer closes it and opens the module at its own address`,
|
||||
);
|
||||
}
|
||||
await send("Emulation.setDeviceMetricsOverride", {
|
||||
deviceScaleFactor: 1,
|
||||
height: 1080,
|
||||
mobile: false,
|
||||
width: 1920,
|
||||
});
|
||||
check(
|
||||
await waitFor(
|
||||
`${selector("host-sidebar")} && !${selector("mobile-navigation-trigger")}${
|
||||
openedModuleId
|
||||
? ` && ${selector(`select-${openedModuleId}`)}?.getAttribute("aria-current") === "page"`
|
||||
: ""
|
||||
}`,
|
||||
"the sidebar after widening the window",
|
||||
),
|
||||
"widening the window brings the sidebar back on the same page",
|
||||
);
|
||||
|
||||
// --- Sign out on the Dashboard, from the account menu at the bottom of the sidebar ---------------
|
||||
await navigate(`${dashboardOrigin}/`);
|
||||
await waitFor(selector("host-account-menu"), "the account control");
|
||||
await evaluate(`${selector("host-account-menu")}.click()`);
|
||||
await waitFor(selector("host-logout"), "the sign-out item in the account menu");
|
||||
await evaluate(`${selector("host-logout")}.click()`);
|
||||
check(
|
||||
await waitFor(selector("login-email"), "the sign-in form after sign-out"),
|
||||
|
||||
Reference in New Issue
Block a user