fix(nav): stop nav bar from jumping when Live is selected (#1046) (#1078)

## Summary

The `🔴 Live` nav link could wrap onto two lines at certain viewport
widths once it became the `.active` link, which grew `.nav-link`'s
height and made the whole `.top-nav` "hop" the instant Live was selected
(issue #1046).

Adding `white-space: nowrap` to the base `.nav-link` rule keeps every
nav label on a single line at every breakpoint (default desktop + the
768–1279px and <768px responsive overrides), eliminating the jump.

## Changes

- `public/style.css` — `white-space: nowrap` on `.nav-link`.
- `test-e2e-playwright.js` — new assertion at viewport 1115px (the width
in the issue screenshots) that:
  - computed `white-space` prevents wrapping
  - the Live link renders on a single line in both states
  - `.top-nav` height does not change when `.active` is toggled

## TDD

- Red commit `ba906a5` — test added, fails because base `.nav-link` has
no `white-space` rule (default `normal`).
- Green commit `51906cb` — single-line CSS fix makes the test pass.

Fixes #1046

---------

Co-authored-by: corescope-bot <bot@corescope.local>
This commit is contained in:
Kpa-clawbot
2026-05-05 01:36:08 -07:00
committed by GitHub
parent 5f6c5af0cf
commit aa3d26f314
2 changed files with 52 additions and 0 deletions
+1
View File
@@ -398,6 +398,7 @@ input[type="week"] {
border-bottom: 2px solid transparent; transition: all .15s;
background: none; border-top: none; border-left: none; border-right: none;
cursor: pointer; font-family: var(--font);
white-space: nowrap; /* #1046: never wrap labels — wrapping makes the nav bar grow taller */
}
.nav-link:hover { color: var(--nav-text); }
.nav-link.active {
+51
View File
@@ -832,6 +832,57 @@ async function run() {
// --- Group: Live page ---
// Test (issue #1046): Activating the Live nav link MUST NOT cause the
// "🔴 Live" label to wrap onto two lines, which makes the whole top
// nav bar grow taller and "hop". The label has to stay on one line in
// every state, and the nav bar height must be identical with/without
// the .active class.
await test('Live nav-link does not wrap or change nav height when active (#1046)', async () => {
// Use the exact viewport width from the issue screenshots.
await page.setViewportSize({ width: 1115, height: 800 });
await page.goto(BASE, { waitUntil: 'domcontentloaded' });
await page.waitForSelector('a.nav-link[data-route="live"]');
const measure = await page.evaluate(() => {
const link = document.querySelector('a.nav-link[data-route="live"]');
const nav = document.querySelector('.top-nav');
const ws = getComputedStyle(link).whiteSpace;
// Force inactive state.
const wasActive = link.classList.contains('active');
link.classList.remove('active');
const inactive = {
navH: nav.getBoundingClientRect().height,
lines: link.getClientRects().length,
};
// Force active state.
link.classList.add('active');
const active = {
navH: nav.getBoundingClientRect().height,
lines: link.getClientRects().length,
};
// Restore.
link.classList.toggle('active', wasActive);
return { ws, inactive, active };
});
assert(
['nowrap', 'pre', 'pre-wrap'].includes(measure.ws),
`Live nav-link must not wrap; computed white-space=${measure.ws}`,
);
assert(
measure.inactive.lines === 1,
`Live nav-link must render on one line when inactive (got ${measure.inactive.lines})`,
);
assert(
measure.active.lines === 1,
`Live nav-link must render on one line when active (got ${measure.active.lines})`,
);
assert(
measure.active.navH === measure.inactive.navH,
`Top nav height must not change when Live becomes active (inactive=${measure.inactive.navH}, active=${measure.active.navH})`,
);
});
// Test: Live page loads with map and stats
await test('Live page loads with map and stats', async () => {
await page.goto(`${BASE}/#/live`, { waitUntil: 'domcontentloaded' });