// ESLint flat config for the JellyTau frontend (Svelte 5 + TypeScript strict). // // TRACES: | DR-205 // // Scope: `src/` (the presentation layer), `scripts/` (build tooling), and the // root config files. The Rust backend is linted by clippy, not by this config. // // Formatting is NOT ESLint's job here — `eslint-config-prettier` is applied last // and switches off every stylistic rule that would fight `prettier`. Run // `bun run format` / `bun run format:check` for layout. import js from "@eslint/js"; import ts from "typescript-eslint"; import svelte from "eslint-plugin-svelte"; import globals from "globals"; import prettier from "eslint-config-prettier"; import svelteConfig from "./svelte.config.js"; export default ts.config( { // Kept in one place so `npx eslint .` and editor integrations agree. ignores: [ "node_modules/", ".svelte-kit/", // Scratch worktrees (git-ignored) hold full checkouts of this repo, // including their own generated .svelte-kit trees. Without this, `eslint .` // lints every in-flight branch and reports its generated code as ours. ".claude/", "build/", "dist/", "coverage/", "package/", "src-tauri/", // Generated by tauri-specta on every Rust build — never hand-edited, and // its shape is dictated by the Rust command definitions. "src/lib/api/bindings.ts", ], }, js.configs.recommended, ...ts.configs.recommended, ...svelte.configs.recommended, prettier, ...svelte.configs.prettier, { languageOptions: { globals: { ...globals.browser, ...globals.es2021, }, }, rules: { // 🔴 TEMPORARILY OFF. A parallel migration is moving all ~468 `console.*` // calls in `src/` onto a logger facade. Turning this on before that lands // would paint the tree red and collide with that work. // // 👉 Switch this to "error" (allowing nothing, or at most // `{ allow: ["warn", "error"] }`) once the logger-facade migration is // merged — that is the whole point of the rule being listed here. "no-console": "off", // Unused values are a real signal, but `_`-prefixed args are the // established way to say "this parameter exists for the signature". // // ⚠️ warn, not error: the tree carries ~94 genuinely dead bindings (stale // imports, `$state` left over from refactors, unused `catch (e)`). Every // one is a real finding, but fixing them here would mean ~50 unrelated // files in this tooling commit. Clear the backlog, then promote to // "error". "@typescript-eslint/no-unused-vars": [ "warn", { argsIgnorePattern: "^_", varsIgnorePattern: "^_", caughtErrorsIgnorePattern: "^_", destructuredArrayIgnorePattern: "^_", }, ], // Warn-only rules: each flags something real, but the existing tree has // more instances than can be fixed without swamping unrelated diffs. // Drive these to zero and promote them to "error" — do not delete them. // // `any` at the Tauri IPC boundary, mostly in code predating the // tauri-specta bindings (~25 sites outside tests). "@typescript-eslint/no-explicit-any": "warn", // Empty catch/if bodies that swallow an error. "no-empty": ["warn", { allowEmptyCatch: true }], // Prefer `import type` so type-only imports are erased cleanly by the // bundler instead of pulling a module in at run time. "@typescript-eslint/consistent-type-imports": "off", // Not applicable to this app (~130 hits, all no-ops). SvelteKit's // `resolve()` exists so hrefs keep working under a non-empty // `kit.paths.base`; JellyTau is an adapter-static SPA served from the // Tauri webview root and svelte.config.js sets no `base`. Re-enable this // the day a base path is introduced — the rule is otherwise correct. // (Declared here, not in the *.svelte block: `goto()` is also called from // plain .ts modules such as src/lib/utils/navigation.ts.) "svelte/no-navigation-without-resolve": "off", }, }, { // Svelte components: the parser needs the project's svelte.config.js so it // resolves preprocessors and Svelte 5 runes the same way the build does. files: ["**/*.svelte", "**/*.svelte.ts", "**/*.svelte.js"], languageOptions: { parserOptions: { parser: ts.parser, svelteConfig, }, }, rules: { // Warn-only — real findings, but each fix is a behavioural refactor that // does not belong in a tooling commit: // require-each-key keyed {#each} changes DOM reuse semantics // prefer-svelte-reactivity Set/Map -> SvelteSet/SvelteMap changes // reactivity, not just syntax // prefer-writable-derived $state + $effect -> writable $derived // no-at-html-tags {@html} sites need an XSS review each "svelte/require-each-key": "warn", "svelte/prefer-svelte-reactivity": "warn", "svelte/prefer-writable-derived": "warn", "svelte/no-at-html-tags": "warn", // Warn-only: this rule cannot see the Svelte *compiler's* warning set, so // it reports `` as unused when the compiler // may still be emitting the warning it suppresses. Verify against a real // `bun run check` before deleting any of them. "svelte/no-unused-svelte-ignore": "warn", }, }, { // Node-side tooling: build/test scripts and root config files run under // Bun/Node, not in the webview. files: [ "scripts/**/*.{ts,js}", "*.config.{ts,js}", "*.config.*.{ts,js}", "svelte.config.js", "eslint.config.js", ], languageOptions: { globals: { ...globals.node, }, }, }, { // Test files: vitest globals are enabled in vitest.config.ts. files: ["**/*.{test,spec}.{ts,js}", "src/test/**/*.{ts,js}"], languageOptions: { globals: { ...globals.node, ...globals.vitest, }, }, rules: { // Test doubles legitimately use `any` for partial mocks. "@typescript-eslint/no-explicit-any": "off", // `vi.mock` factories are hoisted above the import graph, so a lazy // `require()` inside one is the documented escape hatch. "@typescript-eslint/no-require-imports": "off", // Several tests deliberately replay a production assignment sequence // (`currentStreamUrl = newStreamUrl; hasSeeked = false;`) to document the // `$effect` they stand in for. The "useless" write is the subject under // test, not dead code. "no-useless-assignment": "off", }, }, );