diff --git a/.github/actions/start-preview-server/action.yml b/.github/actions/start-preview-server/action.yml index 401230f..6e27f1c 100644 --- a/.github/actions/start-preview-server/action.yml +++ b/.github/actions/start-preview-server/action.yml @@ -34,7 +34,9 @@ runs: shell: bash env: PORT: ${{ inputs.port }} - run: bun run docs:preview -- --port "$PORT" & + # Redirect output: a background process holding the step's stdout open + # makes the runner wait ~5 s before moving on. + run: bun run docs:preview -- --port "$PORT" > "$RUNNER_TEMP/vitepress-preview.log" 2>&1 & - name: Wait for preview server to be ready shell: bash @@ -69,9 +71,9 @@ runs: until curl -sf "${URL}" > /dev/null 2>&1; do if [ "$(date +%s)" -ge "${DEADLINE}" ]; then echo "::error::Preview server did not respond within ${TIMEOUT} s on ${URL}." + cat "$RUNNER_TEMP/vitepress-preview.log" || true exit 1 fi - echo "… retrying in 2 s" - sleep 2 + sleep 0.5 done echo "Preview server is ready." diff --git a/.github/instructions/documentation.instructions.md b/.github/instructions/documentation.instructions.md index f66a516..3b8f7e2 100644 --- a/.github/instructions/documentation.instructions.md +++ b/.github/instructions/documentation.instructions.md @@ -107,8 +107,18 @@ If the PR modifies any Vue component, CSS, or page layout, also run the accessib ```bash bun run docs:build:a11y bun run docs:preview -- --port 4173 & -bun run docs:a11y # pa11y-ci WCAG 2.1 AA — must report 0 errors +bun run docs:a11y # axe-core WCAG 2.2 AA — must report 0 automated violations +bun run docs:test:a11y # audit failure paths and theme semantics bun run docs:test:responsive # Playwright — 20/20 tests green (4 viewports × 5 pages) ``` -See `.github/skills/documentation.md` for pa11y configuration details, WCAG contrast rules, accessible component patterns, and responsive breakpoint guidance. +Use local Playwright Chromium (`bun run playwright install chromium`) or set +`PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH` to an installed Chrome executable. +CI reuses system Chrome. Inspect both audit reports, including incomplete +findings requiring manual review. Do not disable contrast/ARIA rules or treat +passing automation as WCAG certification. + +See `.github/skills/documentation.md` for the axe runner, WCAG contrast rules, +accessible component patterns and responsive guidance, and `CONTRIBUTING.md` for +the manual-review checklist. The audit covers the documentation site only, so do +not document it in the product docs (`docs/`); the benchmark lives in issue #241. diff --git a/.github/instructions/github-actions.instructions.md b/.github/instructions/github-actions.instructions.md index 02a3d4f..eb54938 100644 --- a/.github/instructions/github-actions.instructions.md +++ b/.github/instructions/github-actions.instructions.md @@ -142,6 +142,9 @@ grep -rn "uses:" .github/workflows/ | grep -v "@[a-f0-9]\{40\}" Also verify: +- [ ] Accessibility uses `@axe-core/playwright` with system Chrome, scans the full sitemap, + preserves failing exit codes, and publishes JSON/Markdown reports without requiring + write permissions on fork PRs (see `.github/skills/github-actions.md`). - [ ] All `.yml` workflow files have been renamed to `.yaml` - [ ] Every new third-party action has a `# vX.Y.Z` comment after its SHA - [ ] New reusable composite actions are under `.github/actions//action.yml` diff --git a/.github/skills/documentation.md b/.github/skills/documentation.md index e6affb1..7383793 100644 --- a/.github/skills/documentation.md +++ b/.github/skills/documentation.md @@ -53,19 +53,48 @@ Dark mode: VitePress applies a `.dark` class on ``. Use `.dark .selector { --- -## Accessibility (WCAG 2.1 AA) +## Accessibility (WCAG 2.2 AA automated checks) -This project maintains **zero pa11y-ci violations** at WCAG 2.1 AA level. +This project requires **zero axe-core automated violations** using WCAG 2.0, +2.1 and 2.2 A/AA tags. A passing automated scan is not WCAG certification. ### Tool ```bash bun run docs:build:a11y # build with VITEPRESS_HOSTNAME=http://localhost:4173 bun run docs:preview -- --port 4173 & -bun run docs:a11y # pa11y-ci via sitemap — must report 0 errors +bun run docs:a11y # axe-core via every sitemap URL — must report 0 violations +bun run docs:test:a11y # passing/failing fixtures and theme regression tests ``` -Config: `.pa11yci.json`. F77 (Mermaid duplicate SVG IDs) is ignored — not blocking for AT users. +Runner: `scripts/a11y.ts`; pure helpers/tests: `scripts/a11y-utils.ts` and its +companion test; browser regression tests: `scripts/a11y.pw.ts`. The official +`@axe-core/playwright` adapter reuses Playwright. One browser, up to four workers +(one per CPU) each reusing a browser context, no fixed sleep. Wait for hydrated +VitePress, rendered visible Mermaid diagrams and fonts before scanning. Keep the +Mermaid renderer lazily imported by the theme and the Vue runtime in the `framework` +chunk so pages without diagrams never download Mermaid. All sitemap URLs must stay within the +preview origin/base; empty, malformed or unreachable sitemaps must fail. + +Install local Chromium with `bun run playwright install chromium`, or set +`PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH` to an installed Chrome. CI uses system +Chrome, with no browser download. `PLAYWRIGHT_BASE_URL` overrides the preview +origin and `VITEPRESS_BASE` overrides the base path for both build and audit. + +Reports: `a11y-report.json` (full violations, incomplete rules and errors) and +`a11y-report.md` (per-page summary). Violations and operational errors fail; +incomplete results require manual review. There are no blanket rule exclusions. +Do not translate obsolete HTML_CodeSniffer F77/contrast suppressions into axe +disables. Check duplicate ARIA references and Mermaid semantics explicitly. + +DocSearch derives its accessible name from visible content, including the +shortcut. Its mobile label is visually hidden, not `display:none`. Mermaid +wrappers with `aria-label` receive `role="group"` via a scoped observer so +asynchronously rendered diagrams and client-side navigation stay valid. + +The audit only covers the documentation site, not the product: keep it out of the +product docs (`docs/`). The manual-review checklist is in `CONTRIBUTING.md`; the +sourced alternatives benchmark and engine-coverage differences are in issue #241. ### Common patterns @@ -156,7 +185,8 @@ Before opening a PR for any docs change: bun run docs:build # must complete without errors or dead-link warnings bun run docs:build:a11y # build for a11y audit bun run docs:preview -- --port 4173 & -bun run docs:a11y # 0 pa11y violations +bun run docs:a11y # 0 axe-core automated violations +bun run docs:test:a11y # audit harness and accessible theme semantics bun run docs:test:responsive # 20/20 Playwright tests green bun run format:check # bun fmt --check — no formatting diff ``` diff --git a/.github/skills/feature.md b/.github/skills/feature.md index 824d00d..c65c87f 100644 --- a/.github/skills/feature.md +++ b/.github/skills/feature.md @@ -133,6 +133,7 @@ If the feature touches `docs/`: bun run docs:build bun run docs:build:a11y bun run docs:preview -- --port 4173 & -bun run docs:a11y # 0 pa11y violations +bun run docs:a11y # 0 axe-core WCAG 2.2 AA automated violations +bun run docs:test:a11y # audit harness and theme semantics bun run docs:test:responsive # 20/20 Playwright tests green ``` diff --git a/.github/skills/github-actions.md b/.github/skills/github-actions.md index 0a083c6..4ab4736 100644 --- a/.github/skills/github-actions.md +++ b/.github/skills/github-actions.md @@ -16,7 +16,7 @@ repository. Read this before writing or modifying any `.github/workflows/*.yaml` ci.yaml ← unit tests, native format/type checks, knip + coverage PR comment cd.yaml ← binary build + GitHub Release on tag push docs.yaml ← VitePress Pages deploy + versioned snapshot - a11y.yaml ← WCAG 2.1 AA audit via pa11y-ci + a11y.yaml ← WCAG 2.2 AA automated audit via axe-core + Playwright lighthouse.yaml ← Lighthouse CI eco-design budgets + PR comment responsive.yaml ← Playwright mobile viewport checks ``` @@ -155,7 +155,7 @@ should post a **single sticky comment** per PR, updated on every push: if: github.event_name == 'pull_request' uses: marocchino/sticky-pull-request-comment@5770ad5eb8f42dd2c4f34da00c94c5381e49af88 # v3.0.5 with: - header: # e.g. "lighthouse-ci", "coverage", "pa11y" + header: # e.g. "lighthouse-ci", "coverage", "accessibility-audit" message: ${{ steps.report.outputs.result }} ``` @@ -223,6 +223,35 @@ the Pages site in a broken state; use a more specific group key instead. ## Chrome / Puppeteer on Ubuntu runners +The accessibility workflow uses `@axe-core/playwright` with the already installed +Playwright stack and system Chrome: + +```yaml +env: + PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH: /usr/bin/google-chrome-stable +``` + +Do not install/download another browser in this job. The audit-harness fixture +tests (`docs:test:a11y --grep "audit harness"`) need no docs build and run in a +parallel `harness` job. Once the preview starts, the `audit` job runs the +theme-semantics tests (`--grep-invert "audit harness"`) in the background while +`docs:a11y` runs, then checks their exit code in a later step. Composite actions +that start background servers must redirect their output, otherwise the runner +waits ~5 s for the step's stdout to close. The +audit scans the entire sitemap with up to four workers and explicit WCAG +2.0/2.1/2.2 A/AA tags. Its path filters must include +`scripts/a11y*.ts`, `playwright.config.ts`, `package.json`, `bun.lock` and the shared +preview action as well as docs and the workflow itself. + +Always upload `a11y-report.json` and `a11y-report.md`, publish the Markdown to +`GITHUB_STEP_SUMMARY`, and post the `accessibility-audit` sticky comment only +for same-repository PRs not triggered by `dependabot[bot]` (fork and Dependabot +tokens are read-only and cannot write comments). Preserve a failing +job after reporting violations or operational errors; do not mask scan failures +with `continue-on-error` alone. Incomplete results are manual-review findings, +not proof of compliance. + +Lighthouse still uses Puppeteer-core; do not remove its transitive dependencies. Ubuntu `ubuntu-latest` ships `google-chrome-stable`. Skip the ~200 MB Puppeteer Chromium download and use the system binary: diff --git a/.github/skills/release.md b/.github/skills/release.md index 15e0329..7c3ae9f 100644 --- a/.github/skills/release.md +++ b/.github/skills/release.md @@ -169,6 +169,7 @@ bun run build.ts # binary compiles for current platform bun run docs:build bun run docs:build:a11y bun run docs:preview -- --port 4173 & -bun run docs:a11y # 0 pa11y violations +bun run docs:a11y # 0 axe-core WCAG 2.2 AA automated violations +bun run docs:test:a11y # audit harness and theme semantics bun run docs:test:responsive # 20/20 Playwright tests green ``` diff --git a/.github/workflows/a11y.yaml b/.github/workflows/a11y.yaml index 89b4430..d60220a 100644 --- a/.github/workflows/a11y.yaml +++ b/.github/workflows/a11y.yaml @@ -1,24 +1,26 @@ -# Accessibility audit — WCAG 2.1 AA +# Accessibility audit — axe-core / WCAG 2.2 AA # -# Runs pa11y-ci against the built VitePress docs to ensure the homepage and -# key pages remain WCAG 2.1 AA compliant. +# Audits every built VitePress sitemap page with axe-core's WCAG A/AA rules. +# Automated findings complement, but never replace, manual accessibility review. # # Triggers: -# - push to main touching docs/** or this workflow / config # - every pull_request touching the same paths # - workflow_dispatch (manual run) # # Strategy: # 1. Build the docs with `bun run docs:build:a11y` (sets VITEPRESS_HOSTNAME= # http://localhost:4173 so the generated sitemap.xml contains localhost -# URLs that pa11y-ci can reach directly) +# URLs that the audit can reach directly) # 2. Start `vitepress preview` via the shared composite action -# 3. Run pa11y-ci configured in .pa11yci.json (WCAG 2.1 AA, errors only) +# 3. Scan the full sitemap with up to four workers (one per CPU, each reusing +# a single browser context) while the theme-semantics tests run alongside +# 4. Publish JSON/Markdown artifacts and a sticky same-repository PR summary # -# Chrome: Ubuntu-latest ships google-chrome-stable. We skip Puppeteer's own -# Chromium download (PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=1) and point directly to -# the system Chrome via PUPPETEER_EXECUTABLE_PATH at runtime (~200 MB saved). -name: Accessibility audit (WCAG 2.1 AA) +# The audit-harness fixture tests need no docs build, so they run in a parallel +# `harness` job instead of extending the critical path of the audit job. +# +# Chrome: Playwright reuses Ubuntu's system Chrome; no browser download. +name: Accessibility audit (WCAG 2.2 AA) on: pull_request: @@ -28,7 +30,11 @@ on: - "package.json" - "bun.lock" - ".github/workflows/a11y.yaml" - - ".pa11yci.json" + - ".github/actions/start-preview-server/**" + - "scripts/a11y*.ts" + - "playwright.config.ts" + - "package.json" + - "bun.lock" workflow_dispatch: # One audit at a time per branch; cancel stale runs on new push. @@ -40,9 +46,34 @@ permissions: contents: read jobs: + harness: + name: Audit harness (fixtures) + runs-on: ubuntu-latest + timeout-minutes: 5 + env: + PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH: /usr/bin/google-chrome-stable + + steps: + - name: Checkout + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + + - name: Setup Bun + uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 + with: + bun-version-file: .bun-version + + - name: Install dependencies + run: bun install --frozen-lockfile + + - name: Test accessibility audit harness + run: bun run docs:test:a11y --grep "audit harness" --reporter=dot + audit: - name: WCAG 2.1 AA pages audit + name: WCAG 2.2 AA pages audit runs-on: ubuntu-latest + timeout-minutes: 10 + env: + PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH: /usr/bin/google-chrome-stable permissions: contents: read pull-requests: write @@ -66,20 +97,53 @@ jobs: - name: Start VitePress preview server uses: ./.github/actions/start-preview-server - # Run the audit. The env vars tell Puppeteer (used by pa11y) to use the - # pre-installed system Chrome instead of downloading a Chromium binary. - - name: Run accessibility audit (pa11y-ci) - env: - PUPPETEER_EXECUTABLE_PATH: /usr/bin/google-chrome-stable - PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: "1" + # Theme-semantics tests run concurrently with the audit (same preview); + # their result is checked after the audit step. + - name: Start theme accessibility semantics tests + id: theme + run: | + ( + rc=0 + bun run docs:test:a11y --grep-invert "audit harness" --reporter=dot || rc=$? + echo "$rc" > "$RUNNER_TEMP/theme-tests.exit" + ) > "$RUNNER_TEMP/theme-tests.log" 2>&1 & + + - name: Run accessibility audit (axe-core) + id: audit + continue-on-error: true run: bun run docs:a11y - # Upload the pa11y-ci JSON report as an artifact so failures are - # easy to inspect without re-running the workflow. + - name: Check theme accessibility semantics tests + if: ${{ !cancelled() && steps.theme.outcome == 'success' }} + timeout-minutes: 3 + run: | + until [ -f "$RUNNER_TEMP/theme-tests.exit" ]; do sleep 0.5; done + cat "$RUNNER_TEMP/theme-tests.log" + exit "$(cat "$RUNNER_TEMP/theme-tests.exit")" + + - name: Publish job summary + if: ${{ !cancelled() && steps.audit.outcome != 'skipped' }} + run: cat a11y-report.md >> "$GITHUB_STEP_SUMMARY" + + - name: Post / update accessibility report on PR + if: ${{ !cancelled() && github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository && github.actor != 'dependabot[bot]' && steps.audit.outcome != 'skipped' }} + uses: marocchino/sticky-pull-request-comment@5770ad5eb8f42dd2c4f34da00c94c5381e49af88 # v3.0.5 + with: + header: accessibility-audit + path: a11y-report.md + + - name: Fail if accessibility audit failed + if: ${{ !cancelled() && steps.audit.outcome == 'failure' }} + run: | + echo "::error::Accessibility audit failed — see the report for violations or operational errors" + exit 1 + - name: Upload audit report if: always() uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 with: - name: pa11y-ci-report - path: a11y-report.json + name: a11y-report + path: | + a11y-report.json + a11y-report.md if-no-files-found: ignore diff --git a/.gitignore b/.gitignore index d173354..9547e0c 100644 --- a/.gitignore +++ b/.gitignore @@ -4,8 +4,9 @@ coverage/ .env *.local -# pa11y accessibility testing results +# Accessibility audit reports a11y-report.json +a11y-report.md # Lighthouse CI .lighthouseci/ diff --git a/.pa11yci.json b/.pa11yci.json deleted file mode 100644 index 2c96b54..0000000 --- a/.pa11yci.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "defaults": { - "standard": "WCAG2AA", - "reporters": ["cli", ["json", { "fileName": "./a11y-report.json" }]], - "level": "error", - "wait": 1500, - "timeout": 60000, - "chromeLaunchConfig": { - "args": ["--no-sandbox", "--disable-setuid-sandbox", "--disable-dev-shm-usage"] - }, - "ignore": [ - "WCAG2AA.Principle1.Guideline1_4.1_4_3.G18.BgImage", - "WCAG2AA.Principle1.Guideline1_4.1_4_3.G145.BgImage", - "WCAG2AA.Principle4.Guideline4_1.4_1_1.F77" - ] - } -} diff --git a/AGENTS.md b/AGENTS.md index cf607a2..3fa85c8 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -71,6 +71,36 @@ installation/contributor documentation consistent. Compiled binaries do not need No repository custom agent definitions are currently shipped; any future agents must use the same native tooling and validation commands. +## Documentation accessibility + +The docs audit uses `@axe-core/playwright` and the existing Playwright dependency. +Run `bun run docs:build:a11y`, start `bun run docs:preview -- --port 4173` in another +terminal, then run `bun run docs:a11y` and `bun run docs:test:a11y`. +Install local Chromium with `bun run playwright install chromium`, or point +`PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH` at an installed Chrome executable. +CI uses system Chrome without downloading another browser. + +`scripts/a11y.ts` scans every sitemap URL with up to four workers (one per CPU), each +reusing one browser context, explicit WCAG 2.0/2.1/2.2 A/AA tags, and readiness +checks for Vue hydration, visible Mermaid diagrams and fonts. Call axe's `options()` +before `withTags()`: `options()` replaces the `runOnly` tag filter. +The theme lazy-loads the Mermaid renderer only on pages with a diagram, and the +custom `manualChunks` in `docs/.vitepress/config.mts` must keep the Vue runtime and +Vite helpers in the `framework` chunk; otherwise every page loads ~5 MB of Mermaid. +Pure URL validation and Markdown formatting live in `scripts/a11y-utils.ts`. +The generated `a11y-report.json` includes full axe results and operational +errors; `a11y-report.md` is used for the job summary and sticky PR comment. +Violations and operational failures must fail CI. Incomplete results require +manual review, not an automatic compliance claim. Never disable contrast/ARIA +rules or carry over HTML_CodeSniffer exclusions as a shortcut. + +Read `.github/skills/documentation.md` for accessible theme patterns and +`.github/skills/github-actions.md` before changing the workflow. The audit only +guarantees the accessibility of the documentation site, not of the CLI, so it is +documented for contributors in `CONTRIBUTING.md` (manual-review checklist), not in +the product docs. The alternatives benchmark is in issue #241. Repository-local custom agents are not +currently defined; these shared instructions are the agent entry point. + ## Project layout ``` diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5a4a67c..1fc6ac6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -128,10 +128,28 @@ If your PR touches docs (`docs/**`), also verify: bun run docs:build # no dead links, no build errors bun run docs:build:a11y bun run docs:preview -- --port 4173 & -bun run docs:a11y # 0 pa11y WCAG 2.1 AA violations +bun run docs:a11y # 0 axe-core WCAG 2.2 AA automated violations +bun run docs:test:a11y # audit failure-path fixtures and theme semantics bun run docs:test:responsive # 20/20 Playwright responsive tests green ``` +Install the local test browser once with `bun run playwright install chromium`, +or set `PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH` to an existing Chrome executable. +The accessibility workflow uses `/usr/bin/google-chrome-stable` on Ubuntu, with +no browser download. `PLAYWRIGHT_BASE_URL` overrides the preview origin; +`VITEPRESS_BASE` selects the documentation base path, including versioned builds. +The build and audit must use the same base path. + +Inspect `a11y-report.json` for affected nodes, rule help links, scan errors and +incomplete findings; `a11y-report.md` summarizes every page. Any violation or +operational error fails the command. Do not copy obsolete HTML_CodeSniffer +exceptions into axe or disable rules to make CI pass. Manual keyboard, +screen-reader and interaction checks remain necessary: review `incomplete` results, +keyboard navigation, focus order, screen-reader announcements, zoom/reflow, both +color schemes and interactive states (search dialog, tabs, expanded menus). The audit +only checks the initial desktop state of each page. The alternatives benchmark that +led to `@axe-core/playwright` is in [#241](https://github.com/fulll/github-code-search/issues/241). + ## AI agent tooling This project ships a two-layer system to guide AI coding agents (GitHub Copilot, Claude, etc.): @@ -154,13 +172,14 @@ Each file covers **one task type** — ordered numbered steps, PR conventions, v Deep domain-knowledge files — **read these for reference**, not for step-by-step guidance. Skills provide patterns, checklists, architectural details and examples that are too large for instruction files. -| File | Domain | -| ------------------ | ---------------------------------------------------------------------- | -| `bug-fixing.md` | Extended symptom→module table, test-first patterns, minimal fix rules | -| `feature.md` | Layer map, type-first design, CLI options, render sub-module extension | -| `refactoring.md` | Architectural invariants, safe rename playbook, knip output guide | -| `release.md` | Semver guide, CD pipeline mechanics, blog post format, CHANGELOG rules | -| `documentation.md` | VitePress theme API, CSS variables, WCAG patterns, responsive patterns | +| File | Domain | +| ------------------- | ---------------------------------------------------------------------- | +| `bug-fixing.md` | Extended symptom→module table, test-first patterns, minimal fix rules | +| `feature.md` | Layer map, type-first design, CLI options, render sub-module extension | +| `refactoring.md` | Architectural invariants, safe rename playbook, knip output guide | +| `release.md` | Semver guide, CD pipeline mechanics, blog post format, CHANGELOG rules | +| `documentation.md` | VitePress theme API, CSS variables, WCAG patterns, responsive patterns | +| `github-actions.md` | CI workflows, pinned actions, browser reuse and accessibility reports | **How they fit together:** instruction files say _what steps to follow_; skill files say _what to know deeply_. Instructions reference their companion skill via a `> Skill reference:` note at the top. diff --git a/README.md b/README.md index 18b9ee8..ddd57bf 100644 --- a/README.md +++ b/README.md @@ -131,3 +131,23 @@ The official [`gh` CLI](https://cli.github.com/) does support `gh search code`, | Pagination (up to 1 000 results) | ✓ | ✓ | `github-code-search` is purpose-built for **org-wide code audits and interactive triage** — not just a search wrapper. + +## Contributing and documentation quality + +See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup and checks. +Documentation accessibility uses the maintained Deque `@axe-core/playwright` adapter, +reusing Playwright rather than installing a second browser automation stack: + +```bash +bun run docs:build:a11y +bun run docs:preview -- --port 4173 +# In another terminal, with Playwright Chromium installed: +bun run docs:a11y +``` + +The audit checks every sitemap URL with WCAG 2.0/2.1/2.2 A/AA tags and writes JSON +and Markdown reports. CI reuses system Chrome without downloading a browser. +Automated checks do not certify WCAG compliance: incomplete results, keyboard use, +screen-reader behavior and interactive states still need manual review. +See [CONTRIBUTING.md](CONTRIBUTING.md#contributing) for the audit workflow and +[#241](https://github.com/fulll/github-code-search/issues/241) for the alternatives benchmark. diff --git a/bun.lock b/bun.lock index ef2f4e9..cf51f8d 100644 --- a/bun.lock +++ b/bun.lock @@ -7,13 +7,13 @@ "commander": "^15.0.0", }, "devDependencies": { + "@axe-core/playwright": "^4.14.0", "@lhci/cli": "^0.15.1", "@playwright/test": "^1.63.0", "@resvg/resvg-js": "^2.6.2", "bun-types": "^1.4.2", "knip": "^6.39.0", "mermaid": "^12.2.0", - "pa11y-ci": "^5.0.0", "sharp": "^0.35.5", "vitepress": "^1.6.4", "vitepress-mermaid-renderer": "^1.2.2", @@ -59,6 +59,8 @@ "@antfu/install-pkg": ["@antfu/install-pkg@2.1.0", "", { "dependencies": { "package-manager-detector": "^1.8.0", "tinyexec": "^1.3.1" } }, "sha512-sdg9NxU3zR4Mnawfbc/x6GB5Wf17WYud5qOuEuxXjaKpYpMkISSJEjItGebXJ2bQ4DIcly4NYH23mtkGJjvKUw=="], + "@axe-core/playwright": ["@axe-core/playwright@4.14.0", "", { "dependencies": { "axe-core": "~4.14.0" }, "peerDependencies": { "playwright-core": ">= 1.0.0" } }, "sha512-SGUhG5PIHd2eMdpgBF2PyQLOhX+qyDR2PT4cmoS4Qk2ak+jX/rrSvst9l0PGv5WTHolKpUMvVmoHpr4cZgf1RQ=="], + "@babel/helper-string-parser": ["@babel/helper-string-parser@7.29.7", "", {}, "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw=="], "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.29.7", "", {}, "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg=="], @@ -297,13 +299,11 @@ "@oxc-resolver/binding-win32-x64-msvc": ["@oxc-resolver/binding-win32-x64-msvc@11.24.2", "", { "os": "win32", "cpu": "x64" }, "sha512-UqGPmo56KDfLlfXFAFIrNflHT8tFxWGEivWg3Zeyp4Uy2NlKN1FGPr6/BxcLGG3+kZ6Wp14g5Uj+n71boqZfiw=="], - "@pa11y/html_codesniffer": ["@pa11y/html_codesniffer@2.6.0", "", {}, "sha512-BKA7qG8NyaIBdCBDep0hYuYoF/bEyWJprE6EEVJOPiwj80sSiIKDT8LUVd19qKhVqNZZD3QvJIdFZ35p+vAFPg=="], - "@paulirish/trace_engine": ["@paulirish/trace_engine@0.0.53", "", { "dependencies": { "legacy-javascript": "latest", "third-party-web": "latest" } }, "sha512-PUl/vlfo08Oj804VI5nDPeSk9vyslnBlVzDDwFt8SUVxY8+KdGMkra/vrXjEEHe8gb7+RqVTfOIlGw0nyrEelA=="], "@playwright/test": ["@playwright/test@1.63.0", "", { "dependencies": { "playwright": "1.63.0" }, "bin": { "playwright": "cli.js" } }, "sha512-oxMK4vllB9RK5NQ2l1pq1IfOf2AvnEuj/vYGDj0H2nMtmtZpKtCwt/l00GEO6xjGfpBNAvjovvYdCm50dRQkpQ=="], - "@puppeteer/browsers": ["@puppeteer/browsers@3.2.6", "", { "dependencies": { "modern-tar": "^0.8.6", "yargs": "^18.0.0" }, "peerDependencies": { "proxy-agent": ">=8.0.1", "yauzl": "^2.10.0 || ^3.4.0" }, "optionalPeers": ["proxy-agent", "yauzl"], "bin": { "browsers": "lib/main-cli.js" } }, "sha512-MJP29gVoHznmqmwhH1tlZqzfeQ3aylTpSSa6WKZXtkwYUH02i9imBmWGTyZrbnAou5paX78SE4jMAUqnEPf8ug=="], + "@puppeteer/browsers": ["@puppeteer/browsers@2.13.2", "", { "dependencies": { "debug": "^4.4.3", "extract-zip": "^2.0.1", "progress": "^2.0.3", "proxy-agent": "^6.5.0", "semver": "^7.7.4", "tar-fs": "^3.1.1", "yargs": "^17.7.2" }, "bin": { "browsers": "lib/cjs/main-cli.js" } }, "sha512-5EUZSUIc37H6aIXyWO0Z4y8NlF8NnjgmqeQgOGiswAU7pY0HOo16ho4+alIWmSfdZnjqBRawMsP3I5YqLSn6kw=="], "@resvg/resvg-js": ["@resvg/resvg-js@2.6.2", "", { "optionalDependencies": { "@resvg/resvg-js-android-arm-eabi": "2.6.2", "@resvg/resvg-js-android-arm64": "2.6.2", "@resvg/resvg-js-darwin-arm64": "2.6.2", "@resvg/resvg-js-darwin-x64": "2.6.2", "@resvg/resvg-js-linux-arm-gnueabihf": "2.6.2", "@resvg/resvg-js-linux-arm64-gnu": "2.6.2", "@resvg/resvg-js-linux-arm64-musl": "2.6.2", "@resvg/resvg-js-linux-x64-gnu": "2.6.2", "@resvg/resvg-js-linux-x64-musl": "2.6.2", "@resvg/resvg-js-win32-arm64-msvc": "2.6.2", "@resvg/resvg-js-win32-ia32-msvc": "2.6.2", "@resvg/resvg-js-win32-x64-msvc": "2.6.2" } }, "sha512-xBaJish5OeGmniDj9cW5PRa/PtmuVU3ziqrbr5xJj901ZDN4TosrVaNZpEiLZAxdfnhAe7uQ7QFWfjPe9d9K2Q=="], @@ -555,15 +555,9 @@ "array-flatten": ["array-flatten@1.1.1", "", {}, "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="], - "array-union": ["array-union@1.0.2", "", { "dependencies": { "array-uniq": "^1.0.1" } }, "sha512-Dxr6QJj/RdU/hCaBjOfxW+q6lyuVE6JFWIrAUpuOOhoJJoQ99cUn3igRaHVB5P9WrgFVN0FfArM3x0cueOU8ng=="], - - "array-uniq": ["array-uniq@1.0.3", "", {}, "sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q=="], - "ast-types": ["ast-types@0.13.4", "", { "dependencies": { "tslib": "^2.0.1" } }, "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w=="], - "async": ["async@3.2.6", "", {}, "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA=="], - - "axe-core": ["axe-core@4.13.0", "", {}, "sha512-UzGt8zg7Ny8djbYMhxl2zuEevVa7r2gJjYY5Lwr1xM7+XU2nd6CkIWFTVcCIbAP63vSz71NaVyyuSk9lHKcy0A=="], + "axe-core": ["axe-core@4.14.0", "", {}, "sha512-9WTZxEjsZ7b13TH8JPmbV2z8CHbl80/2hm3XPEG4JgNdQLK81IBRXmSxHfMAOkSqQeRxT/0dwNDz2GOm3zzpcQ=="], "b4a": ["b4a@1.9.0", "", { "peerDependencies": { "react-native-b4a": "*" }, "optionalPeers": ["react-native-b4a"] }, "sha512-dpfcF9fDNR6++cthXR67iyhgqWy9CBouAvIWhIntzBG6cvK/cnIPiZQjBwi/ZqjjBEDGfoNDtmB0kTjroOJ3pQ=="], @@ -581,14 +575,10 @@ "basic-ftp": ["basic-ftp@5.3.1", "", {}, "sha512-bopVNp6ugyA150DDuZfPFdt1KZ5a94ZDiwX4hMgZDzF+GttD80lEy8kj98kbyhLXnPvhtIo93mdnLIjpCAeeOw=="], - "bfj": ["bfj@9.1.3", "", { "dependencies": { "check-types": "^11.2.3", "hoopy": "^0.1.4", "tryer": "^1.0.1" } }, "sha512-1ythbcNNAd2UjTYW6M+MAHd9KM/m3g4mQ+3a4Vom16WgmUa4GsisdmXAYfpAjkObY5zdpgzaBh1ctZOEcJipuQ=="], - "birpc": ["birpc@2.9.0", "", {}, "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw=="], "body-parser": ["body-parser@1.20.8", "", { "dependencies": { "bytes": "~3.1.2", "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", "destroy": "~1.2.0", "http-errors": "~2.0.1", "iconv-lite": "~0.4.24", "on-finished": "~2.4.1", "qs": "~6.16.0", "raw-body": "~2.5.3", "type-is": "~1.6.18", "unpipe": "~1.0.0" } }, "sha512-JNcyFQ64OiijEkPzUBTCe+hyPXUD/3LEldGQ6iF5LR1w00mx9o7xtDWHXBY2iItjdCFGoilOLNQbH943ut7pHA=="], - "boolbase": ["boolbase@1.0.0", "", {}, "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="], - "brace-expansion": ["brace-expansion@1.1.21", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw=="], "buffer-crc32": ["buffer-crc32@0.2.13", "", {}, "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ=="], @@ -613,17 +603,11 @@ "chardet": ["chardet@0.7.0", "", {}, "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA=="], - "check-types": ["check-types@11.2.3", "", {}, "sha512-+67P1GkJRaxQD6PKK0Et9DhwQB+vGg3PM5+aavopCpZT1lj9jeqfvpgTLAWErNj8qApkkmXlu/Ug74kmhagkXg=="], - - "cheerio": ["cheerio@1.2.0", "", { "dependencies": { "cheerio-select": "^2.1.0", "dom-serializer": "^2.0.0", "domhandler": "^5.0.3", "domutils": "^3.2.2", "encoding-sniffer": "^0.2.1", "htmlparser2": "^10.1.0", "parse5": "^7.3.0", "parse5-htmlparser2-tree-adapter": "^7.1.0", "parse5-parser-stream": "^7.1.2", "undici": "^7.19.0", "whatwg-mimetype": "^4.0.0" } }, "sha512-WDrybc/gKFpTYQutKIK6UvfcuxijIZfMfXaYm8NMsPQxSYvf+13fXUJ4rztGGbJcBQ/GF55gvrZ0Bc0bj/mqvg=="], - - "cheerio-select": ["cheerio-select@2.1.0", "", { "dependencies": { "boolbase": "^1.0.0", "css-select": "^5.1.0", "css-what": "^6.1.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3", "domutils": "^3.0.1" } }, "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g=="], - "chevrotain": ["chevrotain@13.2.0", "", { "dependencies": { "@chevrotain/cst-dts-gen": "13.2.0", "@chevrotain/gast": "13.2.0", "@chevrotain/regexp-to-ast": "13.2.0", "@chevrotain/types": "13.2.0", "@chevrotain/utils": "13.2.0" } }, "sha512-GTouk5YzgBxlTPCaDn5q0q2EJKkrYmnPF+HmF8v0Qo4r3iyPG7HJivkN1wthCXZcm3sIcZFpGTRovLPNdJNRCA=="], "chrome-launcher": ["chrome-launcher@0.13.4", "", { "dependencies": { "@types/node": "*", "escape-string-regexp": "^1.0.5", "is-wsl": "^2.2.0", "lighthouse-logger": "^1.0.0", "mkdirp": "^0.5.3", "rimraf": "^3.0.2" } }, "sha512-nnzXiDbGKjDSK6t2I+35OAPBy5Pw/39bgkb/ZAFwMhwJbdYBp6aH+vW28ZgtjdU890Q7D+3wN/tB8N66q5Gi2A=="], - "chromium-bidi": ["chromium-bidi@157.0.8090-0", "", { "dependencies": { "mitt": "^3.0.1", "zod": "^4.6.5" }, "peerDependencies": { "devtools-protocol": "*" } }, "sha512-KJw7IQgqXwxpJgJlEIPg5mr2ZoqA+jkTRtc5EABI7sUJ3bu3cpI7NuEBoj4Gn1AsF78CaT9So5WQWspK3/wYag=="], + "chromium-bidi": ["chromium-bidi@14.0.0", "", { "dependencies": { "mitt": "^3.0.1", "zod": "^3.24.1" }, "peerDependencies": { "devtools-protocol": "*" } }, "sha512-9gYlLtS6tStdRWzrtXaTMnqcM4dudNegMXJxkR0I/CXObHalYeYcAMPrL19eroNZHtJ8DQmu1E+ZNOYu/IXMXw=="], "cli-cursor": ["cli-cursor@2.1.0", "", { "dependencies": { "restore-cursor": "^2.0.0" } }, "sha512-8lgKz8LmCRYZZQDpRyT2m5rKJ08TnU4tR9FFFW2rxpxR1FzWi4PQ/NfyODchAatHaUgnSPVcx/R5w6NuTBzFiw=="], @@ -663,10 +647,6 @@ "csp_evaluator": ["csp_evaluator@1.1.5", "", {}, "sha512-EL/iN9etCTzw/fBnp0/uj0f5BOOGvZut2mzsiiBZ/FdT6gFQCKRO/tmcKOxn5drWZ2Ndm/xBb1SI4zwWbGtmIw=="], - "css-select": ["css-select@5.2.2", "", { "dependencies": { "boolbase": "^1.0.0", "css-what": "^6.1.0", "domhandler": "^5.0.2", "domutils": "^3.0.1", "nth-check": "^2.0.1" } }, "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw=="], - - "css-what": ["css-what@6.2.2", "", {}, "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA=="], - "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], "cytoscape": ["cytoscape@3.34.3", "", {}, "sha512-yfYGhRcGAntq6YBD583j4n0Eg3jIxvWmZtz/5uz9UYkeIStSlMxuUja+ec5j3iBD8nv1rwaOAYMW09tBdkSeaQ=="], @@ -769,18 +749,8 @@ "devtools-protocol": ["devtools-protocol@0.0.1467305", "", {}, "sha512-LxwMLqBoPPGpMdRL4NkLFRNy3QLp6Uqa7GNp1v6JaBheop2QrB9Q7q0A/q/CYYP9sBfZdHOyszVx4gc9zyk7ow=="], - "dir-glob": ["dir-glob@2.2.2", "", { "dependencies": { "path-type": "^3.0.0" } }, "sha512-f9LBi5QWzIW3I6e//uxZoLBlUt9kcp66qo0sSCxL6YZKc75R1c4MFCoe/LaZiBGmgujvQdxc5Bn3QhfyvK5Hsw=="], - - "dom-serializer": ["dom-serializer@2.0.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.2", "entities": "^4.2.0" } }, "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg=="], - - "domelementtype": ["domelementtype@2.3.0", "", {}, "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw=="], - - "domhandler": ["domhandler@5.0.3", "", { "dependencies": { "domelementtype": "^2.3.0" } }, "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w=="], - "dompurify": ["dompurify@3.4.15", "", { "optionalDependencies": { "@types/trusted-types": "^2.0.7" } }, "sha512-EUBjM+B+lkDE41iE82DDSCfkoPGfXx8IxFxPMjNzm/Uk4xDet77rTN9wqlxlVg71kK7XGuUMv6wUxJUwwv+Xyw=="], - "domutils": ["domutils@3.2.2", "", { "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3" } }, "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw=="], - "dot-prop": ["dot-prop@5.3.0", "", { "dependencies": { "is-obj": "^2.0.0" } }, "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q=="], "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], @@ -795,15 +765,11 @@ "encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="], - "encoding-sniffer": ["encoding-sniffer@0.2.1", "", { "dependencies": { "iconv-lite": "^0.6.3", "whatwg-encoding": "^3.1.1" } }, "sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw=="], - "end-of-stream": ["end-of-stream@1.4.5", "", { "dependencies": { "once": "^1.4.0" } }, "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg=="], "enquirer": ["enquirer@2.4.1", "", { "dependencies": { "ansi-colors": "^4.1.1", "strip-ansi": "^6.0.1" } }, "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ=="], - "entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], - - "envinfo": ["envinfo@7.21.0", "", { "bin": { "envinfo": "dist/cli.js" } }, "sha512-Lw7I8Zp5YKHFCXL7+Dz95g4CcbMEpgvqZNNq3AmlT5XAV6CgAAk6gyAMqn2zjw08K9BHfcNuKrMiCPLByGafow=="], + "entities": ["entities@7.0.1", "", {}, "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA=="], "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="], @@ -851,8 +817,6 @@ "figures": ["figures@2.0.0", "", { "dependencies": { "escape-string-regexp": "^1.0.5" } }, "sha512-Oa2M9atig69ZkfwiApY8F2Yy+tzMbazyvqv21R0NsSC8floSOC09BbT1ITWAdoMGQvJ/aZnR1KMwdx9tvHnTNA=="], - "file-url": ["file-url@3.0.0", "", {}, "sha512-g872QGsHexznxkIAdK8UiZRe7SkE6kvylShU4Nsj8NvfvZag7S0QuQ4IgvPDkk75HxgjIVDwycFTDAgIiO4nDA=="], - "finalhandler": ["finalhandler@1.3.2", "", { "dependencies": { "debug": "2.6.9", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "on-finished": "~2.4.1", "parseurl": "~1.3.3", "statuses": "~2.0.2", "unpipe": "~1.0.0" } }, "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg=="], "find-up": ["find-up@4.1.0", "", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="], @@ -873,8 +837,6 @@ "get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="], - "get-east-asian-width": ["get-east-asian-width@1.7.0", "", {}, "sha512-XjH1AECxf0giL2V1aU8vKyRR2ppRUb5c0EvT7zuJTokQ74bNo52zOtghqdWIqrhUD79fo3x0WfKZdOqxF6LG1Q=="], - "get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="], "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="], @@ -887,8 +849,6 @@ "glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="], - "globby": ["globby@7.1.1", "", { "dependencies": { "array-union": "^1.0.1", "dir-glob": "^2.0.0", "glob": "^7.1.2", "ignore": "^3.3.5", "pify": "^3.0.0", "slash": "^1.0.0" } }, "sha512-yANWAN2DUcBtuus5Cpd+SKROzXHs2iVXFZt/Ykrfz6SAXqacLX25NZpltE+39ceMexYF4TtEadjuSTw8+3wX4g=="], - "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="], "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], @@ -907,12 +867,8 @@ "hookable": ["hookable@5.5.3", "", {}, "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ=="], - "hoopy": ["hoopy@0.1.4", "", {}, "sha512-HRcs+2mr52W0K+x8RzcLzuPPmVIKMSv97RGHy0Ea9y/mpcaK+xTrjICA04KAHi4GRzxliNqNJEFYWHghy3rSfQ=="], - "html-void-elements": ["html-void-elements@3.0.0", "", {}, "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg=="], - "htmlparser2": ["htmlparser2@10.1.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.3", "domutils": "^3.2.2", "entities": "^7.0.1" } }, "sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ=="], - "http-errors": ["http-errors@2.0.1", "", { "dependencies": { "depd": "~2.0.0", "inherits": "~2.0.4", "setprototypeof": "~1.2.0", "statuses": "~2.0.2", "toidentifier": "~1.0.1" } }, "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ=="], "http-link-header": ["http-link-header@1.1.4", "", {}, "sha512-xT3GPW6/ZbGuw4UvwHqErSCEjNUlwbQJuZn9/q5U4WEKfp2kENVCAlousG1zLxHeaQ/ffOHUNpWamvkbBW0eNw=="], @@ -923,8 +879,6 @@ "iconv-lite": ["iconv-lite@0.4.24", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3" } }, "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA=="], - "ignore": ["ignore@3.3.10", "", {}, "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug=="], - "image-ssim": ["image-ssim@0.2.0", "", {}, "sha512-W7+sO6/yhxy83L0G7xR8YAc5Z5QFtYEXXRV6EaE8tuYBZJnA3gVgp3q7X7muhLZVodeb9UfvjSbwt9VJwjIYAg=="], "immediate": ["immediate@3.0.6", "", {}, "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ=="], @@ -947,8 +901,6 @@ "ipaddr.js": ["ipaddr.js@1.9.1", "", {}, "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="], - "is": ["is@3.3.2", "", {}, "sha512-a2xr4E3s1PjDS8ORcGgXpWx6V+liNs+O3JRD2mb9aeugD7rtkkZ0zgLdYgw0tWsKhsdiezGYptSiMlVazCBTuQ=="], - "is-docker": ["is-docker@2.2.1", "", { "bin": { "is-docker": "cli.js" } }, "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ=="], "is-fullwidth-code-point": ["is-fullwidth-code-point@2.0.0", "", {}, "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w=="], @@ -987,8 +939,6 @@ "lighthouse-stack-packs": ["lighthouse-stack-packs@1.12.2", "", {}, "sha512-Ug8feS/A+92TMTCK6yHYLwaFMuelK/hAKRMdldYkMNwv+d9PtWxjXEg6rwKtsUXTADajhdrhXyuNCJ5/sfmPFw=="], - "lilconfig": ["lilconfig@3.1.4", "", {}, "sha512-zJZyBmQhArDRefuWTypZxwDpFDIYmrj8ypSBvwGD5P13Y95s6DjzKgu4DVdGbeQa5PDEsAOCAPNk4X+QOR006w=="], - "localforage": ["localforage@1.10.0", "", { "dependencies": { "lie": "3.1.1" } }, "sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg=="], "locate-path": ["locate-path@5.0.0", "", { "dependencies": { "p-locate": "^4.1.0" } }, "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g=="], @@ -1053,12 +1003,8 @@ "mkdirp": ["mkdirp@0.5.6", "", { "dependencies": { "minimist": "^1.2.6" }, "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw=="], - "modern-tar": ["modern-tar@0.8.6", "", {}, "sha512-LYSks0tNVqbtez/9OB0YFP6qLRSqkdAxJWOQpVaR67lNTbj2zNLyYpowkRMia5lXLCZKzxwoxkxH2MbbBR2aEg=="], - "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], - "mustache": ["mustache@4.2.0", "", { "bin": { "mustache": "bin/mustache" } }, "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ=="], - "mute-stream": ["mute-stream@0.0.7", "", {}, "sha512-r65nCZhrbXXb6dXOACihYApHw2Q6pV0M3V0PSxd74N0+D8nzAdEAITq2oAjA1jVnKI+tGvEBUpqiMh0+rW6zDQ=="], "nanoid": ["nanoid@3.3.19", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-Y2tUNy4ouw6tq5oDSKeQYGOyhkUBhNOcGV/02KC+6kd9eDGqdZd++mjMiIDilrBYvjEnCYvVtsuHCuP+okSfug=="], @@ -1069,10 +1015,6 @@ "node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="], - "node.extend": ["node.extend@2.0.3", "", { "dependencies": { "hasown": "^2.0.0", "is": "^3.3.0" } }, "sha512-xwADg/okH48PvBmRZyoX8i8GJaKuJ1CqlqotlZOhUio8egD1P5trJupHKBzcPjSF9ifK2gPcEICRBnkfPqQXZw=="], - - "nth-check": ["nth-check@2.1.1", "", { "dependencies": { "boolbase": "^1.0.0" } }, "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w=="], - "object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="], "on-finished": ["on-finished@2.4.1", "", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg=="], @@ -1099,10 +1041,6 @@ "p-try": ["p-try@2.2.0", "", {}, "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="], - "pa11y": ["pa11y@10.0.0", "", { "dependencies": { "@pa11y/html_codesniffer": "^2.6.0", "axe-core": "~4.13.0", "bfj": "~9.1.3", "commander": "~15.0.0", "envinfo": "~7.21.0", "mustache": "~4.2.0", "node.extend": "~2.0.3", "puppeteer": "^25.9.0", "semver": "~7.8.0" }, "bin": { "pa11y": "bin/pa11y.js" } }, "sha512-bgNMoSH/a57sLhlllkKstcI6KRWsHVhBhZyWRbM9Z4uV9+nFsnra213QdO+KPVKD9mRtzQj48510CHLG4BBDZg=="], - - "pa11y-ci": ["pa11y-ci@5.0.0", "", { "dependencies": { "async": "~3.2.6", "cheerio": "~1.2.0", "commander": "~14.0.3", "globby": "~7.1.1", "lodash": "~4.18.1", "node-fetch": "~2.7.0", "pa11y": "^10.0.0", "protocolify": "~3.0.0", "puppeteer": "^25.9.0", "wordwrap": "~1.0.0" }, "bin": { "pa11y-ci": "bin/pa11y-ci.js" } }, "sha512-4izxemb7KKxXISqE92YNULCTxAYN+1LDJXhCTJgkTpOvO94uh6lRNHa0bUpgfiW3nUfaGkqdO536n4u4fjLFNQ=="], - "pac-proxy-agent": ["pac-proxy-agent@7.2.0", "", { "dependencies": { "@tootallnate/quickjs-emscripten": "^0.23.0", "agent-base": "^7.1.2", "debug": "^4.3.4", "get-uri": "^6.0.1", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.6", "pac-resolver": "^7.0.1", "socks-proxy-agent": "^8.0.5" } }, "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA=="], "pac-resolver": ["pac-resolver@7.0.1", "", { "dependencies": { "degenerator": "^5.0.0", "netmask": "^2.0.2" } }, "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg=="], @@ -1111,12 +1049,6 @@ "parse-cache-control": ["parse-cache-control@1.0.1", "", {}, "sha512-60zvsJReQPX5/QP0Kzfd/VrpjScIQ7SHBW6bFCYfEP+fp0Eppr1SHhIO5nd1PjZtvclzSzES9D/p5nFJurwfWg=="], - "parse5": ["parse5@7.3.0", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw=="], - - "parse5-htmlparser2-tree-adapter": ["parse5-htmlparser2-tree-adapter@7.1.0", "", { "dependencies": { "domhandler": "^5.0.3", "parse5": "^7.0.0" } }, "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g=="], - - "parse5-parser-stream": ["parse5-parser-stream@7.1.2", "", { "dependencies": { "parse5": "^7.0.0" } }, "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow=="], - "parseurl": ["parseurl@1.3.3", "", {}, "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="], "path-data-parser": ["path-data-parser@0.1.0", "", {}, "sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w=="], @@ -1127,8 +1059,6 @@ "path-to-regexp": ["path-to-regexp@0.1.13", "", {}, "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA=="], - "path-type": ["path-type@3.0.0", "", { "dependencies": { "pify": "^3.0.0" } }, "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg=="], - "pend": ["pend@1.2.0", "", {}, "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg=="], "perfect-debounce": ["perfect-debounce@1.0.0", "", {}, "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA=="], @@ -1137,8 +1067,6 @@ "picomatch": ["picomatch@4.0.7", "", {}, "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA=="], - "pify": ["pify@3.0.0", "", {}, "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg=="], - "playwright": ["playwright@1.63.0", "", { "dependencies": { "playwright-core": "1.63.0" }, "bin": { "playwright": "cli.js" } }, "sha512-+7ziBLidS4NaNCdt57SUDT+wYmmd5fmiQejUic/kb+YsYSCPyOOE9sebzMjNmQrsnNpDJqd4WHvV/8lfKfUDUg=="], "playwright-core": ["playwright-core@1.63.0", "", { "bin": { "playwright-core": "cli.js" } }, "sha512-rYCsBF/M5HjUch52bbtVONEFjv6Xu8sm8h72dNlR5bzIE1fvC/bxgspzkjSfU+MweEMmPM8KJebG6nnyxo5mCg=="], @@ -1151,14 +1079,10 @@ "preact": ["preact@10.29.8", "", { "peerDependencies": { "preact-render-to-string": ">=5" }, "optionalPeers": ["preact-render-to-string"] }, "sha512-ej2aVZ+vZ8WO7tvlQWRM9N63A0KzF9q4mWJfDUHgYaIofWY9hu74QdnQrjoPMmZi2/nZ5gN0bJCQF49xQqx09Q=="], - "prepend-http": ["prepend-http@3.0.1", "", {}, "sha512-BLxfZh+m6UiAiCPZFJ4+vYoL7NrRs5XgCTRrjseATAggXhdZKKxn+JUNmuVYWY23bDHgaEHodxw8mnmtVEDtHw=="], - "progress": ["progress@2.0.3", "", {}, "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA=="], "property-information": ["property-information@7.2.0", "", {}, "sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg=="], - "protocolify": ["protocolify@3.0.0", "", { "dependencies": { "file-url": "^3.0.0", "prepend-http": "^3.0.0" } }, "sha512-PuvDJOkKJMVQx8jSNf8E5g0bJw/UTKm30mTjFHg4N30c8sefgA5Qr/f8INKqYBKfvP/MUSJrj+z1Smjbq4/3rQ=="], - "proxy-addr": ["proxy-addr@2.0.8", "", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-5nnx0yGyVUcY6t9RnWcARWtwT9F1D8O9rt08htPvnd49W1IgZtmLkhu9WfMzQj1cFxjHIO6connUNVW5k7AVyQ=="], "proxy-agent": ["proxy-agent@6.5.0", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "^4.3.4", "http-proxy-agent": "^7.0.1", "https-proxy-agent": "^7.0.6", "lru-cache": "^7.14.1", "pac-proxy-agent": "^7.1.0", "proxy-from-env": "^1.1.0", "socks-proxy-agent": "^8.0.5" } }, "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A=="], @@ -1167,8 +1091,6 @@ "pump": ["pump@3.0.4", "", { "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA=="], - "puppeteer": ["puppeteer@25.16.0", "", { "dependencies": { "@puppeteer/browsers": "3.2.6", "chromium-bidi": "157.0.8090-0", "devtools-protocol": "0.0.1704951", "lilconfig": "^3.1.3", "puppeteer-core": "25.16.0", "typed-query-selector": "^2.12.3" }, "bin": { "puppeteer": "lib/puppeteer/node/cli.js" } }, "sha512-LOr3reYnxAYYfQkpopz1fQnL0LdvqhpR5U2Fu+QnwzTX2gpVTHsn91UETo5NNC1md2/oC54Zu1ChBXgZDNSP1A=="], - "puppeteer-core": ["puppeteer-core@24.43.1", "", { "dependencies": { "@puppeteer/browsers": "2.13.2", "chromium-bidi": "14.0.0", "debug": "^4.4.3", "devtools-protocol": "0.0.1608973", "typed-query-selector": "^2.12.2", "webdriver-bidi-protocol": "0.4.1", "ws": "^8.20.0" } }, "sha512-T5ScUMAsmhdNbgDR41AGESYeS6V9MSgetkSnVhhW+gXvzC42VesKCn5ld87gAZDJ6vLHL9GkRvY9WtQWSnwFbw=="], "qs": ["qs@6.16.0", "", { "dependencies": { "es-define-property": "^1.0.1", "side-channel": "^1.1.1" } }, "sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA=="], @@ -1239,8 +1161,6 @@ "signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], - "slash": ["slash@1.0.0", "", {}, "sha512-3TYDR7xWt4dIqV2JauJr+EJeW356RXijHeUlO+8djJ+uBXPn8/2dpzBc8yQhh583sVvc9CvFAeQVgijsH+PNNg=="], - "smart-buffer": ["smart-buffer@4.2.0", "", {}, "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg=="], "smol-toml": ["smol-toml@1.8.0", "", {}, "sha512-kCZr2V3ch9i00x8zXRhjUNVcjG9ijES5dDudkXvUVCT5QlJNQWElSJdZqyPemffHoLNUYwOcou0Fy+ojN0uHSQ=="], @@ -1311,22 +1231,18 @@ "trim-lines": ["trim-lines@3.0.1", "", {}, "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg=="], - "tryer": ["tryer@1.0.1", "", {}, "sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA=="], - "ts-dedent": ["ts-dedent@2.3.0", "", {}, "sha512-JfJeIHke7y2egdGGgRAvpCwYFUsHlM2gPcrVOxFkznt/4uzQ7HFmvE63iFHVLBJNDuyDOQgijDK/tXH/f6Msjg=="], "tslib": ["tslib@1.14.1", "", {}, "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="], "type-is": ["type-is@1.6.18", "", { "dependencies": { "media-typer": "0.3.0", "mime-types": "~2.1.24" } }, "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g=="], - "typed-query-selector": ["typed-query-selector@2.12.3", "", {}, "sha512-/dtuMZyutHLlrxWhhC1uib00ELUr2dZ8C9B8YzZYsJmeE3V56OxRblrSM2UaF142pcMtak7ehZ6+poWGeehNIg=="], + "typed-query-selector": ["typed-query-selector@2.12.2", "", {}, "sha512-EOPFbyIub4ngnEdqi2yOcNeDLaX/0jcE1JoAXQDDMIthap7FoN795lc/SHfIq2d416VufXpM8z/lD+WRm2gfOQ=="], "typedarray-to-buffer": ["typedarray-to-buffer@3.1.5", "", { "dependencies": { "is-typedarray": "^1.0.0" } }, "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q=="], "unbash": ["unbash@4.0.11", "", {}, "sha512-FoSOKV7NEofQSkAefMVHam4ZPKYMxjAydxiV72UFEDNV/YofxjGfiZ2A9pZjdL/lRJzTjcu4PABo1JYJX8N5iQ=="], - "undici": ["undici@7.30.1", "", {}, "sha512-03KrMbjIFDWQR5RNPrSw3bd1r12JpyFQl9/yxjUbJtqkjmllqC5lFjZh6ujeuJCKr8EOQvRW872Oy/az+nLYxg=="], - "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], "unique-string": ["unique-string@2.0.0", "", { "dependencies": { "crypto-random-string": "^2.0.0" } }, "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg=="], @@ -1367,18 +1283,12 @@ "webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="], - "whatwg-encoding": ["whatwg-encoding@3.1.1", "", { "dependencies": { "iconv-lite": "0.6.3" } }, "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ=="], - "whatwg-fetch": ["whatwg-fetch@3.6.20", "", {}, "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg=="], - "whatwg-mimetype": ["whatwg-mimetype@4.0.0", "", {}, "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg=="], - "whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="], "which-module": ["which-module@2.0.1", "", {}, "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ=="], - "wordwrap": ["wordwrap@1.0.0", "", {}, "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q=="], - "wrap-ansi": ["wrap-ansi@6.2.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA=="], "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="], @@ -1423,12 +1333,10 @@ "@paulirish/trace_engine/third-party-web": ["third-party-web@0.30.0", "", {}, "sha512-p+PfyL5U0ediGzvwPzMSdD9MnC8rHW6nWJJVLIQvqN7RsSqfaZ+7n4c9GILqH36EYR0eXB9CzAeCRnKFr/9uJg=="], - "@puppeteer/browsers/yargs": ["yargs@18.2.0", "", { "dependencies": { "cliui": "^9.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "string-width": "^8.2.1", "y18n": "^5.0.5", "yargs-parser": "^22.0.0" } }, "sha512-9OpKOLeaoNFecEp7P6iYbzze/5CqWoH7N3SMs/6y1XF4nMvFMspEGZzJ6uFi9MmQwXhyzqYoSEKO3tvA3Z/o2w=="], + "@puppeteer/browsers/yargs": ["yargs@17.7.3", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g=="], "@tybys/wasm-util/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - "@vue/compiler-core/entities": ["entities@7.0.1", "", {}, "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA=="], - "accepts/negotiator": ["negotiator@0.6.3", "", {}, "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="], "ast-types/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], @@ -1437,6 +1345,8 @@ "chrome-launcher/lighthouse-logger": ["lighthouse-logger@1.4.2", "", { "dependencies": { "debug": "^2.6.9", "marky": "^1.2.2" } }, "sha512-gPWxznF6TKmUHrOQjlVo2UbaL2EJ71mb2CCeRs/2qBpi4L/g4LUVc9+3lKQ6DTUZwJswfM7ainGrLO1+fOqa2g=="], + "chromium-bidi/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], + "cliui/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], "cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], @@ -1453,8 +1363,6 @@ "d3-sankey/d3-shape": ["d3-shape@1.3.7", "", { "dependencies": { "d3-path": "1" } }, "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw=="], - "encoding-sniffer/iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="], - "enquirer/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], "express/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], @@ -1463,10 +1371,10 @@ "finalhandler/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], - "htmlparser2/entities": ["entities@7.0.1", "", {}, "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA=="], - "intl-messageformat/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + "lighthouse/axe-core": ["axe-core@4.13.0", "", {}, "sha512-UzGt8zg7Ny8djbYMhxl2zuEevVa7r2gJjYY5Lwr1xM7+XU2nd6CkIWFTVcCIbAP63vSz71NaVyyuSk9lHKcy0A=="], + "lighthouse/chrome-launcher": ["chrome-launcher@1.2.1", "", { "dependencies": { "@types/node": "*", "escape-string-regexp": "^4.0.0", "is-wsl": "^2.2.0", "lighthouse-logger": "^2.0.1" }, "bin": { "print-chrome-path": "bin/print-chrome-path.cjs" } }, "sha512-qmFR5PLMzHyuNJHwOloHPAHhbaNglkfeV/xDtt5b7xiFFyU1I+AZZX0PYseMuhenJSSirgxELYIbswcoc+5H4A=="], "lighthouse/lighthouse-logger": ["lighthouse-logger@2.0.2", "", { "dependencies": { "debug": "^4.4.1", "marky": "^1.2.2" } }, "sha512-vWl2+u5jgOQuZR55Z1WM0XDdrJT6mzMP8zHUct7xTlWhuQs+eV0g+QL0RQdFjT54zVmbhLCP8vIVpy1wGn/gCg=="], @@ -1487,22 +1395,8 @@ "mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], - "pa11y-ci/commander": ["commander@14.0.3", "", {}, "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw=="], - - "parse5/entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="], - - "puppeteer/devtools-protocol": ["devtools-protocol@0.0.1704951", "", {}, "sha512-GQhQOs9u/Mvm1zMtJIH872JS4AskuZdUSsPNPk4ChahtWAUqgz6atEtIsMt+nO7MqF1Ic4HxII/k8Cu36JivNg=="], - - "puppeteer/puppeteer-core": ["puppeteer-core@25.16.0", "", { "dependencies": { "@puppeteer/browsers": "3.2.6", "chromium-bidi": "157.0.8090-0", "devtools-protocol": "0.0.1704951", "typed-query-selector": "^2.12.3", "webdriver-bidi-protocol": "1.0.1", "ws": "^8.22.0" } }, "sha512-VA5Pf54N8shfopNkZZXCdiiwfnnY8ZJYAMqFQRhlH5mGUhfwf53Ar770GPtdpTDLUqvQeYpIBtC9/G2Jij2IJw=="], - - "puppeteer-core/@puppeteer/browsers": ["@puppeteer/browsers@2.13.2", "", { "dependencies": { "debug": "^4.4.3", "extract-zip": "^2.0.1", "progress": "^2.0.3", "proxy-agent": "^6.5.0", "semver": "^7.7.4", "tar-fs": "^3.1.1", "yargs": "^17.7.2" }, "bin": { "browsers": "lib/cjs/main-cli.js" } }, "sha512-5EUZSUIc37H6aIXyWO0Z4y8NlF8NnjgmqeQgOGiswAU7pY0HOo16ho4+alIWmSfdZnjqBRawMsP3I5YqLSn6kw=="], - - "puppeteer-core/chromium-bidi": ["chromium-bidi@14.0.0", "", { "dependencies": { "mitt": "^3.0.1", "zod": "^3.24.1" }, "peerDependencies": { "devtools-protocol": "*" } }, "sha512-9gYlLtS6tStdRWzrtXaTMnqcM4dudNegMXJxkR0I/CXObHalYeYcAMPrL19eroNZHtJ8DQmu1E+ZNOYu/IXMXw=="], - "puppeteer-core/devtools-protocol": ["devtools-protocol@0.0.1608973", "", {}, "sha512-Tpm17fxYzt+J7VrGdc1k8YdRqS3YV7se/M6KeemEqvUbq/n7At1rWVuXMxQgpWkdwSdIEKYbU//Bve+Shm4YNQ=="], - "puppeteer-core/typed-query-selector": ["typed-query-selector@2.12.2", "", {}, "sha512-EOPFbyIub4ngnEdqi2yOcNeDLaX/0jcE1JoAXQDDMIthap7FoN795lc/SHfIq2d416VufXpM8z/lD+WRm2gfOQ=="], - "puppeteer-core/ws": ["ws@8.21.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw=="], "send/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], @@ -1511,8 +1405,6 @@ "tmp/rimraf": ["rimraf@2.7.1", "", { "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "./bin.js" } }, "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w=="], - "whatwg-encoding/iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="], - "wrap-ansi/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], "wrap-ansi/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], @@ -1525,13 +1417,13 @@ "@img/sharp-wasm32/@emnapi/runtime/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - "@puppeteer/browsers/yargs/cliui": ["cliui@9.0.1", "", { "dependencies": { "string-width": "^7.2.0", "strip-ansi": "^7.1.0", "wrap-ansi": "^9.0.0" } }, "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w=="], + "@puppeteer/browsers/yargs/cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="], - "@puppeteer/browsers/yargs/string-width": ["string-width@8.3.0", "", { "dependencies": { "get-east-asian-width": "^1.5.0", "strip-ansi": "^7.1.2" } }, "sha512-ZbmZM0JCihQN91dWnxoipT2KOEyHqEyfRXUyjuRhW8b/xnqPDoq4gWEVApTVa9db2wN8mmoikgFBbjh71+cGeQ=="], + "@puppeteer/browsers/yargs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], "@puppeteer/browsers/yargs/y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="], - "@puppeteer/browsers/yargs/yargs-parser": ["yargs-parser@22.0.0", "", {}, "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw=="], + "@puppeteer/browsers/yargs/yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="], "body-parser/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], @@ -1565,14 +1457,6 @@ "lighthouse/yargs/y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="], - "puppeteer-core/@puppeteer/browsers/yargs": ["yargs@17.7.3", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g=="], - - "puppeteer-core/chromium-bidi/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], - - "puppeteer/puppeteer-core/webdriver-bidi-protocol": ["webdriver-bidi-protocol@1.0.1", "", {}, "sha512-WXWs/aXF2a6Al1Z2/V/7Gp2oPZOAAmHjJOTmv4KCXRJ122IMj3tSSMc6a4q3K4QOmhQbse/Ahr+oTVJOU2dUiw=="], - - "puppeteer/puppeteer-core/ws": ["ws@8.22.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-Ydggc987+RO0AnWtZ/7Wq9FtNvcrL1b/RO0ud9mWjUPgDrsAAwQSF51sm2hm1XofbU/4jkpGEsLFsZZxU+1DOg=="], - "send/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], "string-width/strip-ansi/ansi-regex": ["ansi-regex@3.0.1", "", {}, "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw=="], @@ -1587,13 +1471,13 @@ "yargs/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], - "@puppeteer/browsers/yargs/cliui/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="], + "@puppeteer/browsers/yargs/cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], - "@puppeteer/browsers/yargs/cliui/strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="], + "@puppeteer/browsers/yargs/cliui/wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], - "@puppeteer/browsers/yargs/cliui/wrap-ansi": ["wrap-ansi@9.0.2", "", { "dependencies": { "ansi-styles": "^6.2.1", "string-width": "^7.0.0", "strip-ansi": "^7.1.0" } }, "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww=="], + "@puppeteer/browsers/yargs/string-width/is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], - "@puppeteer/browsers/yargs/string-width/strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="], + "@puppeteer/browsers/yargs/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], "chrome-launcher/lighthouse-logger/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], @@ -1605,25 +1489,15 @@ "lighthouse/yargs/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], - "puppeteer-core/@puppeteer/browsers/yargs/cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="], - - "puppeteer-core/@puppeteer/browsers/yargs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], - - "puppeteer-core/@puppeteer/browsers/yargs/y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="], - - "puppeteer-core/@puppeteer/browsers/yargs/yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="], - "wrap-ansi/ansi-styles/color-convert/color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], "yargs/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - "@puppeteer/browsers/yargs/cliui/string-width/emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="], - - "@puppeteer/browsers/yargs/cliui/strip-ansi/ansi-regex": ["ansi-regex@6.4.0", "", {}, "sha512-KzTVk2tCWAHtYrvvvaP8bJKJq2pVinhLcGEQdtLIYPbmNGNyYe8QwNaTUYQp2J7/vIsUKt5QCqAfUkYyG9DkOw=="], + "@puppeteer/browsers/yargs/cliui/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - "@puppeteer/browsers/yargs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], + "@puppeteer/browsers/yargs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], - "@puppeteer/browsers/yargs/string-width/strip-ansi/ansi-regex": ["ansi-regex@6.4.0", "", {}, "sha512-KzTVk2tCWAHtYrvvvaP8bJKJq2pVinhLcGEQdtLIYPbmNGNyYe8QwNaTUYQp2J7/vIsUKt5QCqAfUkYyG9DkOw=="], + "@puppeteer/browsers/yargs/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], "lighthouse/yargs/cliui/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], @@ -1631,26 +1505,12 @@ "lighthouse/yargs/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - "puppeteer-core/@puppeteer/browsers/yargs/cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], - - "puppeteer-core/@puppeteer/browsers/yargs/cliui/wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], - - "puppeteer-core/@puppeteer/browsers/yargs/string-width/is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], - - "puppeteer-core/@puppeteer/browsers/yargs/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + "@puppeteer/browsers/yargs/cliui/wrap-ansi/ansi-styles/color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], "lighthouse/yargs/cliui/wrap-ansi/ansi-styles/color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], - "puppeteer-core/@puppeteer/browsers/yargs/cliui/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - - "puppeteer-core/@puppeteer/browsers/yargs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], - - "puppeteer-core/@puppeteer/browsers/yargs/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + "@puppeteer/browsers/yargs/cliui/wrap-ansi/ansi-styles/color-convert/color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], "lighthouse/yargs/cliui/wrap-ansi/ansi-styles/color-convert/color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], - - "puppeteer-core/@puppeteer/browsers/yargs/cliui/wrap-ansi/ansi-styles/color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], - - "puppeteer-core/@puppeteer/browsers/yargs/cliui/wrap-ansi/ansi-styles/color-convert/color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], } } diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts index 6d34789..6d5f10f 100644 --- a/docs/.vitepress/config.mts +++ b/docs/.vitepress/config.mts @@ -203,24 +203,36 @@ export default defineConfig({ }, ], // ── Chunk splitting ────────────────────────────────────────────────────── - // Four async chunk groups — all loaded lazily via vitepress-mermaid-renderer, - // so none affect initial page load. Target: keep every chunk under 500 kB. + // The theme imports vitepress-mermaid-renderer dynamically, only on pages + // that contain a diagram, so the Mermaid chunk groups below never weigh on + // other pages (~200 kB of JS modulepreloaded instead of ~5.4 MB). // // Approximate minified sizes (actual, measured): - // mermaid ~1 769 kB (mermaid core + d3, circular-dep locked together) - // mermaid-graph ~442 kB (cytoscape core) - // mermaid-layout ~201 kB (cytoscape-fcose + cytoscape-cose-bilkent) - // mermaid-utils ~163 kB (lodash-es, khroma, dayjs, roughjs, …) - // katex ~261 kB (own chunk, handled by vitepress-mermaid-renderer) + // mermaid ~4 040 kB (mermaid core + d3, circular-dep locked together) + // mermaid-graph ~433 kB (cytoscape core) + // mermaid-layout ~196 kB (cytoscape-fcose + cytoscape-cose-bilkent) + // mermaid-utils ~162 kB (lodash-es, khroma, dayjs, roughjs, …) + // katex ~255 kB (own chunk, handled by vitepress-mermaid-renderer) // - // mermaid+d3 is the one inescapable large chunk (~1 769 kB). The 1 800 kB - // limit silences the Rollup warning for that known bundle without masking - // real bloat in any other chunk (next largest is mermaid-graph at ~442 kB). + // mermaid+d3 is the one inescapable large chunk, so it still triggers the + // Rollup size warning; the 1 800 kB limit keeps every other chunk in check + // (next largest is mermaid-graph at ~433 kB). build: { chunkSizeWarningLimit: 1800, rollupOptions: { output: { manualChunks(id: string) { + // This function replaces VitePress's own chunking. Rollup pulls the + // unassigned dependencies of a manual chunk into it, so keep the + // Vue runtime in VitePress's "framework" chunk: otherwise every page + // statically imports the multi-MB Mermaid chunk just to get Vue. + if ( + id.startsWith("\0vite") || + id.includes("plugin-vue:export-helper") || + /node_modules\/(@vue\/|vue\/)/.test(id) + ) + return "framework"; + // cytoscape-fcose + cytoscape-cose-bilkent: layout algorithm extensions // (~200 kB combined). Must be checked before the bare "cytoscape" catch. if ( @@ -254,7 +266,6 @@ export default defineConfig({ // circular dependencies that Rollup cannot untangle further. if ( id.includes("node_modules/mermaid") || - id.includes("node_modules/vitepress-mermaid-renderer") || id.includes("node_modules/d3") || id.includes("node_modules/dagre-d3-es") || id.includes("node_modules/internmap") || @@ -417,9 +428,10 @@ export default defineConfig({ // ── Sitemap ─────────────────────────────────────────────────────────────── // VITEPRESS_HOSTNAME overrides the default for local/CI a11y audits: // VITEPRESS_HOSTNAME=http://localhost:4173 vitepress build docs - // → sitemap.xml contains localhost URLs that pa11y-ci can reach directly. + // → sitemap.xml contains localhost URLs that the accessibility audit can reach. sitemap: { hostname: - (process.env.VITEPRESS_HOSTNAME ?? "https://fulll.github.io") + "/github-code-search/", + (process.env.VITEPRESS_HOSTNAME ?? "https://fulll.github.io") + + (process.env.VITEPRESS_BASE ?? "/github-code-search/"), }, }); diff --git a/docs/.vitepress/theme/custom.css b/docs/.vitepress/theme/custom.css index bae204d..f1bb47a 100644 --- a/docs/.vitepress/theme/custom.css +++ b/docs/.vitepress/theme/custom.css @@ -485,3 +485,17 @@ html.dark .vp-doc table code { background-color: #2d333b; color: #c9d1d9; } +/* Keep the icon-only mobile search button named without overriding its desktop label. */ +@media (max-width: 767px) { + .DocSearch-Button .DocSearch-Button-Placeholder { + display: block; + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip-path: inset(50%); + white-space: nowrap; + } +} diff --git a/docs/.vitepress/theme/index.ts b/docs/.vitepress/theme/index.ts index 5607698..7c568b0 100644 --- a/docs/.vitepress/theme/index.ts +++ b/docs/.vitepress/theme/index.ts @@ -1,9 +1,8 @@ -import { h, nextTick, onMounted, watch } from "vue"; +import { h, nextTick, onMounted, onUnmounted, watch } from "vue"; import type { App } from "vue"; import type { Theme } from "vitepress"; import DefaultTheme from "vitepress/theme"; import { useData } from "vitepress"; -import { createMermaidRenderer } from "vitepress-mermaid-renderer"; import TerminalDemo from "./TerminalDemo.vue"; import UseCaseTabs from "./UseCaseTabs.vue"; import ComparisonTable from "./ComparisonTable.vue"; @@ -16,6 +15,35 @@ import VersionBadge from "./VersionBadge.vue"; import TestimonialsSection from "./TestimonialsSection.vue"; import "./custom.css"; +// aria-label is only permitted on elements with a role; Mermaid containers lack one. +function labelDiagrams() { + for (const diagram of document.querySelectorAll(".mermaid[aria-label]:not([role])")) { + diagram.setAttribute("role", "group"); + } +} + +// Mermaid and its renderer weigh ~5 MB of JS: fetch them only once a page +// actually contains a diagram (first render or client-side navigation). +const MERMAID_SOURCE = ".language-mermaid, code.mermaid"; +let mermaidRenderer: Promise | undefined; +let mermaidTheme: "dark" | "base" = "base"; + +function initMermaid(theme = mermaidTheme) { + mermaidTheme = theme; + if (typeof document === "undefined") return; + if (!mermaidRenderer && !document.querySelector(MERMAID_SOURCE)) return; + mermaidRenderer ??= import("vitepress-mermaid-renderer"); + void mermaidRenderer.then(({ createMermaidRenderer }) => + createMermaidRenderer({ + startOnLoad: false, + theme: mermaidTheme, + securityLevel: "strict", + // Fulll brand theme variables applied via %%{init}%% in each diagram. + // The renderer handles re-render on theme toggle automatically. + }), + ); +} + export default { extends: DefaultTheme, enhanceApp({ app }: { app: App }) { @@ -28,35 +56,29 @@ export default { app.component("TestimonialsSection", TestimonialsSection); }, setup() { - // Fix: DocSearch button has aria-label="Search" but its visible text includes - // the keyboard-shortcut keys ("⌘ K"), which triggers a label-content-name- - // mismatch accessibility failure. Marking the keys container aria-hidden hides - // it from assistive technology so the button's accessible name ("Search") and - // its visible label agree. + let diagramObserver: MutationObserver | undefined; onMounted(() => { - const keys = document.querySelector(".DocSearch-Button-Keys"); - if (keys) keys.setAttribute("aria-hidden", "true"); + // Derive the name from visible content, including the shortcut. The label + // stays screen-reader-visible on mobile via custom.css. + document.querySelector(".DocSearch")?.removeAttribute("aria-label"); + labelDiagrams(); + const content = document.getElementById("VPContent"); + if (content) { + diagramObserver = new MutationObserver(() => { + labelDiagrams(); + if (!mermaidRenderer) initMermaid(); + }); + diagramObserver.observe(content, { childList: true, subtree: true }); + } }); + onUnmounted(() => diagramObserver?.disconnect()); }, Layout: () => { const { isDark, frontmatter } = useData(); - const initMermaid = () => { - createMermaidRenderer({ - startOnLoad: false, - theme: isDark.value ? "dark" : "base", - securityLevel: "strict", - // Fulll brand theme variables applied via %%{init}%% in each diagram. - // The renderer handles re-render on theme toggle automatically. - }); - }; - - nextTick(() => initMermaid()); - - watch( - () => isDark.value, - () => initMermaid(), - ); + const theme = () => (isDark.value ? "dark" : "base"); + nextTick(() => initMermaid(theme())); + watch(isDark, () => initMermaid(theme())); return h(DefaultTheme.Layout, null, { // Version badge pill ("v1.x is here!") — only on the homepage, above hero name diff --git a/package.json b/package.json index 0245b56..b36f3e7 100644 --- a/package.json +++ b/package.json @@ -51,21 +51,22 @@ "docs:build:og": "bun scripts/generate-og.ts", "generate-ico": "bun scripts/generate-ico.ts", "docs:preview": "vitepress preview docs", - "docs:a11y": "pa11y-ci --config .pa11yci.json --sitemap http://localhost:4173${VITEPRESS_BASE:-/github-code-search/}sitemap.xml", + "docs:a11y": "bun scripts/a11y.ts", "docs:lhci": "lhci autorun --config .lhci.config.cjs", - "docs:test:responsive": "playwright test" + "docs:test:responsive": "playwright test responsive.pw.ts", + "docs:test:a11y": "playwright test a11y.pw.ts" }, "dependencies": { "commander": "^15.0.0" }, "devDependencies": { + "@axe-core/playwright": "^4.14.0", "@lhci/cli": "^0.15.1", "@playwright/test": "^1.63.0", "@resvg/resvg-js": "^2.6.2", "bun-types": "^1.4.2", "knip": "^6.39.0", "mermaid": "^12.2.0", - "pa11y-ci": "^5.0.0", "sharp": "^0.35.5", "vitepress": "^1.6.4", "vitepress-mermaid-renderer": "^1.2.2" diff --git a/playwright.config.ts b/playwright.config.ts index d594412..fbecf8c 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -1,5 +1,5 @@ /** - * Playwright configuration — responsive tests only. + * Playwright configuration — responsive and accessibility harness tests. * * Run: bun run docs:test:responsive * @@ -13,7 +13,7 @@ const BASE_URL = process.env.PLAYWRIGHT_BASE_URL ?? "http://localhost:4173"; export default defineConfig({ testDir: "./scripts", - testMatch: ["responsive.pw.ts"], + testMatch: ["responsive.pw.ts", "a11y.pw.ts"], /* Run all (viewport × page) pairs concurrently — they are fully independent */ fullyParallel: true, @@ -31,6 +31,12 @@ export default defineConfig({ baseURL: BASE_URL, /* Disable JS animations — they don't affect layout but speed up page load */ reducedMotion: "reduce", + /* `vitepress preview` Brotli-compresses every response on the fly (~5 s for + the 4 MB Mermaid chunk); encoding is irrelevant to these tests. */ + extraHTTPHeaders: { "Accept-Encoding": "identity" }, + launchOptions: { + executablePath: process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH, + }, }, /* Up to 4 workers locally; CI defaults to 2 (GitHub-hosted runners are 2-core) */ diff --git a/scripts/a11y-utils.test.ts b/scripts/a11y-utils.test.ts new file mode 100644 index 0000000..d6856e4 --- /dev/null +++ b/scripts/a11y-utils.test.ts @@ -0,0 +1,71 @@ +import { describe, expect, it } from "bun:test"; +import { formatSummary, validateSitemapUrls, WCAG_TAGS } from "./a11y-utils.ts"; + +describe("accessibility sitemap scope", () => { + const base = "http://localhost:4173/github-code-search/"; + + it("keeps all distinct pages, including versioned paths and encoded URLs", () => { + const urls = [base, `${base}reference/cli.html`, `${base}v1/a%20b.html?x=1&y=2`]; + expect(validateSitemapUrls([...urls, base], base)).toEqual(urls); + }); + + it("supports a versioned documentation base", () => { + expect(validateSitemapUrls([`${base}v2/page.html`], `${base}v2/`)).toEqual([ + `${base}v2/page.html`, + ]); + }); + + it("fails closed on an empty sitemap", () => { + expect(() => validateSitemapUrls([], base)).toThrow("no page URLs"); + }); + + it.each([ + "https://example.com/github-code-search/page.html", + "http://localhost:4173/github-code-search-other/page.html", + "http://localhost:4173/github-code-search/../page.html", + "http://user:password@localhost:4173/github-code-search/", + "file:///github-code-search/page.html", + "not a URL", + ])("rejects out-of-scope or invalid sitemap URL %s", (url) => { + expect(() => validateSitemapUrls([url], base)).toThrow(); + }); + + it.each(["file:///docs/", "http://localhost:4173/docs"])("rejects invalid base %s", (url) => { + expect(() => validateSitemapUrls([url], url)).toThrow(); + }); + + it("runs WCAG A/AA tags through 2.2 without disabling contrast or ARIA", () => { + expect(WCAG_TAGS).toEqual(["wcag2a", "wcag2aa", "wcag21a", "wcag21aa", "wcag22aa"]); + }); +}); + +describe("accessibility summary", () => { + it("distinguishes violations, manual review and operational errors", () => { + const report = formatSummary( + [ + { url: "http://localhost/pass", violations: 0, incomplete: 1, error: null }, + { url: "http://localhost/fail", violations: 2, incomplete: 0, error: null }, + { url: "http://localhost/error", violations: 0, incomplete: 0, error: "HTTP 404" }, + ], + null, + ); + expect(report).toContain("| `/pass` | 0 | 1 | PASS |"); + expect(report).toContain("| `/fail` | 2 | 0 | FAIL |"); + expect(report).toContain("| `/error` | 0 | 0 | ERROR |"); + expect(report).toContain("not WCAG certification"); + expect(report).toContain("a11y-report.json"); + }); + + it("reports startup failures without presenting an empty audit as success", () => { + expect(formatSummary([], "Malformed sitemap")).toContain("**Audit failed:"); + }); + + it("escapes table delimiters in URL paths", () => { + expect( + formatSummary( + [{ url: "http://localhost/a|b", violations: 0, incomplete: 0, error: null }], + null, + ), + ).toContain("`/a%7Cb`"); + }); +}); diff --git a/scripts/a11y-utils.ts b/scripts/a11y-utils.ts new file mode 100644 index 0000000..afab0d3 --- /dev/null +++ b/scripts/a11y-utils.ts @@ -0,0 +1,49 @@ +export const WCAG_TAGS = ["wcag2a", "wcag2aa", "wcag21a", "wcag21aa", "wcag22aa"]; + +export function validateSitemapUrls(locations: string[], base: string): string[] { + const root = new URL(base); + if (!["http:", "https:"].includes(root.protocol) || !root.pathname.endsWith("/")) { + throw new Error(`Expected an HTTP(S) base URL ending in /, got ${base}`); + } + if (locations.length === 0) throw new Error("The sitemap contains no page URLs"); + + return [ + ...new Set( + locations.map((location) => { + const url = new URL(location); + if ( + url.origin !== root.origin || + !url.pathname.startsWith(root.pathname) || + url.username || + url.password + ) { + throw new Error(`Sitemap URL is outside the preview base: ${location}`); + } + return url.href; + }), + ), + ]; +} + +export function formatSummary( + pages: { url: string; violations: number; incomplete: number; error: string | null }[], + error: string | null, +): string { + const rows = pages.map((page) => { + const pathname = new URL(page.url).pathname.replaceAll("|", "%7C"); + return `| \`${pathname}\` | ${page.violations} | ${page.incomplete} | ${page.error ? "ERROR" : page.violations ? "FAIL" : "PASS"} |`; + }); + return [ + "## Accessibility audit — axe-core / WCAG 2.2 AA", + "", + `Pages scanned: ${pages.length}. ${error ? "**Audit failed: see the JSON report and job logs.**" : ""}`, + "", + "| Page | Violations | Incomplete rules | Status |", + "| --- | ---: | ---: | --- |", + ...rows, + "", + "Incomplete results require manual review; zero violations is not WCAG certification.", + "Full rule details, affected nodes and operational errors are in `a11y-report.json`.", + "", + ].join("\n"); +} diff --git a/scripts/a11y.pw.ts b/scripts/a11y.pw.ts new file mode 100644 index 0000000..a177f47 --- /dev/null +++ b/scripts/a11y.pw.ts @@ -0,0 +1,133 @@ +import { test, expect } from "@playwright/test"; +import { spawn } from "node:child_process"; +import { createServer } from "node:http"; +import { mkdtemp, readFile, rm } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join, resolve } from "node:path"; + +const script = resolve("scripts/a11y.ts"); + +for (const scenario of [ + "pass", + "violation", + "empty", + "malformed", + "missing-loc", + "outside", + "http-error", + "sitemap-error", + "sitemap-redirect", +]) { + test(`audit harness: ${scenario}`, async () => { + const directory = await mkdtemp(join(tmpdir(), "gcs-a11y-")); + const server = createServer((request, response) => { + if (request.url === "/docs/sitemap.xml" && scenario === "sitemap-redirect") { + response.writeHead(301, { Location: "/docs/moved-sitemap.xml" }).end(); + } else if (request.url === "/docs/sitemap.xml" || request.url === "/docs/moved-sitemap.xml") { + response.setHeader("Content-Type", "application/xml"); + if (scenario === "sitemap-error") { + response.writeHead(500).end("Unavailable"); + } else if (scenario === "malformed") { + response.end(""); + } else if (scenario === "empty") { + response.end(""); + } else if (scenario === "missing-loc") { + response.end(""); + } else { + const origin = `http://${request.headers.host}`; + const urls = + scenario === "outside" + ? [`${origin}/outside.html`] + : [`${origin}/docs/page.html`, `${origin}/docs/second.html`]; + response.end( + `${urls.map((url) => `${url}`).join("")}`, + ); + } + } else if (scenario === "http-error") { + response.writeHead(404).end("Not found"); + } else { + response.setHeader("Content-Type", "text/html"); + response.end(`Audit fixture +

