SvelteKit 2 + Svelte 5 frontend. See root CLAUDE.md for full context.
pnpm check # must pass (svelte-check + TypeScript)
pnpm lint # prettier + eslint --fix
pnpm build # production build
pnpm dev # dev server- Create
src/routes/<path>/+page.svelte - Use
$props(),$state,$derived,$effect— never Svelte 4 syntax - Fetch data in
onMount, not SvelteKitloadfunctions - Add
onPullRefresh(() => loadData())for mobile pull-to-refresh - Auth guard:
$effect(() => { if (!auth.isLoading && !auth.isAuthenticated) goto('/'); })
- Place in
src/lib/components/common/(shared) or feature-specific directory - Export from barrel
index.ts - Use
$props()with TypeScript interface - Use
{@render children()}not<slot />
In src/lib/api.ts, add typed method:
async getFoo(id: string) {
return this.request<Foo>('GET', `/api/foos/${id}`);
}Available: alert, alert-dialog, avatar, badge, breadcrumb, button, card, dialog, dropdown-menu, empty, input, input-group, label, separator, sheet, skeleton, sonner, spinner, switch, tabs, textarea, tooltip.
NO Popover. Use dropdown-menu or sheet.
Import pattern:
import * as Card from '$lib/components/ui/card'; <!-- namespace -->
import {Button} from '$lib/components/ui/button'; <!-- direct -->value={x}vsbind:value={x}: shadcn components use$bindable— usebind:for two-way- $effect not tracking: reading
$derivedinside falsyif= not tracked. Read before guards. - Plain
letin effects: use$stateif effects depend on the variable transformparent +fixedchild: CSS transforms create new containing block, breaking fixed positioning- Missing
cursor-pointer: add to all interactive/clickable elements - iOS zoom on inputs: use
text-base(16px+) on input/textarea elements - Svelte 4 syntax: no
<slot />, noexport let, no$:reactive declarations