diff --git a/tests/browser/workspace.spec.mjs b/tests/browser/workspace.spec.mjs index 2c0e2be..45166e2 100644 --- a/tests/browser/workspace.spec.mjs +++ b/tests/browser/workspace.spec.mjs @@ -112,6 +112,25 @@ test('skip to content focuses the current view without changing its route', asyn await expect(page.locator('#workspace-main h1')).toHaveText('Prompt library'); }); +// The concept focuses its page title on every hashchange navigation (docs/concepts/workspace/app.js 1168), so a screen +// reader announces ", heading level 1"; the first render leaves focus alone. +test('a sidebar route change focuses the page title, as the concept does', async ({ page }) => { + await page.goto(`${workspace}#/library`); + const title = page.locator('#page-title'); + await expect(title).toHaveText('Prompt library'); + await expect(title).not.toBeFocused(); + await sidebarLink(page, 'Images').click(); + await expect(title).toHaveText('Images'); + await expect(title).toBeFocused(); + await expect(page.locator('#workspace-main')).not.toBeFocused(); + await sidebarLink(page, 'Tags & categories').click(); + await expect(title).toHaveText('Tags & categories'); + await expect(title).toBeFocused(); + await page.goBack(); + await expect(title).toHaveText('Images'); + await expect(title).toBeFocused(); +}); + test('returning to overview refreshes persisted counts without duplicating navigation', async ({ page, request }) => { await page.goto(`${workspace}#/overview`); const prompts = statistic(page, 'Prompts'); diff --git a/web/js/workspace/heading.mjs b/web/js/workspace/heading.mjs index dacba4a..ba111f1 100644 --- a/web/js/workspace/heading.mjs +++ b/web/js/workspace/heading.mjs @@ -18,7 +18,8 @@ function partsOf(heading) { /** * Replace `main`'s content with `.heading` > div (`h1#page-title[tabindex=-1]`, `p.muted`) + `.heading-actions`. - * The h1 is the focus target after a save or a route change, as the concept's page title is. + * The h1 is the focus target after a save, and the shell focuses it on every route change after the first, as the + * concept focuses its page title on hashchange (app.js 1168). The skip link focuses `main` instead. */ export function renderHeading(main, { title = '', description = '', actions = [] } = {}) { const heading = element('div', 'heading'); diff --git a/web/js/workspace/index.mjs b/web/js/workspace/index.mjs index b57de5f..44208cb 100644 --- a/web/js/workspace/index.mjs +++ b/web/js/workspace/index.mjs @@ -89,8 +89,9 @@ function mountWorkspace() { } document.title = `${routeTitle(route)} ยท Prompt Manager`; // The concept's heading(); each view fills its description and actions. - const { description } = renderHeading(main, { title: route.label }); - if (!firstRender) main.focus(); + // The concept focuses its page title on a hashchange navigation (app.js 1168); the skip link focuses `main`. + const { description, h1 } = renderHeading(main, { title: route.label }); + if (!firstRender) h1.focus(); firstRender = false; document.getElementById('workspace-breadcrumb').textContent = routeTitle(route); if (route.id === 'images') {