From ca0d41ee762f1e340be156f991b3820d981dcbfa Mon Sep 17 00:00:00 2001 From: "Christopher S. Aondona" Date: Thu, 12 Nov 2026 13:18:49 +0100 Subject: [PATCH 1/6] fix(ui): preserve compact toolbar around WebKit native controls --- src/styles/throughline.css | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/styles/throughline.css b/src/styles/throughline.css index 3960a22..e05a3b6 100644 --- a/src/styles/throughline.css +++ b/src/styles/throughline.css @@ -88,7 +88,8 @@ column-gap: 0.75rem; row-gap: 0.375rem; min-width: 0; - padding-block: 0.375rem; + /* Preserve native select height (notably WebKit) within the compact bar. */ + padding-block: 0.25rem; border-top: 1px solid var(--thl-line); border-bottom: 1px solid var(--thl-line); } From e964bd2d0e543603283ce2e1cfa4a54f3a37a435 Mon Sep 17 00:00:00 2001 From: "Christopher S. Aondona" Date: Thu, 12 Nov 2026 13:18:51 +0100 Subject: [PATCH 2/6] test(ui): assert compact toolbar fully contains interactive controls --- src/react/__tests__/toolbar.browser.test.tsx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/react/__tests__/toolbar.browser.test.tsx b/src/react/__tests__/toolbar.browser.test.tsx index 83791ee..752b1f9 100644 --- a/src/react/__tests__/toolbar.browser.test.tsx +++ b/src/react/__tests__/toolbar.browser.test.tsx @@ -27,7 +27,15 @@ it("forms one aligned compact toolbar on wide screens and adapts by container wi expect(Math.abs(centerY(chapter) - centerY(entity))).toBeLessThan(3); expect(Math.abs(centerY(chapter) - centerY(play))).toBeLessThan(3); expect(Math.abs(centerY(chapter) - centerY(timeline))).toBeLessThan(3); - expect(bottom.getBoundingClientRect().height).toBeLessThan(55); + const toolbarBounds = bottom.getBoundingClientRect(); + expect(toolbarBounds.height).toBeLessThan(55); + // A compact bar must still enclose native form controls in every engine. + for (const control of bottom.querySelectorAll("button, select, input")) { + const rect = control.getBoundingClientRect(); + const name = control.getAttribute("aria-label") ?? control.tagName; + expect(rect.top, name + " escapes toolbar top").toBeGreaterThanOrEqual(toolbarBounds.top - 1); + expect(rect.bottom, name + " escapes toolbar bottom").toBeLessThanOrEqual(toolbarBounds.bottom + 1); + } expect(zoom.getBoundingClientRect().width).toBe(34); expect(getComputedStyle(zoom.querySelector("svg")!).width).toBe("14px"); expect(chapter.getBoundingClientRect().height).toBe(34); From 498c4d9801a4dd075d73a514d06c24bd544ca6e0 Mon Sep 17 00:00:00 2001 From: "Christopher S. Aondona" Date: Thu, 12 Nov 2026 13:18:53 +0100 Subject: [PATCH 3/6] docs: record cross-browser toolbar sizing correction --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fb614f8..31f8369 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ Semantic Versioning independently of the version-1 trace and program schemas. ## Unreleased +### Fixed (cross-browser toolbar) + +- Keep the wide playback toolbar compact in WebKit without constraining native selector height; verify the compact bar still encloses every control across browser engines. + ### Fixed (narration review) - Coordinate browser-global speech synthesis between multiple Throughline players; newer narration takes over while displaced players pause and stop owning cancellation. From 509901c1f89567cc97b181c121c05bb5ccb15a8f Mon Sep 17 00:00:00 2001 From: "Christopher S. Aondona" Date: Thu, 12 Nov 2026 13:19:07 +0100 Subject: [PATCH 4/6] test(browser): allow full regression suite to target Firefox and WebKit --- vitest.browser.config.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/vitest.browser.config.ts b/vitest.browser.config.ts index c1e468d..ad34f2e 100644 --- a/vitest.browser.config.ts +++ b/vitest.browser.config.ts @@ -1,6 +1,14 @@ import { playwright } from "@vitest/browser-playwright"; import { defineConfig } from "vitest/config"; +// Main quality CI uses Chromium. The separate release QA workflow exercises +// the identical browser suite in Firefox and WebKit without changing defaults. +const requestedBrowser = process.env.THROUGHLINE_QA_BROWSER ?? "chromium"; +if (!["chromium", "firefox", "webkit"].includes(requestedBrowser)) { + throw new Error("Unsupported Throughline QA browser: " + requestedBrowser); +} +const browser = requestedBrowser as "chromium" | "firefox" | "webkit"; + export default defineConfig({ test: { css: true, @@ -9,7 +17,7 @@ export default defineConfig({ browser: { enabled: true, provider: playwright(), - instances: [{ browser: "chromium" }], + instances: [{ browser }], }, }, }); From 8a8c892eaaf33b6d41d704dce7811ef3378a6744 Mon Sep 17 00:00:00 2001 From: "Christopher S. Aondona" Date: Thu, 12 Nov 2026 13:19:09 +0100 Subject: [PATCH 5/6] ci(qa): verify toolbar regression in Firefox and WebKit --- .github/workflows/release-qa-browsers.yml | 45 +++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 .github/workflows/release-qa-browsers.yml diff --git a/.github/workflows/release-qa-browsers.yml b/.github/workflows/release-qa-browsers.yml new file mode 100644 index 0000000..4b35dd9 --- /dev/null +++ b/.github/workflows/release-qa-browsers.yml @@ -0,0 +1,45 @@ +name: Throughline release QA browsers + +# Additional, read-only release-candidate verification. The regular CI +# retains Chromium and the distributable/consumer checks. +on: + pull_request: + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: throughline-release-browser-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + cross-browser: + name: Browser regressions (${{ matrix.browser }}) + runs-on: ubuntu-latest + timeout-minutes: 30 + strategy: + fail-fast: false + matrix: + browser: [firefox, webkit] + steps: + - name: Checkout source + uses: actions/checkout@v6 + - name: Setup pnpm + uses: pnpm/action-setup@v6 + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version: 24 + cache: pnpm + cache-dependency-path: pnpm-lock.yaml + - name: Install locked dependencies + run: pnpm install --frozen-lockfile + - name: Verify lockfile unchanged + run: git diff --exit-code -- pnpm-lock.yaml + - name: Install tested browser and system dependencies + run: pnpm exec playwright install --with-deps ${{ matrix.browser }} + - name: Run the complete browser regression suite + env: + THROUGHLINE_QA_BROWSER: ${{ matrix.browser }} + run: pnpm test:browser:run From 4392eaa806a4b031013c667adaf71fb7630917ed Mon Sep 17 00:00:00 2001 From: "Christopher S. Aondona" Date: Thu, 12 Nov 2026 13:22:17 +0100 Subject: [PATCH 6/6] test(ui): allow measured native select height while enforcing compact bounds --- src/react/__tests__/toolbar.browser.test.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/react/__tests__/toolbar.browser.test.tsx b/src/react/__tests__/toolbar.browser.test.tsx index 752b1f9..20d0140 100644 --- a/src/react/__tests__/toolbar.browser.test.tsx +++ b/src/react/__tests__/toolbar.browser.test.tsx @@ -38,7 +38,12 @@ it("forms one aligned compact toolbar on wide screens and adapts by container wi } expect(zoom.getBoundingClientRect().width).toBe(34); expect(getComputedStyle(zoom.querySelector("svg")!).width).toBe("14px"); - expect(chapter.getBoundingClientRect().height).toBe(34); + // Native select widgets are 34px in Chromium/Firefox and 42px in WebKit. + // Constrain the allowable size while the toolbar-height and bounds checks + // above enforce the actual compact, unclipped layout contract. + const chapterHeight = chapter.getBoundingClientRect().height; + expect(chapterHeight).toBeGreaterThanOrEqual(34); + expect(chapterHeight).toBeLessThanOrEqual(42); expect(getComputedStyle(chapter).borderTopLeftRadius).toBe("2px"); expect(getComputedStyle(chapter).paddingLeft).toBe("8px");