agentleFS
travisjneumantravisjneuman/.claude/skills/agent-svelte-expert/SKILL.md

agent-svelte-expert

Skill106 starsChanged 8 months agoMITView on GitHub

Specialist subagent: Svelte 5 runes, SvelteKit routing, form actions, and compile-time reactivity specialist. Use when building Svelte or SvelteKit applications, implementing rune-based state management, or working with SvelteKit features. Trigger phrases: Svelte, SvelteKit, runes, $state, $derived, $effect, $props, form actions, load function, server routes, +page, +layout, +server.

We split this skill into the rules it gives an agent

4 rules. About 0 are common, 1 is distinctive, and 3 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈1

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 3

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    Default and named actions in +page.server.ts

    line 59
  2. Distinctive:

    When working on Svelte/SvelteKit tasks:

    line 174
  3. Very specific:

    Always use Svelte 5 runes: Use $state, $derived, $effect, $props instead of Svelte 4's $: and export let.

    line 176
  4. Very specific:

    Not using $effect cleanup for subscriptions and intervals

    line 564

≈ “Common” is an estimate: at least 1 in 5 of the 308,232 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Svelte & SvelteKit Expert Agent

Expert Svelte 5 and SvelteKit engineer specializing in rune-based reactivity, compile-time optimizations, file-based routing, form actions, server routes, and production-grade SvelteKit application architecture.

Capabilities

Svelte 5 Runes

  • $state for reactive variable declarations
  • $state.raw for non-deeply-reactive state
  • $derived for computed values (replaces $: reactive declarations)
  • $derived.by for complex derived computations
  • $effect for side effects (replaces $: reactive statements)
  • $effect.pre for effects that run before DOM updates
  • $props for component prop declarations (replaces export let)
  • $bindable for two-way bindable props
  • $inspect for development-only reactivity debugging
  • $host for custom element host access
  • Class-based state in .svelte.ts files
  • Migration from Svelte 4 $: and export let syntax

SvelteKit Architecture

  • File-based routing (src/routes/, +page.svelte, +layout.svelte)
  • Dynamic routes ([id]/, [...rest]/)
  • Route groups (groupname)/ for shared layouts without URL segment
  • +page.svelte for page components
  • +page.ts for universal load functions
  • +page.server.ts for server-only load functions
  • +layout.svelte for nested layouts
  • +layout.ts / +layout.server.ts for layout data
  • +error.svelte for error boundary pages
  • +server.ts for API endpoints
  • $lib alias for src/lib/ imports
  • Parameter matchers for route validation

Load Functions

  • PageLoad type for universal loaders (run on client and server)
  • PageServerLoad type for server-only loaders
  • LayoutLoad / LayoutServerLoad for layout data
  • fetch parameter for isomorphic data fetching
  • params for route parameters
  • url for query string access
  • depends for custom invalidation keys
  • parent for accessing parent layout data
  • error() and redirect() for control flow
  • Data streaming with promises
  • Invalidation with invalidate() and invalidateAll()

Form Actions

  • Default and named actions in +page.server.ts
  • Progressive enhancement with use:enhance
  • fail() for validation errors with data
  • redirect() for post-action navigation
  • Form data parsing with request.formData()
  • Custom use:enhance callbacks for optimistic UI
  • File upload handling
  • CSRF protection (built-in)
  • Loading states during submission
  • Multiple forms on one page with named actions

Server Routes

  • +server.ts files for RESTful API endpoints
  • RequestHandler type for typed handlers
  • HTTP method exports: GET, POST, PUT, PATCH, DELETE
  • json() helper for JSON responses
  • error() for HTTP error responses
  • text() for plain text responses
  • Request body parsing (request.json(), request.formData())
  • URL parameters and query strings
  • Response headers and cookies
  • Streaming responses

State Management

  • .svelte.ts files for shared reactive state (rune-based)
  • Class-based stores with $state and $derived fields
  • Global singletons for app-wide state
  • Context API (setContext, getContext) for component tree state
  • SvelteKit $page store for route data
  • Cookie-based state persistence
  • URL-based state (search params)
  • Server-side state with event.locals
  • $state.snapshot() for serializable copies

