The repo configured four frontend gates and enforced one of them. eslint
and prettier ran in no workflow and no hook; `bun run check` ran only in
build-release.yml, so a type error could sit on master until somebody cut
a tag; and `bun run test:coverage` had been dead for months.
CI (build-and-test.yml) now runs format:check, lint, check and coverage
alongside the existing boundary and doc-link tripwires.
The coverage script failure was a version mismatch, not a config problem:
@vitest/coverage-v8 resolved to 4.1.10, whose peer range pins vitest
exactly, while package.json asked for ">=1.0.0 <5.0.0" and got 4.0.16 --
every run died on a missing BaseCoverageProvider export. The loose range
is what allowed the pair to drift, so it is now ^4.1.10.
Two ratchets, same policy as MIN_THRESHOLD in traceability-check.yml:
eslint --max-warnings=159 (0 errors; 159 is today's backlog, only
ever lower it)
vitest thresholds (statements 51 / branches 45 /
functions 46 / lines 52, measured at
54.6 / 48.7 / 49.6 / 55.1)
no-console is promoted from "off" to "error": the logger-facade
migration it was waiting on is finished -- 8 calls remained, 2 of them
real stragglers in the settings page, now on the facade the file already
imported. The sink itself, tests, and scripts/ are exempted; a CLI whose
stdout is the product is not a stray debug statement.
The threshold was verified to bite by raising it to 99 and watching the
run go red, not by assuming an unfailed gate works.
DR-205 moves to Done; the coverage gate is DR-215.
200 lines
7.8 KiB
JavaScript
200 lines
7.8 KiB
JavaScript
// 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: {
|
|
// The logger-facade migration this rule was waiting on is done: the ~468
|
|
// `console.*` calls that used to live in `src/` are gone, replaced by
|
|
// `createLogger(...)` from src/lib/utils/logger.ts (DR-204), which is now
|
|
// the single sink. Nothing is allowed through — not even warn/error —
|
|
// because the facade's own `warn`/`error` levels are always emitted, so a
|
|
// raw call has no capability the facade lacks. It only loses the scope tag
|
|
// and the runtime level control.
|
|
//
|
|
// The sink itself is exempted below, as are tests (a test that asserts on
|
|
// logging has to be able to talk about `console`).
|
|
"no-console": "error",
|
|
|
|
// 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 `<!-- svelte-ignore a11y_… -->` 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",
|
|
},
|
|
},
|
|
|
|
{
|
|
// The logging facade is the one place allowed to touch `console` — it *is*
|
|
// the sink every other module reaches it through (see the `no-console`
|
|
// comment above). `createLogger`'s `console[method](...)` dispatch is a
|
|
// computed member access, which the rule flags like any other.
|
|
files: ["src/lib/utils/logger.ts"],
|
|
rules: {
|
|
"no-console": "off",
|
|
},
|
|
},
|
|
|
|
{
|
|
// 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,
|
|
},
|
|
},
|
|
rules: {
|
|
// These are command-line tools (extract-traces, release-notes, ...) whose
|
|
// stdout IS the product — `bun run traces:markdown > docs/traceability.md`
|
|
// depends on it. The logging facade is a webview concern; a CLI printing
|
|
// its result is not a stray debug statement.
|
|
"no-console": "off",
|
|
},
|
|
},
|
|
|
|
{
|
|
// 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: {
|
|
// Tests are allowed to talk about `console` — several spy on it to assert
|
|
// what the logging facade emits, and scripts/ tooling tests capture output.
|
|
"no-console": "off",
|
|
// 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",
|
|
},
|
|
},
|
|
);
|