Skip to content
Open
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: 8 additions & 0 deletions .github/workflows/lint-and-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,14 @@ jobs:
uses: ./.github/actions/setup-node
- name: Check Types
run: pnpm run check
check-a11y:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Checkout and Setup Node
uses: ./.github/actions/setup-node
- name: Assert no Svelte a11y warnings
run: pnpm run check:a11y
unit-tests:
runs-on: ubuntu-latest
steps:
Expand Down
13 changes: 12 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,18 @@ const { form, errors, enhance } = $derived(
3. **Test execution**: Run appropriate test suites based on changes
4. **Follow patterns**: Use existing component patterns and utility functions
5. **Design system**: Prefer Holocene components over custom implementations
6. **Accessibility**: Ensure proper ARIA attributes and semantic HTML
6. **Accessibility**: Enforced, not optional — see the Accessibility section below; run `pnpm check:a11y` before pushing UI changes.

## Accessibility

Svelte compiler accessibility warnings are gated as **CI errors** via `pnpm check:a11y` (the `check-a11y` job) — a PR that introduces an `a11y_*` warning fails. Run it locally before pushing.

- **Compose Holocene primitives; don't hand-roll interactive elements.** `Button`, `Input`, `Select`, `Combobox`, `Checkbox`, `RadioGroup`, `Tooltip`, `Modal`, `Drawer`, `Menu`, `Table`, `Accordion` own the a11y contract (name, role, keyboard, focus). A `<div role="button">`, `<tr onclick>`, or CSS `:after` "tooltip" is the top source of audit findings.
- **Every interactive control needs** an accessible name, a keyboard path (Enter/Space, Escape to dismiss), and a visible focus indicator. Icon-only buttons get an `aria-label`. Form controls get a real (non-empty) label; forward native `required`; set `aria-invalid`/`aria-describedby` on the focusable control.
- **Don't signal state by color alone; live regions must pre-exist empty; `<th>` needs `scope`; DOM order must equal visual order (no `flex-*-reverse`).**
- axe (Storybook `addon-a11y`, Playwright `tests/accessibility/`) is a floor — it can't see keyboard operability, focus order, or name quality. Keyboard-walk changes and verify announcements with a screen reader.

Audit/review conventions (manifest, PR labels, per-SC authoring & review guidance) live in the `winston-claude-skills` a11y skills.

## Code Generation

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
"format": "pnpm prettier:fix; pnpm eslint:fix; pnpm stylelint:fix",
"check": "VITE_TEMPORAL_UI_BUILD_TARGET=local svelte-check --tsconfig ./tsconfig.json --tsgo --incremental",
"check:watch": "VITE_TEMPORAL_UI_BUILD_TARGET=local svelte-check --tsconfig ./tsconfig.json --tsgo --incremental --watch",
"check:a11y": "node ./scripts/a11y/assert-no-a11y-warnings.mjs",
"prettier": "prettier --check --plugin prettier-plugin-svelte --plugin prettier-plugin-tailwindcss .",
"prettier:fix": "prettier --write --plugin prettier-plugin-svelte --plugin prettier-plugin-tailwindcss .",
"preview:local": "VITE_TEMPORAL_UI_BUILD_TARGET=local vite preview",
Expand Down
50 changes: 50 additions & 0 deletions scripts/a11y/assert-no-a11y-warnings.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#!/usr/bin/env node
// Gate: fail if svelte-check surfaces any Svelte accessibility warning (code `a11y_*`).
//
// Svelte emits a11y issues as compiler *warnings*, which svelte-check does not
// fail on by default. `--fail-on-warnings` can't be used here because the repo
// carries unrelated non-a11y warnings, and Svelte 5's `compilerOptions.warningFilter`
// only suppresses warnings, it can't escalate them. So this script runs svelte-check,
// keeps every other warning as-is, and fails *only* on `a11y_*` codes — turning
// accessibility warnings into a CI error without touching anything else.
import { spawnSync } from 'node:child_process';

const result = spawnSync(
'svelte-check',
['--tsconfig', './tsconfig.json', '--output', 'machine'],
{
encoding: 'utf8',
shell: true,
maxBuffer: 64 * 1024 * 1024,
env: { ...process.env, VITE_TEMPORAL_UI_BUILD_TARGET: 'local' },
},
);

const output = `${result.stdout ?? ''}${result.stderr ?? ''}`;

// Svelte 5 a11y warning codes are all `a11y_*` (e.g. a11y_missing_attribute) and
// appear in the diagnostic message / doc URL. Match the code specifically so a path
// segment like `scripts/a11y/` never produces a false positive.
const a11yDiagnostics = output
.split('\n')
.filter((line) => /\b(ERROR|WARNING)\b/.test(line) && /a11y_/.test(line));

if (a11yDiagnostics.length > 0) {
console.error(
`\n✖ ${a11yDiagnostics.length} Svelte accessibility warning(s) — a11y warnings are gated as errors:\n`,
);
for (const line of a11yDiagnostics) console.error(` ${line.trim()}`);
console.error(
'\nFix the accessibility issues above (prefer a Holocene primitive; see CLAUDE.md → Accessibility).\n',
);
process.exit(1);
}

if (result.status !== 0 && output.trim() === '') {
console.error(
'svelte-check produced no output and exited non-zero — cannot verify a11y state.',
);
process.exit(result.status ?? 1);
}

console.log('✓ svelte-check surfaced no Svelte a11y warnings.');
Loading