Audit fixture

+ ${scenario === "violation" ? '' : "

Accessible content

"} +
`); + } + }); + + try { + await new Promise((ready) => server.listen(0, "127.0.0.1", ready)); + const address = server.address(); + if (!address || typeof address === "string") throw new Error("Missing fixture server port"); + const logs: string[] = []; + const child = spawn("bun", [script], { + cwd: directory, + env: { + ...process.env, + PLAYWRIGHT_BASE_URL: `http://127.0.0.1:${address.port}`, + VITEPRESS_BASE: "/docs/", + }, + stdio: ["ignore", "pipe", "pipe"], + }); + child.stdout.on("data", (chunk) => logs.push(chunk.toString())); + child.stderr.on("data", (chunk) => logs.push(chunk.toString())); + const code = await new Promise((done, reject) => { + child.once("error", reject); + child.once("close", done); + }); + expect(code, logs.join("")).toBe(scenario === "pass" ? 0 : 1); + const report = JSON.parse(await readFile(join(directory, "a11y-report.json"), "utf8")); + const summary = await readFile(join(directory, "a11y-report.md"), "utf8"); + expect(summary).toContain("not WCAG certification"); + if (scenario === "pass" || scenario === "violation" || scenario === "http-error") { + expect(report.pages).toHaveLength(2); + expect(report.error).toBeNull(); + if (scenario === "violation") { + expect( + report.pages[0].results.violations.map((rule: { id: string }) => rule.id), + ).toContain("image-alt"); + expect(summary).toContain("FAIL"); + } else if (scenario === "http-error") { + expect(report.pages[0].error).toContain("HTTP 404"); + expect(summary).toContain("ERROR"); + } else { + expect( + report.pages.every( + (page: { results: { violations: unknown[] } }) => + page.results.violations.length === 0, + ), + ).toBe(true); + expect(summary).toContain("PASS"); + } + } else { + expect(report.pages).toHaveLength(0); + expect(report.error).toBeTruthy(); + expect(summary).toContain("**Audit failed:"); + } + } finally { + await new Promise((done, reject) => + server.close((error) => (error ? reject(error) : done())), + ); + await rm(directory, { recursive: true, force: true }); + } + }); +} + +test("search remains named at desktop and mobile widths", async ({ page }) => { + const base = process.env.VITEPRESS_BASE ?? "/github-code-search/"; + await page.goto(base); + for (const width of [1280, 375, 1280]) { + await page.setViewportSize({ width, height: 800 }); + await expect(page.locator(".DocSearch")).toHaveAccessibleName(/Search/); + await expect(page.locator(".DocSearch")).not.toHaveAttribute("aria-label"); + } +}); + +test("Mermaid containers retain permitted labels after client navigation", async ({ page }) => { + const base = process.env.VITEPRESS_BASE ?? "/github-code-search/"; + await page.goto(`${base}architecture/overview.html`); + const diagram = page.locator(".mermaid[aria-label]").first(); + await expect(diagram).toHaveAttribute("role", "group"); + await page.getByRole("link", { name: "Containers (C4 L2)", exact: true }).first().click(); + await expect(page.getByRole("heading", { level: 1, name: "Level 2: Containers" })).toBeVisible(); + await expect(page.locator(".mermaid[aria-label]").first()).toHaveAttribute("role", "group"); +}); diff --git a/scripts/a11y.ts b/scripts/a11y.ts new file mode 100644 index 0000000..1535629 --- /dev/null +++ b/scripts/a11y.ts @@ -0,0 +1,162 @@ +import AxeBuilder from "@axe-core/playwright"; +import { chromium, type Browser, type Page } from "@playwright/test"; +import { formatSummary, validateSitemapUrls, WCAG_TAGS } from "./a11y-utils.ts"; +import { availableParallelism } from "node:os"; + +async function readSitemap(page: Page, base: string): Promise { + const url = new URL("sitemap.xml", base).href; + // Redirects are not followed: a moved sitemap must fail like any other non-2xx. + const response = await page.request.get(url, { maxRedirects: 0, timeout: 30_000 }); + if (!response.ok()) throw new Error(`HTTP ${response.status()} on ${url}`); + const locations = await page.evaluate( + (xml) => { + const doc = new DOMParser().parseFromString(xml, "application/xml"); + if (doc.querySelector("parsererror") || doc.documentElement.localName !== "urlset") { + throw new Error("Expected a valid sitemap urlset"); + } + return Array.from(doc.documentElement.children).map((entry) => { + const location = entry.querySelector("loc")?.textContent?.trim(); + if (entry.localName !== "url" || !location) throw new Error("Invalid sitemap URL entry"); + return location; + }); + }, + await response.text(), + ); + return validateSitemapUrls(locations, base); +} + +// `vitepress preview` Brotli-compresses on every request (~5 s for the 4 MB Mermaid +// chunk). Encoding is irrelevant to axe, so request raw bytes. +const newAuditContext = (browser: Browser) => + browser.newContext({ + reducedMotion: "reduce", + extraHTTPHeaders: { "Accept-Encoding": "identity" }, + }); + +async function auditPage(page: Page, url: string) { + let timer: ReturnType | undefined; + try { + const results = await Promise.race([ + (async () => { + const response = await page.goto(url, { waitUntil: "load", timeout: 30_000 }); + if (!response?.ok()) throw new Error(`HTTP ${response?.status()} on ${url}`); + if (page.url() !== url) throw new Error(`Unexpected redirect: ${url} -> ${page.url()}`); + await page.locator("#VPContent").waitFor(); + await page.waitForFunction(() => { + const app = document.getElementById("app"); + return app && "__vue_app__" in app; + }); + // Mermaid is lazy-loaded: wait until every visible diagram is rendered. + // Off-screen blocks stay as source until scrolled into view (200px margin). + await page.waitForFunction( + () => + !document.querySelector(".mermaid-rendering") && + Array.from(document.querySelectorAll("div.language-mermaid")).every( + (block) => + block.querySelector(".mermaid svg") || + (block.querySelector("pre[data-mermaid-processed]") && + block.getBoundingClientRect().top >= innerHeight + 200), + ), + ); + await page.evaluate(() => document.fonts.ready); + // options() replaces runOnly, so it must come before withTags(). + return new AxeBuilder({ page }) + .options({ resultTypes: ["violations", "incomplete"] }) + .withTags(WCAG_TAGS) + .analyze(); + })(), + new Promise((_, reject) => { + timer = setTimeout(() => reject(new Error(`Audit timed out on ${url}`)), 60_000); + }), + ]); + return { url, results, error: null }; + } catch (error) { + return { url, results: null, error: String(error) }; + } finally { + clearTimeout(timer); + } +} + +async function runAudit(): Promise { + const started = performance.now(); + const pages: Awaited>[] = []; + let browser: Browser | undefined; + let error: string | null = null; + try { + const origin = process.env.PLAYWRIGHT_BASE_URL ?? "http://localhost:4173"; + const base = new URL(process.env.VITEPRESS_BASE ?? "/github-code-search/", origin).href; + // Validate before requesting the sitemap, including the trailing slash. + validateSitemapUrls([base], base); + const activeBrowser = await chromium.launch({ + executablePath: process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH, + }); + browser = activeBrowser; + const discovery = await browser.newPage(); + const urls = await readSitemap(discovery, base); + await discovery.close(); + let next = 0; + // One context per worker: navigations reuse the HTTP and V8 code caches + // (~4x faster page loads). A fresh context replaces it after any error. + const worker = async () => { + let context = await newAuditContext(activeBrowser); + let page = await context.newPage(); + while (next < urls.length) { + const url = urls[next++]; + const result = await auditPage(page, url); + pages.push(result); + if (result.error) { + await context.close().catch(() => {}); + context = await newAuditContext(activeBrowser); + page = await context.newPage(); + } + console.log( + `${result.error ? "ERROR" : result.results?.violations.length ? "FAIL" : "PASS"} ${url}`, + ); + if (result.error) console.error(result.error); + for (const violation of result.results?.violations ?? []) { + console.error(`${violation.id}: ${violation.help} (${violation.helpUrl})`); + for (const node of violation.nodes) + console.error(` ${node.target}: ${node.failureSummary}`); + } + } + await context.close(); + }; + await Promise.all( + Array.from({ length: Math.min(4, availableParallelism(), urls.length) }, worker), + ); + } catch (failure) { + error = String(failure); + console.error(error); + } finally { + try { + await browser?.close(); + } catch (failure) { + error = String(failure); + console.error(error); + } + pages.sort((a, b) => a.url.localeCompare(b.url)); + const durationMs = Math.round(performance.now() - started); + const summary = formatSummary( + pages.map((page) => ({ + url: page.url, + violations: page.results?.violations.length ?? 0, + incomplete: page.results?.incomplete.length ?? 0, + error: page.error, + })), + error, + ); + await Bun.write( + "a11y-report.json", + JSON.stringify({ tags: WCAG_TAGS, durationMs, error, pages }, null, 2), + ); + await Bun.write("a11y-report.md", summary); + console.log(`Audited ${pages.length} pages in ${(durationMs / 1000).toFixed(2)}s`); + } + return error || + pages.length === 0 || + pages.some((page) => page.error || page.results?.violations.length) + ? 1 + : 0; +} + +if (import.meta.main) process.exitCode = await runAudit();