diff --git a/AGENTS.md b/AGENTS.md
index e1233f0..86549ef 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -27,15 +27,17 @@ Requires `.env` with at least `DATABASE_URL`, `BETTER_AUTH_SECRET`, `BETTER_AUTH
## Testing quirks
-- **Two Vitest projects**: `client` (Playwright/Chromium headless, `src/**/*.svelte.{test,spec}.{js,ts}`) and `server` (node env, everything else). Tests co-located beside source files.
-- **`expect: { requireAssertions: true }`** — every test must assert something.
+- **Two Vitest projects**: `client` (Playwright/Chromium headless, `src/**/*.svelte.{test,spec}.{js,ts}`) and `server` (node env, everything else — `src/**/*.{test,spec}.{js,ts}` excluding `.svelte.*`). Tests co-located beside source files.
+- **`expect: { requireAssertions: true }`** — every test must assert something. No assertions = test fails.
- **All route tests mock `$lib/db`** (Prisma client) and `$lib/auth-client`/`$lib/auth` via `vi.mock()`. Stub factories in `src/lib/test-utils.ts`.
- **No `.env.test`** — mocking `$lib/db` makes real DB unnecessary.
+- **Mock `$app/forms`** for any component using `use:enhance` — provide `enhance: () => () => {}` (action → destroy fn).
+- **Mock TipTap** for client tests rendering ProclamationForm — the Svelte 5 component mock pattern is a function `($anchor: Element) => { …; $anchor.replaceWith(div); }`.
## Architecture notes
- **Route groups**: `(protected)/` requires auth + profile; `(user)/` is user-facing; `admin/` requires roleId <= 3 (STAFF+).
-- **Svelte-Admin**: custom config-driven CRUD in `src/lib/svelte-admin/`. Models registered in `registry.ts`. Dynamic routes `[model]/[id]`.
+- **Admin forms**: Each route has its own form component (e.g. `ProclamationForm.svelte`, `CategoryForm.svelte`). The generic `AdminForm.svelte` / `AdminPage.svelte` were **deleted**. Registry config (`registry.ts`) still provides metadata, list fields, search fields, relation options, etc., but forms are hand-written per route.
- **Prisma client** generated to `src/lib/generated/prisma/` (not `node_modules/.prisma/client`). Lazy singleton via Proxy in `src/lib/db.ts`.
- **Better-Auth** server instance in `src/lib/auth.ts`, browser client in `src/lib/auth-client.ts`. Session fetched in `src/hooks.server.ts`.
- **Roles**: integer hierarchy (SUPER=1, ADMIN=2, STAFF=3, USER=4). `canAccessAdmin` checks roleId <= 3.
@@ -44,7 +46,242 @@ Requires `.env` with at least `DATABASE_URL`, `BETTER_AUTH_SECRET`, `BETTER_AUTH
## Formatting conventions
- Tabs, single quotes, no trailing commas, 100 print width (enforced by Prettier).
-- Svelte 5 runes only (`$state`, `$derived`, `$effect`, `$props`). No legacy stores.
+- Svelte 5 runes only (`$state`, `$derived`, `$effect`, `$props`). No legacy stores (`writable`, `derived`, etc.).
+
+---
+
+## Svelte 5 Runes Gotchas & Patterns
+
+### `$state` initialization vs `$effect` sync
+
+State variables declared with `$state('')` are set once on component mount. To keep them in sync with a changing `$props()` value, use a **separate `$effect`**:
+
+```svelte
+
+```
+
+This is the **only correct pattern** for form fields that edit an existing record. Do NOT initialize `$state` directly from the prop — that only captures the initial value and won't re-sync when the parent changes the record (e.g. switching from one row to another in a modal).
+
+### `$effect` ordering matters
+
+Effects run in declaration order. If you have multiple effects, the first one's writes are applied before the second one runs. This matters for the `dirty` flag:
+
+```svelte
+let dirty = $state(false);
+
+// Runs first — resets dirty on mount / record change
+$effect(() => {
+ dirty = false;
+});
+
+// Runs second — syncs field values
+$effect(() => {
+ name = String(record.name ?? '');
+ // …
+});
+```
+
+Both effects run synchronously during mount, so by the time the browser paints, all state is consistent.
+
+### `bind:value` on `