Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions .github/actions/start-preview-server/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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."
14 changes: 12 additions & 2 deletions .github/instructions/documentation.instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
3 changes: 3 additions & 0 deletions .github/instructions/github-actions.instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/<name>/action.yml`
Expand Down
40 changes: 35 additions & 5 deletions .github/skills/documentation.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,19 +53,48 @@ Dark mode: VitePress applies a `.dark` class on `<html>`. 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

Expand Down Expand Up @@ -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
```
3 changes: 2 additions & 1 deletion .github/skills/feature.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
```
33 changes: 31 additions & 2 deletions .github/skills/github-actions.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
```
Expand Down Expand Up @@ -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: <unique-workflow-id> # e.g. "lighthouse-ci", "coverage", "pa11y"
header: <unique-workflow-id> # e.g. "lighthouse-ci", "coverage", "accessibility-audit"
message: ${{ steps.report.outputs.result }}
```

Expand Down Expand Up @@ -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:

Expand Down
3 changes: 2 additions & 1 deletion .github/skills/release.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
```
108 changes: 86 additions & 22 deletions .github/workflows/a11y.yaml
Original file line number Diff line number Diff line change
@@ -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:
Expand All @@ -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.
Expand All @@ -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
Expand All @@ -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
Comment thread
Copilot marked this conversation as resolved.
Resolved

# 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
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ coverage/
.env
*.local

# pa11y accessibility testing results
# Accessibility audit reports
a11y-report.json
a11y-report.md

# Lighthouse CI
.lighthouseci/
Expand Down
17 changes: 0 additions & 17 deletions .pa11yci.json

This file was deleted.

Loading
Loading