build(lint): add ESLint 9 + eslint-plugin-react-hooks/react-refresh (R1)

There was no linting at all — nothing enforced rules-of-hooks (conditional-hook
crashes) or exhaustive-deps (stale closures) across 125 client components/pages.
Added an ESLint flat config (eslint.config.mjs) scoped to client/, an 'npm run
lint' script, and the devDeps. First run: 0 rules-of-hooks violations (good),
6 errors + 13 exhaustive-deps warnings to work through next.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
null 2026-07-03 19:41:24 -05:00
parent 38417ad8da
commit c679022592
3 changed files with 980 additions and 0 deletions

36
eslint.config.mjs Normal file
View File

@ -0,0 +1,36 @@
import js from '@eslint/js';
import globals from 'globals';
import reactHooks from 'eslint-plugin-react-hooks';
import reactRefresh from 'eslint-plugin-react-refresh';
// Flat config scoped to the React client. The point is enforcement of the two
// rules that catch real React bugs — rules-of-hooks (conditional hooks) and
// exhaustive-deps (stale closures) — which nothing previously checked. Server
// code (CommonJS Node) is out of scope here; `npm run check:server` covers it.
export default [
{ ignores: ['dist/**', 'node_modules/**', 'coverage/**', 'client/**/*.test.*'] },
{
files: ['client/**/*.{js,jsx}'],
languageOptions: {
ecmaVersion: 2023,
sourceType: 'module',
globals: { ...globals.browser },
parserOptions: { ecmaFeatures: { jsx: true } },
},
plugins: {
'react-hooks': reactHooks,
'react-refresh': reactRefresh,
},
rules: {
...js.configs.recommended.rules,
// The two that matter most for correctness:
'react-hooks/rules-of-hooks': 'error',
'react-hooks/exhaustive-deps': 'warn',
// HMR safety (Vite fast-refresh); allow non-component constant exports.
'react-refresh/only-export-components': ['warn', { allowConstantExport: true }],
// Keep the js-recommended noise as warnings so hook signal isn't drowned out.
'no-unused-vars': ['warn', { varsIgnorePattern: '^[A-Z_]', argsIgnorePattern: '^_' }],
'no-empty': ['warn', { allowEmptyCatch: true }],
},
},
];

938
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -9,6 +9,7 @@
"dev": "concurrently \"npm run dev:api\" \"npm run dev:ui\"", "dev": "concurrently \"npm run dev:api\" \"npm run dev:ui\"",
"build": "vite build", "build": "vite build",
"check:server": "find server.js db middleware routes services utils -name '*.js' -print0 | xargs -0 -n1 node --check", "check:server": "find server.js db middleware routes services utils -name '*.js' -print0 | xargs -0 -n1 node --check",
"lint": "eslint client",
"check": "npm run check:server && npm run build", "check": "npm run check:server && npm run build",
"test": "node --test tests/*.test.js", "test": "node --test tests/*.test.js",
"test:client": "vitest run", "test:client": "vitest run",
@ -64,11 +65,16 @@
}, },
"devDependencies": { "devDependencies": {
"@axe-core/playwright": "^4.10.1", "@axe-core/playwright": "^4.10.1",
"@eslint/js": "^9.39.4",
"@playwright/test": "^1.50.1", "@playwright/test": "^1.50.1",
"@testing-library/react": "^16.3.2", "@testing-library/react": "^16.3.2",
"@vitejs/plugin-react": "^4.3.3", "@vitejs/plugin-react": "^4.3.3",
"autoprefixer": "^10.4.20", "autoprefixer": "^10.4.20",
"concurrently": "^9.1.0", "concurrently": "^9.1.0",
"eslint": "^9.39.4",
"eslint-plugin-react-hooks": "^5.2.0",
"eslint-plugin-react-refresh": "^0.4.26",
"globals": "^17.7.0",
"jsdom": "^29.1.1", "jsdom": "^29.1.1",
"postcss": "^8.4.47", "postcss": "^8.4.47",
"tailwindcss": "^3.4.14", "tailwindcss": "^3.4.14",