diff --git a/docs/scrum4me-architecture.md b/docs/scrum4me-architecture.md index 5197f99..6439d5f 100644 --- a/docs/scrum4me-architecture.md +++ b/docs/scrum4me-architecture.md @@ -733,8 +733,9 @@ scrum4me/ │ ├── product-access.ts # productAccessFilter helper (eigenaar of teamlid) │ └── env.ts # Zod-gevalideerde env vars ├── stores/ # Zustand stores +│ ├── backlog-store.ts # PBI/story/task hydration + applyChange (SSE) │ ├── planner-store.ts # Optimistische drag-and-drop volgorde -│ ├── selection-store.ts # Geselecteerd PBI / story +│ ├── selection-store.ts # Geselecteerd PBI / story (cascade-reset) │ ├── sprint-store.ts # Sprint Backlog taakvolgordes │ ├── solo-store.ts # Solo board optimistische taakstatus │ └── product-store.ts # Actief product (naam + id) voor navbar @@ -1003,6 +1004,67 @@ Iron-session cookie of Bearer-token (demo). De auth-check loopt éénmalig bij d --- +## Realtime — Backlog SSE (ST-1115) + +De Product Backlog-pagina (`/products/[id]`) update live als PBI's, stories of taken worden gemuteerd. De pijplijn is gelijk aan de Solo-SSE (M8), maar met een eenvoudiger server-side filter: alleen `product_id`-scope, geen sprint- of user-scope. + +``` +┌─────────────────────────┐ +│ Mutatie (Prisma write) │ Server Action, MCP, etc. +└────────────┬────────────┘ + ▼ +┌─────────────────────────┐ +│ Postgres row trigger │ AFTER INSERT/UPDATE/DELETE +│ scrum4me_notify_change()│ entity: 'pbi' | 'story' | 'task' +└────────────┬────────────┘ + ▼ pg_notify('scrum4me_changes', json) +┌─────────────────────────┐ +│ /api/realtime/backlog │ Node runtime, dedicated pg.Client +│ LISTEN scrum4me_changes │ filtert op entity ∈ {pbi,story,task} +│ │ én product_id matcht query-param +└────────────┬────────────┘ + ▼ text/event-stream +┌─────────────────────────┐ +│ EventSource (browser) │ beheerd door useBacklogRealtime +│ → backlog-store.apply │ via applyChange(entity, op, data) +│ Change(entity,op,data)│ +└────────────┬────────────┘ + ▼ +┌─────────────────────────┐ +│ PbiList / StoryPanel / │ re-render op basis van Zustand state +│ TaskPanel re-render │ +└─────────────────────────┘ +``` + +### Hydration en SSE-mount + +De pagina is een Server Component die alle data parallel fetcht. Resultaten worden doorgegeven aan `BacklogHydrationWrapper` (client component), die: +1. `useBacklogStore.setInitialData(...)` aanroept op mount (eenmalig). +2. `useBacklogRealtime(productId)` mount — opent de SSE-stream. + +Alle client-componenten (PbiList, StoryPanel, TaskPanel) lezen uitsluitend uit de Zustand store; ze accepteren geen data-props meer. + +### backlog-store en applyChange + +```ts +// stores/backlog-store.ts +applyChange(entity: 'pbi' | 'story' | 'task', op: 'I' | 'U' | 'D', data: Record) +``` + +- **I (Insert):** voegt het nieuwe object toe aan de juiste sub-array +- **U (Update):** patcht de bestaande entry in-place via spread (`{ ...existing, ...data }`) +- **D (Delete):** filtert de entry weg op `id`; doorzoekt alle sub-arrays omdat de parent-ID afwezig kan zijn in het delete-payload + +### Server-side filter (backlog) + +`/api/realtime/backlog?product_id=...` filtert op: +- `entity ∈ {pbi, story, task}` — job/worker-events en questions worden genegeerd +- `product_id` matcht de query-param + +Demo-gebruikers mogen lezen (geen 403). Overige lifecycle-kenmerken (heartbeat, hard-close, backoff, visibility-pause) zijn identiek aan de Solo SSE. + +--- + ## Demo-user policy (ST-1110) Demo-gebruikers (`is_demo = true` in de database, `isDemo: true` in de iron-session) hebben volledig read-only toegang. Bescherming is drielaags: diff --git a/docs/scrum4me-functional-spec.md b/docs/scrum4me-functional-spec.md index 9b5712e..69dc93a 100644 --- a/docs/scrum4me-functional-spec.md +++ b/docs/scrum4me-functional-spec.md @@ -194,27 +194,37 @@ Gebruikers kunnen producten aanmaken, bewerken en archiveren. Een product is het --- -### F-04: Product Backlog — gesplitst scherm +### F-04: Product Backlog — 3-paneels gesplitst scherm **Prioriteit:** v1 — Kritiek **Persona:** Lars, Dina, Remi **Omschrijving:** -De Product Backlog wordt weergegeven als een gesplitst scherm: links de PBI's gerangschikt op prioriteit en volgorde, rechts de stories van het geselecteerde PBI. De splitter is horizontaal versleepbaar. Elk paneel heeft een eigen navigatiebar met acties (aanmaken, filteren, verwijderen). +De Product Backlog wordt weergegeven als een 3-paneels gesplitst scherm: PBI's (links) | Stories (midden) | Taken (rechts). De splitters zijn versleepbaar. Selectie cascadeert: klikken op een PBI toont de bijbehorende stories; klikken op een story toont de bijbehorende taken. Elk paneel heeft een eigen navigatiebar met acties. **Acceptatiecriteria:** -- [ ] Standaard splitverhouding is 40/60 (PBI's / stories) -- [ ] Splitter is versleepbaar; positie wordt lokaal opgeslagen (localStorage) -- [ ] Selecteren van een PBI links toont de bijbehorende stories rechts +- [ ] Standaard splitverhouding is 20/45/35 (PBI's / Stories / Taken) +- [ ] Splitters zijn versleepbaar; positie wordt opgeslagen in een cookie (`sp:backlog-{id}`) +- [ ] Selecteren van een PBI toont de bijbehorende stories in het middenpaneel - [ ] Geselecteerd PBI is visueel gemarkeerd (achtergrondkleur of rand) -- [ ] Linkerpaneel navigatiebar bevat: [+ PBI aanmaken], [filter], [verwijderen] -- [ ] Rechterpaneel navigatiebar bevat: [+ Story aanmaken], [filter], [verwijderen] -- [ ] Lege staat links: prompt om eerste PBI aan te maken -- [ ] Lege staat rechts (geen PBI geselecteerd): instructie om een PBI te selecteren -- [ ] Lege staat rechts (PBI geselecteerd, geen stories): prompt om eerste story aan te maken +- [ ] Selecteren van een story toont de bijbehorende taken in het rechterpaneel +- [ ] Geselecteerde story is visueel gemarkeerd +- [ ] Cascade-reset: selecteren van een ander PBI wist de geselecteerde story en taken +- [ ] PBI-paneel navigatiebar bevat: [+ PBI aanmaken] +- [ ] Stories-paneel navigatiebar bevat: [+ Story aanmaken], [sorteer], [filter status] +- [ ] Taken-paneel navigatiebar bevat: [+ Nieuwe taak] +- [ ] Lege staat PBI-paneel: prompt om eerste PBI aan te maken +- [ ] Lege staat Stories-paneel (geen PBI geselecteerd): instructie om een PBI te selecteren +- [ ] Lege staat Stories-paneel (PBI geselecteerd, geen stories): prompt om eerste story aan te maken +- [ ] Lege staat Taken-paneel (geen story geselecteerd): instructie om een story te selecteren +- [ ] Lege staat Taken-paneel (story geselecteerd, geen taken): prompt om eerste taak aan te maken +- [ ] Taak aanmaken opent TaskDialog via `?newTask=1&storyId={id}` +- [ ] Taak bewerken opent TaskDialog via `?editTask={id}` **Randgevallen:** -- Scherm smaller dan 768px → gesplitst scherm schakelt over naar tabbladen (PBI's / Stories) +- Scherm smaller dan 1024px → 3-paneels scherm schakelt over naar 3 tabbladen (PBI's | Stories | Taken) +- Mobile tab-navigatie: klikken op PBI schakelt automatisch naar Stories-tab; klikken op story schakelt naar Taken-tab +- Mobile ← terug-knop in tab-header op tabs 2 en 3 navigeert naar het vorige tabblad ---