Remove the generic $lib/svelte-admin CRUD layer and AdminTable.svelte; each admin route now owns its own form/modal logic. Add DataTable.svelte, superform-errors.ts, and profile-specific modal/field components. Flatten proclamation categories/groups and drop subscription levels; add the v2 groups API; update v1/v2 endpoints, serializers, auth, and regenerated Prisma client with the new migrations. Document the overhaul plan in docs/ADMIN_OVERHAUL.md.
2.6 KiB
Svelte 5 Patterns and Rules
This document contains Svelte-specific implementation rules.
Only read this file when modifying Svelte components, routes, or Svelte-specific logic.
Svelte Version
This project uses:
- SvelteKit 2
- Svelte 5
- runes mode
Always use:
- $state()
- $derived()
- $effect()
- $props()
Do not use legacy stores:
- writable()
- readable()
- derived()
Component Props
Always destructure $props() with defaults.
Preferred pattern:
let { record = {} as Record<string, unknown>, fieldErrors = {} as Record<string, string>, isSuper = false }: { record?: Record<string, unknown>; fieldErrors?: Record<string, string>; isSuper?: boolean; } = $props();
Do not assume props exist.
State Synchronization
IMPORTANT:
Do not initialize $state from props when the prop can change.
Incorrect:
let name = $state(record.name);
This only captures the initial value.
Correct:
let name = $state('');
$effect(() => { name = String(record.name ?? ''); });
Use $effect whenever component state needs to follow changing props.
Effect Ordering
$effect blocks execute in declaration order.
When multiple effects modify related state:
- Reset state first.
- Populate state second.
- Perform dependent calculations afterward.
Example:
let dirty = $state(false);
$effect(() => { dirty = false; });
$effect(() => { name = String(record.name ?? ''); });
This ensures a new record starts clean.
Select Inputs
IMPORTANT:
Do not use bind:value on select elements whose values are synchronized through $effect.
Incorrect:
OneThis can fail to update correctly.
Use:
<select onchange={(e) => frequency = e.currentTarget.value}>
OneUse selected on options.
Input Elements
Text inputs are different.
This is acceptable:
bind:value works correctly with normal input elements.
Forms
Admin forms use:
- $state for field values
- $effect for prop synchronization
- oninput for dirty tracking
- use:enhance for submission
Example:
SaveAvoid Unnecessary State
Prefer:
$derived()
for values computed from other state.
Use:
$state()
only for values that change independently.
Component Changes
Before modifying a component:
- Read the entire component.
- Find similar components.
- Follow existing patterns.
- Avoid introducing new abstractions unless necessary.
Small, consistent changes are preferred.