fix: let npm run lint join CI - #324
Conversation
`npm run lint` is `prettier --check . && eslint .`, and the prettier half fails on `main`. This is `npm run format` and nothing else -- reflow only, no token changes -- kept as its own commit so it does not bury the eslint fixes that follow. Refs #316 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`npm run lint` is `prettier --check . && eslint .`; the previous commit fixed the prettier half, this fixes the eslint half. Scope note: `paths.base` is unset in svelte.config.js, so `resolve()` is identity today and none of this changes behaviour. It is NOT a claim that the app is base-path correct -- ~30 sites the rule cannot see (`redirect()` in load functions, `window.location.href` in api.ts, `window.open` in the row click handlers) still emit unresolved absolute paths. This clears the flagged sites so the gate can go green; base-path support is a separate job. ## svelte/prefer-svelte-reactivity (3, all in overview-tab) All three were false positives, not the latent staleness bugs #316 suspected. The rule does no escape or scope analysis: it flags any `new Map()` / `new URLSearchParams()` on which `set`/`delete`/`clear` is called anywhere in a `.svelte` file. All three instances are function-local scratch -- two inside `$derived.by` callbacks that return arrays, one inside a pure function returning a string -- so a reactive SvelteMap would only add proxying to paths that re-run on every search keystroke. Two sites drop the mutation instead, which is both simpler and satisfies the rule honestly: `projectOptions` builds its Map from entries, and `instanceHref` folds its conditional search param into the constructor. The third genuinely needs get-or-create and carries a disable with the reason. ## svelte/no-navigation-without-resolve (7) Four were real, and each needed a different shape: - root + connection pages: `goto('/setup')` is a static route, so it just takes `resolve()`. - dashboards `setServerScope`: was re-emitting `url.pathname` read back from `window.location`, which already carries the base path -- wrapping that in `resolve()` would prepend it twice. This function only ever runs on /dashboards, so it resolves the route id instead. The `let` split is forced: the rule follows an identifier to its declarator but not through a `+` chain. - finish-setup: `returnTo` is a concrete path from the SSO handoff, and there is no safe `resolve()` form for it -- `resolve()` substitutes `[params]`, and `resolve_route('/a[b]c', undefined)` throws `TypeError: Cannot read properties of undefined (reading 'b')` (verified against the installed kit 2.49.2). So it prefixes `base` directly and carries a disable explaining why. Note `gotoHref`/`resolveHref` in utils/navigation and utils/links have this same latent crash, which already reaches login/+page.svelte:94 and auth/callback/+page.svelte:45; that is pre-existing and left for its own change. The three `href` errors in overview-tab were false positives -- both helpers already resolved internally, but the rule only recognises a literal `resolve()` at the link site. These now use `resolveHref` from utils/links behind the `{...{ href }}` spread, the idiom six other `<a>` elements already use for a helper-built href, with the helpers returning unresolved paths so resolution happens exactly once. A plain `href={instanceHref(server)}` still errors, so this does not quietly widen what the rule accepts. Closes #316 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two things this repo's own drift is traceable to. `npm run lint` was missing from the Development Commands table, which lists dev/build/check only. An agent following CLAUDE.md runs `npm run check` -- which passes on unformatted, lint-failing code -- and never learns the gate exists. That is how 11 unformatted files and 10 eslint errors accumulated. The "Adding a New Frontend Page" template taught `catch (e: any)`, which trips @typescript-eslint/no-explicit-any; #314 introduced one by following it. No page actually does this -- it is `catch (e)` 93 times and `catch (e: unknown)` 31 times, reading status and message through getErrorStatus/getErrorMessage from $lib/utils/errors. The template now shows that, matching e.g. routes/tasks/[task]/+page.svelte. Refs #316 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Filed the pre-existing The |
|
Folded into #315 and closing. #315 is now retargeted to Keeping both open would reproduce the exact problem raised on #315: two PRs against Verified on the #315 branch just now, in |
Closes #316.
npm run lint(prettier --check . && eslint .) exits 1 onmain. This makes it exit 0 so it can become a CI gate — #315 is stacked on this branch and adds the step.What was actually there
The issue expected the three
svelte/prefer-svelte-reactivityerrors to be latent staleness bugs — plainMap/URLSearchParamsmutated under runes, so the UI never updates. They aren't.prefer-svelte-reactivity.jsdoes no escape or scope analysis: it flags anynew Map()/new URLSearchParams()on whichset/delete/clearis called anywhere in a.sveltefile. All three instances are function-local scratch — two inside$derived.bycallbacks that return arrays, one inside a pure function returning a string.So reaching for
SvelteMapwould have added reactive proxying to paths that re-run on every search keystroke, while implying a requirement that isn't there. Two sites drop the mutation instead (build theMapfrom entries; fold the conditional search param into the constructor) and the third, which genuinely needs get-or-create, carries a disable with the reason.Three of the seven
no-navigation-without-resolveerrors were false positives too — bothoverview-tabhelpers already resolved internally, but the rule only recognises a literalresolve()at the link site. Those now useresolveHrefbehind the{...{ href }}spread, the idiom six other<a>elements already use. A plainhref={instanceHref(server)}still errors, so this doesn't quietly widen what the rule accepts.The four real ones
resolve()setServerScopeurl.pathnameread back fromwindow.location, which already carries the base path —resolve()would prepend it twice. Resolves the route id instead.finish-setupis the one worth review time.returnTois a concrete path from the SSO handoff, and there is no saferesolve()form for it —resolve()substitutes[params], andresolve_route('/a[b]c', undefined)throwsTypeError: Cannot read properties of undefined (reading 'b')(verified against the installed kit 2.49.2). It prefixesbasedirectly with a disable explaining why.gotoHref/resolveHrefhave this same latent crash, already reachable via user-suppliedreturnToatlogin/+page.svelte:94andauth/callback/+page.svelte:45. Pre-existing, not touched here — I'll file it separately. Note that passing{}for params does not fix it; it just becomesMissing parameter.Scope
paths.baseis unset insvelte.config.js, soresolve()is identity and nothing here changes runtime behaviour. This is explicitly not a claim that the app is base-path correct — roughly 30 sites the rule cannot see (redirect()in load functions,window.location.hrefinapi.ts,window.openin the row click handlers) still emit unresolved absolute paths. That's a separate job.CLAUDE.md
Two entries traceable to this drift:
npm run lintwas missing from the command table (an agent runsnpm run check, which passes on lint-failing code, and never learns the gate exists), and the "Adding a New Frontend Page" template taughtcatch (e: any)— which tripsno-explicit-any, and which #314 introduced by following it. No page actually does that; it'scatch (e)93× andcatch (e: unknown)31× via the$lib/utils/errorshelpers.Verification
npm run lint0 ·npm run check0 errors (12 warnings, unchanged) ·npm run test25 passed ·npm run buildok.The prettier commit is separate and is
npm run formatoutput only — reflow, no token changes.🤖 Generated with Claude Code