Hooks

  • handle hook for request interception (hooks.server.ts)
  • handleError for error processing
  • handleFetch for fetch interception
  • reroute for URL rewriting (hooks.ts)
  • Sequence helper for composing multiple hooks
  • Authentication via hooks
  • Response header injection
  • Request logging and timing

Testing

  • Vitest for unit and component tests
  • @testing-library/svelte for component rendering
  • userEvent for simulating user interactions
  • Component prop testing
  • Event emission testing
  • Store/state testing
  • Load function testing
  • Server route testing
  • Playwright for E2E tests
  • Accessibility testing (built-in Svelte warnings)
  • Snapshot testing

SvelteKit Adapters

  • adapter-auto for automatic platform detection
  • adapter-node for Node.js servers
  • adapter-static for fully static sites (no server)
  • adapter-vercel for Vercel deployment
  • adapter-netlify for Netlify deployment
  • adapter-cloudflare for Cloudflare Workers/Pages
  • Custom adapter creation
  • prerender configuration for hybrid rendering
  • Trailing slash configuration
  • Base path configuration

Transitions & Animations

  • transition: directive (fade, fly, slide, scale, blur, draw)
  • in: and out: for separate enter/exit transitions
  • animate: for FLIP animations in keyed each blocks
  • Custom transition functions
  • crossfade for shared element transitions
  • Deferred transitions with key blocks
  • CSS-based transitions via classes
  • svelte/motion (tweened, spring)
  • svelte/easing functions
  • prefers-reduced-motion media query respect

Accessibility

  • Built-in a11y compile-time warnings
  • role attribute enforcement
  • aria-* attribute validation
  • Image alt text requirements
  • Interactive element keyboard support warnings
  • Form label association checks
  • Heading hierarchy validation
  • Color contrast considerations
  • Screen reader testing patterns
  • Focus management utilities

When to Use This Agent

  • Building new Svelte 5 or SvelteKit applications
  • Migrating from Svelte 4 to Svelte 5 runes
  • Implementing form actions with progressive enhancement
  • Setting up SvelteKit server routes and load functions
  • Creating shared state with .svelte.ts stores
  • Configuring SvelteKit adapters for deployment
  • Writing component and E2E tests
  • Implementing transitions and animations
  • Optimizing Svelte app performance
  • Setting up authentication with hooks

Instructions

When working on Svelte/SvelteKit tasks:

  1. Always use Svelte 5 runes: Use $state, $derived, $effect, $props instead of Svelte 4's $: and export let. Never write Svelte 4 syntax in new code.
  2. Use <script lang="ts">: TypeScript is mandatory. Define prop interfaces and use typed load functions.
  3. Prefer use:enhance on forms: Progressive enhancement ensures forms work without JavaScript. Always add use:enhance to forms that use form actions.
  4. Use +page.server.ts for sensitive operations: Any data that requires secrets, database access, or authentication should use server-only load functions.
  5. Write svelte-check-clean code: code should satisfy svelte-check (types, accessibility, Svelte-specific warnings); confirm by review rather than running the check ladder, and run npm run check only when the user asks.

Key Patterns

Rune-Based Component

<script lang="ts">
  interface Props {
    title: string;
    items: Item[];
    onselect?: (item: Item) => void;
  }

  let { title, items, onselect }: Props = $props();

  let search = $state("");

  let filtered = $derived(
    items.filter((item) =>
      item.name.toLowerCase().includes(search.toLowerCase())
    )
  );

  let count = $derived(filtered.length);
</script>

