From 88ebb075781d8376747d0b68220220f2bdcdd562 Mon Sep 17 00:00:00 2001 From: BenStullsBets Date: Thu, 2 Jul 2026 05:40:05 -0700 Subject: [PATCH] fix(ui): stop the 'Video speed' label wrapping in the Output panel Widen the panel (280->300px), keep control labels on one line (white-space: nowrap), and let the range shrink (min-width:0) instead of pushing the label to a second row. +1 e2e (panel-layout.spec.ts). Co-Authored-By: Claude Opus 4.8 --- simulator/e2e/tests/panel-layout.spec.ts | 25 ++++++++++++++++++++++++ simulator/static/style.css | 7 +++++-- 2 files changed, 30 insertions(+), 2 deletions(-) create mode 100644 simulator/e2e/tests/panel-layout.spec.ts diff --git a/simulator/e2e/tests/panel-layout.spec.ts b/simulator/e2e/tests/panel-layout.spec.ts new file mode 100644 index 0000000..8d6674a --- /dev/null +++ b/simulator/e2e/tests/panel-layout.spec.ts @@ -0,0 +1,25 @@ +import { test, expect, Page } from "@playwright/test"; + +// Guard: the "Video speed" control label must stay on ONE line in the Output panel +// (it was wrapping to two rows at the old panel width). + +async function enter(page: Page) { + await page.goto("/"); + await page.waitForFunction(() => (window as any).__hefReady === true, null, { timeout: 45_000 }); + await page.locator("#welcome-launch").click(); + await page.waitForSelector("#welcome", { state: "detached", timeout: 10_000 }); +} + +test('the "Video speed" label does not wrap to a second line', async ({ page }) => { + await enter(page); + const info = await page.evaluate(() => { + const span = document.querySelector('.panel .play-speed span[data-i18n="speed.label"]') as HTMLElement; + const cs = getComputedStyle(span); + let lh = parseFloat(cs.lineHeight); + if (Number.isNaN(lh)) lh = parseFloat(cs.fontSize) * 1.3; // "normal" + return { text: span.textContent, height: span.getBoundingClientRect().height, lineHeight: lh }; + }); + expect(info.text).toBe("Video speed"); + // A wrapped label is ~2× a single line; allow slack but stay well under two lines. + expect(info.height).toBeLessThan(info.lineHeight * 1.6); +}); diff --git a/simulator/static/style.css b/simulator/static/style.css index 2bba2f2..7bc2081 100644 --- a/simulator/static/style.css +++ b/simulator/static/style.css @@ -97,7 +97,7 @@ main { display: flex; gap: 1rem; padding: 1rem; flex-wrap: wrap; align-items: fl .audio-status { margin-top: 0.5rem; font: 11px/1.4 monospace; color: #9ab; } .audio-status.playing { color: #4e9; } .audio-status.blocked { color: #f97; } -.panel { flex: 0 0 280px; display: flex; flex-direction: column; gap: 0.8rem; +.panel { flex: 0 0 300px; display: flex; flex-direction: column; gap: 0.8rem; max-height: calc(100vh - 2rem); overflow-y: auto; position: sticky; top: 1rem; } /* The control panel stays hidden behind the welcome screen and appears only once @@ -151,7 +151,10 @@ input[type=range], select { width: 100%; } /* Audio level dial (0–10): label · slider · value on one row, matching the switch look. */ .audio-level { display: flex; align-items: center; gap: 0.5rem; margin-top: 0.45rem; color: #9af; font-weight: 600; letter-spacing: 0.3px; } -.audio-level input[type=range] { flex: 1; width: auto; } +/* Keep the control label on one line ("Video speed" was wrapping); let the range + shrink (min-width:0) instead of pushing the label to a second row. */ +.audio-level > span:first-child { white-space: nowrap; } +.audio-level input[type=range] { flex: 1; width: auto; min-width: 0; } #audio-level-val { font-variant-numeric: tabular-nums; min-width: 1.2em; text-align: right; } /* Playback speed (−2×…2×) reuses the audio-level row look; wider value field for the sign + decimals. */ #play-speed-val, #welcome-play-speed-val { font-variant-numeric: tabular-nums; min-width: 3.4em; text-align: right; }