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

3.4 KiB

title status audience language last_updated when_to_read
Zustand optimistische update + rollback active
ai-agent
contributor
nl 2026-05-10 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. 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

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)

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.