# Pathrule Pattern: SvelteKit (1.0.0)
# ::pathrule:package:sveltekit

### [RULE] Server-only data and secrets live in +page.server.ts  (path: /src/routes)
<!-- scope: project | priority: medium | advisory -->

Any data that needs secrets, cookies, a database, or private APIs belongs in a server load function, not a universal one.

- Use `+page.server.ts` / `+layout.server.ts` for `load` that touches `$env/static/private`, `$env/dynamic/private`, `cookies`, or `locals`.
- Reserve `+page.ts` / `+layout.ts` (universal) for public external APIs and non-serializable returns like component constructors.
- Never import anything from `$lib/server`, a `*.server.ts` module, or a `$env/.../private` module into universal load or any client-reachable code. SvelteKit treats the whole import chain as unsafe and Vite refuses to build it; that build error is the signal the logic is in the wrong file, not something to work around.
- When both load functions exist on a route, the server load runs first and its result reaches the universal load via the `data` property. Universal output cannot flow back to the server.
- Server load runs only on the server. Universal load runs on the server during SSR, then again in the browser on hydration and on client-side navigation, so it must never assume a server-only global is present.

---

### [RULE] Mutations go through form actions or remote functions, never load  (path: /src/routes)
<!-- scope: folder | priority: high | advisory -->

`load` functions are for reading and must stay side-effect free, because SvelteKit reruns them on navigation, invalidation, and SSR. A write placed in `load` will silently re-fire.

- Handle writes with named `actions` in `+page.server.ts` plus a `<form method="POST">`, or with remote `form` / `command` functions in a `.remote.ts` file.
- Validate input server-side. In a form action, return `fail(status, data)` on validation errors and a serializable object on success.
- Use the `enhance` action for progressive enhancement instead of a hand-rolled `fetch`, and call `invalidate('app:key')` / `invalidateAll()` to refresh affected `load` data after the mutation.
- Do not perform POST/PUT/DELETE logic inside a `load`, and do not call a mutating remote `command` from a `load`.

---

### [MEMORY] Svelte 5 runes are the default; reactive classes replace stores  (path: /src)

This codebase targets Svelte 5, so reactivity is rune-based, not store-based.

- Use `$state` for mutable reactive values, `$derived` (or `$derived.by`) for computed values, `$props` for component inputs, and `$effect` only as an escape hatch for genuine side effects (DOM, analytics, subscriptions).
- Do not use `$effect` to synchronise or recompute state from other state. The docs are explicit: use `$derived` instead. Writing to state that the same effect reads also risks an infinite loop; reach for `untrack` only when you deliberately need to read without depending.
- Reach for `$state` only when a value drives the UI. Plain `const` / `let` is cheaper and clearer for everything else.
- For shared logic, write a reactive class in `$lib` whose fields use `$state` / `$derived`, and import it, instead of authoring `writable` / `readable` stores. Runes work inside plain `.svelte.ts` modules and classes.
- Avoid `$:` reactive statements and the implicit let-is-reactive model from Svelte 4. They do not exist in runes mode.

---

### [MEMORY] Avoid load waterfalls; stream non-critical data  (path: /src/routes)

Load performance hinges on not serializing requests that could run in parallel.

- Start independent `fetch` calls before `await parent()` so they do not block on parent data they do not need.
- Return unresolved promises from a server `load` for non-essential data. SvelteKit streams them to the client so the page renders before they settle, and the markup can `{#await}` them.
- Attach `.catch()` to any streamed promise that does NOT come from SvelteKit's injected `fetch`. An unhandled rejection in a streamed promise can crash the response.
- Use the injected `fetch` argument inside `load` (not global `fetch`) so SvelteKit forwards credentials and cookies, resolves relative URLs, inlines the response during SSR, and tracks the request as a dependency for `invalidate`. For custom clients that bypass `fetch`, call `depends('app:key')` to register a manual dependency.

---

### [MEMORY] Remote functions are experimental and the API is still moving  (path: /src/routes)

Remote functions (available since SvelteKit 2.27) are still experimental as of mid-2026. Treat their surface as unstable and pin against the SvelteKit version in this repo before copying examples.

