// For more info, see https://github.com/storybookjs/eslint-plugin-storybook#configuration-flat-config-format import storybook from "eslint-plugin-storybook"; // eslint-plugin-storybook optional: install if needed for Storybook-specific lint rules // import storybook from "eslint-plugin-storybook"; import js from '@eslint/js'; import typescript from '@typescript-eslint/eslint-plugin'; import typescriptParser from '@typescript-eslint/parser'; import react from 'eslint-plugin-react'; import reactHooks from 'eslint-plugin-react-hooks'; import reactRefresh from 'eslint-plugin-react-refresh'; import jsxA11y from 'eslint-plugin-jsx-a11y'; export default [js.configs.recommended, { files: ['**/*.{ts,tsx,js,jsx}'], languageOptions: { parser: typescriptParser, parserOptions: { ecmaFeatures: { jsx: true, }, ecmaVersion: 2022, sourceType: 'module', }, globals: { // Browser globals window: 'readonly', document: 'readonly', localStorage: 'readonly', sessionStorage: 'readonly', console: 'readonly', setTimeout: 'readonly', clearTimeout: 'readonly', setInterval: 'readonly', clearInterval: 'readonly', fetch: 'readonly', WebSocket: 'readonly', File: 'readonly', FormData: 'readonly', CustomEvent: 'readonly', Event: 'readonly', CloseEvent: 'readonly', MessageEvent: 'readonly', KeyboardEvent: 'readonly', HTMLElement: 'readonly', HTMLDivElement: 'readonly', HTMLInputElement: 'readonly', HTMLButtonElement: 'readonly', HTMLAnchorElement: 'readonly', HTMLParagraphElement: 'readonly', HTMLHeadingElement: 'readonly', HTMLTextAreaElement: 'readonly', HTMLSelectElement: 'readonly', HTMLImageElement: 'readonly', HTMLAudioElement: 'readonly', Element: 'readonly', Node: 'readonly', MouseEvent: 'readonly', Blob: 'readonly', FileReader: 'readonly', Image: 'readonly', global: 'readonly', NodeJS: 'readonly', Buffer: 'readonly', crypto: 'readonly', performance: 'readonly', require: 'readonly', process: 'readonly', // URL API globals URL: 'readonly', URLSearchParams: 'readonly', // DOM API globals DOMRect: 'readonly', DOMRectReadOnly: 'readonly', Headers: 'readonly', navigator: 'readonly', WindowEventMap: 'readonly', requestAnimationFrame: 'readonly', cancelAnimationFrame: 'readonly', Notification: 'readonly', NotificationOptions: 'readonly', NotificationPermission: 'readonly', IntersectionObserver: 'readonly', IntersectionObserverInit: 'readonly', MessageChannel: 'readonly', confirm: 'readonly', alert: 'readonly', // Web API globals AudioContext: 'readonly', AnalyserNode: 'readonly', MediaElementAudioSourceNode: 'readonly', HTMLIFrameElement: 'readonly', HTMLMediaElement: 'readonly', XMLHttpRequest: 'readonly', BinaryType: 'readonly', EventListenerOrEventListenerObject: 'readonly', ReadableStream: 'readonly', BeforeUnloadEvent: 'readonly', CryptoKey: 'readonly', btoa: 'readonly', atob: 'readonly', TextEncoder: 'readonly', TextDecoder: 'readonly', CanvasRenderingContext2D: 'readonly', MutationObserver: 'readonly', Window: 'readonly', Storage: 'readonly', TransformStream: 'readonly', // Service Worker globals self: 'readonly', caches: 'readonly', ServiceWorkerRegistration: 'readonly', Cache: 'readonly', CacheStorage: 'readonly', Response: 'readonly', Request: 'readonly', clients: 'readonly', // React globals React: 'readonly', // Test globals beforeAll: 'readonly', afterAll: 'readonly', afterEach: 'readonly', beforeEach: 'readonly', describe: 'readonly', it: 'readonly', test: 'readonly', expect: 'readonly', vi: 'readonly', vitest: 'readonly', waitFor: 'readonly', jest: 'readonly', AbortController: 'readonly', AbortSignal: 'readonly', BroadcastChannel: 'readonly', DOMException: 'readonly', atob: 'readonly', PerformanceNavigationTiming: 'readonly', PerformanceObserver: 'readonly', HTMLFormElement: 'readonly', HTMLTableElement: 'readonly', HTMLTableSectionElement: 'readonly', HTMLTableRowElement: 'readonly', HTMLTableCellElement: 'readonly', HTMLTableCaptionElement: 'readonly', HTMLSpanElement: 'readonly', HTMLCanvasElement: 'readonly', HTMLLabelElement: 'readonly', FileList: 'readonly', MediaQueryListEvent: 'readonly', IntersectionObserver: 'readonly', IntersectionObserverEntry: 'readonly', IntersectionObserverCallback: 'readonly', ResizeObserver: 'readonly', ResizeObserverEntry: 'readonly', HeadersInit: 'readonly', EventListener: 'readonly', }, }, plugins: { '@typescript-eslint': typescript, 'react': react, 'react-hooks': reactHooks, 'react-refresh': reactRefresh, 'jsx-a11y': jsxA11y, }, rules: { // TypeScript '@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_' }], '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/no-explicit-any': 'warn', '@typescript-eslint/no-non-null-assertion': 'warn', // React 'react/react-in-jsx-scope': 'off', 'react/prop-types': 'off', 'react-hooks/rules-of-hooks': 'error', 'react-hooks/exhaustive-deps': 'warn', 'react-refresh/only-export-components': [ 'warn', { allowConstantExport: true } ], // General 'no-console': 'off', 'no-debugger': 'error', 'prefer-const': 'error', 'no-var': 'error', 'object-shorthand': 'error', 'prefer-template': 'error', 'no-unused-vars': 'off', // Handled by @typescript-eslint/no-unused-vars 'no-undef': 'off', // TypeScript handles this; no-undef doesn't understand TS types (JSX, etc.) 'no-useless-escape': 'error', 'no-prototype-builtins': 'warn', // Typography: Enforce type scale usage // Warn on arbitrary text sizes in className strings (e.g., text-[10px], text-[9px]) // Note: SVG chart text (text-[2px], text-[1.5px]) may need exceptions - review case by case 'no-restricted-syntax': [ 'warn', { selector: "Literal[value=/text-\\[\\d+(\\.\\d+)?(px|rem)\\]/], TemplateElement[value.raw=/text-\\[\\d+(\\.\\d+)?(px|rem)\\]/]", message: 'Use type scale classes (text-xs, text-sm, text-base, text-lg, text-xl, text-2xl, text-3xl, text-4xl) instead of arbitrary sizes. SVG chart text (text-[2px], text-[1.5px]) may be an exception - add eslint-disable comment if needed.', }, // Spacing: Enforce spacing scale usage // Warn on arbitrary spacing values that don't follow 4px base scale // Valid scale values: 0, 1, 2, 3, 4, 5, 6, 8, 10, 12, 16, 20, 24 // Arbitrary values like gap-[7px], p-[9px], etc. should use scale values instead { selector: "Literal[value=/(gap-|p-|m-|px-|py-|mx-|my-|space-[xy]-)\\[\\d+(\\.\\d+)?(px|rem)\\]/], TemplateElement[value.raw=/(gap-|p-|m-|px-|py-|mx-|my-|space-[xy]-)\\[\\d+(\\.\\d+)?(px|rem)\\]/]", message: 'Use spacing scale classes (gap-0 through gap-24, p-0 through p-24, etc.) instead of arbitrary sizes. Valid scale values follow 4px base: 0, 1, 2, 3, 4, 5, 6, 8, 10, 12, 16, 20, 24. For exceptions, add eslint-disable comment.', }, // Colors: Prevent Tailwind default colors (use SUMI design system semantic tokens instead) // Warn on default Tailwind color classes: slate, gray, zinc, neutral, stone, red, orange, amber, yellow, lime, green, emerald, teal, cyan, sky, blue, indigo, violet, purple, fuchsia, pink, rose // Allow: semantic tokens (primary, secondary, destructive, success, warning, muted, etc.) and sumi-* tokens { selector: "Literal[value=/(text-|bg-|border-|ring-|outline-|divide-|placeholder-|from-|via-|to-|accent-|caret-|fill-|stroke-)(slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-(50|100|200|300|400|500|600|700|800|900|950)/], TemplateElement[value.raw=/(text-|bg-|border-|ring-|outline-|divide-|placeholder-|from-|via-|to-|accent-|caret-|fill-|stroke-)(slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-(50|100|200|300|400|500|600|700|800|900|950)/]", message: 'Use SUMI design system semantic tokens (primary, secondary, destructive, success, warning, muted, foreground, etc.) instead of Tailwind default colors. See apps/web/docs/DESIGN_TOKENS.md for token mapping. For exceptions (e.g., test files), add eslint-disable comment.', }, // Hex colors: Prevent literal hex colors in JS/TS strings (use tokens instead). // Matches strings like '#7c9dd6', '#fff', '#0d0d0fAA' — anywhere in code. // Use var(--sumi-*) in CSS contexts (JSX style props, template literals) // OR import { ColorXxx } from '@veza/design-system/tokens-generated' for canvas/runtime. // Exceptions: rgba()/hsla() (not # prefix), the design-system package itself (different rule scope). { selector: "Literal[value=/^#[0-9a-fA-F]{3,8}$/]", message: 'Hardcoded hex color literal. Use SUMI tokens: var(--sumi-*) for CSS strings (JSX style/className), or import {ColorVizIndigo, ColorMizuBase, ...} from \'@veza/design-system/tokens-generated\' for canvas/runtime contexts. Source of truth: packages/design-system/tokens/primitive/color.json. See CHARTE_GRAPHIQUE_TALAS.md §4.', }, // Components: Enforce Button component usage (prevent native button elements) // Warn on native