diff --git a/.github/workflows/astro.yml b/.github/workflows/astro.yml index a022833..f3b19f6 100644 --- a/.github/workflows/astro.yml +++ b/.github/workflows/astro.yml @@ -62,6 +62,29 @@ jobs: with: path: ${{ env.BUILD_PATH }}/dist + lighthouse: + name: Lighthouse CI + runs-on: ubuntu-latest + needs: build + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: "22" + cache: npm + cache-dependency-path: package-lock.json + - name: Install dependencies + run: npm ci + working-directory: ${{ env.BUILD_PATH }} + - name: Build with Astro + run: npx astro build + working-directory: ${{ env.BUILD_PATH }} + - name: Run Lighthouse CI + run: npx @lhci/cli@0.14.x autorun + working-directory: ${{ env.BUILD_PATH }} + deploy: environment: name: github-pages diff --git a/.lighthouserc.json b/.lighthouserc.json new file mode 100644 index 0000000..94c5d2d --- /dev/null +++ b/.lighthouserc.json @@ -0,0 +1,19 @@ +{ + "ci": { + "collect": { + "staticDistDir": "./dist", + "numberOfRuns": 1 + }, + "assert": { + "assertions": { + "categories:performance": ["warn", { "minScore": 0.8 }], + "categories:accessibility": ["error", { "minScore": 0.9 }], + "categories:best-practices": ["warn", { "minScore": 0.9 }], + "categories:seo": ["warn", { "minScore": 0.9 }] + } + }, + "upload": { + "target": "temporary-public-storage" + } + } +} diff --git a/docs/backlog.md b/docs/backlog.md index ca3887c..ad64947 100644 --- a/docs/backlog.md +++ b/docs/backlog.md @@ -11,21 +11,6 @@ construction" placeholders. The old AI-generated versions have been archived at `src/pages/ai-generated/old-entry-point.astro` and `src/pages/ai-generated/old-hello.astro`. -## Low Priority - -### Type external CDN scripts - -`_score.ts` uses `@ts-expect-error` for a CDN ESM import of the WaveSurfer -regions plugin. Consider bundling these dependencies or adding typed wrappers. - -- File: `src/pages/ai-generated/do-olls-that-will-talk/_score.ts` - -### Add Lighthouse CI to GitHub Actions - -No performance budget exists. Lighthouse CI in the workflow would catch regressions. - -- File: `.github/workflows/astro.yml` - ## Improvements ### Consider bundling WaveSurfer and YouTube API @@ -46,3 +31,9 @@ Loading these from CDN introduces external dependencies. Bundling via npm would - **Inline trivial npm script wrappers** — justfile recipes already call tools directly (`eslint`, `tsc`, `astro`, `prettier`, `vitest`); only `precommit` and `prepush` delegate to npm because they run multi-step pipelines. +- **Type external CDN scripts** — added ambient module declaration + `_regions.d.ts` for the WaveSurfer RegionsPlugin CDN URL; removed + `@ts-expect-error` and inline casts from `_score.ts`. +- **Add Lighthouse CI to GitHub Actions** — added `lighthouse` job to + `astro.yml` (runs after build, uses `@lhci/cli`); added `.lighthouserc.json` + with accessibility error threshold and performance/SEO/best-practices warnings. diff --git a/src/pages/ai-generated/do-olls-that-will-talk/_regions.d.ts b/src/pages/ai-generated/do-olls-that-will-talk/_regions.d.ts new file mode 100644 index 0000000..14a4c92 --- /dev/null +++ b/src/pages/ai-generated/do-olls-that-will-talk/_regions.d.ts @@ -0,0 +1,32 @@ +// Typed ambient declaration for the WaveSurfer RegionsPlugin CDN ESM build. +// This removes the @ts-expect-error on the CDN import in _score.ts. + +declare module "https://cdn.jsdelivr.net/npm/wavesurfer.js@7/dist/plugins/regions.esm.js" { + export interface RegionParams { + start: number; + end: number; + color?: string; + content?: string; + resize?: boolean; + drag?: boolean; + id?: string; + } + + export interface Region { + start: number; + } + + export interface RegionsPluginInstance { + addRegion(params: RegionParams): Region; + on( + event: "region-clicked", + fn: (region: Region, event: { stopPropagation(): void }) => void + ): void; + } + + const RegionsPlugin: { + create(): RegionsPluginInstance; + }; + + export default RegionsPlugin; +} diff --git a/src/pages/ai-generated/do-olls-that-will-talk/_score.ts b/src/pages/ai-generated/do-olls-that-will-talk/_score.ts index 326faf0..cab4eef 100644 --- a/src/pages/ai-generated/do-olls-that-will-talk/_score.ts +++ b/src/pages/ai-generated/do-olls-that-will-talk/_score.ts @@ -1,7 +1,7 @@ // File: _score.ts -// @ts-expect-error - CDN ESM import without type declarations import RegionsPlugin from "https://cdn.jsdelivr.net/npm/wavesurfer.js@7/dist/plugins/regions.esm.js"; +import type { RegionsPluginInstance } from "https://cdn.jsdelivr.net/npm/wavesurfer.js@7/dist/plugins/regions.esm.js"; declare global { interface Window { @@ -167,14 +167,13 @@ if (!isBrowser()) { url: "/audio/do-olls-clip.mp4", }); - const regions = ws.registerPlugin(RegionsPlugin.create()); + const regions = ws.registerPlugin( + RegionsPlugin.create() + ) as RegionsPluginInstance; ws.on("ready", () => { markers.forEach((m, i) => { - // regions is untyped (CDN plugin), so we go through unknown. - ( - regions as { addRegion: (cfg: Record) => void } - ).addRegion({ + regions.addRegion({ start: m.time, end: m.time + 0.05, color: categoryColor[m.category], @@ -202,14 +201,7 @@ if (!isBrowser()) { if (playPause) playPause.textContent = "Play"; }); - ( - regions as { - on: ( - evt: string, - fn: (r: { start: number }, e: { stopPropagation: () => void }) => void - ) => void; - } - ).on("region-clicked", (r, e) => { + regions.on("region-clicked", (r, e) => { e.stopPropagation(); ws.setTime(r.start); });