diff --git a/.github/workflows/lighthouse.yml b/.github/workflows/lighthouse.yml index fe5233b..6b40e82 100644 --- a/.github/workflows/lighthouse.yml +++ b/.github/workflows/lighthouse.yml @@ -31,6 +31,13 @@ jobs: - run: pnpm build - run: pnpm check:events + - name: Identify audit browser + id: browser + run: | + chrome_path="$(command -v google-chrome)" + echo "CHROME_PATH=$chrome_path" >>"$GITHUB_ENV" + echo "version=$("$chrome_path" --version | sha256sum | cut -d ' ' -f1)" >>"$GITHUB_OUTPUT" + # The build is deterministic, so an identical `dist/` measured with the same server, config, # and lhci version has already passed; only the marker file is cached. - name: Look up a previous Lighthouse pass @@ -38,7 +45,7 @@ jobs: uses: actions/cache/restore@v6 with: path: .lighthouseci/passed - key: lighthouse-${{ env.LHCI_VERSION }}-${{ hashFiles('dist/**', '.lighthouseci/events/**', 'tools/ci/**') }} + key: lighthouse-${{ runner.os }}-${{ runner.arch }}-${{ steps.browser.outputs.version }}-${{ env.LHCI_VERSION }}-${{ hashFiles('dist/**', '.lighthouseci/events/**', 'tools/ci/**', '.github/workflows/lighthouse.yml') }} lookup-only: true - name: Lighthouse CI diff --git a/docs/tooling.md b/docs/tooling.md index 533bd33..295f5f9 100644 --- a/docs/tooling.md +++ b/docs/tooling.md @@ -186,7 +186,10 @@ and stores it in `.lighthouseci/events/`. The audit server exposes those pages u `/__event-fixture/`; production `dist/` and source dates stay unchanged. The job summary and the log carry the scores per URL, every failed assertion, and for each URL the LCP element, its phases, and the request waterfall; full reports upload as an artifact. A `dist/` byte-identical -to one that already passed skips Lighthouse. +to one that already passed skips Lighthouse. SVG definition IDs use a counter scoped to each +page render so repeated builds preserve the same output bytes; random IDs invalidate this cache. +The cache also includes Chrome’s version, the runner OS/architecture, and the workflow. The +audit uses the installed Chrome on `ubuntu-latest`, with that exact binary passed to Lighthouse. ### Performance budgets diff --git a/src/components/ui/primitives/ChalkOval.astro b/src/components/ui/primitives/ChalkOval.astro index 0c0e592..fe5bc2b 100644 --- a/src/components/ui/primitives/ChalkOval.astro +++ b/src/components/ui/primitives/ChalkOval.astro @@ -1,11 +1,12 @@ --- import { cn } from "@/lib/cn"; import { type ChalkOvalProps, handLinkShadeClass, handMarkClass, handToneClass } from "@/lib/hand"; +import { nextSvgId } from "@/lib/svg-id"; type Props = ChalkOvalProps; const { variant = 1, tone = "marker", around = "word", class: className } = Astro.props; -const maskId = `oval-${crypto.randomUUID()}`; +const maskId = nextSvgId(Astro.locals); /** The inline margin matches the ring's overhang so neighbours clear it. */ const overhang = { diff --git a/src/lib/svg-id.ts b/src/lib/svg-id.ts new file mode 100644 index 0000000..5866ad5 --- /dev/null +++ b/src/lib/svg-id.ts @@ -0,0 +1,10 @@ +import type { AstroGlobal } from "astro"; + +const sequences = new WeakMap(); + +/** Astro.locals scopes the sequence to a page render, including its nested components. */ +export const nextSvgId = (locals: AstroGlobal["locals"]): string => { + const next = (sequences.get(locals) ?? 0) + 1; + sequences.set(locals, next); + return `svg-${String(next)}`; +};