Update AGENTS.md

This commit is contained in:
NeilClack
2026-07-16 20:23:59 -05:00
parent 2d0cf98ed3
commit f5896aa575
5 changed files with 1351 additions and 284 deletions
+184
View File
@@ -0,0 +1,184 @@
# 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:
1. Reset state first.
2. Populate state second.
3. 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:
<select bind:value={frequency}>
<option value="1">One</option>
</select>
This can fail to update correctly.
Use:
<select onchange={(e) => frequency = e.currentTarget.value}>
<option value="1" selected={frequency === '1'}>
One
</option>
</select>
Use selected on options.
---
# Input Elements
Text inputs are different.
This is acceptable:
<input bind:value={name} />
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:
<form oninput={markDirty}>
<button disabled={!!record?.id && !dirty}>
Save
</button>
</form>
---
# Avoid Unnecessary State
Prefer:
$derived()
for values computed from other state.
Use:
$state()
only for values that change independently.
---
# Component Changes
Before modifying a component:
1. Read the entire component.
2. Find similar components.
3. Follow existing patterns.
4. Avoid introducing new abstractions unless necessary.
Small, consistent changes are preferred.