feat(M14): 3-pane backlog — generic SplitPane, BacklogStore, SSE realtime, card-grid TaskPanel (#22)
* feat(split-pane): refactor to generic n-pane SplitPane with cookie persistence New API: panes[], defaultSplit[], cookieKey, tabLabels. Supports arbitrary number of panes with n-1 draggable dividers and JSON cookie persistence. Replaces TriplePane; mobile renders tabs. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(split-pane): migrate callers to new panes[] API Backlog page and sprint board now use generic SplitPane. TriplePane removed; sprint board uses 3-pane with defaultSplit=[28,35,37]. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test(split-pane): add unit tests for 2/3-pane, cookie-restore, mobile tabs Added jsdom + @testing-library/react devDeps for component testing. 7 cases: render, divider count, cookie restore, invalid cookie fallback, mobile tab render/switch, and no-dividers-on-mobile. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(backlog): add BacklogStore Zustand store with applyChange reducer State: pbis, storiesByPbi, tasksByStory. setInitialData for server hydration; applyChange(entity, op, data) handles I/U/D for SSE events. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(backlog): server-fetch tasks + hydrate BacklogStore on page load Page now fetches tasks parallel to stories and groups by story_id. BacklogHydrationWrapper calls setInitialData on mount so the store is ready for downstream SSE consumers. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(backlog): add EmptyPanel shared component, replace inline empty states EmptyPanel takes title?, message, and optional action with DemoTooltip. Replaces duplicate inline empty-state markup in pbi-list and story-panel. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(backlog): add TaskPanel with sortable rows and TaskDialog wiring Reads selectedStoryId + tasksByStory from stores. DnD reorder via reorderTasksAction. Row click → ?editTask, + button → ?newTask&storyId. DemoTooltip on drag handles and + button. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(backlog): wire TaskPanel + TaskDialog into backlog page 3-pane SplitPane [20,45,35]. searchParams for newTask/editTask. TaskDialog and EditTaskLoader render on ?newTask and ?editTask. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test(backlog): add TaskPanel tests for render states and click handlers 7 cases: no-story empty, no-tasks empty+action, tasks render, + button router.push, row click router.push, demo disabled button, demo disabled handles. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(backlog): migrate PbiList to store-driven via useBacklogStore Removes pbis prop; reads from useBacklogStore(s => s.pbis) so SSE updates reflect in real-time without prop drilling. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(backlog): migrate StoryPanel to store-driven + selectStory on click Removes storiesByPbi prop; reads from useBacklogStore. Card click now dispatches selectStory(id) + shows isSelected highlight. Edit moved to inline pencil button. page.tsx drops pbis/storiesByPbi props. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test(backlog): add 3-pane integration tests for click-cascade flow Covers: empty states, PBI→stories, story→tasks, cascade-reset, isSelected highlight. localStorage mocked for sort-mode persistence. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(ST-1115): SSE backlog realtime — endpoint, hook, hydration mount, tests Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(ST-1116): mobile auto-switch tabs + back button in BacklogSplitPane Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * docs(ST-1116): update functional-spec (3-pane backlog + mobile) and architecture (backlog SSE + backlog-store) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(ST-1117): TaskPanel card-grid — BacklogCard + rectSortingStrategy, tests updated Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(tests): correct PbiStatusApi type and remove duplicate mock keys Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
6cd98129f2
commit
8877ea469d
22 changed files with 2474 additions and 305 deletions
|
|
@ -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<string, unknown>)
|
||||
```
|
||||
|
||||
- **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:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue