import js from "@eslint/js"; import tseslint from "typescript-eslint"; import reactHooks from "eslint-plugin-react-hooks"; import reactRefresh from "eslint-plugin-react-refresh"; import prettier from "eslint-config-prettier"; import globals from "globals"; // Flat config (ESLint 9). Deliberately NARROW: the high-value rule here is // react-hooks/exhaustive-deps — 33 suppressions across the app exist for it, and until this // sprint nothing ran to make them mean anything. tsc already owns unused vars, undefined names // and types (noUnusedLocals/Parameters + strict), so typescript-eslint's non-type-checked // `recommended` is layered on but its overlap with tsc is turned off below to avoid double-reporting // and a churny diff. `eslint-config-prettier` is LAST so formatting rules never fight Prettier. // // reportUnusedDisableDirectives: a `// eslint-disable` that no longer suppresses anything is itself // an error — that is what keeps inert disable comments from re-accumulating (the S1b mandate). // E22 S4: every way out of the browser goes through the one module that LOGS it — a request line // with a request id (lib/http.ts), the upload XHR (lib/api/core.ts), the messages socket // (lib/messagesSocket.ts). A bare `fetch` elsewhere is a request the client log never sees, which is // exactly how the baseline survey found four. Each owner is exempt from its OWN global only. const NETWORK_OWNERS = { fetch: "src/lib/http.ts", XMLHttpRequest: "src/lib/api/core.ts", WebSocket: "src/lib/messagesSocket.ts", EventSource: null, }; const networkGlobals = (exempt) => Object.entries(NETWORK_OWNERS) .filter(([name]) => name !== exempt) .map(([name, owner]) => ({ name, message: owner ? `Use the logged request layer: \`${name}\` belongs to ${owner} only (E22 S4 — a request outside it never reaches the client log).` : `No \`${name}\` without a logged owner module (E22 S4).`, })); const networkProperties = [ { object: "window", property: "fetch", message: "Use lib/http.ts `send` (E22 S4)." }, { object: "globalThis", property: "fetch", message: "Use lib/http.ts `send` (E22 S4)." }, { object: "navigator", property: "sendBeacon", message: "Use lib/api/core.ts `beacon` (E22 S4).", }, ]; // A `//` or `/* */` inside JSX children is not a comment — it is TEXT, rendered on the page. One // slipped in when a return was wrapped in a fragment (Playlists.tsx) and only a visual check caught // it: tsc, eslint and every test stayed green. `eslint-plugin-react` has this rule, but it would bring // ~1800 lockfile lines of dependencies for ten lines of logic, so it lives here. const jsxCommentText = { meta: { type: "problem", messages: { text: "A comment inside JSX children renders as text. Wrap it in {/* */}." }, }, create(context) { return { JSXText(node) { if (/^\s*(\/\/|\/\*)/m.test(node.value)) context.report({ node, messageId: "text" }); }, }; }, }; // A raw `