Harness-engineering svelte-state-management

Svelte State Management

install
source · Clone the upstream repo
git clone https://github.com/Intense-Visions/harness-engineering
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/Intense-Visions/harness-engineering "$T" && mkdir -p ~/.claude/skills && cp -r "$T/agents/skills/claude-code/svelte-state-management" ~/.claude/skills/intense-visions-harness-engineering-svelte-state-management && rm -rf "$T"
manifest: agents/skills/claude-code/svelte-state-management/SKILL.md
source content

Svelte State Management

Choose the right state scope in SvelteKit: component-local runes, context API for subtree isolation, and module-level state for true singletons

When to Use

  • You need to decide whether state belongs in a component, a context, or a module-level singleton
  • You are hitting SSR data leakage bugs caused by shared module-level state between requests
  • You need to share state across a component subtree without prop-drilling or global stores
  • You are implementing a typed context pattern (ContextKey) for large applications

Instructions

Layer 1 — component-local state (runes):

  1. For state used only within a single component, use
    $state
    directly. This is always the starting point:
<script lang="ts">
  let count = $state(0)
  let open = $state(false)
</script>

Layer 2 — reactive class / .svelte.ts for shared reactive logic:

  1. Extract shared logic into a
    .svelte.ts
    file using
    $state
    and
    $derived
    . This works in Svelte 5 and is preferred over Svelte 4 stores:
// lib/counter.svelte.ts
export class Counter {
  count = $state(0);
  doubled = $derived(this.count * 2);

  increment() {
    this.count++;
  }
  reset() {
    this.count = 0;
  }
}

// Singleton (app-wide):
export const counter = new Counter();

// Or factory (per-use):
export function createCounter() {
  return new Counter();
}

Layer 3 — context API for subtree isolation:

  1. Use
    setContext
    /
    getContext
    to share state within a component tree without prop-drilling. Context is scoped to the component that calls
    setContext
    and all its descendants:
<!-- FormRoot.svelte — sets context -->
<script lang="ts">
  import { setContext } from 'svelte'

  const form = $state({ values: {}, errors: {} })
  setContext('form', form)
</script>
<slot />
<!-- FormField.svelte — reads context -->
<script lang="ts">
  import { getContext } from 'svelte'
  const form = getContext<FormState>('form')
</script>
  1. Use a typed symbol key (ContextKey pattern) to prevent key collisions in large apps:
// lib/form-context.ts
import { getContext, setContext } from 'svelte';

const FORM_KEY = Symbol('form');

export function setFormContext(state: FormState) {
  setContext(FORM_KEY, state);
}

export function getFormContext(): FormState {
  const ctx = getContext<FormState>(FORM_KEY);
  if (!ctx) throw new Error('getFormContext must be called inside a FormRoot component');
  return ctx;
}

Layer 4 — module-level singletons (client-only):

  1. Module-level state (outside any function or class) persists for the lifetime of the browser tab — useful for truly global singletons like auth state or feature flags:
// lib/auth.svelte.ts
let user = $state<User | null>(null);

export function getUser() {
  return user;
}
export function setUser(u: User | null) {
  user = u;
}
  1. NEVER use module-level state for per-user or per-request data in SSR — it leaks between requests on the server:
// WRONG — server-side singleton shared between ALL users:
let currentUser = $state<User | null>(null); // data leak!

// CORRECT — use locals (set in hooks.server.ts) for per-request server state
// CORRECT — use context API for per-component-tree state

SvelteKit page data as state source:

  1. Page data from load functions is the canonical source for server-fetched state. Do not duplicate it into local stores:
<script lang="ts">
  import type { PageData } from './$types'
  let { data }: { data: PageData } = $props()

  // data.user is already reactive — no need to copy to $state
  // Just use data.user directly in the template
</script>

Details

State management decision tree:

Is the state used in only one component?
  → Yes → $state (rune)

Does it need to be shared across a component subtree?
  → Yes → Context API (setContext/getContext)

Does it need to be shared app-wide (no component boundary)?
  → Client-only? → Module-level $state in .svelte.ts
  → SSR-safe?   → Server: locals / load data; Client: stores or context

Does it come from the server?
  → Use load functions + page data (not stores)

Why stores cause SSR issues:

Svelte stores are module-level singletons. In Node.js, modules are shared across all simultaneous requests. If a user's data is written to a store on request A, it can be visible to request B's render if B starts before A finishes. The fix: use

event.locals
in
hooks.server.ts
, passed through load functions.

Context API and SSR:

Context is per-render-tree on the server (each SSR request creates a fresh component tree), so it is SSR-safe. This makes it the right choice for per-request data in layouts and pages.

Derived state from page data:

If you need computed values based on page data, use

$derived
rather than storing derived values:

<script lang="ts">
  let { data } = $props()
  const totalPrice = $derived(
    data.cartItems.reduce((sum, item) => sum + item.price, 0)
  )
</script>

Persisting state across navigations:

SvelteKit re-runs load functions on navigation, replacing

data
. For client-side persistence across navigations, use module-level state or the browser's
sessionStorage
/
localStorage
.

Source

https://kit.svelte.dev/docs/state-management

Process

  1. Read the instructions and examples in this document.
  2. Apply the patterns to your implementation, adapting to your specific context.
  3. Verify your implementation against the details and edge cases listed above.

Harness Integration

  • Type: knowledge — this skill is a reference document, not a procedural workflow.
  • No tools or state — consumed as context by other skills and agents.

Success Criteria

  • The patterns described in this document are applied correctly in the implementation.
  • Edge cases and anti-patterns listed in this document are avoided.