{"id":51544851,"url":"https://github.com/roninforge/roninforge-sveltekit","last_synced_at":"2026-07-09T17:01:08.519Z","repository":{"id":363762462,"uuid":"1239815712","full_name":"RoninForge/roninforge-sveltekit","owner":"RoninForge","description":"SvelteKit 2 + Svelte 5 (Runes) Cursor plugin - 40 anti-patterns, 10 rules, 5 skills, reviewer agent. Verified May 2026.","archived":false,"fork":false,"pushed_at":"2026-06-10T07:44:54.000Z","size":83,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-10T09:21:32.011Z","etag":null,"topics":["ai-coding","code-review","cursor","cursor-ai","cursor-plugin","cursor-rules","llm","mdc","roninforge","runes","svelte","svelte-5","sveltekit","typescript"],"latest_commit_sha":null,"homepage":"https://roninforge.org/#plugins","language":"Shell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/RoninForge.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-15T13:22:18.000Z","updated_at":"2026-06-10T07:44:58.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/RoninForge/roninforge-sveltekit","commit_stats":null,"previous_names":["roninforge/roninforge-sveltekit"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/RoninForge/roninforge-sveltekit","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RoninForge%2Froninforge-sveltekit","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RoninForge%2Froninforge-sveltekit/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RoninForge%2Froninforge-sveltekit/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RoninForge%2Froninforge-sveltekit/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/RoninForge","download_url":"https://codeload.github.com/RoninForge/roninforge-sveltekit/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RoninForge%2Froninforge-sveltekit/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35306717,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-09T02:00:07.329Z","response_time":57,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ai-coding","code-review","cursor","cursor-ai","cursor-plugin","cursor-rules","llm","mdc","roninforge","runes","svelte","svelte-5","sveltekit","typescript"],"created_at":"2026-07-09T17:01:07.190Z","updated_at":"2026-07-09T17:01:08.509Z","avatar_url":"https://github.com/RoninForge.png","language":"Shell","funding_links":[],"categories":[],"sub_categories":[],"readme":"# roninforge-sveltekit\n\n[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)\n\nCursor plugin for SvelteKit 2 + Svelte 5 (Runes) + TypeScript. Pinned to `svelte ^5.55.7` and `@sveltejs/kit ^2.60.1`. Teaches the Svelte 5 runes that LLMs trained pre-October 2024 do not know (`$state`, `$derived`, `$props`, `$bindable`, `$effect`, `$props.id()`), the snippets-replace-slots model, callback props that replace `createEventDispatcher`, the `.svelte.ts` state module pattern that replaces `writable()` stores, the SvelteKit 2 no-throw `error()` / `redirect()` API, mandatory cookies `path`, and the `$app/state` replacement for `$app/stores` in 2.12+. Catches 40 LLM regressions with BAD / GOOD TypeScript + Svelte pairs.\n\n## The Problem\n\nLLMs trained on Svelte 4 / SvelteKit 1 emit code that does not match a fresh Svelte 5 + SvelteKit 2 install. They write:\n\n- **`let count = 0`** for component state without `$state(0)` (silently non-reactive in runes mode)\n- **`$: doubled = count * 2`** in runes-mode files (syntax error)\n- **`export let prop`** in any file using runes (compile error)\n- **`\u003cbutton on:click={fn}\u003e`** with the `on:` namespace (removed in Svelte 5)\n- **Event modifiers** `|preventDefault`, `|stopPropagation`, `|self`, `|once` (removed)\n- **`createEventDispatcher`** + `dispatch('foo', payload)` (replaced by callback props)\n- **`\u003cslot /\u003e`** and `\u003cslot name=\"x\" /\u003e` (replaced by snippets and `{@render children?.()}`)\n- **`new App({ target })`** for the client root or for tests (Svelte 5 dropped the class instance API; use `mount()` or `render()`)\n- **`bind:this={el}`** to a non-`$state` variable (assignment is invisible to consumers)\n- **Destructuring a `$state` proxy** and reading the destructured local (loses reactivity)\n- **`$effect`** used to derive a value (use `$derived`)\n- **`$effect`** that reads and writes the same state (infinite loop; wrap in `untrack`)\n- **`$state`** for arrays larger than 1000 elements without considering `$state.raw`\n- **`$effect`** used to initialise state on the server (effects only run in the browser)\n- **Reading state inside `$effect` after `await`** (no longer tracked)\n- **`throw error(...)` / `throw redirect(...)`** in SvelteKit 2 (helpers throw internally; just call them)\n- **Wrapping `redirect()` in try/catch** without `isRedirect` re-throw (silently swallowed)\n- **`cookies.set` / `cookies.delete`** without explicit `path: '/'` (throws in SvelteKit 2)\n- **Top-level unawaited promises in `load`** (SvelteKit 2 stopped auto-awaiting; use `Promise.all`)\n- **`goto('https://external')`** (rejected in SvelteKit 2; use `window.location.href`)\n- **`$page` from `$app/stores`** in new code (use `page` from `$app/state`, 2.12+)\n- **`resolvePath`** (renamed `resolveRoute` in `$app/paths`)\n- **`use:enhance` callback args `form` / `data`** (renamed `formElement` / `formData` in v2)\n- **File-input forms** missing `enctype=\"multipart/form-data\"` (throws under enhance)\n- **`$env/dynamic/*`** read inside a prerendered route (use `$env/static/*`)\n- **`writable()` / `readable()` stores** for component-local state (use `$state`)\n- **Stores in plain `.ts` modules** for shared cross-component state (use `.svelte.ts` with a class)\n- **`lucide-svelte` import** (use `@lucide/svelte`, the scoped Svelte 5 fork)\n- **`@melt-ui/svelte`** in new Svelte 5 code (use `bits-ui`, the documented headless layer)\n- **Felte forms** (`felte` + `@felte/validator-zod`) effectively abandoned for SvelteKit (use `sveltekit-superforms` + `formsnap` + `zod`)\n- **`\u003cimg\u003e`** for above-the-fold images (use `\u003cenhanced:img\u003e` from `@sveltejs/enhanced-img`)\n- **`CustomEvent\u003cT\u003e`** typed payloads in handlers (callback props pass plain function args)\n- **`withDefaults`-style prop defaults** (Svelte 5 supports defaults inline in `$props()` destructure)\n- **Manually generating SSR-safe IDs for ARIA** (use `$props.id()` in 5.20+)\n- **`\u003cslot\u003e`** rendered alongside `let { children } = $props()` (the two systems do not co-exist)\n- **Mutating non-`$bindable` props** in the child (throws in dev)\n- **`+page.ts`** exports without `satisfies PageLoad` (loses generated-type propagation)\n- **`mount(App)`** in tests without `unmount()` (handlers leak across tests; use `@testing-library/svelte`)\n\n## Why this plugin (vs the existing community Svelte / SvelteKit rules)\n\nA handful of Svelte and SvelteKit rules already live on cursor.directory: `svelte.mdc`, `sveltekit.mdc`, `svelte-tailwind.mdc`, and a few mixed-stack files. They are shallow and have three structural problems this plugin fixes:\n\n1. **They pin nothing or pin pre-Svelte-5 / pre-Kit-2.** Across all of them, zero mentions of `$state`, `$derived`, `$props`, `$bindable`, `$effect`, `$props.id()`, snippets, callback-prop event handling, `.svelte.ts` state modules, `mount()` / `hydrate()`, the SvelteKit 2 no-throw `error()` / `redirect()`, mandatory `cookies.path`, top-level `Promise.all` in `load`, `$app/state`, or the `formElement` / `formData` rename in `use:enhance`. Svelte 5 (October 2024) and SvelteKit 2 (December 2023, with $app/state in 2.12) post-date all of them. This plugin pins `svelte ^5.55.7` and `@sveltejs/kit ^2.60.1` explicitly.\n2. **Most still teach `on:click` / `\u003cslot\u003e` / `createEventDispatcher` / `export let` / `writable()` for component state.** All five are deprecated or compile errors in Svelte 5 runes mode. They also teach `$page` from `$app/stores` (deprecated 2.12+) and `throw redirect(...)` (broken in v2).\n3. **They ship flat `.cursorrules` text without globs, fixtures, skills, or an agent.** This plugin ships:\n\n   - **10 MDC rules** with proper `globs` so the routes check fires on `src/routes/**`, the runes check on `**/*.svelte` + `**/*.svelte.ts`, the testing check on `**/*.test.ts`, etc.\n   - **40 documented anti-patterns** with BAD / GOOD TypeScript + Svelte pairs (the existing rules have zero)\n   - **5 skills**: `/svelte-new-component`, `/sveltekit-new-route`, `/sveltekit-new-form-action`, `/sveltekit-migrate-to-runes`, `/sveltekit-validate`\n   - **1 reviewer agent** with severity grouping (CRITICAL / ERROR / WARN / NIT) and per-file checks\n   - **2 fixture projects**: `correct-sample` (gold-standard SvelteKit 2.60.1 + Svelte 5.55.7 + bits-ui + sveltekit-superforms + formsnap + zod) and `anti-pattern-sample` (every file violates at least one tracked anti-pattern, pinned to `svelte ^4.2.0` + `@sveltejs/kit ^1.27.0` + `lucide-svelte ^0.300.0` to demonstrate the Svelte 4 / Kit 1 hangover)\n\n## Install\n\nCopy the rules, skills, and agent into your project's Cursor configuration. Back up your existing files first; `cp -r` will overwrite same-named rules.\n\n```bash\ngit clone https://github.com/RoninForge/roninforge-sveltekit.git\n\n# Use -n to avoid clobbering an existing customised rule of the same name.\ncp -rn roninforge-sveltekit/rules/*  your-project/.cursor/rules/\ncp -rn roninforge-sveltekit/skills/* your-project/.cursor/skills/\ncp -rn roninforge-sveltekit/agents/* your-project/.cursor/agents/\n```\n\nOr vendor the whole repo as a git submodule under `your-project/.cursor/plugins/`. Refer to the [Cursor plugin docs](https://docs.cursor.com/plugins) for the current global-install path on your Cursor version.\n\n## What's Included\n\n### Rules (10 files)\n\n| Rule | Scope (globs) | What it does |\n|------|---------------|--------------|\n| `sveltekit-anti-patterns` | `**/*.svelte,**/*.svelte.ts,**/*.svelte.js,**/*.ts,**/*.js,src/routes/**/*,src/hooks.{server,client}.{ts,js}` | 40 LLM regressions with BAD / GOOD pairs. Each entry annotates which Svelte / Kit version dropped or renamed the BAD form |\n| `svelte-5-runes` | `**/*.svelte,**/*.svelte.ts,**/*.svelte.js` | `$state`, `$state.raw`, `$state.snapshot`, `$derived`, `$derived.by`, `$effect`, `$effect.pre`, `$effect.tracking`, `$effect.root`, `$props`, `$props.id`, `$bindable`, `$host`, `$inspect` |\n| `svelte-5-events-and-snippets` | `**/*.svelte` | `onclick` / `oninput` / `onsubmit` plain DOM attributes, callback props for component events, `Snippet\u003c[T]\u003e` typing, `{#snippet}` declarations, `{@render}` rendering |\n| `sveltekit-2-core` | `src/routes/**/*.{ts,js,svelte},src/hooks.ts,src/hooks.js,src/hooks.{server,client}.{ts,js},svelte.config.{js,ts}` | `error()` / `redirect()` no longer thrown, `cookies` `path` mandatory, top-level `load` promises require explicit await, `goto()` rejects external URLs, `$app/state` replaces `$app/stores` (2.12+), `resolvePath` -\u003e `resolveRoute`, `paths.relative` default flip, `reroute` hook, shallow routing |\n| `sveltekit-routing-and-load` | `src/routes/**/*.{ts,js,svelte}` | `+page.svelte` / `+page.ts` / `+page.server.ts` / `+layout.*` / `+error.svelte` / `+server.ts`, `satisfies PageLoad`, `depends()`, `parent()`, `fetch()` proxy, route param patterns, route groups |\n| `sveltekit-forms-and-actions` | `src/routes/**/+page.server.{ts,js},src/routes/**/+page.svelte,src/routes/**/+layout.server.{ts,js}` | Form actions, `use:enhance` v2 callback args, multipart enctype on file forms, `sveltekit-superforms` + `formsnap` + `zod` canonical stack |\n| `sveltekit-app-state` | `src/routes/**/*.{ts,js,svelte},src/lib/**/*.{ts,js,svelte}` | `page` / `navigating` / `updated` reactive objects from `$app/state` (2.12+), migration cheat sheet from `$app/stores` |\n| `svelte-5-reactivity-traps` | `**/*.svelte,**/*.svelte.ts,**/*.svelte.js` | Eight common runes traps: `$effect` SSR-only, reads after await not tracked, infinite loop on read+write, `$effect` for derivation, large arrays + `$state.raw`, destructuring a proxy, mutating non-`$bindable`, `bind:this` to non-`$state` |\n| `svelte-5-typescript` | `**/*.svelte,**/*.svelte.ts,**/*.svelte.js,**/*.ts` | `$props\u003cT\u003e()` inline typing, `Snippet\u003c[...]\u003e` generic, `Component\u003c...\u003e` generic, `satisfies PageLoad` / `Actions`, generic components via `\u003cscript lang=\"ts\" generic=\"T\"\u003e`, discriminated unions for variant props, `$bindable` typing |\n| `sveltekit-testing` | `**/*.test.{ts,js},**/*.spec.{ts,js},vitest.config.{ts,js},playwright.config.{ts,js},e2e/**/*` | Vitest 4 (`pool: 'forks'` default flip), `@testing-library/svelte` 5 `render()`, `happy-dom`, mocking `$app/state` / `$app/navigation`, `.svelte.ts` test setup, Playwright 1.60 for E2E (Cypress NOT recommended) |\n\n### Skills (5 commands)\n\n| Skill | Command | What it does |\n|-------|---------|--------------|\n| New component | `/svelte-new-component` | Scaffold a `.svelte` component using runes (`$state`, `$derived`, `$props`, `$bindable`), `$props.id()` for ARIA, callback props for events, snippets for slots, plus a matching `@testing-library/svelte` test using `render()` |\n| New route | `/sveltekit-new-route` | Scaffold a route directory with `+page.svelte`, `+page.ts` (typed `satisfies PageLoad`), `+page.server.ts` (typed `satisfies Actions`), and optional `+server.ts`. `error()` / `redirect()` without `throw`, `cookies` with `path` |\n| New form action | `/sveltekit-new-form-action` | Scaffold a `sveltekit-superforms` + `formsnap` + `zod` form: one schema, `superValidate(zod(schema))` on server load + action, `superForm(data.form, { validators: zodClient(schema) })` on client. `fail()` for validation, `message()` for action-level errors |\n| Migrate to Runes | `/sveltekit-migrate-to-runes` | Stage-by-stage migration: bump pins, enable runes, run `svelte-migrate svelte-5` and `svelte-migrate sveltekit-2` codemods, then manual edits for each numbered anti-pattern (state modules, dependencies, tests) |\n| Validate | `/sveltekit-validate` | Run `validate-plugin.sh` + `svelte-check` + `vitest` + `playwright`, then a grep audit for syntactic anti-patterns the type checker won't catch |\n\n### Agent (1 subagent)\n\n| Agent | What it does |\n|-------|--------------|\n| `sveltekit-reviewer` | Reviews SvelteKit 2 + Svelte 5 + TypeScript code by severity. CRITICAL: `throw error` / `throw redirect`, `cookies` without `path`, `redirect()` swallowed in try/catch, `$effect` initialising SSR-visible state, `$effect` infinite loop. ERROR: bare `let` for state, `$:` reactive statements, `export let`, `on:click` and event modifiers, `createEventDispatcher`, `\u003cslot\u003e` in runes file, `new App({ target })`, top-level unawaited `load` promises, `goto` external, `$app/stores`, `enhance` callback args `form/data`, file form without enctype, `$env/dynamic` during prerender, `bind:this` to non-`$state`. WARN: `$effect` for derivation, large `$state` array, reads after await in `$effect`, destructured `$state`, `writable()` for local state, `lucide-svelte`, `@melt-ui/svelte`, Felte, raw `\u003cimg\u003e`, `CustomEvent\u003cT\u003e`, `withDefaults`, manual ARIA IDs, `\u003cslot\u003e` alongside `{@render children}`, mutating non-`$bindable`, `+page.ts` without `satisfies PageLoad`, `mount(App)` in tests, `paths.relative` not declared, `vitest` `pool` not declared. NIT: `$inspect` left in shipped code, missing `satisfies Actions`, missing `depends()` key, missing `fail()`, per-form `enhance` import |\n\n## Fixtures\n\n`tests/fixtures/correct-sample/` is a slim SvelteKit 2.60.1 + Svelte 5.55.7 + bits-ui + sveltekit-superforms + formsnap + zod project demonstrating the gold-standard shape: runes everywhere, callback props, snippets, `.svelte.ts` state module, `page` from `$app/state`, `error()` / `redirect()` without `throw`, `cookies` with `path`, `satisfies PageLoad` / `Actions`, superforms-wired login, `reroute` hook.\n\n`tests/fixtures/anti-pattern-sample/` is the inverse. Every file violates a numbered anti-pattern. `package.json` pins `svelte ^4.2.0` + `@sveltejs/kit ^1.27.0` + `lucide-svelte ^0.300.0` + `vitest ^0.34.0` on purpose - the Svelte 4 / Kit 1 era is the LLM-training dataset for most pre-2025 models. Tracked violations: `#2` (`$:`), `#3` (`export let`), `#4` (`on:click`), `#5` (event modifiers), `#6` (`createEventDispatcher`), `#7` (`\u003cslot\u003e`), `#17` (`throw redirect / throw error`), `#19` (`cookies` without `path`), `#20` (top-level unawaited load), `#22` (`$page` from `$app/stores`), `#28` (`writable` for component-local), `#29` (`.ts` shared store), `#30` (`lucide-svelte`).\n\n## Versioning\n\nRules target `svelte ^5.55.7` on `@sveltejs/kit ^2.60.1` with Node 24 LTS. Most patterns work back to Svelte 5.0 / Kit 2.0 with the deltas called out inline. Where the rule cites a version (`$props.id()` 5.20+, `$app/state` Kit 2.12+, `reroute` hook Kit 2.3+, `paths.relative` default flip Kit 2.0, `cookies.path` mandatory Kit 2.0, `error()` / `redirect()` no-throw Kit 2.0), verify against the changelog for the version you have installed before adopting.\n\n## License\n\nMIT - see [LICENSE](LICENSE)\n\n## Links\n\n- [Svelte 5 release notes](https://svelte.dev/blog/svelte-5-is-alive)\n- [SvelteKit 2 migration guide](https://svelte.dev/docs/kit/migrating-to-sveltekit-2)\n- [Runes overview](https://svelte.dev/docs/svelte/what-are-runes)\n- [bits-ui](https://www.bits-ui.com)\n- [sveltekit-superforms](https://superforms.rocks)\n- [Cursor Plugin Documentation](https://docs.cursor.com/plugins)\n- [RoninForge](https://roninforge.org)\n\n\n## More from RoninForge\n\n[RoninForge](https://roninforge.org) builds free tools for developers working with AI coding assistants:\n\n- [LLM API pricing comparison](https://roninforge.org/llm-pricing) - Claude, GPT, Gemini, DeepSeek, Mistral, and Grok token prices side by side, verified against official pricing pages\n- [GitHub Copilot AI Credits calculator](https://roninforge.org/copilot-credits-calculator) - estimate your monthly credit burn under usage-based billing\n- [All Cursor plugins](https://roninforge.org/#plugins)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Froninforge%2Froninforge-sveltekit","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Froninforge%2Froninforge-sveltekit","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Froninforge%2Froninforge-sveltekit/lists"}