Scrum4Me/docs/patterns/demo-client-state.md
Madhura68 96e4423485 docs(PBI-80): ADR-0006 addendum + demo-client-state patroon (ST-1347)
ADR-0006 krijgt een "Updated 2026-05-12"-sectie die de PBI-80-uitzondering
documenteert: client-side UI-prefs (filters, sort, layout, scope-keuze) zijn
voor demo toegestaan via in-memory store, terwijl alle data-mutaties three-layer
beschermd blijven. Patroon-doc beschrijft wanneer en hoe `isDemo` te gebruiken
in nieuwe componenten. CLAUDE.md quickref + docs/INDEX.md ge-update.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 19:33:25 +02:00

4 KiB

title status audience language last_updated when_to_read
Demo client-state (UI-prefs zonder DB) active
ai-agent
contributor
nl 2026-05-12 Bij elk nieuw UI-element dat de demo-gebruiker zou willen kunnen wijzigen — filter, sortering, panel-state, geselecteerde scope (product/sprint), enz.

Patroon: Demo client-state

De demo-gebruiker (session.isDemo === true) deelt één DB-rij met alle andere demo-bezoekers. DB-writes voor demo zouden cross-bezoeker-pollution geven, dus de three-layer policy uit ADR-0006 blokkeert ze. PBI-80 introduceert één uitzondering: client-side UI-state mag gewijzigd worden, in-memory en zonder server-call.


Wanneer toepassen

Soort wijziging Voor demo? Hoe
Filter / sortering / collapse / split-pane / selectie Ja useUserSettingsStore.setPref([...], value) — store regelt de demo-fork al
Wisselen van actief product of sprint Ja router.push('/products/...') zonder server-action
PBI/story/taak/sprint create/update/delete/reorder Nee Server-action met 403-guard blijft hard verplicht
Account, rollen, pairing, web-push Nee Idem

Hoe isDemo lezen (client component)

import { useUserSettingsStore } from '@/stores/user-settings/store'

const isDemo = useUserSettingsStore(s => s.context.isDemo)

UserSettingsBridge hydrateert deze waarde in app/(app)/layout.tsx, dus elke client child ziet meteen de juiste vlag.


Voorbeeld 1 — UI pref (filters, sort, layout)

Geen extra werk. De store-actie regelt de demo-fork zelf:

// Werkt voor alle gebruikers, demo + niet-demo
useUserSettingsStore.getState().setPref(
  ['views', 'pbiList', 'filterStatus'],
  'OPEN',
)

Voor demo doet setPref een lokale Zustand-merge zonder server-call; voor niet-demo gaat het via updateUserSettingsAction (DB + SSE).


Voorbeeld 2 — Scope-wissel (product/sprint)

Fork in de UI-handler — server-action blijft achter de fork onveranderd:

function handleSwitchProduct(productId: string) {
  if (productId === activeId) return
  if (isDemo) {
    router.push(`/products/${productId}`)
    return
  }
  startTransition(async () => {
    const result = await setActiveProductAction(productId)
    // ... bestaande not-demo flow
  })
}

Voor pagina's waarvan de scope al in de URL zit (zoals /products/[id]/sprint/[sprintId]) is router.push met de gewenste path voldoende — server resolveert de juiste data uit de URL-params.


Visuele consistentie na URL-only switch

Server-rendered layouts blijven voor demo de seed-default lezen (user.active_product_id, user.settings.layout.activeSprints[...]). Als de UI een "actief X"-label toont dat van de server-prop komt, leid het voor demo af uit pathname:

const urlProductId = pathname.match(/^\/products\/([^/]+)/)?.[1] ?? null
const displayActive =
  isDemo && urlProductId
    ? products.find(p => p.id === urlProductId) ?? activeProduct
    : activeProduct

Gebruik displayActive in de render in plaats van de prop.


Verboden voor demo

  • Server-action aanroepen zonder fork — 403 + onnodige toast.
  • Wegschrijven naar cookies of localStorage — pollutie tussen bezoekers.
  • setActiveSprintInSettings / vergelijkbare DB-helpers rechtstreeks aanroepen.
  • Web-push subscription registreren — schrijft naar gedeelde PushSubscription-tabel.

Defense in depth

Server-actions (actions/active-product.ts, actions/active-sprint.ts, actions/user-settings.ts) behouden hun if (session.isDemo) return 403-guard. Als toekomstige UI-code per ongeluk de fork mist, faalt de call hard met 403 en zien we het via toast/logs.


Zie ook