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
10 changes: 9 additions & 1 deletion .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,12 @@ jobs:
run: |
npm --prefix engine/web ci --ignore-scripts
npm --prefix engine/web run build
- name: Lint dashboard client (eslint + jsx-a11y)
# JEF-499: the accessibility gate. eslint-plugin-jsx-a11y (recommended, mapped onto Preact
# JSX) fails the build on an a11y authoring regression in engine/web/src; the axe route-smoke
# (the runtime half) runs in the `test` job's `npm run test`. Deps were installed by the
# `npm ci --ignore-scripts` above.
run: npm --prefix engine/web run lint
- name: Format
run: cargo fmt --check
- name: Clippy
Expand Down Expand Up @@ -119,7 +125,9 @@ jobs:
- name: Dashboard client tests
# The v4 Preact client's offline unit + interaction tests (vitest + jsdom, ADR-0025 /
# JEF-397): the keyed reconcile, the state-preservation (JEF-351) acceptance test, client
# escaping, and the honesty empty-states. `npm ci` above installed the dev toolchain.
# escaping, the honesty empty-states, and the JEF-499 vitest-axe route-smoke (every top-level
# view + AuthGate + status strip asserted free of serious/critical axe violations — an a11y
# regression goes red here). `npm ci` above installed the dev toolchain.
run: npm --prefix engine/web run test
- name: Audit dependencies
run: |
Expand Down
84 changes: 84 additions & 0 deletions engine/web/eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// ESLint flat config (eslint 9) for the Preact dashboard client (JEF-499). The point of this config
// is an ACCESSIBILITY gate: eslint-plugin-jsx-a11y's recommended rules, mapped onto Preact JSX, so a
// PR that introduces an a11y regression fails `npm run lint` (wired into CI). jsx-a11y works on JSX
// generically — it inspects JSXElement/JSXAttribute nodes, so it lints our `.jsx` views without any
// React runtime; the stock espree parser handles JSX once `ecmaFeatures.jsx` is on.
//
// We layer `@eslint/js` recommended on top (dead code / real bugs), scoped with the right globals per
// area (browser for the client, vitest for tests, node for the build/config scripts) so `no-undef`
// never false-fires. No eslint-plugin-react: jsx-a11y needs no React plugin, and the engine is Preact.

import js from "@eslint/js";
import jsxA11y from "eslint-plugin-jsx-a11y";
import react from "eslint-plugin-react";
import globals from "globals";

// The vitest test globals (config sets `globals: true`, so tests call describe/it/expect/vi without
// importing them). Declared readonly so eslint's no-undef recognises them in the test tree.
const vitestGlobals = {
describe: "readonly",
it: "readonly",
test: "readonly",
expect: "readonly",
vi: "readonly",
beforeEach: "readonly",
afterEach: "readonly",
beforeAll: "readonly",
afterAll: "readonly",
};

export default [
{ ignores: ["dist/**", "node_modules/**"] },

// Base JS correctness everywhere we lint.
js.configs.recommended,

// The client source (browser runtime) + the a11y gate. jsx-a11y's recommended flat config brings
// the plugin registration and the 34 recommended rules. We ALSO register eslint-plugin-react for a
// single rule — `react/jsx-uses-vars` — so `no-unused-vars` sees a component referenced only in
// JSX (`<CoverageRow/>`) as used; without it core no-unused-vars false-fires on every view helper.
// We deliberately do NOT pull the full react ruleset (the engine is Preact, automatic JSX runtime).
{
...jsxA11y.flatConfigs.recommended,
files: ["src/**/*.{js,jsx}"],
plugins: { ...jsxA11y.flatConfigs.recommended.plugins, react },
languageOptions: {
ecmaVersion: 2024,
sourceType: "module",
parserOptions: { ecmaFeatures: { jsx: true } },
globals: { ...globals.browser },
},
rules: {
...jsxA11y.flatConfigs.recommended.rules,
"react/jsx-uses-vars": "error",
},
},

// Tests: same JSX + a11y gate (the axe route-smoke and view tests are .jsx), plus the vitest and
// browser (jsdom) globals.
{
...jsxA11y.flatConfigs.recommended,
files: ["test/**/*.{js,jsx}"],
plugins: { ...jsxA11y.flatConfigs.recommended.plugins, react },
languageOptions: {
ecmaVersion: 2024,
sourceType: "module",
parserOptions: { ecmaFeatures: { jsx: true } },
globals: { ...globals.browser, ...vitestGlobals },
},
rules: {
...jsxA11y.flatConfigs.recommended.rules,
"react/jsx-uses-vars": "error",
},
},

// The build + vitest config scripts run under node.
{
files: ["build.mjs", "vitest.config.js", "eslint.config.js"],
languageOptions: {
ecmaVersion: 2024,
sourceType: "module",
globals: { ...globals.node },
},
},
];
Loading