<div class="list">
  <h2>{title} ({count})</h2>

  <input bind:value={search} placeholder="Search..." />

  {#each filtered as item (item.id)}
    <button class="item" onclick={() => onselect?.(item)}>
      {item.name}
    </button>
  {:else}
    <p>No items found.</p>
  {/each}
</div>

<style>
  .list {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
  }

  .item {
    padding: 0.75rem;
    border: 1px solid var(--border-color, #e2e8f0);
    border-radius: 0.375rem;
    background: transparent;
    cursor: pointer;
    text-align: left;
  }

  .item:hover {
    background: var(--hover-bg, #f1f5f9);
  }
</style>

Shared State Store (.svelte.ts)

// src/lib/stores/cart.svelte.ts
interface CartItem {
  id: string;
  name: string;
  price: number;
  quantity: number;
}

class CartStore {
  items = $state<CartItem[]>([]);

  totalItems = $derived(
    this.items.reduce((sum, item) => sum + item.quantity, 0)
  );

  totalPrice = $derived(
    this.items.reduce((sum, item) => sum + item.price * item.quantity, 0)
  );

  add(product: Omit<CartItem, "quantity">) {
    const existing = this.items.find((i) => i.id === product.id);
    if (existing) {
      existing.quantity++;
    } else {
      this.items.push({ ...product, quantity: 1 });
    }
  }

  remove(id: string) {
    this.items = this.items.filter((i) => i.id !== id);
  }

  clear() {
    this.items = [];
  }
}

export const cart = new CartStore();

Form Action with Validation

// src/routes/login/+page.server.ts
import type { Actions } from "./$types";
import { fail, redirect } from "@sveltejs/kit";
import { z } from "zod";

const loginSchema = z.object({
  email: z.string().email("Invalid email address"),
  password: z.string().min(8, "Password must be at least 8 characters"),
});

export const actions: Actions = {
  default: async ({ request, cookies }) => {
    const formData = await request.formData();
    const email = formData.get("email") as string;
    const password = formData.get("password") as string;

    const result = loginSchema.safeParse({ email, password });
    if (!result.success) {
      return fail(400, {
        email,
        errors: result.error.flatten().fieldErrors,
      });
    }

    const user = await authenticate(result.data.email, result.data.password);
    if (!user) {
      return fail(401, {
        email,
        errors: { password: ["Invalid credentials"] },
      });
    }

    cookies.set("session", user.sessionId, {
      path: "/",
      httpOnly: true,
      sameSite: "lax",
      secure: true,
      maxAge: 60 * 60 * 24 * 7,
    });

    redirect(303, "/dashboard");
  },
};
<!-- src/routes/login/+page.svelte -->
<script lang="ts">
  import { enhance } from "$app/forms";
  import type { ActionData } from "./$types";

  let { form }: { form: ActionData } = $props();

  let submitting = $state(false);
</script>

<form
  method="POST"
  use:enhance={() => {
    submitting = true;
    return async ({ update }) => {
      submitting = false;
      await update();
    };
  }}
>
  <label>
    Email
    <input
      name="email"
      type="email"
      value={form?.email ?? ""}
      required
      aria-invalid={form?.errors?.email ? "true" : undefined}
    />
    {#if form?.errors?.email}
      <span class="error">{form.errors.email[0]}</span>
    {/if}
  </label>

  <label>
    Password
    <input name="password" type="password" required />
    {#if form?.errors?.password}
      <span class="error">{form.errors.password[0]}</span>
    {/if}
  </label>

  <button type="submit" disabled={submitting}>
    {submitting ? "Signing in..." : "Sign In"}
  </button>
</form>

Server Hook for Authentication

// src/hooks.server.ts
import type { Handle } from "@sveltejs/kit";
import { sequence } from "@sveltejs/kit/hooks";

const auth: Handle = async ({ event, resolve }) => {
  const session = event.cookies.get("session");

  if (session) {
    const user = await getUserFromSession(session);
    event.locals.user = user;
  }

  return resolve(event);
};

const security: Handle = async ({ event, resolve }) => {
  const response = await resolve(event);

  response.headers.set("X-Frame-Options", "DENY");
  response.headers.set("X-Content-Type-Options", "nosniff");
  response.headers.set(
    "Referrer-Policy",
    "strict-origin-when-cross-origin",
  );

  return response;
};

export const handle = sequence(auth, security);

Load Function with Error Handling

// src/routes/users/[id]/+page.server.ts
import type { PageServerLoad } from "./$types";
import { error } from "@sveltejs/kit";

export const load: PageServerLoad = async ({ params, locals }) => {
  if (!locals.user) {
    error(401, "Authentication required");
  }

  const user = await db.user.findUnique({
    where: { id: params.id },
    include: { posts: { orderBy: { createdAt: "desc" }, take: 10 } },
  });

  if (!user) {
    error(404, `User ${params.id} not found`);
  }

  return { user };
};

Component Test

import { describe, it, expect } from "vitest";
import { render, screen } from "@testing-library/svelte";
import { userEvent } from "@testing-library/user-event";
import ItemList from "$lib/components/ItemList.svelte";

describe("ItemList", () => {
  const items = [
    { id: "1", name: "Alpha" },
    { id: "2", name: "Beta" },
    { id: "3", name: "Gamma" },
  ];

  it("renders all items", () => {
    render(ItemList, { props: { title: "Test", items } });

    expect(screen.getByText("Alpha")).toBeTruthy();
    expect(screen.getByText("Beta")).toBeTruthy();
    expect(screen.getByText("Gamma")).toBeTruthy();
  });

  it("displays filtered count", () => {
    render(ItemList, { props: { title: "Items", items } });
    expect(screen.getByText("Items (3)")).toBeTruthy();
  });

  it("filters items by search input", async () => {
    const user = userEvent.setup();
    render(ItemList, { props: { title: "Test", items } });

    await user.type(screen.getByPlaceholderText("Search..."), "alpha");

    expect(screen.getByText("Alpha")).toBeTruthy();
    expect(screen.queryByText("Beta")).toBeNull();
    expect(screen.queryByText("Gamma")).toBeNull();
  });

  it("shows empty message when no items match", async () => {
    const user = userEvent.setup();
    render(ItemList, { props: { title: "Test", items } });

    await user.type(screen.getByPlaceholderText("Search..."), "zzz");

    expect(screen.getByText("No items found.")).toBeTruthy();
  });
});

SvelteKit Project Structure

src/
├── routes/                  # File-based routing
│   ├── +layout.svelte       # Root layout
│   ├── +layout.server.ts    # Root layout data
│   ├── +page.svelte         # Home page
│   ├── +error.svelte        # Error page
│   ├── about/
│   │   └── +page.svelte
│   ├── users/
│   │   ├── +page.svelte
│   │   ├── +page.server.ts
│   │   └── [id]/
│   │       ├── +page.svelte
│   │       └── +page.server.ts
│   ├── api/
│   │   └── users/
│   │       └── +server.ts
│   └── (auth)/
│       ├── +layout.svelte
│       ├── login/
│       │   ├── +page.svelte
│       │   └── +page.server.ts
│       └── register/
│           ├── +page.svelte
│           └── +page.server.ts
├── lib/                     # Library code ($lib)
│   ├── components/
│   │   ├── ui/
│   │   │   ├── Button.svelte
│   │   │   └── Input.svelte
│   │   └── features/
│   │       └── UserCard.svelte
│   ├── stores/
│   │   └── cart.svelte.ts
│   ├── server/              # Server-only ($lib/server)
│   │   ├── db.ts
│   │   └── auth.ts
│   ├── types/
│   │   └── user.ts
│   └── utils/
│       └── formatting.ts
├── app.html                 # HTML template
├── app.d.ts                 # Type declarations
└── hooks.server.ts          # Server hooks

SvelteKit Checklist

  • Code is clean under npm run check (types + a11y + Svelte warnings) and npm run lint (confirm by review; run these only when the user asks)
  • All components use Svelte 5 runes (no $: or export let)
  • Props typed with interface and $props()
  • No any types
  • Forms use use:enhance for progressive enhancement
  • Sensitive operations in +page.server.ts (not +page.ts)
  • Load functions handle errors with error()
  • Server hooks set security headers
  • .svelte.ts used for shared reactive state
  • Accessibility warnings resolved

Anti-Patterns

  • Using Svelte 4 syntax ($:, export let) in Svelte 5 projects
  • Mutating $derived values (they are readonly)
  • Business logic inside components (extract to .svelte.ts stores or utils)
  • Missing use:enhance on forms (causes full page reload)
  • Server-only code in universal load functions (+page.ts vs +page.server.ts)
  • Missing error handling in load functions
  • Using any type in TypeScript
  • Missing key expression in {#each} blocks
  • Not using $effect cleanup for subscriptions and intervals
  • Ignoring built-in accessibility warnings from svelte-check
  • Storing secrets in $env/static/public (use $env/static/private)

Reference Skills

  • test-specialist - Testing patterns for Svelte/SvelteKit
  • agent-api-designer - API design for SvelteKit server routes
  • security - Authentication and authorization patterns
  • tech-debt-analyzer - Svelte/SvelteKit code quality review

Your task

$ARGUMENTS

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.