Verification tools
This course has an in-browser playground for Svelte itself (below), but the only way to prove a claim about a project’s generated code — a real build failure, a real lint config’s actual coverage, a real test — is a real SvelteKit probe, a real svelte-check/eslint run, or a real vitest run. Every output below is real, captured against Svelte 5.57.1 and SvelteKit 2.70.3 in this course’s own probe. None of these tools overlap perfectly, and none is close to complete on its own — the AI Bug Catalog’s “Caught by” line for each of its 28 entries names exactly which one (if any) of the tools below would have caught it.
svelte-check — the fast, first pass
Section titled “svelte-check — the fast, first pass”svelte-check runs the compiler’s own type-checking and diagnostics against every .svelte/.ts/.js file in a project. It catches every hard compile error in this module’s catalog immediately — a missing $bindable(), export let/$: mixed with a rune, a scoped $store subscription, a number-input value treated as a string, a missing experimental.async flag — but it has no way to see behavioral bugs (a stale effect mirror, a keyless {#each}, a swallowed redirect()) at all, since those are runtime facts, not type or syntax facts.
npx svelte-check --tsconfig ./tsconfig.jsonsrc/lib/TogglePanel.svelte:5:8Cannot use 'bind:' with this property. It is declared as non-bindable insidethe component.To mark a property as bindable: 'let { open = $bindable() } = $props()'
src/lib/NumberBug.svelte:7:16Property 'trim' does not exist on type 'number'.Both are real, current errors under Svelte 5.57.1/TypeScript — not style nitpicks. See the AI Bug Catalog, entries 15 and 18.
eslint-plugin-svelte — real coverage, and a real gap in recommended
Section titled “eslint-plugin-svelte — real coverage, and a real gap in recommended”eslint-plugin-svelte’s parser (svelte-eslint-parser) understands Svelte syntax, but by itself it does not surface the compiler’s own deprecation/error diagnostics as lint findings — that specific job belongs to one rule, svelte/valid-compile, which is not included in the recommended config at all:
import js from '@eslint/js';import svelte from 'eslint-plugin-svelte';
export default [ js.configs.recommended, ...svelte.configs.recommended,];npx eslint src/lib/OnColon.svelte # <button on:click={...}> — a real deprecation(no output — exit code 0)Verified directly against the installed [email protected]: recommended’s rule set has no entry for svelte/valid-compile at all — only the all config includes it, at severity error. Adding it explicitly is a one-line change:
import js from '@eslint/js';import svelte from 'eslint-plugin-svelte';
export default [ js.configs.recommended, ...svelte.configs.recommended, { rules: { 'svelte/valid-compile': 'error' } },];npx eslint src/lib/OnColon.svelte4:9 error Using `on:click` to listen to the click event is deprecated. Use the event attribute `onclick` insteadhttps://svelte.dev/e/event_directive_deprecated(event_directive_deprecated) svelte/valid-compileThe same rule, on a file mixing export let with a rune (the AI Bug Catalog’s entry 2):
2:3 error Cannot use `export let` in runes mode — use `$props()` insteadhttps://svelte.dev/e/legacy_export_invalid(legacy_export_invalid) svelte/valid-compilesvelte/valid-compile is quite literally the compiler’s own diagnostics, surfaced as ESLint findings at edit time — but only once it’s actually enabled. A project that installs eslint-plugin-svelte and stops at recommended has not turned this on, verified above with a real zero-finding run.
svelte/compiler’s compile(), $inspect, and $inspect.trace
Section titled “svelte/compiler’s compile(), $inspect, and $inspect.trace”The exact same function that powers svelte-check is a plain, callable API — compile(source, options) — usable directly in a script or a test, which is how several entries in the AI Bug Catalog (2, 3, 16) capture their errors/warnings without needing a full project on disk:
import { test, expect } from 'vitest';import { compile } from 'svelte/compiler';
test('compile() throws the exact same error svelte-check reports, callable directly', () => { const source = `<script> let count = $state(0); export let label;</script><p>{label}: {count}</p>`; expect(() => compile(source, { filename: 'Bad.svelte' })).toThrowError( /Cannot use `export let` in runes mode/, );});✓ compile() throws the exact same error svelte-check reports, callable directlyFor debugging a specific component’s reactivity while it runs rather than checking its source statically, $inspect(value) re-logs on every change (dev-only, a no-op in production), and $inspect.trace(label?) — placed as the first statement in an effect or derived — prints exactly which dependency caused that specific run, proven directly in Advanced Runes:
✓ $inspect.trace() prints which dependency caused a derived/effect to re-runVitest + @testing-library/svelte: a real, running proof
Section titled “Vitest + @testing-library/svelte: a real, running proof”A rendered, interacted-with component is the most direct proof this course has for “does this actually work” — nearly every entry in the AI Bug Catalog is proven this way, not asserted from memory:
<script lang="ts"> let count = $state(0);</script>
<button onclick={() => count++}>count: {count}</button>import { render, screen, fireEvent } from '@testing-library/svelte';import { expect, test } from 'vitest';import SmokeCounter from './SmokeCounter.svelte';
test('a real smoke test: click, then assert on what the user sees', async () => { render(SmokeCounter); await fireEvent.click(screen.getByRole('button', { name: /count: 0/i })); expect(screen.getByRole('button', { name: /count: 1/i })).toBeInTheDocument();});node tools/verify-snippets.mjs --test reading-svelte/verification-tools✓ a real smoke test: click, then assert on what the user sees
Test Files 1 passed (1) Tests 1 passed (1)See Testing for the full RTL-for-Svelte discipline this course follows — queries, fireEvent/userEvent, and testing effects/async state.
The in-browser playground: compile it and read the output
Section titled “The in-browser playground: compile it and read the output”Compile the exact await example from the Async Svelte lesson yourself, right here — no experimental.async flag is set for this embed, so the real compile error should appear in the output panel the moment you click Compile:
<script> // English comments: keep this pinned to Svelte 5 runes mode. let a = $state(1); let b = $state(2);
async function add(x, y) { await new Promise((r) => setTimeout(r, 300)); return x + y; }</script>
<p>{a} + {b} = {await add(a, b)}</p> This is the same compiler.compile() call svelte-check and the Vitest test above both go through, running live in your own browser — the error panel’s message, line/column, and source frame are the actual compiler output, not a canned screenshot. Toggling Dev mode/Server target and recompiling shows how the same source produces different generated JS (or the same error, since a missing compiler flag isn’t affected by either toggle).
svelte-migrate: what it actually rewrites, verified
Section titled “svelte-migrate: what it actually rewrites, verified”svelte-migrate (the svelte-5 migration inside npx sv migrate) doesn’t just flag legacy patterns — it rewrites them, using the exact same migrate() function svelte/compiler exports. Run directly against a genuinely Svelte-4-style file (export let, a top-level $:, on:click):
<!-- src/lib/Legacy.svelte — input to svelte-migrate --><script> export let initialCount = 0; let count = initialCount; $: doubled = count * 2;</script>
<button on:click={() => count++}>{count} (doubled: {doubled})</button>$ node -e "import('svelte/compiler').then(m => { const fs = require('fs'); const src = fs.readFileSync('src/lib/Legacy.svelte', 'utf8'); console.log(m.migrate(src, { filename: 'Legacy.svelte' }).code);});"<script> /** * @typedef {Object} Props * @property {number} [initialCount] */
/** @type {Props} */ let { initialCount = 0 } = $props(); let count = $state(initialCount); let doubled = $derived(count * 2);</script>
<button onclick={() => count++}>{count} (doubled: {doubled})</button>export let became a destructured $props() (with a generated JSDoc Props type for plain-JS files), the plain let count = initialCount became $state(initialCount), $: doubled = ... became let doubled = $derived(...), and on:click became onclick — all four of the AI Bug Catalog’s first group’s patterns, rewritten automatically in one pass. The CLI itself (npx sv migrate, or the standalone svelte-migrate package, version 1.10.3 verified) offers several distinct migrations, not just this one:
$ npx svelte-migrate◆ Which migration would you like to run?│ ● app-state│ ○ package│ ○ routes│ ○ self-closing-tags│ ○ svelte-5│ ○ sveltekit-2app-state is the one named directly in Runes vs Stores for automating the $app/stores-to-$app/state rewrite (the AI Bug Catalog’s entry 21). Like any codemod, treat its output as a draft to review, not a merge-blindly patch — it gets the mechanical rewrite right, but it can’t know whether the surrounding code’s behavior still needs the deeper fixes this module’s catalog covers (a migrated export let that should also gain a $bindable(), for instance).
This course’s own harness: compile every snippet
Section titled “This course’s own harness: compile every snippet”Every code fence in this course tagged with a bare path-comment first line (<!-- src/lib/X.svelte --> for svelte fences, // src/lib/x.ts for ts/js) is a real file, collected by tools/verify-snippets.mjs into a namespaced directory per lesson and type-checked in one svelte-check pass across the whole course:
node tools/verify-snippets.mjsno type errors.
Per-module fence summary (collected / skipped-no-path / expect-error / tests): reading-svelte: ... (see this module's own gate run) ...A fence deliberately showing broken or misbehaving code (nearly every “code AI typically writes” example in the AI Bug Catalog) starts with <!-- @expect-error <reason> -->/// @expect-error <reason> so the harness skips collecting it — the lesson prose carries the real, separately-captured svelte-check/compiler/console output instead of asking the harness to reproduce a deliberate failure on every run. --test [module/lesson] runs just the *.test.ts fences through Vitest, --refresh rescaffolds the probe from scratch, and --self-test is the harness’s own regression check. See the project README’s “Harness” section for the full fence convention and probe layout.
Mental Model
Section titled “Mental Model”flowchart TD
subgraph Static["Static (source/compiler only)"]
Check["svelte-check<br/>real errors: $bindable(), export let/$: in runes mode, number-input types"]
Eslint["eslint-plugin-svelte<br/>svelte/valid-compile surfaces compiler diagnostics — NOT in recommended alone"]
Compiler["svelte/compiler compile()/migrate()<br/>the same function svelte-check and svelte-migrate both call"]
Verify["tools/verify-snippets.mjs<br/>compiles every tagged fence in the course"]
end
subgraph Runtime["Runtime (needs a real render or commit)"]
Vitest["Vitest + @testing-library/svelte<br/>click, then assert on what a user sees"]
Playground["SveltePlayground<br/>real compile() output, live in your browser"]
Inspect["$inspect / $inspect.trace<br/>dev-only, real dependency tracing"]
end
subgraph Build["Needs a real production build or server"]
EnvBuild["vite build<br/>catches a server-only env import in a universal load"]
Curl["curl against a running app<br/>redirect()/error() swallowed by try/catch, streaming timing"]
end