# Scrum4Me-Research — Zustand rearchitecture (reset + execute) > **Scope:** dit plan is geschreven voor de research-repo > [`madhura68/Scrum4Me-Research`](https://github.com/madhura68/Scrum4Me-Research), > niet voor dit hoofdproject. Bestandsverwijzingen die naar > `stores/data-store.ts`, `hooks/use-event-stream.ts`, > `components/*-select.tsx` etc. wijzen, bestaan in de research-repo — > niet hier. Ze staan in `code`-tags zodat de doc-link-checker ze niet > probeert te resolven. ## Context Het bestaande [zustand-store-rearchitecture.md](./zustand-store-rearchitecture.md) beschrijft een doel-architectuur (`product-workspace-store` met genormaliseerde entities, race-safe loaders, resync-laag, optimistic mutations). De research-repo is dé plek om dat eerst te testen voordat het in `Scrum4Me/` belandt. Probleem nu: de research-repo wijkt af van het hoofdproject. Mijn custom `data-store.ts` lijkt qua vorm op de doel-architectuur, maar springt over de baseline heen. We willen aantonen dat de migratie *vanaf* de huidige Scrum4Me-patronen werkt, niet vanaf een verzonnen tussenvorm. Dus: eerst de research-repo terugbrengen naar dezelfde stores/hooks/routes als Scrum4Me nu heeft, dan de rearchitecture daarop uitvoeren. ## Bron-documenten - **Doel-architectuur**: [zustand-store-rearchitecture.md](./zustand-store-rearchitecture.md) (in research-repo). Dit plan voert dat document uit; herhaalt het niet. - **Conventies**: [CLAUDE.md](../../CLAUDE.md) hoofdproject. Taal NL, MD3 tokens, `@base-ui/react` render-prop, `*-server.ts`, enum UPPER_SNAKE↔lowercase via `lib/task-status.ts`. ## Drie-faseplan ### Fase A — Reset naar Scrum4Me-patronen Doel: onze research-pagina werkt op exact dezelfde store/hook/route-vorm als het hoofdproject, met identiek gedrag. **Verwijderen** (research-repo): - `stores/data-store.ts` (research-repo) — mijn megastore - `hooks/use-event-stream.ts` (research-repo) — vervangen door `use-backlog-realtime.ts` - `hooks/use-browser-presence.ts` (research-repo) — niet in main, drop voor reset - `app/api/realtime/events/route.ts` (research-repo) — vervangen door `app/api/realtime/backlog/route.ts` - Mijn custom `loadX/resyncAll`-paden in selectie-componenten **Kopiëren uit `/Users/janpetervisser/Development/Scrum4Me/`** (1-op-1 of stripped van auth): | Bron | Doel | |---|---| | `stores/backlog-store.ts` | `stores/backlog-store.ts` (`pbis`, `storiesByPbi`, `tasksByStory`; `setInitialData`, `applyChange`) | | `stores/planner-store.ts` | `stores/planner-store.ts` (DnD-order; voor research nog niet gebruikt maar we zetten 'm klaar) | | `stores/selection-store.ts` | overschrijf bestaand (state: `selectedPbiId`, `selectedStoryId`, geen taskId/productId in main; add `selectedTaskId` + `productId` als research-uitbreiding) | | `stores/product-store.ts` | `stores/product-store.ts` (`currentProduct`) | | `stores/products-store.ts` | `stores/products-store.ts` (lijst, voor pulldown) | | `lib/realtime/use-backlog-realtime.ts` | `lib/realtime/use-backlog-realtime.ts` (SSE-client → `applyChange` op backlog-store) | | `lib/task-status.ts` | `lib/task-status.ts` (enum-converters) | | `app/api/realtime/backlog/route.ts` | `app/api/realtime/backlog/route.ts` (SSE+LISTEN, **research-only: strip auth/session/getAccessibleProduct** — vraagt enkel `product_id` querystring) | > ⚠️ **Auth-strip is research-only.** Het hoofdproject MOET sessie + `getAccessibleProduct()`-check op SSE en read-routes behouden. Bij backport vanaf de research-repo nooit de geknipte route 1-op-1 overnemen. Dit geldt voor zowel `app/api/realtime/backlog/route.ts` als alle read-routes onder `app/api/products/...`, `/pbis/...`, `/stories/...`, `/tasks/...`. **API-routes (read)** — bestaande paden behouden, alleen `force-dynamic` blijft: - `GET /api/products` (list voor pulldown) - `GET /api/products/[id]/pbis` (open: READY+BLOCKED) - `GET /api/pbis/[id]/stories` - `GET /api/stories/[id]/tasks` - `GET /api/tasks/[id]` **Componenten herschrijven**: - `components/product-select.tsx` (research-repo) → leest `useProductsStore`, schrijft naar `useProductStore.setCurrentProduct` - `components/pbi-select.tsx` (research-repo) → leest `useBacklogStore` (filter op currentProduct), `useSelectionStore.selectPbi`. Triggert fetch op product-mount via een `useBacklogLoader`-helper die initial data binnenhaalt. - `components/story-select.tsx` (research-repo) → idem voor stories - `components/tasks-table.tsx` (research-repo) → leest `tasksByStory[selectedStoryId]`. **Max 10 rijen, scrollbaar** (al ingebouwd, behouden) - `components/task-detail-card.tsx` (research-repo) → fetcht task detail apart (geen full-fat backlog veld; matcht main's `tasks/[id]` route) - `components/event-stream-panel.tsx` (research-repo) → blijft bestaan voor research-doel (event-tap), maar luistert nu mee op dezelfde EventSource via `use-backlog-realtime` (of een tweede readonly listener); selecteerbare events met JSON-detail rechts blijven. Twee checkboxes (Postgres / Browser). Truncate met ellipsis in de lijst. **Werkwijzen (verifiëren tijdens reset)**: - Comments en UI-tekst NL - Geen `bg-blue-500` etc; enkel MD3 tokens (`bg-primary`, `bg-card`, `bg-status-done`, ...) - shadcn-componenten al `base-nova` style - Server-only files krijgen `*-server.ts` suffix waar van toepassing (in deze fase niet nodig — alle DB-toegang loopt via `lib/prisma.ts` in route handlers) - TaskStatus-mapping via `lib/task-status.ts` als de UI lowercase wil **Acceptatie Fase A**: - `npx tsc --noEmit` schoon - Pagina rendert, cascading werkt, tabel toont taken, detail-card vult, events stromen door (preview-verificatie) - Stores matchen het hoofdproject qua vorm (vergelijking via `diff` uitvoerbaar voor backlog-store etc.) ### Fase B — Rearchitecture uitvoeren Volgt de 15 stappen uit [zustand-store-rearchitecture.md](./zustand-store-rearchitecture.md) §Implementatiepad. Concreet voor de research-repo: 1. **Map** `stores/product-workspace/` aanmaken (factory + provider + selectors). 2. **`activeProduct`** wordt nu nog gespiegeld vanuit `useProductStore`; voor de research-pagina geen layout/server-side bepaling — we lezen het uit de pulldown-state. 3. **Selection migreren** — `selection-store` → `context.{activePbiId, activeStoryId, activeTaskId}` + `productId`. Setters cascaden de reset naar children (zoals doc beschrijft). 4. **Backlog naar entities + relations** — `pbisById`, `storiesById`, `tasksById`, `pbiIds`, `storyIdsByPbi`, `taskIdsByStory`. Selectors: - `selectVisiblePbis(productId)` - `selectStoriesForActivePbi(state)` - `selectTasksForActiveStory(state)` - `selectActivePbi/Story/Task(state)` 5. **Planner-state** in dezelfde workspace-store landen (`relations` slice); voor research: niet actief gebruikt, wel structureel meekoppen. 6. **Race-safe loaders** — `ensureProductLoaded`, `ensurePbiLoaded`, `ensureStoryLoaded`, `ensureTaskLoaded` met `requestId`-guard. Implementatie: ```ts setActivePbi(pbiId) { const requestId = crypto.randomUUID() set({ context: { ..., activePbiId: pbiId, ... }, loading: { ..., activeRequestId: requestId } }) void get().ensurePbiLoaded(pbiId, requestId) } // in ensure: if (get().loading.activeRequestId !== requestId) return ``` 7. **localStorage = restore hints** — `lastActivePbiIdByProduct`, `lastActiveStoryIdByProduct`, `lastActiveTaskIdByProduct`. Niet de waarheid, alleen hint die getoetst wordt aan toegankelijkheid. 8. **`use-backlog-realtime` dispatcht naar `applyRealtimeEvent`** — store interpreteert pbi/story/task I|U|D events, doet upsert + parent-id move + sort. 9. **Hidden tab beleid** — `EventSource` openhouden bij `hidden`. Op `visible` → `resyncActiveScopes('visible')`. 10. **Reconnect resync** — bij `ready` na disconnect of na exponential backoff: `resyncActiveScopes('reconnect')`. 11. **Unknown-event fallback** — onbekend event met `payload.product_id === activeProductId` → `resyncActiveScopes('unknown-event')`. Dit is wat het "veel events maar geen update" issue oplost. 12. **`force-dynamic` + `cache: 'no-store'`** — al gedaan in mijn fixes; behouden bij reset en versterken. 13. **Componenten naar selectors** — backlog-componenten lezen via `selectStoriesForActivePbi` etc., niet via raw store-velden. 14. **Tests** (Vitest, conform main): - hydrate snapshot - active selectie cascade - race-safe ensure (laat trage promise van oude selectie geen nieuwe data overschrijven) - SSE I|U|D voor pbi/story/task - parent-move (story verandert van pbi) - hidden→visible resync - reconnect resync - unknown-event resync - delete-cleanup van actieve selectie - localStorage restore-hint validatie tegen toegankelijkheid 15. **Sprint-workspace** — buiten scope; flag voor latere herhaling. **Optimistic mutations** (§Optimistic in doc): voor research geen DnD, dus alleen het patroon dropunten en niet bouwen. Wel: `applyOptimisticMutation`-action wel klaarzetten in de store-API zodat het patroon zichtbaar is. ### Fase C — Werkwijzen verweven en doortrekken **Tijdens Fase A én B respecteren**: 1. **Plan mode workflow** — eerst Plan, ExitPlanMode, dan code. Bij grote wendingen opnieuw plannen. 2. **TodoWrite** voor multi-step werk; markeer immediate completion. 3. **Verify via preview** voor elke observable verandering (de hook reminder doet dit al). 4. **`tsc --noEmit`** voor afronden van een stap. 5. **Comments/Dutch** consistent. WHY-comments over de invariant; geen WHAT-comments. 6. **MD3 tokens** alleen. 7. **Geen secrets in chat** — `.env.local` blijft lokaal. 8. **Niet schrijven naar shared DB** zonder expliciete user-toestemming (geen `pg_notify` op shared channel). 9. **Source of truth = DB**. Zustand is projectie. localStorage = hint. 10. **Vóór elke fase**: kort statusrapport in de chat met wat er aankomt en waarom. **Doortrekken naar hoofdproject** (out-of-scope deze run, maar geflagd): - Na bewezen werking in research-repo: backport `product-workspace-store` + selectors + realtime-apply + resync-laag naar `Scrum4Me/stores/product-workspace/`. - **Niet backporten**: de auth-stripped routes uit research. Main behoudt iron-session, `getAccessibleProduct()`, en alle product-access/sprint/personal filters in z'n SSE- en read-routes. - **Wel backporten**: store-shape, selectors, race-safe `ensure*Loaded`, hidden-tab beleid, `resyncActiveScopes`, unknown-event fallback, restore-hint patroon, `force-dynamic` + `cache: 'no-store'`. - Migratie main-project zal langer duren (DnD, sprint, jobs, tests). Apart plan. ## Bestandsmutaties (overzicht) ### Verwijderen na Fase A - `stores/data-store.ts` (research-repo) - `hooks/use-event-stream.ts` (research-repo) - `hooks/use-browser-presence.ts` (research-repo) — komt deels terug in Fase B als helper voor visibility/online resync trigger - `app/api/realtime/events/route.ts` (research-repo) ### Toevoegen Fase A (uit Scrum4Me) - `stores/backlog-store.ts` - `stores/planner-store.ts` - `stores/selection-store.ts` (overschrijf) - `stores/product-store.ts` - `stores/products-store.ts` - `lib/realtime/use-backlog-realtime.ts` - `lib/task-status.ts` - `app/api/realtime/backlog/route.ts` (zonder auth) ### Toevoegen Fase B (nieuw, conform doc) - `stores/product-workspace/store.ts` (zustand factory) - `stores/product-workspace/selectors.ts` - `stores/product-workspace/types.ts` - `stores/product-workspace/restore.ts` (localStorage hints) - `stores/product-workspace/realtime-apply.ts` (SSE event → store) - `stores/product-workspace/resync.ts` (`resyncActiveScopes`, `resyncLoadedScopes`) - `tests/product-workspace/*.test.ts` (Vitest, install vitest als devDep) ### Te aanpassen in Fase B - Alle `components/*.tsx` (nu shadcn select/table/card panels) → consumeren via selectors uit workspace-store - `lib/realtime/use-backlog-realtime.ts` → dispatcht `applyRealtimeEvent` naar workspace-store i.p.v. `applyChange` naar backlog-store - `event-stream-panel.tsx` → blijft bestaan (research-tap), maar leest events ook uit workspace-store of via een dunne `event-log-store` ernaast (in bounded-context-stijl: aparte log-store voor pure observatie hoort er niet thuis in de workspace-store) ## Verificatie ### Na Fase A (baseline) 1. `npm run dev` op port 3001 2. Pagina laadt, cascading werkt: product → PBI → story → tasks 3. Detail-card vult bij klik op task 4. Event-paneel toont realtime events (truncate + JSON-detail) 5. `npx tsc --noEmit` schoon 6. Vergelijk: `diff Scrum4Me/stores/backlog-store.ts Scrum4Me-Research/stores/backlog-store.ts` → identiek (modulo lokale interface-uitbreidingen waar gedocumenteerd) ### Na Fase B (target) Alle acceptatiecriteria uit [zustand-store-rearchitecture.md §Acceptatiecriteria](./zustand-store-rearchitecture.md): - Eén waarheid per entity in de store ✓ - Selectors als enige UI-leesweg ✓ - SSE patcht zonder full-page refresh ✓ - Hidden→visible herstelt missers binnen één resync-cyclus ✓ - Reconnect resync werkt zonder NOTIFY-replay ✓ - Directe task-edits zonder `entity:'task'` NOTIFY worden via unknown-event fallback zichtbaar ✓ - LocalStorage = hint, geen forced state ✓ - `force-dynamic` + `cache: 'no-store'` overal ✓ ### Manuele preview-verificatie (na elke fase) - TODO via TodoWrite tijdens uitvoer; preview-screenshot na grote stappen - Tab-switch test: open page, switch tab, doe een wijziging via een ander mechanisme (psql na user-akkoord, of UI in main-project), keer terug → verwacht: zonder warnings + data gerefresht ## Open vragen / risico's 1. **Reset-import** uit hoofdproject: voor de research-repo strippen we auth/session-deps uit de gekopieerde routes (research-repo heeft geen auth-laag, draait lokaal). Belangrijk: **dit is een research-repo-keuze; main behoudt de volledige auth-filters**. Zie de waarschuwing onder "API-routes (read)" hierboven. 2. **`use-backlog-realtime` heeft mogelijk auth-headers/session-checks**: bevestigen tijdens copy. Indien zo: research-versie gebruikt geen auth, route is publiek-bereikbaar binnen lokale dev. Geldt alleen lokaal — geen wijziging aan main. 3. **Tests-deps** (vitest, @testing-library/react) toevoegen tijdens Fase B. Of pas in Fase B step 14 vanwege scope. 4. **Event-paneel toekomst**: blijft het in research-repo of stoten we het af zodra de workspace-store af is? Voorstel: behouden als observatie-tool, maar er aparte `event-log-store` (kleine UI store) voor maken zodat het niet meelift in de workspace-store. 5. **README.md** update na Fase B (optioneel) — kort beschrijven dat dit nu het canonical migratie-pad demonstreert.