From 0c7d5dd2aa3b606be3a86f4c83fdfadb650c5a8a Mon Sep 17 00:00:00 2001 From: JCEEE <0xjceee@proton.me> Date: Wed, 29 Jul 2026 10:07:29 +0100 Subject: [PATCH] add optimistic update ff --- AGENTS.md | 26 +++++++++++++++---- .../src/routes/[fam]/[username]/+page.svelte | 8 +++--- .../[fam]/[username]/bonuses/+page.svelte | 8 ++++-- .../[fam]/[username]/chores/+page.svelte | 20 +++++++++----- .../[fam]/[username]/rewards/+page.svelte | 2 +- 5 files changed, 46 insertions(+), 18 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index cddbb45..6d6b254 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -105,13 +105,29 @@ $effect(() => { let items = $state(famStore.initialized ? famStore.items : (data.items || [])); ``` -**Form action callbacks must call `famStore.applyRecord()`:** -```ts -// In use:enhance callback: -famStore.applyRecord('collection_name', record, 'create' | 'update' | 'delete'); +## Update Patterns + +Two patterns based on who's acting: + +| Pattern | Who | Frequency | Sensitivity | Optimistic? | Auth | +|---------|-----|-----------|-------------|-------------|------| +| Direct `fetch` + `memberApi` | Member | High (chore toggles) | None | Yes (instant UI, reconcile on response) | `x-device-token` header | +| Form action | Admin | Low (CRUD) | High (settings, members) | No — form is server-side, wait for round trip | httpOnly `session` cookie | + +**Member direct fetch** — optimistic UI via local state mutation, reconciled on response: +```svelte +let completions = $state(data.completions) +async function toggle(chore) { + // optimistic update + completions = [...completions, { id: 'optimistic-...', ... }] + try { + await memberApi.toggleCompletion(token, famId, chore.id, date) + // reconcile — remove optimistic, keep server truth + } catch { /* revert */ } +} ``` -The `famStore` has reactive `$state` properties and an `applyRecord()` method designed for instant UI feedback from form actions. Calling `applyRecord()` mutates the store directly — the UI updates immediately without waiting for PB SSE. The SSE subscription is a backup for multi-user sync only. Do NOT add `$effect` watchers to bridge the gap between form actions and reactive state. +**Admin form actions** — no optimistic `applyRecord` needed in `use:enhance` callbacks. The form action is a server round trip, and PB SSE pushes the change back through `famStore.handleRealtime()` within milliseconds. The famStore + SSE subscription is the single source of truth for cross-user sync. Do NOT add `$effect` watchers to bridge the gap between form actions and reactive state. ## UI Component Conventions diff --git a/frontend/src/routes/[fam]/[username]/+page.svelte b/frontend/src/routes/[fam]/[username]/+page.svelte index b83869f..ed0937b 100644 --- a/frontend/src/routes/[fam]/[username]/+page.svelte +++ b/frontend/src/routes/[fam]/[username]/+page.svelte @@ -380,7 +380,7 @@ import type { AssignedChore, Completion, ChoreTemplate, BonusConfig, Reward } fr {#each todays as c}