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 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. diff --git a/src/react/__tests__/toolbar.browser.test.tsx b/src/react/__tests__/toolbar.browser.test.tsx index 83791ee..20d0140 100644 --- a/src/react/__tests__/toolbar.browser.test.tsx +++ b/src/react/__tests__/toolbar.browser.test.tsx @@ -27,10 +27,23 @@ 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); + // 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"); 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); } 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 }], }, }, });