Drain the long-tail audit queue captured in alpha-uat-master.md.
- next-intl ripped out (zero useTranslations callers ever existed):
package.json, next.config.ts plugin wrap, src/i18n/, messages/, and
the layout NextIntlClientProvider all gone; <html lang="en"> hardcoded.
- RTL lint nudge added: warn-only no-restricted-syntax on physical
Tailwind utilities (ml-/mr-/pl-/pr-/text-left/text-right/border-l/
border-r/rounded-l-/rounded-r-) inside JSX className literals.
Existing ~1,000 sites grandfathered; new code trends toward logical.
- Icon-only button accessibility lint: jsx-a11y/control-has-associated-
label enabled at warn; 4 empty <th>/<td> action placeholders gain
sr-only labels.
- Currency: SUPPORTED_CURRENCIES drops the hardcoded English labels;
new currencyLabel(code, locale?) helper resolves via Intl.DisplayNames.
CurrencySelect + settings-manager migrated.
- Date locale sweep: 7 surfaces flip from toLocaleString('en-GB'|'en-US')
to toLocaleString(undefined, ...) so dates honour runtime locale.
- Dialog/Sheet width: 10 document/EOI/entity-form dialogs gain a
lg:max-w-4xl or lg:max-w-5xl step so wide desktops get breathing room.
- PaymentsSection collapsed-bar: slim one-line bar showing
"Payments - Not received yet" or "Payments - \$X received - N payments
- Expand"; per-interest collapse state persists in localStorage; the
RecordPayment flow auto-expands.
- muted-foreground opacity sweep: 10 text-bearing
text-muted-foreground/{60,70,80} hits dropped to plain
text-muted-foreground for AA contrast on muted bg. Icon-only
(aria-hidden) opacity hits left as-is.
- Micro-type bump: text-[10px] and text-[11px] -> text-xs (12px)
across 87 files in src/components + src/app. Pure mechanical sweep.
- Audit-doc cleanup: alpha-uat-master.md stale 2026-05-25 summary
rewritten with cumulative state through today. Items genuinely still
open are now a short long-tail list.
- New docs/marketing-site-followups.md: Umami Phase 4a/3/5, email
pixel E2E verification, and website-cutover work parked here so
they don't get lost in the CRM audit doc.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
132 lines
5.6 KiB
JavaScript
132 lines
5.6 KiB
JavaScript
import nextCoreWebVitals from 'eslint-config-next/core-web-vitals';
|
|
import prettier from 'eslint-config-prettier/flat';
|
|
|
|
const eslintConfig = [
|
|
...nextCoreWebVitals,
|
|
prettier,
|
|
{
|
|
// Scope the typescript-eslint rule overrides to TS/TSX files. Without
|
|
// the `files` filter, eslint flat-config attempts to apply these
|
|
// rules to every walked file (including root-level JS / mjs / json
|
|
// configs) and fails because the typescript-eslint plugin only
|
|
// registers itself for TS/TSX. Surfaced 2026-05-14 when CI's
|
|
// `pnpm lint` command ran across the whole repo root.
|
|
files: ['**/*.ts', '**/*.tsx'],
|
|
rules: {
|
|
'@typescript-eslint/no-explicit-any': 'error',
|
|
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
|
|
// React Compiler safety rules shipped with eslint-config-next@16 /
|
|
// react-hooks@7. Triage status (2026-05-13 sweep):
|
|
// purity, set-state-in-render, immutability, refs,
|
|
// set-state-in-effect — promoted to error after the cleanup
|
|
// sweep (Wave 3 of the 2026-05-12 audit). All hits migrated to
|
|
// either useQuery, render-phase derivation, key-based remount,
|
|
// or a justified eslint-disable for canonical setState-on-
|
|
// subscription patterns. New regressions block CI.
|
|
// incompatible-library — informational only ("Compiler
|
|
// skipped this file because of a non-Compiler-safe import").
|
|
// No action needed; silenced to keep `pnpm lint` output
|
|
// actionable.
|
|
'react-hooks/purity': 'error',
|
|
'react-hooks/set-state-in-render': 'error',
|
|
'react-hooks/immutability': 'error',
|
|
'react-hooks/refs': 'error',
|
|
'react-hooks/set-state-in-effect': 'error',
|
|
'react-hooks/incompatible-library': 'off',
|
|
// Icon-only buttons must carry a label that screen readers can
|
|
// surface — either an explicit `aria-label`, an `aria-labelledby`,
|
|
// a `title`, or a visible-but-sr-only text child. Catches the
|
|
// pattern where a `<button><Trash2 /></button>` ships with no
|
|
// accessible name. Default Next config enables this at `error`;
|
|
// we keep it loud so new code doesn't regress.
|
|
'jsx-a11y/control-has-associated-label': [
|
|
'warn',
|
|
{
|
|
labelAttributes: ['label'],
|
|
controlComponents: ['Button'],
|
|
ignoreElements: ['audio', 'canvas', 'embed', 'input', 'textarea', 'tr', 'video'],
|
|
ignoreRoles: [
|
|
'grid',
|
|
'listbox',
|
|
'menu',
|
|
'menubar',
|
|
'radiogroup',
|
|
'row',
|
|
'tablist',
|
|
'toolbar',
|
|
'tree',
|
|
'treegrid',
|
|
],
|
|
depth: 5,
|
|
},
|
|
],
|
|
},
|
|
},
|
|
{
|
|
// User-facing copy in src/components and src/app should never use
|
|
// em-dashes (—) in JSX text. The user reads em-dashes as a
|
|
// tell-tale "AI-generated" marker; we prefer periods, commas, or
|
|
// simple hyphens. Code comments / audit-log strings / templates
|
|
// outside these directories are exempt.
|
|
//
|
|
// Same rule block also nudges new code toward CSS logical properties
|
|
// (ms-/me-/ps-/pe-/text-start/text-end/border-s/border-e) instead of
|
|
// physical Tailwind utilities. RTL isn't a roadmap requirement today,
|
|
// but every new ml-/mr-/pl-/pr-/text-left/text-right we accept now
|
|
// is a class we'd have to migrate later. Existing 1,000+ sites stay
|
|
// untouched (warn-only). Inline `// eslint-disable-next-line` when
|
|
// the directional intent is truly physical (e.g. a chevron icon).
|
|
files: ['src/components/**/*.tsx', 'src/app/**/*.tsx'],
|
|
rules: {
|
|
// Both selectors share `warn` severity because the RTL nudge is
|
|
// grandfathered (1,000+ existing sites use ml-/mr-/etc). The
|
|
// em-dash sweep cleared every existing instance (2026-05-21), so
|
|
// `warn` still effectively gates new code — it just doesn't break
|
|
// CI on grandfathered RTL utilities. Inline
|
|
// `// eslint-disable-next-line no-restricted-syntax` when the
|
|
// directional intent is truly physical.
|
|
'no-restricted-syntax': [
|
|
'warn',
|
|
{
|
|
selector: "JSXText[value=/\\u2014/]",
|
|
message:
|
|
'No em-dash in user-facing JSX text. Use period, comma, or hyphen instead.',
|
|
},
|
|
{
|
|
selector:
|
|
"JSXAttribute[name.name='className'] > Literal[value=/(?:^|[\\s:])(?:ml-|mr-|pl-|pr-|text-left|text-right|border-l\\b|border-r\\b|rounded-l-|rounded-r-)/]",
|
|
message:
|
|
'Prefer CSS logical properties (ms-/me-/ps-/pe-/text-start/text-end/border-s/border-e/rounded-s-/rounded-e-) over physical directional Tailwind utilities. Existing code is grandfathered; new code should default to logical so a future RTL pass is bounded.',
|
|
},
|
|
],
|
|
},
|
|
},
|
|
{
|
|
// Tests assert response shape via expect() — narrowing every
|
|
// `res.json()` to a structural type adds boilerplate without catching
|
|
// bugs. Allow `any` casts at JSON boundaries in test files. Also
|
|
// relax unused-vars to warn (destructured-but-unused helpers are
|
|
// common in setup/teardown patterns).
|
|
files: ['tests/**/*.ts', 'tests/**/*.tsx'],
|
|
rules: {
|
|
'@typescript-eslint/no-explicit-any': 'off',
|
|
'@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_' }],
|
|
},
|
|
},
|
|
{
|
|
ignores: [
|
|
'client-portal/**',
|
|
'next-env.d.ts',
|
|
// Agent worktree artifacts — not part of the canonical tree.
|
|
'.claude/**',
|
|
// Build output + Next generated types
|
|
'.next/**',
|
|
'dist/**',
|
|
// Other sub-projects with their own toolchains
|
|
'website/**',
|
|
],
|
|
},
|
|
];
|
|
|
|
export default eslintConfig;
|