- Enable them with BOTH flags: `kit.experimental.remoteFunctions: true` and `compilerOptions.experimental.async: true` in `svelte.config.js`. They live in `*.remote.ts` files and export `query` (read, deduped + cached), `form` (progressively-enhanced writes bound to `<form>`), `command` (writes from anywhere, not form-bound), and `prerender` (build-time static data).
- Every function that takes an argument must validate it with a Standard Schema validator (Zod, Valibot) passed as the first parameter. Do not trust raw input.
- Use single-flight mutations: inside a `form` / `command` handler, call `.refresh()` / `.set()` on affected queries so the mutation and the data refresh travel in one round-trip. Use `getRequestEvent()` for request context, and `query.batch` to collapse N parallel calls into one request and avoid n+1.
- Watch the breaking churn: `.run()` was removed from remote queries (await the query directly); `enhance` callbacks now receive a copy of the form instance rather than `{ form, data, submit }`; `query.live()` is the async-iterable real-time subscription helper. Confirm exact signatures against the installed version, not blog posts.

---

### [MEMORY] Async-in-components is experimental; reads after await are not tracked  (path: /src)

Svelte 5 (since 5.36) lets you use `await` directly in component `<script>`, in `$derived`, and in markup, but it is experimental and must be opted into via `compilerOptions.experimental.async` in `svelte.config.js`. The flag is slated to be removed in Svelte 6.

- Reactive values read ASYNCHRONOUSLY are not tracked. Anything read after an `await`, inside a `setTimeout`, or in a `.then()` callback will not register as a dependency, so the effect or derived will not re-run when it changes. Read the reactive value synchronously first, then await.
- Use `$effect.pending()` to know how many async operations are still settling in the current boundary (it does not count child boundaries) when you need loading UI.
- Svelte holds the UI in a consistent state while an `await` that depends on reactive state is in flight, rather than flashing intermediate values; rely on that instead of manual loading flags where possible.
- This is independent of SvelteKit `load` streaming. Prefer `load` + streamed promises for route data and reserve in-component `await` for leaf-level async that is local to one component.

---

### [SKILL] sveltekit-review  (path: /)

---
name: sveltekit-review
description: Review a SvelteKit 2 (Svelte 5) change before merge - verify load placement, secret isolation, runes usage, form actions, remote functions, and load performance. Use when reviewing or authoring routes, load functions, form actions, remote functions, or shared $lib reactivity.
---

# SvelteKit review

## Secrets and data boundaries
- [ ] Secrets, DB access, `cookies`, and `locals` appear only in `+page.server.ts` / `+layout.server.ts`, never in universal load or client code.
- [ ] No `$lib/server`, `*.server.ts`, or `$env/.../private` import reaches a `+page.ts`, `+layout.ts`, or component (the build would fail; if it builds, the chain is clean).
- [ ] Universal `load` returns only serializable data or intentional non-serializable values (components/classes); server `load` returns serializable data only.

## Mutations
- [ ] `load` functions are read-only; all writes go through `actions` or remote `form` / `command`.
- [ ] Form actions validate input server-side and use `fail(status, data)` for errors; `<form>` uses `enhance`.
- [ ] Data is refreshed after mutations via `invalidate` / `invalidateAll` (or remote single-flight `.refresh()` / `.set()`), not manual refetch.

## Reactivity
- [ ] Reactivity uses runes (`$state`, `$derived`, `$props`); no `$:` statements or new `writable` / `readable` stores where a reactive class fits.
- [ ] `$effect` is used only for true side effects, never to synchronise or recompute state that `$derived` should produce.
- [ ] If async-in-components is used, no reactive value is depended on only after an `await` / in a `setTimeout`.

## Load performance
- [ ] `load` uses the injected `fetch` argument; independent fetches start before `await parent()`.
- [ ] Slow non-critical data is streamed via returned promises, with `.catch()` on any promise not from the injected `fetch`.

## Remote functions (if used)
- [ ] Both `experimental.remoteFunctions` and `experimental.async` flags are set; functions live in `*.remote.ts`.
- [ ] Every argument-taking remote function validates input with a Standard Schema (Zod / Valibot).
- [ ] API usage matches the installed SvelteKit version (no removed `.run()`; correct `enhance` callback shape).
