diff --git a/frontend-modern/browser-verification.json b/frontend-modern/browser-verification.json index 0ab28a8fd..dfb21230c 100644 --- a/frontend-modern/browser-verification.json +++ b/frontend-modern/browser-verification.json @@ -1,42 +1,23 @@ { "version": 1, - "base_sha": "2f8a4ec629b75c7ecefd38a9abc4b1511bc9a891", - "verified_at": "2026-09-01T19:54:19Z", + "base_sha": "44e274e5b386e5e990e9bf09b85b6258043487ba", + "verified_at": "2026-09-02T02:59:02Z", "result": "passed", "changed_paths": [ - "frontend-modern/src/App.tsx", "frontend-modern/src/AppLayout.tsx", - "frontend-modern/src/components/shared/MobileNavBar.tsx", - "frontend-modern/src/components/shared/mobileNavBarModel.ts", - "frontend-modern/src/features/home/HomePageSurface.tsx", - "frontend-modern/src/features/home/homePageModel.ts", - "frontend-modern/src/i18n/messages.de.ts", - "frontend-modern/src/i18n/messages.es.ts", - "frontend-modern/src/i18n/messages.ts", - "frontend-modern/src/routing/navigation.ts", - "frontend-modern/src/routing/resourceLinks.ts", - "frontend-modern/src/routing/routePreload.ts", - "frontend-modern/src/utils/assistantPageContext.ts" + "frontend-modern/src/components/Settings/GeneralSettingsPanel.tsx" ], "content_sha256": { - "frontend-modern/src/App.tsx": "d5473f7838148edeaaf5564f01c166d97b6086232dcba70f6296b5c58e563a4b", - "frontend-modern/src/AppLayout.tsx": "db685903fef2a1509edd812acb3904f7ff3ffab0bbd1af2bff183c1b3ae8e373", - "frontend-modern/src/components/shared/MobileNavBar.tsx": "27358f3e77267cc111ce97b32d715b0069e2d1370ddd2b3ecc4f2d34a3e6dac5", - "frontend-modern/src/components/shared/mobileNavBarModel.ts": "ab9e69d379579d02e33a2abd1224d8c13e6543dd6aa0ad40cc1f65697a289d04", - "frontend-modern/src/features/home/HomePageSurface.tsx": "deleted", - "frontend-modern/src/features/home/homePageModel.ts": "deleted", - "frontend-modern/src/i18n/messages.de.ts": "602246d3d4ce11a1a8a914027d950a30ba1f2f25850c385836013853957c2d0c", - "frontend-modern/src/i18n/messages.es.ts": "ed5a749603efad29293cd6964215f6edc21a4ce14bca99a48429ddd120770263", - "frontend-modern/src/i18n/messages.ts": "43a757e00eaa7879c400895c9c59a930e31513dbf03e0ae6d60f7e72a6ad9962", - "frontend-modern/src/routing/navigation.ts": "8ae1ad012e60ef345ffb3d18bb66f5d8af056758109655a8ec362ebf4c7b9556", - "frontend-modern/src/routing/resourceLinks.ts": "dee9a426de785e23390ba49c9067f55c8f923cecfce1247ff18f9b1004e0cc90", - "frontend-modern/src/routing/routePreload.ts": "ee79d423db0afcf8d76d1d39da59a13cb2c98e908e516a016cbb425982b25873", - "frontend-modern/src/utils/assistantPageContext.ts": "bfca19b4e183777ee1535073d48cce92ce31980f4105bab337f33a28538a6a35" + "frontend-modern/src/AppLayout.tsx": "be386bcaa58656a9397fed71a9d70147e2540ff6cb4aff53261d9c03e6ca3834", + "frontend-modern/src/components/Settings/GeneralSettingsPanel.tsx": "f1e1c0630cc841ae2820b15130a887aeef0ec7838cb93f652213ebb8fb278199" }, "routes": [ - "/", - "/home", - "/proxmox/overview" + "/actions", + "/alerts/overview", + "/settings/infrastructure", + "/settings/system-general", + "/patrol", + "/" ], "viewports": [ { @@ -44,21 +25,22 @@ "height": 720 }, { - "width": 375, - "height": 812 + "width": 390, + "height": 844 } ], "states": [ - "signed-in desktop shell with primary tabs Proxmox, Docker, Kubernetes, TrueNAS, vSphere, Machines and no Home tab", - "/home renders the Page Not Found surface with 'No route matched /home' and a Go to workspace button", - "signed-in narrow shell on /proxmox/overview with the bottom navigation bar showing Proxmox, Alerts, Patrol, Actions, More and no Home entry", - "More navigation sheet open at narrow width listing Settings only", - "no horizontal page overflow at 375px; no console errors at either width" + "authenticated empty Actions surface at phone width with the RC Preview badge visible, no horizontal overflow, and the skip link focused", + "authenticated General Settings surface with Full-width mode off and Outbound usage telemetry on, each exposed as a named pressed-state button", + "authenticated Alerts, Infrastructure, General Settings, and Patrol surfaces with reduced motion enabled and no automatically detectable WCAG A/AA violations", + "Add infrastructure dialog open with its accessible description, close control focused, and underlying shell retained", + "logged-out welcome surface with reduced motion enabled and no automatically detectable WCAG A/AA violations" ], "interactions": [ - "navigated to / and /home at desktop width and read the rendered nav and main headings", - "pressed Go to workspace on the /home not-found page and landed on /proxmox/overview", - "resized to 375x812, loaded /proxmox/overview, opened the More navigation sheet, closed it with Escape", - "confirmed the default landing route and nav order are unchanged from the parent revision apart from the removed Home entry" + "tabbed from the document start to Skip to main content at phone width, activated it with Enter, and confirmed focus moved to the main landmark", + "inspected the rendered 390x844 Actions screenshot for badge contrast, placement, clipping, scrolling, and bottom-navigation coherence", + "navigated the authenticated desktop routes and scanned their rendered states for WCAG A/AA violations and unexpected reduced-motion effects", + "opened Add infrastructure, verified initial close-control focus and dialog description, dismissed it with Escape, and confirmed focus returned to Add infrastructure", + "opened the logged-out entry surface and verified its heading, disabled welcome/form motion, and accessibility scan" ] } diff --git a/frontend-modern/src/AppLayout.tsx b/frontend-modern/src/AppLayout.tsx index 2fae9e46d..3d4ea0def 100644 --- a/frontend-modern/src/AppLayout.tsx +++ b/frontend-modern/src/AppLayout.tsx @@ -258,6 +258,7 @@ export function AppLayout(props: AppLayoutProps) { const [skipLinkFocused, setSkipLinkFocused] = createSignal(false); const [primaryRouteMemoryVersion, setPrimaryRouteMemoryVersion] = createSignal(0); let headerEl: HTMLDivElement | undefined; + let mainContentEl: HTMLElement | undefined; let assistantLauncherEl: HTMLButtonElement | undefined; let restoreAssistantLauncherFocus = false; let headerHideTimeout: ReturnType | undefined; @@ -730,6 +731,7 @@ export function AppLayout(props: AppLayoutProps) { jump past the chrome straight into the page content. */} mainContentEl?.focus()} onFocus={() => setSkipLinkFocused(true)} onBlur={() => setSkipLinkFocused(false)} class={ @@ -814,7 +816,7 @@ export function AppLayout(props: AppLayoutProps) { - + Preview @@ -980,7 +982,9 @@ export function AppLayout(props: AppLayoutProps) {
diff --git a/frontend-modern/src/__tests__/AppLayout.test.tsx b/frontend-modern/src/__tests__/AppLayout.test.tsx index 821a963de..d097bb98a 100644 --- a/frontend-modern/src/__tests__/AppLayout.test.tsx +++ b/frontend-modern/src/__tests__/AppLayout.test.tsx @@ -174,7 +174,16 @@ describe('AppLayout navigation icons', () => { expect(container.querySelector('.pulse-shell')).toHaveClass('pb-safe-or-14'); expect(container.querySelector('.pulse-shell')).not.toHaveClass('pb-safe-or-16'); expect(container.querySelector('.header')).toHaveClass('mb-1', 'sm:mb-3'); - expect(container.querySelector('main')).toHaveClass('mb-1', 'sm:mb-2'); + const main = container.querySelector('main'); + expect(main).toHaveClass('mb-1', 'sm:mb-2'); + expect(main).toHaveAttribute('id', 'main'); + expect(main).toHaveAttribute('tabindex', '-1'); + const skipLink = screen.getByRole('link', { name: 'Skip to main content' }); + expect(skipLink).toHaveAttribute('href', '#main'); + fireEvent.click(skipLink); + expect(main).toHaveFocus(); + expect(screen.getByText('Preview')).toHaveClass('bg-orange-700', 'text-white'); + expect(screen.getByText('Preview')).not.toHaveClass('bg-orange-500'); expect(container.querySelector('footer')).toHaveClass('pulse-footer', 'px-2', 'sm:px-4'); const desktopNav = screen.getByRole('navigation', { name: 'Primary navigation' }); diff --git a/frontend-modern/src/components/Settings/GeneralSettingsPanel.tsx b/frontend-modern/src/components/Settings/GeneralSettingsPanel.tsx index 0f3b9ad5f..d6f00707e 100644 --- a/frontend-modern/src/components/Settings/GeneralSettingsPanel.tsx +++ b/frontend-modern/src/components/Settings/GeneralSettingsPanel.tsx @@ -223,6 +223,7 @@ export const GeneralSettingsPanel: Component = (props layoutStore.toggle()} />
@@ -276,6 +277,7 @@ export const GeneralSettingsPanel: Component = (props props.handleTelemetryEnabledChange(!props.telemetryEnabled())} /> diff --git a/frontend-modern/src/components/Settings/__tests__/GeneralSettingsPanel.localization.test.tsx b/frontend-modern/src/components/Settings/__tests__/GeneralSettingsPanel.localization.test.tsx index d37365766..3cd87ddc7 100644 --- a/frontend-modern/src/components/Settings/__tests__/GeneralSettingsPanel.localization.test.tsx +++ b/frontend-modern/src/components/Settings/__tests__/GeneralSettingsPanel.localization.test.tsx @@ -86,6 +86,14 @@ describe('GeneralSettingsPanel localization', () => { ).toBeInTheDocument(); expect(screen.getByText('Usage data and privacy')).toBeInTheDocument(); expect(screen.getByText('Outbound usage telemetry')).toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'Full-width mode' })).toHaveAttribute( + 'aria-pressed', + 'false', + ); + expect(screen.getByRole('button', { name: 'Outbound usage telemetry' })).toHaveAttribute( + 'aria-pressed', + 'true', + ); expect(screen.getByRole('button', { name: 'Preview payload' })).toBeInTheDocument(); expect(screen.getByRole('button', { name: 'Reset ID' })).toBeInTheDocument(); expect(screen.getByText('Monitoring cadence')).toBeInTheDocument(); diff --git a/tests/integration/tests/83-product-trust-accessibility.spec.ts b/tests/integration/tests/83-product-trust-accessibility.spec.ts index 553474bb9..48b74f8d7 100644 --- a/tests/integration/tests/83-product-trust-accessibility.spec.ts +++ b/tests/integration/tests/83-product-trust-accessibility.spec.ts @@ -137,8 +137,19 @@ test("Actions remains named, directly reachable, keyboard accessible, and free o document.documentElement.clientWidth, ); expect(overflow).toBeFalsy(); + + const skipLink = page.getByRole("link", { name: "Skip to main content" }); + await page.evaluate(() => { + document.body.tabIndex = -1; + document.body.focus(); + document.body.removeAttribute("tabindex"); + }); await page.keyboard.press("Tab"); - await expect(page.locator(":focus")).toBeVisible(); + await expect(skipLink).toBeFocused(); + await expect(skipLink).toBeVisible(); + await page.keyboard.press("Enter"); + await expect(page.locator("#main")).toBeFocused(); + await testInfo.attach("actions-phone-width", { body: await page.screenshot(), contentType: "image/png",