Writing Skills
writing-skills
效率与协作技能,来自 obra/superpowers,查看原文获取完整用途和操作说明。
prefix, works outside components. Stores remain the right shape for push-based external sources (sockets, geolocation, third-party subscriptions) and for anything consuming the `subscribe` contract; `fromStore`/`toStore` bridge the two rather than forcing a rewrite.\n- **How much `$effect` is acceptable.** The framework position is that effects are a last resort for synchronizing with systems outside Svelte; the pragmatic position accepts effects for analytics, logging, and persistence. Both agree on the hard line: an effect that assigns state which feeds back into its own dependencies is a bug, not a style choice.\n- **SSR by default vs SPA.** Prerendering or SSR wins for public, indexable, first-paint-sensitive pages. `adapter-static` with `ssr = false` is a legitimate choice for an internal dashboard behind auth where every view needs the client anyway — the deciding factors are SEO, first paint, and no-JS support, not fashion.\n- **Validation libraries in form actions.** Hand-rolled checks are fine for one to three fields. Once you need repopulating on failure, nested data, arrays, or multi-step wizards, a schema library plus a form helper stops the per-field boilerplate from drifting out of sync with the server.\n\n## Related Skills\nMore Clawic skills, get them at https://clawic.com/skills/svelte (install if the user confirms):\n- `typescript` — type-system depth beyond the Svelte-specific typings\n- `vite` — dev server, plugins, and build configuration under SvelteKit\n- `playwright` — end-to-end testing of the running app\n- `tailwindcss` — utility styling inside Svelte components\n- `nodejs` — running and hardening the `adapter-node` server\n\n## Feedback\n\n- If useful, star it: https://clawic.com/skills/svelte\n- Latest version: https://clawic.com/skills/svelte\n\nPart of [Clawic](https://clawic.com), the verified skill library. Get this skill: https://clawic.com/skills/svelte.\n","licenseDocuments":[]}}
Svelte【部署与运维】Builds, debugs, and reviews Svelte and SvelteKit apps: runes, stores, snippets, load functions, form actions, adapters. Use when the UI does not update after a state change, an effect loops forever ("effect_update_depth_exceeded"), props or bindings stop reacting, a Map or class field is not tracked, state leaks between users on the server, "window i…
独立 GitHub 快照 · 自动分类 · 以原作者文档为准
User preferences and memory live in ~/Clawic/data/svelte/ (see setup.md on first use, memory-template.md for the file format). If you have data at an old location (~/svelte/ or ~/clawic/svelte/), move it to ~/Clawic/data/svelte/, and say in one line that you moved it and from where.
.svelte.js state modules, or SvelteKit routes+server.js endpointswindow is not defined, state shared between requests, adapter build errorsvue, nuxt), React (react), or language-level JavaScript semantics (javascript)| Situation | Play |
|---|---|
| UI does not update after a change | Is the variable $state? Destructured? A Map/Set/class instance? → Reactivity Model table, then debug.md |
effect_update_depth_exceeded | An effect writes state it also reads — convert to $derived, or untrack() the read (rule 2) |
| Value computed from other state | $derived / $derived.by; never $effect + assignment (rule 2) |
Deep dive on $state, $derived, $effect, $props | runes.md |
| Shared state across files, context, legacy stores | stores.md |
| Snippets, bindings, callback props, attachments, wrapping an imperative library | components.md |
| Scoped CSS not applying, unused-selector warning, transitions, motion | styling.md |
Typing props, snippets, route data, app.d.ts | typescript.md |
| Symptom-first debugging and the error-code catalog | debug.md |
Svelte 4 → 5: export let, $:, slots, on:click, new Component() | migration.md |
| Routes, layouts, groups, param matchers, navigation, shallow routing | routing.md |
load, invalidate, streaming, depends, serialization | data-loading.md |
Form actions, use:enhance, validation, uploads, remote functions | forms.md |
window is not defined, hydration mismatch, per-request state, env vars | ssr.md |
| Login, sessions, cookies, route guards, roles, CSRF, XSS, CSP, leaked secrets | security.md |
| Bundle size, slow lists, rerender cost, preloading | performance.md |
Vitest, component tests, mocking $app/*, Playwright, svelte-check | testing.md |
| Adapters, prerendering, CSP, service workers, self-hosting | deployment.md |
| Packaging components for npm, custom elements, library API design | library.md |
| Anything else | Apply Core Rules; reproduce in a single component with no props before blaming the framework |
$state, not from assignment. In runes mode a bare let count = 0 never re-renders no matter how you assign it — the compiler emits non_reactive_update. let count = $state(0) then count++ or obj.items.push(x) both work: $state on a plain object or array is a deep proxy.$derived for values, $effect for the outside world. Decision rule: if the new value is a pure function of other state → $derived; if it touches the DOM, network, storage, or a non-Svelte library → $effect. An effect that assigns state costs an extra render pass and is the direct cause of effect_update_depth_exceeded.await; anything read after an await, in a setTimeout, or in a .then() is invisible to the tracker and the effect will not rerun. Read your dependencies at the top, then await.let user = $state(null) exported from a .svelte.js module is one value per Node process, not per request — request A writes it, request B renders it. Per-request data goes in event.locals (server) and setContext/props (components).{#each} whose items can move. {#each rows as row (row.id)}. Unkeyed, Svelte maps DOM to array index: delete row 0 and the input, focus, and component state of index 0 stay attached to what is now a different row. This is a correctness bug, not an optimization.+page.server.js. Universal +page.js runs in the browser too, so anything it imports ships. Server load output crosses the wire through devalue serialization: Date, Map, Set, BigInt, and cycles survive; class instances and functions do not (register them with the transport hook).params or url property it accessed changes, OR an invalidate(key) matches something it depends() on or a URL it fetched, OR a parent it await parent()-ed reran, OR invalidateAll() fired. After any mutation outside use:enhance, call invalidateAll() yourself or the page keeps serving the old data.<form method="POST" action="?/save"> works with JavaScript disabled and on the first paint; use:enhance upgrades the same form to a fetch with no reload and reinvalidates data on success. Reach for a +server.js endpoint only for non-form clients: webhooks, third parties, file streaming.$effect and onMount never run during SSR — that is the guard. Module scope and load functions run on the server, so window, document, localStorage, and IntersectionObserver there need import { browser } from '$app/environment' or a dynamic import().What is tracked, and what silently is not:
| Declaration | Reactive on | Rule |
|---|---|---|
let x = $state(0) | reassignment | Primitives: assign to update |
let o = $state({a: {b: 1}}) | reassignment + deep mutation | Plain objects/arrays become recursive proxies; o.a.b = 2 and arr.push() both work |
$state.raw(bigList) | reassignment only | No proxy: cheaper for large data you replace wholesale |
new Map(), Set, Date, URL inside $state | nothing | Not proxied — use SvelteMap, SvelteSet, SvelteDate, SvelteURL from svelte/reactivity |
Class field count = $state(0) | reassignment | Methods and get accessors stay reactive; the instance itself is not a proxy |
let { a, b = 1 } = $props() | parent updates | Destructuring props IS reactive in runes mode — the compiler rewrites the reads |
const { a } = someStateObject | nothing | Destructuring state reads the value once; keep the object, or wrap in $derived |
Exported let x = $state(0) from a module | not across the import | Importers get a snapshot binding — export an object, a class instance, or a getter |
let x = 0 in a runes file | nothing | Compiler warning non_reactive_update |
Passing state to a non-Svelte library (structuredClone, IndexedDB, postMessage, charting libs) hands it a Proxy; send $state.snapshot(x) instead.
svelte >=5 — runes, snippets, onclick event attributes, mount()/unmount(); $:, export let, slots, and new Component() only work in legacy modesvelte >=5.3 — <svelte:boundary> with failed snippet and onerrorsvelte >=5.29 — attachments ({@attach fn}) supersede actions (use:fn); actions still worksvelte >=5.36 — experimental await inside components and $effect.pending(), behind the experimental.async compiler option@sveltejs/kit >=2 — error() and redirect() throw internally: call them, never throw them; cookies.set requires an explicit path@sveltejs/kit >=2.12 — $app/state (page, navigating, updated) replaces the $app/stores subscriptions; page.url, not $page.url@sveltejs/kit >=2.16 — PageProps and LayoutProps in ./$types; below it, type $props() with { data: PageData; form: ActionData }@sveltejs/kit >=2.27 — remote functions (query, form, command) in .remote.js, behind experimental.remoteFunctions| File | Runs | Can hold secrets | Shipped to browser |
|---|---|---|---|
+page.svelte, +layout.svelte | SSR render, then client | No | Yes |
+page.js (universal load) | Server on first load, client on navigation | No | Yes |
+page.server.js (server load, actions) | Server only | Yes | No |
+server.js (endpoint) | Server only | Yes | No |
hooks.server.js | Server, every request | Yes | No |
hooks.client.js | Browser only | No | Yes |
$lib/server/**, *.server.js | Server only | Yes | Import from client code = build error |
Order per request: handle hook → server loads (+layout.server.js, then +page.server.js) → universal loads → render. Loads at the same level run in parallel unless one calls await parent(), which serializes it behind the parent.
| Code or message | Meaning | First move |
|---|---|---|
state_unsafe_mutation | State written while a derived or the template was computing it | Move the write into an event handler or $effect |
effect_update_depth_exceeded | An effect writes state that it (or a chained effect) reads | Convert to $derived; if the read is genuinely one-way, wrap it in untrack() |
hydration_mismatch | Server HTML differs from the first client render | Remove Date.now()/Math.random()/window from render; check invalid nesting (<div> inside <p>) |
rune_outside_svelte | A rune used in a .js/.ts file | Rename the file to .svelte.js / .svelte.ts |
lifecycle_outside_component | onMount, setContext, or getContext called after an await or inside a callback | Call synchronously during component initialization |
derived_references_self | A $derived reads its own value | Compute from source state, or keep the accumulator in $state |
each_key_duplicate | Two items produced the same key | Key by a unique id; never by index or by a repeated value |
bind_invalid_export / binding error | bind: to a prop the child did not declare bindable | let { value = $bindable() } = $props() |
ownership_invalid_mutation (dev warning) | A child mutated an object owned by its parent | $bindable() or a callback prop |
css_unused_selector (warning) | Selector matches nothing in this component's own markup | :global(...), or move the rule into the child component |
403 on a form POST | Kit's CSRF origin check rejected a cross-origin submission | Submit same-origin, or set csrf.checkOrigin deliberately |
Cannot import $lib/server/... into client-side code | A server-only module reached a client bundle | Import it in +page.server.js and pass the result through load |
User-dependent variables. Defaults apply until the user states a preference; store them in ~/Clawic/data/svelte/config.yaml.
| Variable | Type | Default | Effect |
|---|---|---|---|
| syntax_mode | runes | legacy | mixed | runes | Selects the syntax of every generated component ($state/$props vs let/export let) and whether migration advice is offered at all |
| kit_project | bool | true | When false, drops routing, load, actions, and adapter guidance and treats the app as Svelte + Vite with client-side routing |
| deployment_adapter | auto | node | static | vercel | cloudflare | netlify | auto | Drives the deploy checklist, which env-var mechanism is valid, and whether prerender/SPA fallback is required |
| package_manager | npm | pnpm | yarn | bun | npm | Sets the command syntax in install, sv add, build, and test instructions |
| styling | scoped-css | tailwind | unocss | css-modules | scoped-css | Shapes generated markup and which scoped-CSS caveats apply |
| typescript | bool | true | Emits <script lang="ts">, typed $props(), and ./$types imports; false switches to JSDoc annotations |
| experimental_features | bool | false | When false, remote functions, experimental.async/$effect.pending(), and attachments-over-actions are mentioned as available but never the recommended form; when true they are written by default |
| check_threshold | error | warning | error | The svelte-check --threshold value in every CI recipe, and whether a warning-level finding blocks the work |
Preference areas to record as the user reveals them:
$lib structure, barrel filesexperimental_features, upgrade cadence, and whether to propose a migration on a codebase that currently workscheck_thresholdBefore emitting a component or route, verify:
$state; no bare let expected to rerender?$derived, with $effect reserved for DOM, network, storage, or third-party libraries?{#each} over reorderable data keyed by a stable id?$env/static/private import reachable from +page.svelte or +page.js?$effect/onMount or behind a browser check?event.locals or context — never a module-level variable?export let or $: in a file that uses runes?| Trap | Why it fails | Do instead |
|---|---|---|
$effect used to compute derived state | Extra render pass and a loop the moment the effect reads what it writes | $derived / $derived.by |
Destructuring a $state object, then mutating the copy | The destructured constants captured values, not the proxy | Keep the object and read o.a; $derived for a stable view |
let count = $state(0) exported from a .svelte.js module | Importers bind to the value at import time | Export a class instance or { get count() {...} } |
| Per-user data in a server module variable | One value per process, shared by all concurrent requests | event.locals in hooks, setContext in components |
throw redirect(...) / throw error(...) inside a try | Kit 2 helpers throw internally; your catch swallows the control flow | Call them after the try/catch block |
Fetching your own +server.js from a server load | An extra HTTP hop, lost types, cookie forwarding you now own | Call the function or database directly in server load |
{@html userInput} | XSS, and component-scoped styles do not apply to injected markup | Sanitize server-side; style with :global |
Passing $state straight to a chart or map library | The library receives a Proxy and its identity checks fail | $state.snapshot(value) |
await early in an $effect, then reading state | Reads after the await are untracked; the effect never reruns | Read every dependency synchronously first |
on:click or createEventDispatcher in a runes component | Legacy syntax; the runes compiler rejects or deprecates it | onclick={...} and callback props |
<svelte:component this={C}> in runes mode | Deprecated: components are already dynamic values | Capitalized variable: <C /> |
bind:value to a prop the child never declared bindable | Bindings are opt-in in runes mode | let { value = $bindable() } = $props() |
document or window at module scope | Module bodies execute during SSR | Move into $effect/onMount, or guard with browser |
Unkeyed {#each} around inputs or stateful children | DOM nodes are reused by index | Key by id (rule 5) |
$state fields in a .svelte.js module — plain values, no $ prefix, works outside components. Stores remain the right shape for push-based external sources (sockets, geolocation, third-party subscriptions) and for anything consuming the subscribe contract; fromStore/toStore bridge the two rather than forcing a rewrite.$effect is acceptable. The framework position is that effects are a last resort for synchronizing with systems outside Svelte; the pragmatic position accepts effects for analytics, logging, and persistence. Both agree on the hard line: an effect that assigns state which feeds back into its own dependencies is a bug, not a style choice.adapter-static with ssr = false is a legitimate choice for an internal dashboard behind auth where every view needs the client anyway — the deciding factors are SEO, first paint, and no-JS support, not fashion.More Clawic skills, get them at https://clawic.com/skills/svelte (install if the user confirms):
typescript — type-system depth beyond the Svelte-specific typingsvite — dev server, plugins, and build configuration under SvelteKitplaywright — end-to-end testing of the running apptailwindcss — utility styling inside Svelte componentsnodejs — running and hardening the adapter-node serverPart of Clawic, the verified skill library. Get this skill: https://clawic.com/skills/svelte.