fix(ui): pin the OPERATIONS tab bar while panel content scrolls (#31)
CI / build + unit + e2e (pull_request) Successful in 2m47s

The TabsList is now position:sticky top:0 with a z-index above the panel
content. In wide mode it pins to the top of the OPERATIONS scroll area
(#21's center-pane scroller); in narrow mode, where the body scrolls and
there is no inner scroll container, it pins to the viewport. The existing
opaque background keeps scrolled content from showing through.
This commit is contained in:
2026-09-29 10:22:14 +00:00
parent 1bb04d2a51
commit 1710e50b63
4 changed files with 39 additions and 2 deletions
+30
View File
@@ -82,6 +82,36 @@ test('active tab survives a page reload (#27)', async ({ page }) => {
await expect(page.getByRole('tab', { name: 'CREW' })).toHaveAttribute('aria-selected', 'true');
});
test('tab bar stays pinned while panel content scrolls (#31)', async ({ page }) => {
await page.goto('/');
// Shrink the viewport so the center pane's content overflows its scroll area regardless of save state.
await page.setViewportSize({ width: 1280, height: 560 });
// WORLD MAP is always tall enough to overflow (fixed-height chart) — deterministic across retries.
await page.getByRole('tab', { name: 'WORLD MAP' }).click();
const tablist = page.getByRole('tablist');
await expect(tablist).toBeVisible();
const before = await tablist.boundingBox();
expect(before).not.toBeNull();
// Scroll the nearest scroll container above the tab list all the way down.
const scrolled = await page.evaluate(() => {
let el = document.querySelector('[role="tablist"]')?.parentElement ?? null;
while (el && !['auto', 'scroll'].includes(getComputedStyle(el).overflowY)) el = el.parentElement;
if (!el) throw new Error('no scroll container above the tab list');
el.scrollTop = el.scrollHeight;
return el.scrollTop;
});
expect(scrolled).toBeGreaterThan(50); // content really overflowed — guards against a vacuous pass
await expect(tablist).toBeVisible();
const after = await tablist.boundingBox();
expect(after).not.toBeNull();
// Pinned: the tab bar's viewport position is unchanged even though the content scrolled away (#31).
expect(Math.abs(after!.y - before!.y)).toBeLessThan(2);
});
test('world map shows the generated starting system and its neighbours', async ({ page }) => {
const world = (await (await page.request.get('/api/world')).json()) as WorldState;