Scrum4Me/docs/patterns/zustand-optimistic.md
Madhura68 f9c56ab074 docs(PBI-74): richtlijn workspace-store + realtime patroon
Documenteert het patroon dat in Stories 1-8 is opgeleverd, zodat een
volgende workspace-store (sprint, of een nieuwe bounded context) hetzelfde
recept volgt.

- docs/patterns/workspace-store.md (nieuw): wanneer een workspace-store, de
  vijf state-slices, selectors-regels (G1/G2), race-safe ensure*Loaded met
  activeRequestId-guard (G4), SSE-hook + applyRealtimeEvent met
  unknown-event filter, hidden-tab + reconnect resync via
  useWorkspaceResync, restore-hint flow met await-chain en URL-prioriteit,
  optimistic mutations (applyOptimisticMutation/rollback/settle), API
  endpoint-vereisten (force-dynamic, cache: no-store), test-setup met
  MemoryStorage + originalActions snapshot + mockImplementation, gotchas
  G1-G8 als comment-template, en het 8-staps migratiepad.
- docs/patterns/zustand-optimistic.md: bijgewerkt voor de nieuwe
  workspace-store API; verwijst voor het bredere patroon naar
  workspace-store.md. Voorbeelden voor pbi-order + entity-patch.
- CLAUDE.md: patterns quickref aangevuld met workspace-store-rij.

Verify: typecheck clean.

Refs: PBI-74

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 01:53:55 +02:00

101 lines
3.4 KiB
Markdown

---
title: "Zustand optimistische update + rollback"
status: active
audience: [ai-agent, contributor]
language: nl
last_updated: 2026-05-10
when_to_read: "When adding client-side state mutations that need optimistic UI and rollback (DnD, status toggles)."
---
# Patroon: Zustand optimistische update + rollback
Sinds PBI-74 lopen optimistic mutations via `applyOptimisticMutation`/
`rollbackMutation`/`settleMutation` op de **workspace-store**. Het bredere
patroon (store-design, SSE-integratie, restore-hints, tests) staat in
[workspace-store.md](./workspace-store.md). Dit document beschrijft het
DnD/status-mutation flow specifiek.
## Patroon
1. Snapshot rollback-info via `applyOptimisticMutation` — krijgt `mutationId`.
2. Pas state direct aan via `setState`.
3. Server-actie aanroepen.
4. Op success: `settleMutation(mutationId)` (ruimt pending-record op).
5. Op error: `rollbackMutation(mutationId)` (herstelt vorige state + toast).
Cross-priority drag vereist twee mutaties (order + entity-patch) die samen
settlen of rollbacken.
## Voorbeeld — PBI reorder
```tsx
import { useProductWorkspaceStore } from '@/stores/product-workspace/store'
function handleDragEnd(event: DragEndEvent) {
const { active, over } = event
if (!over || active.id === over.id) return
const store = useProductWorkspaceStore.getState()
const prevOrder = [...store.relations.pbiIds]
const oldIndex = prevOrder.indexOf(active.id as string)
const newIndex = prevOrder.indexOf(over.id as string)
if (oldIndex === -1 || newIndex === -1) return
const newOrder = arrayMove([...prevOrder], oldIndex, newIndex)
// 1. Snapshot rollback-info
const mutationId = store.applyOptimisticMutation({
kind: 'pbi-order',
prevPbiIds: prevOrder,
})
// 2. Optimistisch toepassen
useProductWorkspaceStore.setState((s) => {
s.relations.pbiIds = newOrder
})
// 3-5. Server bevestigt of niet
startTransition(async () => {
const result = await reorderPbisAction(productId, newOrder)
const st = useProductWorkspaceStore.getState()
if (result.success) {
st.settleMutation(mutationId)
} else {
st.rollbackMutation(mutationId)
toast.error('Volgorde opslaan mislukt')
}
})
}
```
## Voorbeeld — entity-patch (priority-wijziging)
```tsx
const prevPbi = store.entities.pbisById[id]
const patchMutationId = store.applyOptimisticMutation({
kind: 'entity-patch',
entity: 'pbi',
id,
prev: prevPbi,
})
useProductWorkspaceStore.setState((s) => {
const pbi = s.entities.pbisById[id]
if (pbi) pbi.priority = newPriority
})
// settle/rollback identiek aan order-flow
```
## Mutation-soorten
| `kind` | Rollback-data | Use-case |
|---|---|---|
| `pbi-order` | `prevPbiIds` | DnD reorder van PBI's |
| `story-order` | `pbiId` + `prevStoryIds` | DnD reorder van stories binnen een PBI |
| `task-order` | `storyId` + `prevTaskIds` | DnD reorder van tasks binnen een story |
| `entity-patch` | `entity` + `id` + `prev` (volledig vorig record of `undefined` voor delete-rollback) | Property-wijzigingen (priority, status), of optimistic delete/undelete |
## SSE-echo idempotent verwerken
Wanneer de server bevestigt en de NOTIFY-trigger het bijbehorende event
emitteert, mag `applyRealtimeEvent` **geen dubbele insert** veroorzaken en
**geen rollback triggeren**. INSERTs checken bestaan; UPDATEs mergen
into-existing. Zie `applyRealtimeEvent` in [`stores/product-workspace/store.ts`](../../stores/product-workspace/store.ts).