diff --git a/src/frontend/src/styles/site.css b/src/frontend/src/styles/site.css index 0d680223e..a11491f47 100644 --- a/src/frontend/src/styles/site.css +++ b/src/frontend/src/styles/site.css @@ -1094,21 +1094,28 @@ li a.external-link::after { } } -:root:has(.topics-sidebar[data-api-ref])[data-sidebar-collapsed] { - --sl-sidebar-width: 5rem; -} - -:root:has(.topics-sidebar[data-topic-nav])[data-topic-sidebar-collapsed] { - --sl-sidebar-width: 5rem; -} - :root[data-has-toc] { --aspire-toc-panel-width: clamp(13.5rem, 16vw, 16rem); } /* Collapsed sidebar = narrow "icon rail" showing just the topic icons. - The expand button still floats off the rail's right edge (left: var(--sl-sidebar-width)). */ + The expand button still floats off the rail's right edge (left: var(--sl-sidebar-width)). + + The width shrinker (`--sl-sidebar-width: 5rem`) lives inside the same + media query as the rail-mode rules on purpose. If the persisted collapsed + preference is replayed at a narrower viewport (where the mobile/tablet + overlay sidebar takes over and the rail-mode rules below don't apply), + the sidebar must stay at its default width — otherwise the contents get + squished into one-letter-per-line columns instead of being hidden. */ @media (min-width: 72rem) { + :root:has(.topics-sidebar[data-api-ref])[data-sidebar-collapsed] { + --sl-sidebar-width: 5rem; + } + + :root:has(.topics-sidebar[data-topic-nav])[data-topic-sidebar-collapsed] { + --sl-sidebar-width: 5rem; + } + :root[data-has-toc] .right-sidebar-panel { flex: 0 0 var(--aspire-toc-panel-width); min-width: var(--aspire-toc-panel-width); diff --git a/src/frontend/tests/e2e/ui-regressions.spec.ts b/src/frontend/tests/e2e/ui-regressions.spec.ts index bd129b0e9..f3fcbc387 100644 --- a/src/frontend/tests/e2e/ui-regressions.spec.ts +++ b/src/frontend/tests/e2e/ui-regressions.spec.ts @@ -447,3 +447,66 @@ test('topic sidebar custom controls persist collapse state and filter reset on r await expect.poll(() => hasTopicSidebarCollapsed(page)).toBe(false); await expect.poll(() => readTopicSidebarCollapsedPreference(page)).toBe('0'); }); + +test('persisted collapsed sidebar preference does not squish labels at sub-72rem viewports', async ({ + page, +}) => { + // Regression for the "collapsed icon-rail at narrow widths" bug, where a + // persisted collapsed preference combined with a viewport below 72rem + // shrank the sidebar to a 5rem rail without applying the rail-mode rules + // (which hide labels and centre icons). Result: every topic / sublist + // label wrapped one letter per line. + const viewport = page.viewportSize(); + test.skip( + !viewport || viewport.width < 800 || viewport.width >= 1152, + 'Bug only reproduces between the Starlight mobile breakpoint (50rem) and the desktop collapse breakpoint (72rem).' + ); + + // Pre-seed the persisted preferences so Head.astro's inline restore script + // applies `data-topic-sidebar-collapsed` / `data-sidebar-collapsed` to the + // documentElement before first paint — exactly the user-reported scenario + // of collapsing on a wide screen and then resizing/zooming down. + await page.addInitScript(() => { + localStorage.setItem('topic-sidebar-collapsed', '1'); + localStorage.setItem('api-sidebar-collapsed', '1'); + }); + + await page.goto('/app-host/certificate-configuration/'); + await dismissCookieConsentIfVisible(page); + + // Wait for the page and the topic sidebar element itself to render before + // we read computed styles or measure widths. At tablet widths we can't use + // `waitForTopicSidebarReady` — that helper asserts collapse/expand button + // visibility, but those controls live in rail mode (>= 72rem) which is + // exactly the breakpoint this bug sits outside of. + await expect(page.locator('main h1').first()).toBeVisible(); + await expect(page.locator('.topics-sidebar[data-topic-nav]')).toBeAttached(); + await expect + .poll(() => + page.evaluate(() => document.documentElement.hasAttribute('data-topic-sidebar-ready')) + ) + .toBe(true); + + // Confirm the persisted preference round-tripped (so we know we're + // exercising the buggy code path, not a no-op). + await expect.poll(() => readTopicSidebarCollapsedPreference(page)).toBe('1'); + await expect.poll(() => hasTopicSidebarCollapsed(page)).toBe(true); + + // The squish bug was the result of `--sl-sidebar-width: 5rem` applying + // outside its intended `@media (min-width: 72rem)` scope. At narrow + // viewports the variable must keep its default (much wider) value. + const sidebarWidthVar = await page.evaluate(() => + getComputedStyle(document.documentElement).getPropertyValue('--sl-sidebar-width').trim() + ); + expect(sidebarWidthVar).not.toBe('5rem'); + + // And the actual rendered sidebar element width must clear the 80px + // icon-rail threshold — labels need horizontal room or they wrap badly. + const topicSidebarWidth = await page.evaluate(() => { + const el = document.querySelector('.topics-sidebar'); + if (!el) return null; + return el.getBoundingClientRect().width; + }); + expect(topicSidebarWidth).not.toBeNull(); + expect(topicSidebarWidth ?? 0).toBeGreaterThan(120); +});