docs(cleanup): archief verouderde plannen, backlog en root-duplicaten (#191)

* docs(cleanup): archief verouderde plannen, backlog en root-duplicaten

- 6 plans naar docs/old/plans/ (PBI-11/75/78, user-settings-store, Local github setup, lees-de-readme — laatste was verkeerde repo)
- docs/backlog/ naar docs/old/backlog/ (pre-MCP statische registry; live werk loopt via Scrum4Me-MCP)
- 6 root-level duplicaten naar docs/old/ (functional, {pbi,story,task}-dialog, product-backlog, backlog)
- 2 landing plans (niet uitgevoerd) krijgen archived: true frontmatter — blijven op plek maar uit INDEX
- scripts/generate-docs-index.mjs: skip docs/old/** + skip archived: true
- CLAUDE.md: rijen docs/backlog/, docs/plans/<key>-*.md, docs/manual/ weg; Track B-sectie verwijderd
- README.md / CHANGELOG.md / docs/plans/v1-readiness.md: link-fixes naar nieuwe locaties

Verify groen (lint + typecheck + 718 tests). docs/INDEX.md geregenereerd.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(cleanup): registreer handmatige verplaatsingen en fix referenties

- 4 plans verplaatst naar docs/old/plans/ (M10-qr-pairing-login, auto-pr-deploy-sync, docs-restructure-ai-lookup, v1-readiness)
- 3 archive-plans verplaatst naar docs/old/plans/ (archive-map nu leeg)
- ST-1114-copilot-reviews + 3 research-docs naar nieuwe docs/Ideas/ map
- Duplicaat docs/old/2026-04-27-m8-realtime-solo.md verwijderd (origineel zit in docs/old/plans/)
- Link-fixes naar nieuwe locaties:
  - CHANGELOG.md → docs/old/plans/v1-readiness.md
  - docs/runbooks/deploy-control.md → docs/old/plans/auto-pr-deploy-sync.md (2x)
  - docs/runbooks/worker-idempotency.md → docs/old/plans/auto-pr-deploy-sync.md
  - docs/plans/docs-restructure-pbi-spec.md → docs/old/plans/docs-restructure-ai-lookup.md (4x text + 2x href)
- docs/INDEX.md geregenereerd (96 docs, was 100)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Janpeter Visser 2026-05-11 19:46:00 +02:00 committed by GitHub
parent d587be2fb3
commit b39c3ec2e1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
36 changed files with 1068 additions and 49 deletions

View file

@ -0,0 +1 @@
# placeholder — remove when first file is added

828
docs/old/backlog/index.md Normal file
View file

@ -0,0 +1,828 @@
---
title: "Scrum4Me — Implementatie Backlog"
status: active
audience: [maintainer, contributor]
language: nl
last_updated: 2026-05-03
---
# Scrum4Me — Implementatie Backlog
**Versie:** 0.1 — april 2026
**Volgt op:** Functionele Specificatie v0.2, Architectuur v0.1
---
## MVP-definitie
De MVP is klaar wanneer Lars — de primaire persona — de volledige cyclus kan doorlopen: een product aanmaken, een Product Backlog opbouwen met PBI's en stories, een Sprint plannen, taken aanmaken, en Claude Code de volgende story laten ophalen, implementeren en vastleggen — allemaal zonder hulp of handleiding. De app draait stabiel op Vercel en is volledig lokaal opzetbaar via één README.
---
## Milestone-overzicht
| Milestone | Doel | Tasks |
|---|---|---|
| M0: Foundation | Project, database, auth, navigatieshell | ST-001 ST-008 |
| M1: Producten & Product Backlog | Producten, PBI's, gesplitst scherm | ST-101 ST-110 |
| M2: Stories & Drag-and-drop | Stories als blokken, dnd-kit, Zustand | ST-201 ST-210 |
| M3: Sprint Backlog & Sprint Planning | Sprint aanmaken, stories slepen, taken | ST-301 ST-313 |
| M3.5: Solo Paneel & Story Assignment | Story-claim, persoonlijk Kanban-bord per product | ST-350 ST-360 |
| M4: Claude Code REST API | Alle endpoints, tokenbeheer | ST-401 ST-410 |
| M5: Todo-lijst | Todo CRUD, promotie naar PBI/story; Data Table + detail-kaart | ST-501 ST-506, ST-509 ST-510 |
| M6: Polish & Launch-ready | Foutafhandeling, toegankelijkheid, CI/CD, beveiliging | ST-601 ST-612 |
| M7: MCP-server voor Claude Code | Native MCP-laag bovenop Scrum4Me-DB (aparte repo `mcp`) | ST-701 ST-710 |
| M8: Realtime Solo Paneel | Live updates voor stories/tasks via SSE + Postgres LISTEN/NOTIFY | ST-801 ST-806 |
| M9: Actief Product Backlog | Persistente actieve PB-keuze, gesplitste navigatie, disabled-states | ST-901 ST-907 |
| M10: Password-loze inlog via QR-pairing | Mobiel als bevestigingskanaal voor desktop-login zonder wachtwoord | ST-1001 ST-1008 |
| M11: Claude vraagt, gebruiker antwoordt | Persistent vraag-antwoord-kanaal tussen Claude (MCP) en de actieve gebruiker | ST-1101 ST-1108 |
| M12: Ideeën & Grill/Plan jobs | Idee-entity tussen Todo en PBI; interactief grillen + deterministisch materialiseren | ST-1192 ST-1201 |
---
## Backlog
### M0: Foundation
- [x] **ST-001** Project scaffolding
- `create-next-app` met TypeScript strict, Tailwind CSS, App Router; installeer shadcn/ui, Zustand, dnd-kit, iron-session, bcrypt, Zod; configureer path aliases (`@/`)
- Done when: `npm run dev` start zonder fouten; `npm run lint` geeft geen errors; shadcn `Button` rendert op een testpagina
- [x] **ST-002** Prisma v7 setup + `prisma.config.ts`
- Installeer Prisma v7 + `@prisma/adapter-pg`; schrijf `prisma.config.ts` met `DATABASE_URL` via Zod-gevalideerde env; schrijf `lib/prisma.ts` singleton
- Done when: `npx prisma db push` slaagt; Prisma Client importeerbaar in een testbestand zonder fouten
- [x] **ST-003** Database schema migratie (volledige initiële migratie)
- Schrijf het volledige `schema.prisma` op basis van het architectuurdocument: `User`, `UserRole`, `ApiToken`, `Product`, `Pbi`, `Story`, `StoryLog`, `Sprint`, `Task`, `Todo`; alle enums, indexes, cascade deletes
- Done when: `npx prisma migrate dev --name init` slaagt; alle tabellen zichtbaar in DB-client; `npx prisma validate` geeft geen fouten
- [x] **ST-004** Seed met testdata
- Schrijf `prisma/seed.ts` op basis van het Product Backlog document (devplanner-product-backlog.md); seed één gebruiker, één product (Scrum4Me zelf), alle PBI's en stories als testdata; voeg demo-gebruiker toe
- Done when: `npx prisma db seed` slaagt; DB bevat alle PBI's en stories uit het backlog-document; demo-gebruiker aanwezig
- [x] **ST-005** Environment variabelen + `lib/env.ts`
- Schrijf Zod-schema voor alle env vars (`DATABASE_URL`, `DIRECT_URL`, `SESSION_SECRET`, `NODE_ENV`); exporteer gevalideerd `env` object; schrijf `.env.example` met instructies
- Done when: app gooit een begrijpelijke fout bij ontbrekende env var; `.env.example` volledig gedocumenteerd
- [x] **ST-006** Authenticatie — registratie en inloggen
- Schrijf `lib/auth.ts` (registreer met bcrypt hash, verifieer bij inloggen); schrijf `lib/session.ts` (iron-session config); implementeer `/register` en `/login` pagina's met Server Actions; sla `{ userId, isDemo }` op in sessiecookie
- Done when: registreren → ingelogde sessie → redirect `/dashboard`; inloggen met verkeerde credentials geeft generieke foutmelding; sessie blijft actief na paginaverversing
- [x] **ST-007** Route-beveiliging via `proxy.ts`
- Schrijf `proxy.ts` die sessiecookie-aanwezigheid controleert; redirect naar `/login` bij alle `/dashboard`, `/products/*`, `/todos`, `/settings/*` routes zonder sessiecookie; authenticated users worden van `/login` en `/register` doorgestuurd naar `/dashboard`; volledige sessievalidatie gebeurt server-side in de app layout
- Done when: directe navigatie naar `/dashboard` zonder sessie redirect naar `/login`; ingelogde gebruiker op `/login` redirect naar `/dashboard`
- [x] **ST-008** Navigatieshell + dashboard-layout
- Schrijf `app/(app)/layout.tsx` met navigatiebalk (logo, productenlink, todolink, instellingen, uitlogknop); implementeer uitlog Server Action; implementeer `/dashboard` als lege productenlijstpagina met "Maak je eerste product aan" lege staat; zet demo-badge zichtbaar als `isDemo === true`
- Done when: volledige auth-flow (register → login → dashboard → logout → login) werkt end-to-end; demo-gebruiker ziet badge in navigatie
---
### M1: Producten & Product Backlog
- [x] **ST-101** Product aanmaken
- `/products/new` pagina met formulier (naam, beschrijving, repo URL, definition of done); `createProduct` Server Action met Zod-validatie; uniekheidscontrole op naam per gebruiker; redirect naar `/products/[id]` na aanmaken
- Done when: product aangemaakt en zichtbaar op dashboard; dubbele naam geeft inline validatiefout; lege naam blokkeert submit
- [x] **ST-102** Productenlijst op dashboard
- Haal actieve producten op via Prisma Server Component; toon naam, beschrijving (ingekort 80 tekens), repo-link; lege staat met CTA; klikken opent Product Backlog
- Done when: twee producten zichtbaar na aanmaken; gearchiveerd product niet zichtbaar in standaardlijst
- [x] **ST-103** Product bewerken en archiveren
- Bewerkformulier (naam, beschrijving, repo URL, DoD) via Server Action; archiveerknop met bevestigingsdialoog; hersteloptie voor gearchiveerde producten; "toon gearchiveerd"-filter op dashboard
- Done when: naam bijwerken persisteert; archiveren verbergt product; herstel maakt het weer zichtbaar
- [x] **ST-104** Gesplitst scherm layout component (`SplitPane`)
- Bouw herbruikbaar `<SplitPane>` Client Component met versleepbare horizontale splitter; sla splitter-positie op in `localStorage` per sleutel; standaard 40/60 verhouding; minimale panelbreedte 200px; responsive fallback naar tabs op < 1024px
- Done when: splitter versleepbaar en positie behouden na paginaverversing; tabs getoond op smal scherm
- [x] **ST-105** Navigatiebar-component per paneel
- Bouw herbruikbaar `<PanelNavBar>` component met slots voor knoppen (aanmaken, filter, verwijderen); consistent design voor linker- en rechterpaneel
- Done when: navigatiebar herbruikt in minimaal twee gesplitste schermen zonder duplicatie
- [x] **ST-106** PBI aanmaken en weergeven
- Linkerpaneel van `/products/[id]`: haal PBI's op gegroepeerd op prioriteit en sort_order; "PBI aanmaken" knop opent inline formulier (titel, prioriteit); `createPbi` Server Action; nieuw PBI verschijnt onderaan de juiste prioriteitsgroep
- Done when: PBI aangemaakt en zichtbaar in juiste prioriteitsgroep; lege staat toont prompt
- [x] **ST-107** PBI prioriteitsgroepen met visuele scheiding
- Render PBI's gegroepeerd per prioriteit (14) met gelabelde scheidingslijn per groep (bijv. "Kritiek", "Hoog"); lege groepen zijn niet zichtbaar; prioriteitsbadge per PBI
- Done when: vier prioriteitsgroepen correct gerenderd met labels; PBI met prioriteit 1 staat boven prioriteit 4
- [x] **ST-108** PBI bewerken en verwijderen
- Inline bewerkingsmodus via dubbelklik of contextmenu (titel, omschrijving, prioriteit); `updatePbi` Server Action; verwijderen met bevestigingsdialoog inclusief waarschuwing cascade; `deletePbi` Server Action
- Done when: titelbewering opgeslagen zonder paginaverversing; verwijderen cascade-verwijdert stories (verifieerbaar in DB)
- [x] **ST-109** PBI selecteren → stories laden
- Klikken op PBI in linkerpaneel toont bijbehorende stories rechts via `useSelectionStore`; geselecteerd PBI visueel gemarkeerd; lege staat rechts als geen stories
- Done when: klikken op PBI A toont stories van A rechts; klikken op PBI B schakelt direct over
- [x] **ST-110** PBI filter
- Filterknop in linkerpaneel navigatiebar; dropdown voor prioriteit (14, alle); filter werkt realtime op gerenderde lijst; actief filter zichtbaar als badge; wissen via ×-knop
- Done when: filter op prioriteit 1 verbergt alle andere PBI's; wissen herstelt volledige lijst
---
### M2: Stories & Drag-and-drop
- [x] **ST-201** `usePlannerStore` Zustand-store
- Schrijf `stores/planner-store.ts` met `pbiOrder`, `storyOrder`, `taskOrder`; `init*`, `reorder*`, `rollback*` actions; TypeScript strict types
- Done when: store importeerbaar in een Client Component; `initPbis` vult order; `reorderPbis` muteert order; `rollbackPbis` herstelt vorige staat
- [x] **ST-202** `useSelectionStore` Zustand-store
- Schrijf `stores/selection-store.ts` met `selectedPbiId`, `selectedStoryId`, setters en `clearSelection`
- Done when: selectie in linkerpaneel via store zichtbaar in rechterpaneel zonder prop drilling
- [x] **ST-203** dnd-kit setup + PBI drag-and-drop
- Installeer dnd-kit; wrap linkerpaneel in `DndContext` + `SortableContext`; implementeer `useSortable` per PBI-rij; `onDragEnd`: bereken nieuwe `sort_order` via float-gemiddelde; optimistisch updaten via `usePlannerStore`; `reorderPbisAction` Server Action; rollback bij fout
- Done when: PBI versleepbaar binnen prioriteitsgroep; volgorde opgeslagen na loslaten; UI rollback bij gesimuleerde server-fout
- [x] **ST-204** PBI drag-and-drop over prioriteitsgrens
- Uitbreiding ST-203: slepen over een prioriteitsgrens wijzigt `priority` van het PBI; `sort_order` wordt onderaan de doelgroep geplaatst; `updatePbiPriority` Server Action
- Done when: PBI naar prioriteit 2 slepen vanuit prioriteit 3 wijzigt zowel prioriteit als volgorde
- [x] **ST-205** Story aanmaken en weergeven als blokken
- Rechterpaneel van Product Backlog: haal stories op voor geselecteerd PBI; render als blokken (~10% schermbreedte, horizontaal); elk blok toont titel (ingekort), prioriteitsbadge, statusbadge; "Story aanmaken" knop; `createStory` Server Action
- Done when: drie stories zichtbaar als blokken; nieuw blok verschijnt in juiste prioriteitsgroep
- [x] **ST-206** Story prioriteitsgroepen met visuele scheiding
- Groepeer story-blokken per prioriteit; gekleurde band of scheidingslijn per groep; blokken horizontaal gerangschikt per rij; nieuwe rij bij overloop
- Done when: stories van vier prioriteiten correct gescheiden weergegeven
- [x] **ST-207** Story drag-and-drop (horizontaal, binnen en tussen groepen)
- dnd-kit horizontale `SortableContext` per prioriteitsgroep; `onDragEnd`: herrangschikking via float-gemiddelde in `storyOrder`; slepen naar andere groep wijzigt prioriteit; optimistisch via `usePlannerStore`; `reorderStoriesAction` Server Action; rollback bij fout
- Done when: story versleepbaar binnen groep en naar andere groep; volgorde en prioriteit persistent na loslaten
- [x] **ST-208** Story detail-modal / slide-over
- Klikken op storyblok opent slide-over of modal met titel, omschrijving, acceptatiecriteria, statusbadge, activiteitenlog (leeg bij nieuwe story); bewerkformulier voor titel/omschrijving/acceptatiecriteria; `updateStory` Server Action
- Done when: klikken op blok opent detail; bewerken persisteert; sluiten keert terug naar backlog
- [x] **ST-209** Story verwijderen
- Verwijderknop in story-detail of contextmenu; bevestigingsdialoog met waarschuwing cascade (taken); `deleteStory` Server Action; blok verdwijnt optimistisch uit het rechterpaneel
- Done when: story verwijderd incl. cascade-taken (verifieerbaar in DB); blok direct verdwenen uit UI
- [x] **ST-210** Story filter in rechterpaneel
- Filterknop in rechterpaneel navigatiebar; filter op status (OPEN, IN_SPRINT, DONE) en prioriteit; realtime; actief filter als badge; wissbaar
- Done when: filter op OPEN verbergt IN_SPRINT stories
---
### M3: Sprint Backlog & Sprint Planning
- [x] **ST-301** `useSprintStore` Zustand-store
- Schrijf `stores/sprint-store.ts`; `initSprint`, `addStoryToSprint`, `removeStoryFromSprint`, `reorderSprintStories`, `rollbackSprint`
- Done when: store beheert sprint-story-volgorde onafhankelijk van planner-store
- [x] **ST-302** Sprint aanmaken
- "Sprint starten" knop op productpagina (zichtbaar als geen actieve Sprint); modal met Sprint Goal invoerveld; `createSprint` Server Action; max. 1 actieve Sprint per product afgedwongen in service-laag
- Done when: Sprint aangemaakt met Goal; tweede sprint aanmaken terwijl eerste actief is geeft foutmelding
- [x] **ST-303** Sprint Backlog scherm — layout
- `/products/[id]/sprint` pagina; `SplitPane` met Sprint Backlog links (stories in Sprint op volgorde) en rechts de Product Backlog stories gegroepeerd per PBI (inklapbaar); Sprint Goal zichtbaar bovenaan; lege staat links met instructie
- Done when: pagina rendert correct; Sprint Goal zichtbaar; beide panelen tonen juiste data
- [x] **ST-304** Story vanuit Product Backlog naar Sprint slepen
- dnd-kit drag vanuit rechterpaneel naar linkerpaneel; `onDragEnd`: `addStoryToSprint` in store; story krijgt badge "In Sprint" in Product Backlog; `addStoryToSprintAction` Server Action (zet `sprint_id` + status `IN_SPRINT`); rollback bij fout
- Done when: story gesleept naar Sprint verschijnt links en toont "In Sprint" badge rechts; persistent na herlaad
- [x] **ST-305** Sprint Backlog story volgorde aanpassen
- dnd-kit verticale `SortableContext` in linkerpaneel; herrangschikking via float-gemiddelde in `useSprintStore`; `reorderSprintStoriesAction` Server Action
- Done when: volgorde in Sprint Backlog persistent na loslaten en na paginaverversing
- [x] **ST-306** Story uit Sprint verwijderen
- Verwijderknop per story in Sprint Backlog; `removeStoryFromSprintAction` Server Action (wist `sprint_id`, zet status terug op `OPEN`); story verdwijnt links en badge verdwijnt rechts
- Done when: verwijderen persistent; story beschikbaar in Product Backlog rechterpaneel
- [x] **ST-307** Sprint Planning scherm — layout
- `/products/[id]/sprint/planning` pagina; `SplitPane` met Sprint Backlog stories links (op volgorde) en taken van geselecteerde story rechts; Sprint Goal zichtbaar; lege staat rechts als geen story geselecteerd
- Done when: pagina rendert; story selecteren links toont taken rechts
- [x] **ST-308** Taak aanmaken
- "Taak aanmaken" knop in rechterpaneel navigatiebar; inline formulier (titel, omschrijving, prioriteit); `createTask` Server Action; voortgangsindicator per story (bijv. "0/0 Done")
- Done when: taak aangemaakt en zichtbaar in takenlijst; voortgangsindicator toont "0/1 Done"
- [x] **ST-309** Taak drag-and-drop (verticaal)
- dnd-kit verticale `SortableContext` in rechterpaneel; herrangschikking via float-gemiddelde in `usePlannerStore.taskOrder`; `reorderTasksAction` Server Action
- Done when: taken versleepbaar; volgorde persistent na loslaten
- [x] **ST-310** Taakstatus bijhouden
- Status-toggle per taak (TO_DO → IN_PROGRESS → DONE) via klikbare badge of dropdown; `updateTaskStatus` Server Action; voortgangsindicator op story updatet optimistisch
- Done when: taak op DONE zetten verhoogt teller in voortgangsindicator; persistent na herlaad
- [x] **ST-311** Taak bewerken en verwijderen
- Inline bewerken van titel, omschrijving en prioriteit; `updateTask` Server Action; verwijderen met bevestiging; `deleteTask` Server Action
- Done when: titelwijziging persisteert; verwijderde taak verdwijnt uit lijst
- [x] **ST-312** Sprint afronden
- "Sprint afronden" knop op Sprint-pagina; dialoog toont per story de status en vraagt: "Markeer als Done of terug naar Backlog?"; `completeSprint` Server Action zet Sprint op COMPLETED, verwerkt keuzes per story
- Done when: Sprint afgerond; stories correct verplaatst naar DONE of OPEN; nieuwe Sprint aanmaakbaar
- [x] **ST-313** Sprint Board — drie-panelen layout (vervangt ST-303 + ST-307)
- **Doel:** `/products/[id]/sprint` wordt één scherm met drie panelen van links naar rechts: Product Backlog · Sprint Backlog · Taken. De losse `/sprint/planning` route wordt verwijderd (redirect → `/sprint`).
- **Panelen:**
- *Links — Product Backlog:* PBIs met stories gegroepeerd en inklapbaar; stories die al in sprint zijn grijs/disabled; klikken of slepen voegt story toe aan Sprint Backlog (midden)
- *Midden — Sprint Backlog:* stories in sprint op volgorde; klikken selecteert story → taken laden rechts; versleepbaar om te sorteren; trash-knop verwijdert uit sprint
- *Rechts — Taken:* `TaskList` voor de geselecteerde story; lege staat "Selecteer een story" als niets geselecteerd; "+ Taak" knop zoals huidig
- **Layout:** `TriplePane` component — drie verticale panelen met twee versleepbare scheidingslijnen; opslaan in `localStorage` per product (key: `sprint-triple-${productId}`)
- **DnD:** één `DndContext` omhult alle drie panelen; drag van links naar midden werkt via `DragOverlay`; reorder binnen midden via `SortableContext`; taken-reorder in eigen geneste `DndContext`
- **State:** `SprintBoardClient` beheert sprint stories, product backlog data, `selectedStoryId`, en taken per story (vanuit server props); `useSelectionStore.selectedStoryId` voor story-selectie
- **Navigatie:** "Sprint Planning →" link onderaan Sprint Backlog pagina verwijderd; `SprintHeader` blijft bovenaan met "Sprint afronden"
- **Route cleanup:** `/sprint/planning/page.tsx` vervangt door redirect naar `/products/[id]/sprint`; `PlanningLeft`, `PlanningRightClient` components verwijderen
- Done when: één `/sprint` pagina toont alle drie panelen; story slepen van links naar midden werkt; story selecteren toont taken rechts; taak aanmaken en sorteren werkt; pagina hervat na herlaad met juiste data; `/sprint/planning` redirect werkt
---
### M3.5: Solo Paneel & Story Assignment
> **Doel:** een persoonlijk Kanban-bord per product dat de taken toont van stories die geclaimd zijn door de ingelogde developer. Story-level assignment volgt het Scrum self-organizing principe: developers claimen vrijwillig stories (pull, niet push). Volledige technische specificatie in `specs/functional.md#solo-panel`.
- [x] **ST-350** Story.assignee_id schema-migratie + auth-helpers
- **Schema:** voeg `assignee_id String?` + `assignee User? @relation("StoryAssignee", fields: [assignee_id], references: [id], onDelete: SetNull)` toe aan `Story`; voeg `assigned_stories Story[] @relation("StoryAssignee")` toe aan `User`; voeg index `@@index([sprint_id, assignee_id])` toe; migratie via `prisma migrate dev --name add_story_assignee`
- **Auth-helpers:** schrijf `lib/auth.ts` met `getSession`, `requireUser`, `requireWriter`, `requireProductAccess`, `requireProductWriter` — laatste twee doen membership-check via owner (`Product.user_id`) OF lid (`ProductMember`); demo-check op basis van `session.isDemo` (uit ST-006); throwt *"Niet beschikbaar in demo-modus"* bij demo-write-poging
- Done when: migratie slaagt; `requireProductWriter` blokkeert demo-user; `requireProductAccess` accepteert zowel owner als member
- [x] **ST-351** `<UserAvatar>` herbruikbare component
- Wrapper rond shadcn `Avatar`; props: `userId`, `username`, `size` ('xs' | 'sm' | 'md' | 'lg'), `className`; `<AvatarImage src="/api/users/{userId}/avatar">` met fallback naar initialen (eerste 2 tekens username) op `bg-primary-container`; vier groottes via Tailwind classes
- Done when: avatar rendert in 4 sizes; bij ontbrekende avatar-data (404) fallback naar initialen zichtbaar; component bruikbaar in story-kaart, sprint board, instellingen
- [x] **ST-352** Story-claim Server Actions
- Vier acties in `actions/stories.ts`: `claimStoryAction` (zet `assignee_id = currentUserId`), `unclaimStoryAction` (null), `reassignStoryAction` (valideert dat target user lid van product is), `claimAllUnassignedInActiveSprintAction` (bulk via `updateMany` voor ongeclaimde stories in actieve sprint); allemaal Zod-gevalideerd, achter `requireProductWriter`, met `revalidatePath` voor `/sprint` én `/solo`; tenant-guard via `where: { id, product_id }`
- Done when: alle vier acties testbaar via testbestand; demo-user krijgt foutmelding; reassignment naar niet-lid faalt met foutmelding; bulk claimt alleen ongeclaimde
- [x] **ST-353** Sprint Board: assignee-chip + dropdown menu op story-kaart
- Op story-kaart in middenpaneel van ST-313 Sprint Board: assignee-chip onderaan met `<UserAvatar size="xs">` + username (of muted "Niet geclaimd" badge als `assignee_id === null`); shadcn `DropdownMenu` (3-dots rechtsboven) met items "Pak op" / "Geef terug aan team" / "Wijs toe aan ▶" (submenu met members); items conditioneel zichtbaar op basis van huidige assignee; demo-modus: dropdown disabled met tooltip "Niet beschikbaar in demo-modus"
- Done when: chip toont juiste state; dropdown roept juiste acties aan; revalidatie ververst kaart; toast "Story geclaimd" / "Toegewezen aan X" bij succes; demo-user ziet disabled-tooltip
- [x] **ST-354** Sprint Board: bulk-claim knop "Claim alle ongeclaimde"
- Knop bovenaan Sprint Backlog paneel met telling: "Claim alle ongeclaimde stories (N)"; disabled als N=0 of `isDemo`; klik roept `claimAllUnassignedInActiveSprintAction` aan; Sonner success-toast "{count} stories geclaimd"; pending state via `useTransition`
- Done when: telling correct; claimen werkt; knop disabled bij 0 ongeclaimd of demo; toast verschijnt na succes
- [x] **ST-355** Solo route — `/solo` redirect + `/products/[id]/solo` pagina + cookie
- **Cookie-helper:** schrijf `lib/cookies.ts` met `setLastProductCookie(productId)` (HTTP-only, sameSite lax, 30 dagen)
- **`/solo` page.tsx:** Server Component; leest cookie `lastProductId`; valideert toegang en redirect naar `/products/[id]/solo`, of toont `<ProductPicker>` als geen cookie of cookie ongeldig
- **`/products/[id]/solo` page.tsx:** Server Component; haalt active sprint op (404 → empty state `<NoActiveSprint>`); haalt taken op via `Task.findMany` met `where: { sprint_id, story: { assignee_id: session.userId } }` + count ongeclaimde stories parallel; geeft data door aan `<SoloBoard>`; zet `lastProductId` cookie bij elk bezoek
- **Empty state:** `<NoActiveSprint>` met titel, uitleg, link naar productpagina
- **`<ProductPicker>`:** lijst van toegankelijke producten, klikken redirect naar `/products/[id]/solo`
- Done when: `/solo` zonder cookie toont picker; met geldige cookie redirect; pagina toont juiste taken; geen actieve sprint toont empty state; cookie persisteert tussen sessies
- [x] **ST-356** Solo Kanban-bord met DnD en Zustand
- **Store `stores/solo-store.ts`:** `tasks`, `initTasks`, `optimisticMove(taskId, toStatus)` (returnt vorige status), `rollback(taskId, prevStatus)`, `updatePlan(taskId, plan)`; volgt patroon van `usePlannerStore` (ST-201)
- **`<SoloBoard>` Client Component:** root met `DndContext` (overslaan als `isDemo`), `PointerSensor` met `activationConstraint: { distance: 5 }`, `closestCorners` collision detection; header met productnaam, sprint goal, knop "Toon openstaande stories (N)"; grid met drie kolommen
- **`<SoloColumn>`:** drop target per status (`TO_DO` / `IN_PROGRESS` / `DONE`); header met statuskleur via MD3 tokens (`bg-status-todo/15` etc.); count en lege staat
- **`<SoloTaskCard>`:** hergebruik bestaande task-card (ST-310); draggable; toont prioriteit-indicator, taaktitel, story-titel; klik opent detail-dialoog (ST-357); demo: niet draggable
- **`onDragEnd` flow:** optimistische update via `optimisticMove`, dan `updateTaskStatusAction` aanroepen, op error rollback + Sonner error-toast "Status bijwerken mislukt — taak teruggeplaatst"; geen success-toast (te frequent)
- Done when: kaart sleepbaar tussen kolommen; status persisteert; gesimuleerde server-fout rollbackt UI; demo-user kan niet slepen
- [x] **ST-357** Task detail-dialoog + `updateTaskPlanAction`
- **`updateTaskPlanAction`** in `actions/tasks.ts`: Zod-schema `{ taskId, productId, implementationPlan }`; `requireProductWriter`; tenant-guard via `where: { id: taskId, story: { product_id: productId } }`; `revalidatePath`
- **`<TaskDetailDialog>`** shadcn `Dialog`: header met taaktitel + statusbadge (MD3 tokens); sectie *Beschrijving* (read-only, volg bestaand patroon); sectie *Implementatieplan* met `<Textarea>` save-on-blur; on-blur roept `updateTaskPlanAction`, indicator rechtsonder ("Bezig met opslaan…" → "Opgeslagen", vervaagt na 2s); error-toast bij fout; footer-link "Open in Sprint Board ↗"; demo-modus: textarea `readOnly` met tooltip
- Done when: edit + blur + refresh persisteert; gesimuleerde server-fout toont error-toast; demo-user kan dialoog openen maar niet bewerken
- [x] **ST-358** Openstaande stories sheet
- Knop "Toon openstaande stories (N)" bovenaan Solo bord opent shadcn `<Sheet>` (slide-out van rechts); inhoud: lijst van ongeclaimde stories in actieve sprint met titel, taakaantal, "Pak op"-knop per item; klik roept `claimStoryAction`, sheet blijft open (zodat meerdere achter elkaar claimen kan); Sonner success-toast per claim; lege staat "Geen ongeclaimde stories. Lekker bezig!"; pending state via `useFormStatus`; demo: knoppen disabled met tooltip
- Done when: sheet opent met N items; claimen verwijdert item uit lijst en verlaagt teller; lege staat correct; demo-user ziet sheet maar kan niet claimen
- [x] **ST-359** Navbar-link "Solo"
- Voeg `<NavLink href="/solo" icon={<UserSquare />}>Solo</NavLink>` toe aan navigatieshell (ST-008); altijd zichtbaar voor ingelogde users (geen product-context); plek tussen "Producten" en "Todos"
- Done when: link altijd zichtbaar in nav; klik gaat naar `/solo` en redirect verder
- [x] **ST-360** Demo-seed uitbreiden met geclaimde stories
- Update `prisma/seed.ts` (ST-004): demo-user (`is_demo = true`) heeft minimaal één product met ACTIVE sprint; minimaal 3 stories met `assignee_id = demoUser.id` (variërend over taakstatussen TO_DO, IN_PROGRESS, DONE); minimaal 1 ongeclaimde story (om "Toon openstaande" te demonstreren — demo-user kan niet claimen, ziet wel hoe het werkt)
- Done when: login als demo → Solo bord toont werkend Kanban met taken in alle drie kolommen; "Toon openstaande" sheet toont ten minste 1 story (claim-knoppen disabled)
---
### M4: Claude Code REST API
- [x] **ST-401** API-token infrastructuur
- Schrijf `lib/api-auth.ts`: parseer `Authorization: Bearer` header; bereken SHA-256 hash; zoek op in `api_tokens`; controleer `revoked_at`; retourneer `userId` of 401; retourneer 403 als `is_demo`
- Done when: geldige token geeft userId terug; ongeldige token geeft 401; ingetrokken token geeft 401; demo-token op schrijf-endpoint geeft 403
- [x] **ST-402** API-tokenbeheer UI
- `/settings/tokens` pagina; token aanmaken (label optioneel); token eenmalig getoond in kopieerbaar veld na aanmaken; tokenoverzicht (label, datum, actief/ingetrokken); intrekken via Server Action; max. 10 actieve tokens
- Done when: token aangemaakt en waarde zichtbaar; na sluiten dialoog niet meer te zien; intrekken maakt token onbruikbaar (getest via curl)
- [x] **ST-403** `GET /api/products` — productenlijst
- Route Handler; authenticatie via `api-auth.ts`; retourneer actieve producten `[{ id, name, repo_url }]` als JSON voor producten waar de tokengebruiker eigenaar of teamlid is
- Done when: `curl -H "Authorization: Bearer <token>" /api/products` retourneert correct JSON inclusief gedeelde product backlogs; 401 zonder token
- [x] **ST-404** `GET /api/products/:id/next-story` — volgende story ophalen
- Route Handler; haal hoogst geprioriteerde OPEN story op van actieve Sprint van het product (priority ASC, sort_order ASC); retourneer `{ id, title, description, acceptance_criteria, tasks[] }`; 404 als geen open stories
- Done when: endpoint retourneert eerste story van Sprint; 404 als Sprint leeg; 404 als geen actieve Sprint
- [x] **ST-405** `GET /api/sprints/:id/tasks` — taken ophalen
- Route Handler met `?limit=N` query param (default 10, max 50); retourneer taken van actieve Sprint op `(story.sort_order, task.priority, task.sort_order)` volgorde; retourneer `{ id, title, story_id, priority, sort_order, status }`
- Done when: endpoint retourneert max N taken in juiste volgorde; `?limit=5` retourneert max 5
- [x] **ST-406** `PATCH /api/stories/:id/tasks/reorder` — taakvolgorde aanpassen
- Route Handler; body: `{ task_ids: string[] }`; valideer alle IDs behoren tot de story; update `sort_order` via float-verdeling; retourneer `{ success: true }`
- Done when: volgorde in DB veranderd na PATCH; gewijzigde volgorde zichtbaar in Sprint Planning UI na herlaad; ongeldige task_id geeft 400
- [x] **ST-407** `POST /api/stories/:id/log` — activiteit vastleggen
- Route Handler; body: `{ type, content, status?, commit_hash?, commit_message? }`; Zod-validatie per type; schrijf naar `story_logs`; retourneer `{ id, created_at }`
- Done when: drie typen werken (IMPLEMENTATION_PLAN, TEST_RESULT, COMMIT); log-entry zichtbaar in story-detail UI na aanmaken via API; ontbrekend verplicht veld geeft 400
- [x] **ST-408** `PATCH /api/tasks/:id` — taakstatus en implementatieplan bijwerken
- Route Handler; body: `{ status?: "TO_DO" | "IN_PROGRESS" | "DONE", implementation_plan?: string }`; minimaal één veld verplicht; valideer dat taak aan requester's product behoort; retourneer `{ id, status, implementation_plan }`
- Done when: status update via API zichtbaar in Sprint Planning UI; implementation_plan opgeslagen en opvraagbaar; lege body geeft 400; andere gebruikers taak geeft 403
- [x] **ST-409** `POST /api/todos` — todo aanmaken
- Route Handler; body: `{ title: string, product_id: string }`; valideer dat product bij de geverifieerde gebruiker hoort; schrijf naar `todos`; retourneer `{ id, title, created_at }`
- Done when: todo aangemaakt via API met product_id verschijnt in todo-lijst UI gekoppeld aan het juiste product; lege titel of ontbrekend product_id geeft 400; onbekend product geeft 404
- [x] **ST-410** Story-activiteitenlog UI
- Activiteitenlog sectie in story-detail slide-over; haal `story_logs` op via Server Component; render chronologisch; visuele stijl per type (IMPLEMENTATION_PLAN = blauw, TEST_RESULT passed = groen, failed = rood, COMMIT = paars); commit-hash klikbaar als `repo_url` ingesteld; lege staat
- Done when: drie log-entries (plan, test, commit) correct gestyled; commit-hash link opent in nieuw tabblad
---
### M5: Todo-lijst
> **Herontwerp (april 2026):** ST-501505 beschreven de oorspronkelijke QuickInput-aanpak. Die is geïmplementeerd maar vervangen door een Data Table + detail-kaart ontwerp (ST-509510). ST-501505 zijn als referentie bewaard; de functionele eisen zijn ongewijzigd.
- [x] **ST-501** Todo-lijst pagina *(vervangen door ST-509)*
- `/todos` pagina; haal actieve (niet-gearchiveerde) todos op inclusief productnaam; snel-invoerveld bovenaan met product-dropdown (verplicht) en titel (Enter om op te slaan); `createTodo` Server Action; lege staat met instructie; productnaam-badge per todo-item
- Done when: todo aanmaken via Enter persisteert en verschijnt in lijst met productnaam; aanmaken zonder product geblokkeerd; lege staat zichtbaar bij geen todos
- [x] **ST-502** Todo afvinken *(vervangen door ST-509)*
- Checkbox per todo; `toggleTodo` Server Action; afgevinkte todos visueel doorgestreept; afgevinkte todos blijven zichtbaar onderaan de lijst
- Done when: afvinken persistent na herlaad; visuele doorstreping correct
- [x] **ST-503** Afgevinkte todos archiveren *(vervangen door ST-510)*
- "Archiveer afgeronde items" knop; `archiveCompletedTodos` Server Action; gearchiveerde todos verdwijnen uit standaardweergave
- Done when: archiveren verbergt alle afgevinkte todos; telling correct
- [x] **ST-504** Todo promoveren naar PBI *(vervangen door ST-510)*
- "Promoveer naar PBI" contextmenu of knop per todo; dialoog: product dropdown (actieve producten), prioriteit dropdown; titel vooringevuld (bewerkbaar); bevestigingswaarschuwing; `promoteTodeToPbi` Server Action (maak PBI aan, verwijder todo)
- Done when: gepromoveerde todo verdwijnt; PBI zichtbaar in juist product met juiste prioriteit
- [x] **ST-505** Todo promoveren naar story *(vervangen door ST-510)*
- "Promoveer naar story" knop per todo; dialoog: product dropdown → PBI dropdown (gefilterd op product), prioriteit; titel vooringevuld; `promoteTodoToStory` Server Action (maak story aan, verwijder todo)
- Done when: gepromoveerde todo verdwijnt; story zichtbaar in juist PBI met juiste prioriteit
- [x] **ST-509** Todo Data Table
- Installeer `@tanstack/react-table`; voeg shadcn `data-table`-patroon toe
- **Kolommen:**
- Selectie-checkbox (kolom 1): multi-select voor bulk-archivering; header-checkbox selecteert/deselecteert alle zichtbare rijen
- Titel (kolom 2): max 2 regels, `line-clamp-2 truncate`; afgevinkte todos doorgestreept; klik op rij (buiten checkbox) opent detail-kaart
- Productnaam-badge (kolom 3)
- Aanmaakdatum (kolom 4)
- **Toolbar boven de tabel:**
- Product-filter dropdown (Alles / Geen product / per product)
- "+" knop: opent lege detail-kaart voor nieuw aanmaken (erft geselecteerd filter-product)
- "Archiveer geselecteerde (N)" knop: actief zodra ≥ 1 checkbox aangevinkt; roept `archiveSelectedTodosAction` aan met de geselecteerde IDs; resettet selectie na afloop
- **Paginering:** max 10 rijen per pagina; vorige/volgende knoppen; paginatelling ("110 van 23")
- **Lege staat:** "Geen todo's voor deze selectie." bij lege filter; "Nog geen todo's. Gebruik + om er een aan te maken." bij volledig lege lijst
- **`archiveSelectedTodosAction`** toevoegen aan `actions/todos.ts`: valideert dat alle meegegeven IDs bij de ingelogde gebruiker horen vóór schrijven; `archiveMany` via `updateMany`
- Done when: tabel toont alle actieve todos; paginering werkt; product-filter werkt; selectie-checkbox selecteert meerdere rijen; bulk-archiveren verwijdert geselecteerde rijen uit de weergave
- [x] **ST-510** Todo detail-kaart
- Kaart onder de tabel; altijd zichtbaar (leeg als geen todo geselecteerd of aangemaakt wordt)
- **Aanmaken:** "+" in toolbar zet kaart in aanmaak-modus; velden: product-dropdown (erft filter-product, of "Geen product" bij "Alles"), titel; opslaan via `createTodoAction`; na opslaan kaart leegmaken en tabel ververst
- **Bewerken:** klik op tabelrij (buiten checkbox) laadt todo in kaart; velden: product-dropdown, titel, done-toggle; opslaan via nieuwe `updateTodoAction` (title + product_id + done); annuleren deselecteert rij en leegt kaart
- **Promoveren:** knoppen "→ PBI" en "→ Story" in de kaart; openen de bestaande `PromotePbiDialog` en `PromoteStoryDialog`; alleen zichtbaar bij een bestaande geselecteerde todo
- **Demo-modus:** kaart-invoervelden uitgeschakeld; knoppen verborgen of disabled
- **`updateTodoAction`** toevoegen aan `actions/todos.ts`: valideert eigenaarschap; past `title`, `product_id` en/of `done` aan; `revalidatePath('/todos')`
- Done when: aanmaken via kaart persisteert; bewerken van titel, product en done-status werkt; promote vanuit kaart opent juist dialoog en verwijdert todo na bevestiging; kaart leeg bij geen selectie; demo-gebruiker ziet uitgeschakelde kaart
- [x] **ST-506** Rolbeheer in instellingen
- `/settings` pagina met roltoewijzing (checkbox per rol: Product Owner, Scrum Master, Developer); minimaal één rol verplicht; `updateRoles` Server Action; geselecteerde rollen zichtbaar in profielbalk
- Done when: rollen bijwerken persisterend; profielbalk toont gekozen rollen; uitvinken van alle rollen geeft validatiefout
- [x] **ST-507** Gebruikersprofiel (buiten originele backlog toegevoegd)
- Profielfoto-upload (JPEG/PNG/WebP, max 12 MB), server-side resizing naar max 700×700 WebP met Sharp, opgeslagen als bytea in Neon; bio (max 160) en bio_detail (max 2000) als aparte velden; `POST /api/profile/avatar` + `GET /api/profile/avatar` + `updateProfileAction`
- Done when: foto geüpload en zichtbaar in instellingen; bio opgeslagen; ongeldige bestanden geweigerd vóór verwerking
- [x] **ST-508** Product Backlog-overzicht in instellingen (buiten originele backlog toegevoegd)
- Gecombineerde lijst op `/settings` van eigen producten (badge "Eigenaar") en team-lidmaatschappen (badge "Developer" + eigenaarsnaam); klikbaar naar product; "Verlaten"-knop met bevestiging voor lidmaatschappen; lege staat met CTA
- Done when: eigenaar-producten en team-producten zichtbaar in één lijst; verlaten werkt en verwijdert rij
- [x] **ST-511** Entity codes voor Product, PBI en Story (buiten originele backlog toegevoegd)
- **Schema:** `code String? @db.VarChar(30)` op `Product`, `Pbi` en `Story`; unique per parent (`user_id` voor Product, `product_id` voor Pbi/Story); `Task` heeft geen DB-veld — code wordt afgeleid als `${story.code}.${index_in_story}`
- **Auto-generatie:** `lib/code-server.ts` met `generateNextStoryCode` (`ST-001`, `ST-002`, … 3-cijferig per product) en `generateNextPbiCode` (`PBI-1`, `PBI-2`, … per product); `createWithCodeRetry`-helper vangt P2002 op het code-veld op en probeert max 3× opnieuw zodat gelijktijdige inserts niet crashen
- **Validatie:** Zod max 30 tekens, regex `^[A-Za-z0-9._-]+$`; handmatige override mag elk format dat aan de basis-regex voldoet (geen format-enforcement op `ST-NNN`)
- **Forms:** code-input op Product/Pbi/Story dialogen; auto-default zichtbaar als placeholder `auto`; field-level error-rendering onder code-input voor zowel create- als edit-mode (uniciteits-conflict, ongeldig format)
- **Display:** `CodeBadge` (`components/shared/code-badge.tsx`) consistent op dashboard product-list, PBI-list, story-blocks (Product Backlog), sprint board (alle drie panelen incl. PBI-headers), solo-bord task-cards, task-detail-dialoog, sprint-afronden-dialoog en de story-dialoog title; task-card toont derived `${story.code}.${index}`-badge rechtsboven uitgelijnd
- **Seed:** parser strip `ST-XXX:`-prefix uit titles, vult `code` apart; product `Scrum4Me` krijgt `code: 'SCRUM4ME'`, milestones krijgen `M0`/`M3.5`/etc., stories krijgen `ST-001…ST-612`
- Done when: auto-toegekende codes per product oplopend en uniek; race-conflict wordt opgevangen door retry-helper i.p.v. te crashen; handmatige duplicate code toont inline error onder de input in zowel create- als edit-mode; codes zichtbaar als badge in alle lijsten/cards/dialogen; seed verdeelt codes correct (8 PBI's met `M*`, 84 stories met `ST-NNN`)
- [x] **ST-512** REST API uitbreidingen voor codes, todo-description en task implementation_plan (buiten originele backlog toegevoegd)
- **`GET /api/products`:** voeg `code` toe (naast `id`, `name`, `repo_url`); optioneel `description` en `definition_of_done`
- **`GET /api/products/:id/next-story`:** voeg `code` toe op story; voeg per task `code` (derived `${story.code}.${index_in_story}`) en `implementation_plan` toe
- **`GET /api/sprints/:id/tasks`:** voeg `description`, `implementation_plan` en `story_code` toe per task; voeg een derived `code`-veld per task toe (`${story.code}.${index_in_story}`)
- **`POST /api/todos`:** accepteer optionele `description` (max 2000 tekens); valideer en sla op; retourneer `description` in response
- Done when: alle vier endpoints retourneren / accepteren de nieuwe velden zoals beschreven; curl-test toont `code` op products, story en tasks; todo aanmaken via API met `description` slaat op
- [x] **ST-513** REST API hardening voor Claude Code (buiten originele backlog toegevoegd)
- **Health:** nieuwe `GET /api/health` zonder auth; retourneert `{ status, version, time }`; optioneel `?db=1` voor DB-ping (`{ database: 'ok' | 'down' }`)
- **Claude-context:** nieuwe `GET /api/products/:id/claude-context` (auth) die in één call `product`, `active_sprint`, `next_story` (met tasks), en `open_todos` van de gebruiker terugbrengt — voorkomt round-trips
- **Status-case op API-boundary:** nieuwe `lib/task-status.ts` mapper; API exposeert lowercase (`todo`/`in_progress`/`review`/`done` voor tasks; `open`/`in_sprint`/`done` voor stories); DB blijft UPPER_SNAKE; UI ongewijzigd
- **`PATCH /api/tasks/:id`:** accepteert lowercase `status` via mapper; retourneert lowercase
- **Story-log metadata:** nieuwe optionele `metadata Json?` kolom op `StoryLog`; `POST /api/stories/:id/log` accepteert per type een optioneel `metadata`-veld (bv. `{ branch: 'feat/x' }`); bestaande velden ongewijzigd → backwards-compatible
- **Foutcodes:** Zod-validatie geeft `422` (was `400`); `400` blijft voor malformed body; `401`/`403`/`404`/`500` ongewijzigd
<<<<<<<< HEAD:docs/backlog/index.md
- **API-documentatie:** nieuwe `docs/api/rest-contract.md` met endpoints, request/response, foutcodes, status-enums en curl-voorbeelden; `CLAUDE.md` verwijst ernaar
- Done when: `curl /api/health` werkt zonder auth; `curl /api/products/:id/claude-context` retourneert bundled JSON; PATCH/PUT routes accepteren lowercase status en geven 422 bij ongeldige body; story-log POST bewaart `metadata`; `docs/api/rest-contract.md` is gepubliceerd
========
- **API-documentatie:** nieuwe `docs/api.md` met endpoints, request/response, foutcodes, status-enums en curl-voorbeelden; `CLAUDE.md` verwijst ernaar
- Done when: `curl /api/health` werkt zonder auth; `curl /api/products/:id/claude-context` retourneert bundled JSON; PATCH/PUT routes accepteren lowercase status en geven 422 bij ongeldige body; story-log POST bewaart `metadata`; `docs/api.md` is gepubliceerd
>>>>>>>> origin/main:docs/backlog.md
- **`GET /api/products`:** voeg `code` toe (naast `id`, `name`, `repo_url`); optioneel `description` en `definition_of_done`
- **`GET /api/products/:id/next-story`:** voeg `code` toe op story; voeg per task `code` (derived `${story.code}.${index_in_story}`) en `implementation_plan` toe
- **`GET /api/sprints/:id/tasks`:** voeg `description`, `implementation_plan` en `story_code` toe per task; voeg een derived `code`-veld per task toe (`${story.code}.${index_in_story}`)
- **`POST /api/todos`:** accepteer optionele `description` (max 2000 tekens); valideer en sla op; retourneer `description` in response
- **Backwards-compat:** alle wijzigingen zijn additief — bestaande clients negeren onbekende keys; nieuwe input-velden zijn optioneel
- Done when: alle vier endpoints retourneren / accepteren de nieuwe velden zoals beschreven; curl-test toont `code` op products, story en tasks; todo aanmaken via API met `description` slaat op
---
### M6: Polish & Launch-ready
- [x] **ST-601** Loading states en skeletons
- `loading.tsx` voor alle zware routes (`/products/[id]`, `/sprint`, `/sprint/planning`); skeletoncomponenten voor PBI-lijst, story-blokken en takenlijst; pending states op alle form-submit-knoppen via `useFormStatus`
- Done when: navigeren naar een trage route toont skeleton; submit-knoppen disablen tijdens Server Action
- [x] **ST-602** Error boundaries
- `error.tsx` voor alle beschermde routes; toon gebruiksvriendelijke foutmelding met "Probeer opnieuw" knop; log fout naar console (Sentry in M6)
- Done when: gesimuleerde Server Action-fout toont error boundary zonder witte pagina
- [x] **ST-603** Toast-notificaties (Sonner)
- Installeer Sonner; success-toast na aanmaken/bewerken/verwijderen van producten, PBI's, stories, taken, todos; error-toast bij mislukte Server Actions; toast niet bij drag-and-drop (te frequent)
- Done when: aanmaken van PBI toont success-toast; gesimuleerde netwerk-fout toont error-toast
- [x] **ST-604** Demo-gebruiker write-protection in UI
- Alle aanmaak-, bewerk- en verwijderknoppen disabled + tooltip "Niet beschikbaar in demo-modus" voor demo-sessies; gebaseerd op `isDemo` in sessie
- Done when: demo-gebruiker ziet alle knoppen maar kan niets wijzigen; tooltip zichtbaar bij hover
- [x] **ST-605** Keyboard-navigatie
- Tab-volgorde logisch in alle formulieren; Enter submits formulieren; Escape sluit modals/slide-overs; dnd-kit keyboard-drag (Space om te pakken, pijltjestoetsen, Space om te laten vallen)
- Done when: volledige PBI aanmaken-flow keyboard-only uitvoerbaar; dnd-kit drag via keyboard werkt
- [x] **ST-606** Desktop-first UI-review
- Test alle flows op 1280px, 1440px en 1920px breedte; fix overflow, uitlijning en proportie-issues; controleer minimum schermbreedte 1024px (toon melding bij smaller)
- Done when: alle M0M5 flows correct op drie schermbreedtes; melding bij < 1024px
- [x] **ST-607** Toegankelijkheid (WCAG AA)
- Kleurcontrast-check op alle tekst en badges; aria-labels op icon-only knoppen; focus-ring zichtbaar op alle interactieve elementen; `role` en `aria-selected` op geselecteerde PBI in linkerpaneel
- Done when: geen WCAG AA contrastfouten op primaire flows; alle knoppen hebben toegankelijke labels
- [x] **ST-608** Ratelimiting op auth-endpoints
- Max. 10 inlogpogingen per IP per minuut; max. 5 registraties per IP per uur; implementeer via in-memory counter (v1) of Vercel Edge middleware
- Done when: 11 snelle inlogpogingen leiden tot 429-respons met duidelijke melding
- [x] **ST-609** Beveiligingsreview API-endpoints
- Controleer alle Route Handlers: elke schrijfoperatie valideert dat de resource binnen de toegankelijke product-scope valt; cross-scope reads zijn onmogelijk tenzij de gebruiker via `product_members` gekoppeld is; voeg integratietests toe die cross-user toegang testen
- Done when: poging om een niet-gedeeld product van een andere gebruiker op te halen via API geeft 403 of 404; gedeelde producten zijn wel zichtbaar; getest met twee test-gebruikers
- [x] **ST-610** CI/CD via GitHub Actions
- Workflow: `lint` (ESLint), `typecheck` (tsc --noEmit), `prisma validate`, `build` (next build) op elke PR en push naar main; Vercel auto-deploy op main
- Done when: een TypeScript-fout in een PR blokkeert merge; succesvolle merge triggert Vercel-deploy
- [x] **ST-611** README en lokale setup-documentatie
- Schrijf `README.md` met: wat is Scrum4Me, quickstart lokaal (clone → env → prisma push → seed → dev), cloud deployment (Vercel + Neon stappenplan), API-documentatie (alle 7 endpoints met voorbeelden), Claude Code-integratie uitleg, Vercel Analytics status en directe dependencies zoals Sharp
- De in-app landingspagina (`/`) bevat al een gebruikershandleiding, Scrum-samenvatting en API-overzicht — de README richt zich op lokale setup en deployment
- Done when: iemand zonder context de app lokaal kan draaien op basis van alleen de README en `.env.example`
- [x] **ST-612** End-to-end acceptatietest
- Voer handmatig de volledige Lars-flow uit: product aanmaken → PBI's en stories aanmaken → Sprint starten → stories slepen → taken aanmaken → API-token aanmaken → curl `next-story` → curl `log` (plan, test, commit) → activiteitenlog controleren in UI
- Done when: volledige flow werkt zonder fouten of onverwacht gedrag; alle API-responses correct JSON
### M7: MCP-server voor Claude Code
Aparte repo: [`madhura68/scrum4me-mcp`](https://github.com/madhura68/scrum4me-mcp). Native Prisma-toegang (geen REST-tussenlaag), stdio-transport, Scrum4Me-schema gevendord als git submodule. Tokens hergebruikt uit `api_tokens`. v1 is alleen dev-flow tools — geen PBI/sprint-creatie of profielbeheer.
- [x] **ST-701** Repo-skeleton mcp
- npm init, tsconfig strict, .gitignore, MCP SDK 1.29, Prisma 7, zod, tsx; lege `src/index.ts` die op stdio start
- Done when: `npx tsx src/index.ts` print `running on stdio` zonder crash; `tsc --noEmit` slaagt
- [x] **ST-702** Schema-sync via git submodule
- Submodule `vendor/scrum4me`, `scripts/sync-schema.sh` kopieert `schema.prisma` en strip de `generator erd`-block, `npm run prisma:generate` als postinstall
- Done when: `npm run sync-schema && npm run prisma:generate` werkt op een verse clone
- [x] **ST-703** Auth en Prisma-singleton
- `src/auth.ts` SHA-256 hash van `SCRUM4ME_TOKEN` → lookup in `api_tokens`, cached `{ userId, isDemo }`; `requireWriteAccess()` throwt `PermissionDeniedError` voor demo
- `src/prisma.ts` lazy proxy zodat bootstrap niet crasht zonder `DATABASE_URL`
- Done when: ongeldig token geeft `SCRUM4ME_TOKEN is invalid or revoked`; demo-tokens blokkeren writes
- [x] **ST-704** Status-mappers + error-helpers
- `src/status.ts` zelfde mappers als REST `lib/task-status.ts`
- `src/errors.ts` `formatZodError`, `toolError`, `toolJson`, `withToolErrors` wrapper
- Done when: zod-fouten en `PermissionDenied` worden als gestructureerde MCP-errors teruggegeven
- [x] **ST-705** Read-tools — `health`, `list_products`, `get_claude_context`
- `health` doet `SELECT 1`; `list_products` met product-access filter; `get_claude_context` bundelt product + active sprint + next story (met tasks) + 50 open todos
- Done when: smoke-test tegen live DB groen voor alle drie
- [x] **ST-706** Write-tools tasks — `update_task_status`, `update_task_plan`
- Status-input lowercase (`todo|in_progress|review|done`), conversie via mapper; access-check via story → product → membership/owner
- Done when: niet-eigenaar krijgt 'not accessible'; demo geeft `PERMISSION_DENIED`
- [x] **ST-707** Log-tools — `log_implementation`, `log_test_result`, `log_commit`
- Append `StoryLog` met juiste `type`; optioneel `metadata` JSONB
- Done when: drie logs verschijnen in story-activiteit met `type`/`status`/`commit_hash`/`metadata` zoals meegegeven
- [x] **ST-708** `create_todo`-tool
- Optionele `description` (max 2000) en `product_id` (gevalideerd via access-check)
- Done when: nieuwe todo verschijnt in `/todos` voor de tokengebruiker
- [x] **ST-709** Prompt `implement_next_story`
- Workflow: `get_claude_context` → plan → log_implementation → per task `in_progress`/`done` → tests → `log_test_result``log_commit`
- Done when: prompt zichtbaar in MCP-clients met argument `product_id`
- [x] **ST-710** README + Claude Code-config + smoke-test
- README beschrijft setup, tools-tabel, schema-sync, `~/.claude/mcp_servers.json` snippet, risico's
- `scripts/smoke-test.ts` valideert read-tools tegen live DB
- Done when: smoke-test groen; MCP Inspector toont 9 tools + 1 prompt
### M8: Realtime Solo Paneel
Live updates voor stories en tasks in het Solo Paneel zonder pagina-refresh. Wanneer Claude Code (via MCP), Codex (via REST) of een andere browser-tab een task/story muteert, ziet de gebruiker het binnen 12 seconden in zijn kanban-bord.
Transport: Server-Sent Events (Vercel ondersteunt geen stateful WebSockets). Bron: Postgres `LISTEN/NOTIFY` via row-level triggers op `tasks` en `stories`. Eén-richting (server → client) — mutaties blijven via Server Actions/REST/MCP.
Filtering server-side: alleen events binnen de actieve sprint van een product waar de gebruiker eigenaar of lid van is, plus `assignee_id == userId` (eigen kolommen) of `assignee_id IS NULL` (claim-lijst).
- [x] **ST-801** Postgres LISTEN/NOTIFY-infrastructuur
- Migratie met `notify_solo_change()`-functie + `AFTER INSERT/UPDATE/DELETE`-triggers op `tasks` en `stories`; payload bevat `op`, `entity`, `id`, `product_id`, `sprint_id`, `assignee_id`, `fields` (gewijzigde kolommen)
- Done when: `psql $DIRECT_URL -c "LISTEN scrum4me_solo;"` toont een payload bij een UI-mutatie
- [x] **ST-802** SSE-route `/api/realtime/solo`
- `app/api/realtime/solo/route.ts`, `runtime: 'nodejs'`, `maxDuration: 300`; auth via iron-session, query-param `product_id`, opent `pg.Client` op `DIRECT_URL` met `LISTEN`; heartbeat 25s; hard close 240s; in-handler filtering op product/sprint/assignee
- Done when: `curl -N` op localhost levert binnen 1s een event op na een task-mutatie via UI
- [x] **ST-803** Client hook `useSoloRealtime(productId)`
- `lib/realtime/use-solo-realtime.ts`; opent `EventSource`, exponential backoff reconnect (1s → 30s); Page Visibility API voor pauseren/hervatten; cleanup op unmount
- Done when: tab wisselen sluit/opent connectie zichtbaar in DevTools Network
- [x] **ST-804** Solo-store realtime-acties
- `applyTaskUpdate`, `applyTaskCreate`, `applyTaskDelete`, `applyStoryAssignment`, `markPending`/`clearPending` om eigen optimistic-echo te onderdrukken
- Done when: unit-test op solo-store met gesimuleerde events laat juiste eindstate zien
- [x] **ST-805** Wire-up in SoloBoard + UI-indicator
- `components/solo/solo-board.tsx` roept de hook aan; klein "live"/"verbinden..."-statusindicator; toast bij langer dan 5s disconnected
- Done when: twee tabs van Solo Paneel — mutatie in tab A komt binnen 12s in tab B zonder refresh
- [x] **ST-806** Documentatie + acceptatietest
<<<<<<<< HEAD:docs/backlog/index.md
- Sectie "Realtime updates" in `docs/architecture.md` met diagram en filtering-regels; vermelding in `CLAUDE.md`; korte note over `/api/realtime/solo` in `docs/api/rest-contract.md`; handmatig E2E-scenario's gedraaid (zelfde gebruiker twee tabs, MCP-write, REST-write, story-claim, network-flap)
========
- Sectie "Realtime updates" in `docs/architecture.md` met diagram en filtering-regels; vermelding in `CLAUDE.md`; korte note over `/api/realtime/solo` in `docs/api.md`; handmatig E2E-scenario's gedraaid (zelfde gebruiker twee tabs, MCP-write, REST-write, story-claim, network-flap)
>>>>>>>> origin/main:docs/backlog.md
- Done when: alle scenario's lopen door zonder onverwachte gedragingen
Volledig plan in `.Plans/2026-04-27-m8-realtime-solo.md` (lokaal, niet gecommit).
### M9: Actief Product Backlog
**Implementatieplan:** [docs/plans/M9-active-product-backlog.md](../plans/M9-active-product-backlog.md)
Eén "actief Product Backlog" per gebruiker — persistent in DB. De NavBar wordt gesplitst in **Producten** (lijst) en **Product Backlog** (PB-view van actief PB), met **Sprint** en **Solo** als aparte tabs die op het actieve PB werken. Geen actief PB → die drie tabs zijn disabled. Vervangt de bestaande `last_product`-cookieflow.
- [x] **ST-901** Database — `user.active_product_id`
- Voeg `active_product_id String? @db.Uuid` toe aan `User` met FK naar `Product.id` en `onDelete: SetNull`; migratie `add_user_active_product_id`; index op `active_product_id` voor join-performance
- Done when: `npx prisma migrate dev` slaagt; `prisma studio` toont kolom; `npx prisma validate` zonder fouten; submodule `vendor/scrum4me` in mcp draait `prisma generate` + `tsc --noEmit` zonder fouten
- [x] **ST-902** Server Actions — actief product zetten en wissen
- `actions/active-product.ts` met `setActiveProduct(productId)` en `clearActiveProduct()`; Zod + auth + `productAccessFilter`; demo-gebruikers mogen wisselen (sessie-effect alleen, geen DB-write); `archiveProduct` en `leaveProduct` zetten `active_product_id` op `null` als het hetzelfde product betreft
- Done when: setActive met onbekend/onbereikbaar product → 422; archiveren van actief product clearet de keuze; demo-flow geeft toast "Niet beschikbaar in demo-modus"
- [x] **ST-903** App-layout laadt actief product + redirects
- `app/(app)/layout.tsx` haalt `activeProduct` (id, name, archived) op naast user; geef door aan `NavBar`; `app/(app)/solo/page.tsx` gebruikt `user.active_product_id` i.p.v. `getLastProductCookie`; helper `lib/cookies.ts:getLastProductCookie` markeren deprecated of verwijderen plus call-sites opruimen
- Done when: ingelogd zonder actief PB toont NavBar zonder geactiveerde tabs; met actief PB redirect `/solo``/products/[active]/solo` zonder cookie te raadplegen
- [x] **ST-904** NavBar — splits + disabled-states + switcher
- Tabs worden: **Producten** (`/dashboard`) | **Product Backlog** (`/products/[active]`) | **Sprint** (`/products/[active]/sprint`) | **Solo** (`/products/[active]/solo`) | **Todo's** (`/todos`); zonder actief PB zijn de middelste drie disabled-spans (zelfde stijl als huidige Sprint-disabled); productnaam in midden wordt een dropdown-trigger (shadcn `DropdownMenu`) met je producten + "Producten beheren →"; Sprint krijgt `aria-disabled` + tooltip "Geen actieve sprint" als er geen sprint met status `ACTIVE` is
- Done when: handmatige test: zonder PB drie tabs grijs; activeer PB → tabs klikbaar; dropdown wisselt PB en redirect naar Product Backlog; Sprint-tab disabled tot sprint gestart
- [x] **ST-905** Producten-scherm — Activeer-knop per rij
- `components/dashboard/product-list.tsx`: per rij "Activeer"-knop (verborgen voor reeds actief PB); actieve rij krijgt badge "Actief" (MD3-token `bg-primary-container`); klik op Activeer → `setActiveProduct` + `router.push('/products/[id]')`; ook in `/products/[id]` header een Activeer-knop als dat product nog niet actief is
- Done when: activeer in dashboard markeert juiste rij + landt op Product Backlog; demo-gebruiker krijgt toast en geen DB-mutatie
- [x] **ST-906** Edge cases — toegangsverlies en archivering
- Wanneer een PB wordt gearchiveerd, ge-leaved, of een productmember wordt verwijderd: `active_product_id` automatisch `null` voor betroffen users (server actions van `archiveProduct`, `leaveProduct`, `removeMember`); guard in `app/(app)/layout.tsx`: als `active_product_id` is gezet maar product is archived/onbereikbaar, server-side clear + redirect naar `/dashboard` met toast "Je actieve product is niet meer beschikbaar"
- Done when: scenario test — eigenaar archiveert → membership-gebruikers landen op dashboard met toast en active is gecleared
- [x] **ST-907** Documentatie en tests
- Functional spec: nieuw hoofdstuk "Actief Product Backlog" (concept, menugedrag, edge cases); README: navigatie-screenshot bijwerken; `docs/patterns/` indien nieuwe patroon (n.v.t. tenzij dropdown-switcher een herbruikbaar component wordt); jest-tests in `__tests__/actions/active-product.test.ts` voor setActive (toegang, demo, archived); Playwright/manueel scenario: log in → activeer PB → wissel via dropdown → archiveer → verifieer auto-clear
- Done when: `npm run lint && npx tsc --noEmit && npm test && npm run build` groen; spec-secties geschreven; `vendor/scrum4me`-submodule in mcp gesynced
---
### M10: Password-loze inlog via QR-pairing
**Implementatieplan:** [docs/plans/M10-qr-pairing-login.md](../plans/M10-qr-pairing-login.md)
Inloggen op een (publieke) desktop zonder wachtwoord: de desktop toont een QR-code, de gebruiker scant met een telefoon waar hij al ingelogd is, bevestigt expliciet, en de desktop is binnen 12 seconden ingelogd. Bouwt voort op de Postgres LISTEN/NOTIFY-infra van M8 (eigen kanaal `scrum4me_pairing`). Geen wachtwoord ingetypt op het publieke apparaat, geen credentials op de draad, demo-accounts geblokkeerd, paired-sessie heeft eigen kortere TTL (8 u) + `paired`-vlag voor toekomstige remote-revoke.
**Beveiligingsuitgangspunt:** `mobileSecret` reist alleen via QR-fragment (`#s=…`) → `location.hash` op de mobiel → POST-body. Desktop-SSE en claim authenticeren via een **HttpOnly pre-auth cookie** (`s4m_pair`, `Path=/api/auth/pair`, `Max-Age=120`, `SameSite=Lax`). Twee gescheiden hashes in DB (`secret_hash` voor mobiel-bewijs, `desktop_token_hash` voor desktop-bewijs) zodat geheim materiaal niet in URL-paden, querystrings, access logs, reverse-proxy logs, observability of browsergeschiedenis kan belanden.
Volledige flow + threat-model: `docs/patterns/qr-login.md` (op te leveren in ST-1008).
- [ ] **ST-1001** LoginPairing schema + Postgres-trigger
- **Schema:** `LoginPairing { id, secret_hash, desktop_token_hash, status, user_id?, desktop_ua?, desktop_ip?, created_at, expires_at, approved_at?, consumed_at? }`; back-relation `User.login_pairings`; `@@index([expires_at])`, `@@index([status, expires_at])`; `status` als string (`pending|approved|consumed|cancelled`); twee hash-kolommen scheiden mobiel-bewijs van desktop-bewijs
- **Trigger:** `notify_pairing_change()` + `AFTER INSERT/UPDATE` op `login_pairings`; `pg_notify('scrum4me_pairing', payload)` met `{ pairing_id, status, op }`; analoog aan `notify_solo_change` uit ST-801
- **Migratie:** `prisma migrate dev --name add_login_pairing`
- Done when: migratie slaagt; `psql $DIRECT_URL -c "LISTEN scrum4me_pairing;"` levert payload bij INSERT op `login_pairings`; beide hash-kolommen zijn `NOT NULL`
- [ ] **ST-1002** Pairing-helpers + sessie-uitbreiding + pre-auth-cookie
- **`lib/auth/pairing.ts`:** `generateMobileSecret()` en `generateDesktopToken()` (beide 32 bytes → base64url, los gegenereerd zodat ze elkaar niet onthullen), `hashToken(t)` (sha256-hex), `verifyToken(t, hash)` (timing-safe compare)
- **`lib/auth/pair-cookie.ts`:** `setPairCookie(response, desktopToken)` (`HttpOnly`, `Secure` in prod, `SameSite=Lax`, `Path=/api/auth/pair`, `Max-Age=120`); `readPairCookie(request)` returnt `desktopToken | null`; `clearPairCookie(response)` op claim/cancel
- **`SessionData` in `lib/session.ts`:** voeg optionele `paired?: boolean` en `pairedExpiresAt?: number` toe
- **`app/(app)/layout.tsx`:** extra guard — als `session.paired && session.pairedExpiresAt < Date.now()``session.destroy()` + `redirect('/login')`
- Done when: helpers hebben unit-tests; paired-sessie verloopt zichtbaar na vervaltijd; cookie wordt nooit door client-JS gelezen (HttpOnly-test)
- [ ] **ST-1003** `POST /api/auth/pair/start` — pairing aanmaken (anon)
- Route Handler zonder auth; leest UA + best-effort IP (`x-forwarded-for`); genereert los `mobileSecret` + `desktopToken`; insert `LoginPairing` met beide hashes, `status='pending'`, `expires_at = now() + 2 min`
- **Response body:** `{ pairingId, mobileSecret, expiresAt, qrUrl }``qrUrl = ${origin}/m/pair#id=…&s=…` (fragment, geen querystring)
- **Response header:** `Set-Cookie: s4m_pair=<desktopToken>; HttpOnly; Secure; SameSite=Lax; Path=/api/auth/pair; Max-Age=120`
- **Rate-limit:** patroon ST-608 (max 10 starts per IP per minuut)
- Done when: curl POST levert pairingId+mobileSecret in body en `s4m_pair`-cookie in header; 11e call binnen 60s geeft 429; rij in `login_pairings` zonder plaintext secret of desktop-token
- [ ] **ST-1004** SSE-route `/api/auth/pair/stream/[pairingId]` (cookie-auth)
- `runtime: 'nodejs'`, `maxDuration: 300`; pairingId in pad (niet sensitief), auth via `s4m_pair`-cookie: sha256(cookie) matcht `desktop_token_hash` van pairing met `pairingId` en `expires_at > now()`; anders 401
- **Geen query-parameters met geheim materiaal.** Browser stuurt cookie automatisch mee.
- Hergebruik LISTEN/NOTIFY-pattern uit `app/api/realtime/solo/route.ts` op kanaal `scrum4me_pairing`; filter notifications op `pairing_id`
- Auto-close bij status `consumed`/`cancelled` of na 240 s; heartbeat 25 s
- Done when: SSE-verbinding zonder `s4m_pair`-cookie geeft 401; met geldige cookie levert event binnen 1s na approve; stream sluit na consume; pairingId in URL is OK (niet vertrouwelijk)
- [ ] **ST-1005** Server actions + mobiele bevestigingspagina
- **`actions/pairing.ts`:** `getPairingForApproval(pairingId, mobileSecret)`, `approvePairing(pairingId, mobileSecret)` (demo-blokkade, hash-vergelijk tegen `secret_hash`, status pending→approved, bumpt `expires_at` +5 min, zet `user_id` + `approved_at`), `cancelPairing(pairingId, mobileSecret)`
- **`app/(app)/m/pair/page.tsx`:** Server Component achter de bestaande `(app)/layout.tsx` auth-guard; **leest géén query-params** — alleen statische uitleg + een client-island
- **`app/(app)/m/pair/pair-confirmation.tsx`:** Client Component die bij mount `window.location.hash` parseert (`#id=…&s=…`), via Server Action `getPairingForApproval` de UA/IP/username ophaalt, dan toont *"Inloggen op {ua} ({ip}) als {jouw-username}?"* met Bevestig/Annuleer-knoppen die `approvePairing`/`cancelPairing` aanroepen; succes-state *"Klaar — je kunt deze tab sluiten"*. Wist `location.hash` na approve zodat back/forward de secret niet onthult
- Demo-modus: approve geeft Nederlandse foutmelding (consistent ST-604)
- Done when: ingelogde mobiel ziet bevestigingspagina met UA + IP; secret komt nooit in een GET-URL voor; tap "Bevestig" zet status approved; demo-user ziet foutmelding en pairing blijft `pending`
- [ ] **ST-1006** `POST /api/auth/pair/claim` — desktop-cookie zetten (cookie-auth)
- Auth via `s4m_pair`-cookie (geen body-secret nodig); atomic update: `UPDATE login_pairings SET status='consumed', consumed_at=now() WHERE id=$1 AND status='approved' AND desktop_token_hash=$2 AND expires_at > now() RETURNING user_id`
- Bij rij geretourneerd: `getIronSession``session.userId = user.id; session.isDemo = user.is_demo; session.paired = true; session.pairedExpiresAt = Date.now() + 8h`; clear `s4m_pair`-cookie; anders 410 (al consumed) / 404 / 401
- Logging alleen `pairingId`, nooit cookie-waarde of mobileSecret
- Done when: claim met geldige cookie schrijft iron-session cookie en retourneert 200; tweede claim 410; ontbrekende/foute cookie 401; `s4m_pair` is na succes geclear'd
- [ ] **ST-1007** Desktop UI: QR-render + SSE-listener op `/login`
- **Dependency:** `qrcode.react` (client SVG; mobileSecret blijft op desktop in JS-geheugen)
- **`app/login/qr-login-button.tsx`:** Client Component; klik → POST `pair/start` (`credentials: 'same-origin'` zodat `s4m_pair`-cookie wordt geaccepteerd) → render QR met `qrUrl` (fragment-URL) → open `EventSource('/api/auth/pair/stream/<pairingId>', { withCredentials: true })` → bij `approved` event POST `pair/claim` (cookie-only) → bij succes `router.push('/dashboard')`; aftellende timer (2 min); bij timeout "Vernieuwen"-knop; cleanup bij unmount/redirect
<<<<<<<< HEAD:docs/backlog/index.md
- **`app/login/page.tsx`:** knop "Inloggen via mobiel" naast bestaande wachtwoord-form (MD3-tokens uit `docs/design/styling.md`)
========
- **`app/login/page.tsx`:** knop "Inloggen via mobiel" naast bestaande wachtwoord-form (MD3-tokens uit `docs/styling.md`)
>>>>>>>> origin/main:docs/backlog.md
- A11y: QR heeft alt-tekst met de URL voor screenreaders/copy-paste (de hash-suffix is onderdeel van die alt-tekst, niet van de page-URL die in browsergeschiedenis komt)
- Done when: end-to-end happy path werkt op localhost (twee browsers): A toont QR → B scant + bevestigt → A redirect naar `/dashboard` met `session.paired === true`; QR vernieuwt na expiry; geen secret zichtbaar in DevTools Network-tab onder URL-kolommen
- [ ] **ST-1008** Documentatie + acceptatietest
<<<<<<<< HEAD:docs/backlog/index.md
- **`docs/api/rest-contract.md`:** drie nieuwe endpoints (start/stream/claim) met request/response, cookie-mechaniek, foutcodes (400/401/403/404/410/422/429), curl-voorbeelden inclusief `--cookie-jar`
========
- **`docs/api.md`:** drie nieuwe endpoints (start/stream/claim) met request/response, cookie-mechaniek, foutcodes (400/401/403/404/410/422/429), curl-voorbeelden inclusief `--cookie-jar`
>>>>>>>> origin/main:docs/backlog.md
- **`docs/architecture.md`:** sectie "QR-pairing flow" met sequence-diagram + threat-model; expliciete subsectie *"Waarom geen secret in URL"* — fragments worden niet naar server gestuurd; SSE/claim authenticeren via HttpOnly cookie zodat secret-materiaal niet in access logs / reverse-proxy logs / observability-tools / browsergeschiedenis kan belanden
- **`docs/patterns/qr-login.md`:** nieuw pattern-doc voor toekomstige features die hetzelfde unauth-SSE-via-pre-auth-cookie-patroon willen hergebruiken
- **`CLAUDE.md`:** verwijzing naar het nieuwe pattern-doc in de patterns-tabel
- **Acceptatietest:** zeven scenario's handmatig: happy path, demo-block, replay, expiry tijdens pending, expiry tussen approve+claim, ontbrekende cookie op SSE/claim, secret niet aanwezig in `nginx`/Vercel access logs (controle via runtime-logs MCP-tool)
- Done when: docs gepubliceerd; alle zeven scenario's groen
---
### M11: Claude vraagt, gebruiker antwoordt
**Implementatieplan:** [docs/plans/M11-claude-questions.md](../plans/M11-claude-questions.md)
Persistent vraag-antwoord-kanaal tussen Claude Code (via MCP) en de actieve Scrum4Me-gebruiker. Claude schrijft een vraag naar `claude_questions` als hij vastloopt op een keuze; een Postgres-trigger emit op het bestaande `scrum4me_changes`-kanaal (uitgebreid met `entity: 'question'`); de Scrum4Me-app toont een notificatie-badge in de NavBar; iedereen met product-toegang kan antwoorden; Claude leest het antwoord (sync via polling met `wait_seconds`, of in een latere sessie via `get_question_answer`) en gaat door. Eerste concrete uitwerking van de AI-driven dev-flow-richting.
**Beveiligingsuitgangspunt:** atomic answer via `updateMany WHERE status='open'` voorkomt double-submit; demo-blok op zowel MCP-write-tools als Server Action; access-check via `productAccessFilter` in DB-query én SSE-filter; cron-endpoint voor expire-cleanup beveiligd met `Authorization: Bearer ${CRON_SECRET}`-header; logging alleen `question_id` (vraag/antwoord-tekst kan gevoelig materiaal bevatten).
- [ ] **ST-1101** `ClaudeQuestion` schema + Postgres-trigger
- **Schema:** `ClaudeQuestion { id, story_id, task_id?, product_id, asked_by, question, options?: Json, status, answer?, answered_by?, answered_at?, created_at, expires_at }`; relations op `User` (`asked_questions`, `answered_questions`), `Story`, `Task`, `Product`; indexes `(story_id, status)`, `(product_id, status)`, `(status, expires_at)`; `product_id` gedenormaliseerd voor SSE-filter
- **Trigger:** `notify_question_change()` `AFTER INSERT/UPDATE`; emit op `scrum4me_changes`-kanaal met payload `{ op, entity: 'question', id, product_id, story_id, task_id, assignee_id, status }`
- **Migratie:** `prisma migrate dev --name add_claude_questions`
- Done when: migratie slaagt; `psql LISTEN scrum4me_changes` toont nieuwe `entity: 'question'`-payload bij INSERT; bestaande solo-realtime-flow ongewijzigd; submodule sync na merge
- [ ] **ST-1102** MCP-tools voor Claude (in mcp-repo)
- **`ask_user_question`** (write): input `{ story_id, question, options?, task_id?, wait_seconds? }`; insert pairing + optioneel pollen tot `wait_seconds` (max 600); demo-blok via `requireWriteAccess`; access-check via `userCanAccessProduct(story.product_id, ...)`
- **`get_question_answer`** (read): haalt status + antwoord op een specifieke vraag op
- **`list_open_questions`** (read): lijst van eigen vragen (laatste 50, status open of answered)
- **`cancel_question`** (write): asker mag eigen vraag annuleren; status pending→cancelled
- Smoke-test in `scripts/smoke-test.ts`: `ask_user_question` met `wait_seconds=5` + parallel answer roundtrip
- Done when: MCP Inspector toont 4 nieuwe tools; smoke-test groen; demo-token op write-tools krijgt PERMISSION_DENIED; `tsc --noEmit` clean
- [ ] **ST-1103** Server Action `answerQuestion`
- `actions/questions.ts`: `answerQuestion(questionId, answer)` met getSession + Zod + demo-blok + `requireProductWriter` via `question.product_id`; atomic `updateMany WHERE status='open' AND expires_at>now`; `revalidatePath('/', 'layout')` voor badge-refresh
- Bij `count === 0`: disambigueer (al-answered/expired/access-fail) met begrijpelijke foutmelding
- Tests: 6 cases (happy, demo-block, geen access, race, expired, lege answer)
- Done when: `npm test` 6/6; handmatig: open vraag → antwoord → badge-count daalt met 1; demo-toast bij submit
- [ ] **ST-1104** User-scoped SSE-route `/api/realtime/notifications`
- Route Handler `runtime: 'nodejs'`, `maxDuration: 300`; auth via iron-session; **user-scoped** (geen product_id-param); filter `payload.entity === 'question'` én `payload.product_id` in user's accessible-product-ids
- Initial-state-event direct na connect (na LISTEN actief, conform M10 ST-1004 race-fix): summary-array van openstaande vragen voor deze user
- Update solo-route in `app/api/realtime/solo/route.ts`: in `shouldEmit` `if (payload.entity === 'question') return false` toevoegen — anders krijgen solo-clients ongewenst question-events
- Tests: 401 zonder cookie, filter op product-access, geen `entity:'question'`-events op solo-route
- Done when: `curl -N` levert events binnen 1s na INSERT; cross-product-test (user-A ziet user-B's vragen niet)
- [ ] **ST-1105** Notifications-UI (Bell + Sheet + Answer-modal + Zustand-store)
- **`stores/notifications-store.ts`** — Zustand store volgens `solo-store.ts`-patroon: `init`, `add`, `update`, `remove`, `optimisticAnswer`, `rollbackAnswer`; selectors `openCount`, `forYouCount`
- **`lib/realtime/use-notifications-realtime.ts`** — analoog aan `useSoloRealtime`; EventSource op `/api/realtime/notifications` met reconnect-backoff
- **`components/notifications/notifications-bridge.tsx`** — Server Component die initial-data fetcht en aan store geeft; mount in `app/(app)/layout.tsx` naast `<SoloRealtimeBridge />`
<<<<<<<< HEAD:docs/backlog/index.md
- **`components/shared/notifications-bell.tsx`** — Bell-icon (Lucide) met badge in NavBar (links van avatar); MD3-tokens uit `docs/design/styling.md`
========
- **`components/shared/notifications-bell.tsx`** — Bell-icon (Lucide) met badge in NavBar (links van avatar); MD3-tokens uit `docs/styling.md`
>>>>>>>> origin/main:docs/backlog.md
- **`components/notifications/notifications-sheet.tsx`** — shadcn Sheet van rechts; lijst gegroepeerd per product; story-assignee krijgt visuele *"wacht op jou"*-emphase
- **`components/notifications/answer-modal.tsx`** — shadcn Dialog; story-context-link, vraag-tekst, RadioGroup (als options) of Textarea (free-text), submit via `useTransition` + Server Action; demo-blok met tooltip
- Done when: bell + badge zichtbaar; klik opent Sheet met items; submit verwijdert item optimistisch; tweede tab van zelfde user ziet nieuwe vraag binnen 1-2s; demo-modus rendert maar Verstuur disabled
- [ ] **ST-1106** Demo-policy + access-tests
- Demo: Sheet rendert + Modal opent + Verstuur disabled met tooltip
- Access-isolation: cross-product test in `__tests__/api/notifications-stream.test.ts` (al gedeeltelijk in ST-1104)
- Story-assignee-emphase: visueel-only, toegang blijft product-membership-breed
- Done when: 4 access-tests groen; handmatige cross-product-verificatie
- [ ] **ST-1107** Vercel cron `expire-questions`
- **`app/api/cron/expire-questions/route.ts`** — POST handler beveiligd via `Authorization: Bearer ${CRON_SECRET}`; `updateMany WHERE status='open' AND expires_at<now → status='expired'`
- **`vercel.json`** — `crons` entry: `{ path: '/api/cron/expire-questions', schedule: '0 4 * * *' }` (dagelijks; Vercel Hobby-plan staat alleen daily crons toe)
- **`lib/env.ts`** + `.env.example``CRON_SECRET` via Zod
- Optioneel: ook M10's `login_pairings`-cleanup in dezelfde route opnemen
- Done when: handmatige `curl -X POST` met secret expireert oude rijen; Vercel-dashboard toont cron-config na deploy; onbevoegde call → 401
- [ ] **ST-1108** Documentatie + acceptatietest
<<<<<<<< HEAD:docs/backlog/index.md
- **`docs/api/rest-contract.md`:** secties "SSE — Notifications" + "Cron — Expire questions" met curl-voorbeelden
========
- **`docs/api.md`:** secties "SSE — Notifications" + "Cron — Expire questions" met curl-voorbeelden
>>>>>>>> origin/main:docs/backlog.md
- **`docs/architecture.md`:** sectie "Vraag-antwoord-kanaal Claude ↔ user" met Mermaid sequence-diagram + threat-model + "Waarom hergebruik scrum4me_changes-kanaal"
- **`docs/patterns/claude-question-channel.md`:** nieuw herbruikbaar pattern-doc voor toekomstige bidirectionele async-communicatie tussen MCP-agents en interactieve users
- **`CLAUDE.md`:** rij in Implementatiepatronen-tabel voor het nieuwe pattern
- **Acceptatietest** zes scenario's: sync happy (wait_seconds), async happy (geen wait), demo-block, access-isolation, expiry via cron, race op double-submit
- Done when: docs gepubliceerd; alle zes scenario's groen; backlog-parser-self-test toont M11 met ACTIVE-status
---
### M12: Ideeën & Grill/Plan jobs
**Implementatieplan:** [docs/plans/M12-ideas.md](../plans/M12-ideas.md)
**Dialog-profiel:** [docs/specs/dialogs/idea.md](../specs/dialogs/idea.md)
Idee is een nieuw concept tussen Todo en PBI. Strikt user_id-only (privé), met
twee Claude-jobs: **Grill Me** (interactief vragen-stellen via MCP) en **Make
Plan** (single-pass yaml-frontmatter genereren). De **Materialiseer**-knop
parseert het plan deterministisch en creëert PBI + stories + taken.
- [x] **ST-1192** — DB-schema & migratie voor Idea (T-491, T-492, T-489)
- Idea-model + IdeaLog-model + 3 enums; ClaudeJob.task_id nullable + idea_id +
kind; ClaudeQuestion.story_id nullable + idea_id; check-constraints +
pg_notify-trigger update
- [x] **ST-1193** — Lib + schemas + embedded prompts (T-493, T-494, T-495)
- zod-schemas, status-mapper + transition-guard, atomic code-generator,
yaml-frontmatter parser, embedded grill+make-plan prompts
- [x] **ST-1194** — Server actions + Todo→Idea promotie (T-496..T-499)
- CRUD, md-edit, job-triggers, materialize, relink, promoteTodoToIdeaAction
- [x] **ST-1195** — REST API + proxy demo-laag (T-500, T-501)
- /api/ideas + /api/ideas/[id]; demo-403 via proxy.ts catch-all
- [x] **ST-1196** — Realtime SSE + idea-store (T-502, T-503)
- SSE-routing voor idea-events; Zustand idea-store; extension van bestaande
notifications-realtime hook
- [ ] **ST-1197** — MCP-server tools (extern: madhura68/scrum4me-mcp)
- get_idea_context, update_idea_grill_md, update_idea_plan_md, log_idea_decision;
uitbreiding ask_user_question/wait_for_job/update_job_status; Docker rebuild
- [x] **ST-1198** — UI lijstpagina + row-actions (T-507, T-508, T-509)
- /ideas pagina, IdeaList tabel met filters, IdeaRowActions met
disabled-rules per status, idea-status-badge helper
- [x] **ST-1199** — UI detail + dialog + tabs (T-510..T-513)
- /ideas/[id] met 4 tabs (Idee/Grill/Plan/Timeline); md-editor met
yaml-validate; timeline met UNION view; pbi-link-card; dialog-profiel doc
- [x] **ST-1200** — Promote-from-Todo + sidebar (T-514, T-515)
- "→ Idee" knop in TodoCard, PromoteIdeaDialog, "Ideeën" nav-entry
- [ ] **ST-1201** — End-to-end smoke + docs-update (T-516, T-517)
- Volledige flow doorlopen volgens M12-ideas.md verificatie-script;
docs/runbooks/mcp-integration.md uitbreiden voor IDEA_*-job-kinds
- Done when: docs/INDEX.md opnieuw gegenereerd, alle stories ✓, MCP-server
PR met passende versie-bump gedeployed
---
## v2 Backlog (na MVP)
- [ ] Uitnodigingsflow voor teams — e-mailuitnodiging of link-gebaseerd; nu kunnen alleen admins met toegang tot het systeem Developers toevoegen via gebruikersnaam
- [ ] Daily Scrum scherm — voortgang per story bijhouden tijdens Sprint
- [ ] Sprint Review scherm — demo en feedback vastleggen per story
- [ ] Sprint Retrospective scherm — reflectie per Sprint
- [ ] Automatische story-statusupdate na commit via API
- [ ] Velocity tracking — statistieken over meerdere Sprints
- [ ] Definition of Done per product configureerbaar (nu vaste instelling)
- [ ] Notificaties / reminders
- [ ] Timeline / kalenderweergave per Sprint
- [ ] Integratie GitHub Issues / Linear
- [ ] Mobiele app — uitsluitend taken afvinken
- [ ] Export van Product Backlog en Sprint als markdown of CSV
---
## Definition of MVP Done
- [x] Alle M0M6 tasks afgerond
- [x] Volledige Lars-flow succesvol doorlopen (ST-612)
- [x] Alle 7 API-endpoints getest via curl (ST-403 t/m ST-409)
- [x] Demo-gebruiker kan inloggen en heeft geen schrijfrechten (ST-604)
- [x] App lokaal opzetbaar via README zonder extra hulp (ST-611)
- [x] CI/CD actief — falende build blokkeert merge (ST-610)
- [x] Beveiligingsreview API geslaagd (ST-609)
- [x] Geen bekende blocker-bugs

View file

@ -0,0 +1,462 @@
---
title: "DevPlanner — Product Backlog"
status: active
audience: [maintainer]
language: nl
last_updated: 2026-05-03
---
# DevPlanner — Product Backlog
**Versie:** 0.1 — april 2026
**Product:** DevPlanner
**Beschrijving:** Een lichtgewicht Scrum-gebaseerde projectplanner voor solo developers en kleine Scrum Teams die meerdere softwareprojecten parallel beheren. De app organiseert werk hiërarchisch (product → PBI → story → taak), biedt een visuele planningslaag en integreert met Claude Code voor geautomatiseerde implementatieflows.
**Git repo:** https://github.com/devplanner/devplanner
**Definition of Done:** Feature is geïmplementeerd, getest (unit + integratie), gedocumenteerd in code, en gedeployed naar de staging-omgeving zonder regressies.
---
## Prioriteiten
| Prioriteit | Betekenis |
|---|---|
| 1 — Kritiek | Blokkeert alle andere functionaliteit. Moet eerst. |
| 2 — Hoog | Core waarde van het product. MVP vereiste. |
| 3 — Middel | Verhoogt bruikbaarheid significant. v1 wenselijk. |
| 4 — Laag | Waardevol maar niet blokkerend. v2 kandidaat. |
---
## PBI-01 — Authenticatie & gebruikersbeheer
**Prioriteit:** 1 — Kritiek
**Omschrijving:** Het Scrum Team kan een account aanmaken en inloggen met gebruikersnaam en wachtwoord. Een demo-gebruiker heeft alleen leesrechten. Gebruikers kunnen één of meerdere Scrum-rollen aannemen.
### Stories
**S-01-01: Account aanmaken**
Als bezoeker wil ik een account aanmaken met gebruikersnaam en wachtwoord, zodat ik toegang krijg tot de app.
Acceptatiecriteria:
- Gebruikersnaam en wachtwoord zijn verplicht
- Gebruikersnaam is uniek; dubbele aanmelding geeft foutmelding
- Wachtwoord heeft minimaal 8 tekens
- Na aanmaken wordt de gebruiker direct ingelogd
- Geen e-mailverificatie vereist in v1
**S-01-02: Inloggen**
Als geregistreerde gebruiker wil ik inloggen met gebruikersnaam en wachtwoord, zodat ik mijn projecten kan beheren.
Acceptatiecriteria:
- Incorrecte combinatie geeft generieke foutmelding (geen onderscheid gebruikersnaam/wachtwoord)
- Na inloggen wordt de gebruiker doorgestuurd naar het dashboard
- Sessie blijft actief totdat de gebruiker uitlogt
**S-01-03: Uitloggen**
Als ingelogde gebruiker wil ik kunnen uitloggen, zodat mijn sessie veilig afgesloten wordt.
Acceptatiecriteria:
- Uitlogknop altijd zichtbaar in de navigatie
- Na uitloggen wordt de gebruiker naar de loginpagina gestuurd
- Sessiedata wordt gewist
**S-01-04: Demo-gebruiker (read-only)**
Als bezoeker wil ik kunnen inloggen als demo-gebruiker, zodat ik de app kan verkennen zonder een account aan te maken.
Acceptatiecriteria:
- Vaste inloggegevens voor de demo-gebruiker zijn beschikbaar op de loginpagina
- Demo-gebruiker ziet alle data maar kan niets aanmaken, aanpassen of verwijderen
- Alle actieknoppen (aanmaken, bewerken, verwijderen) zijn zichtbaar maar uitgeschakeld met tooltip "Niet beschikbaar in demo-modus"
- Demo-gebruiker kan niet van rol wisselen
**S-01-05: Roltoewijzing**
Als gebruiker wil ik één of meerdere Scrum-rollen kunnen aannemen (Product Owner, Scrum Master, Developer), zodat de app weet in welke context ik werk.
Acceptatiecriteria:
- Gebruiker kan bij registratie of in instellingen rollen selecteren
- Minimaal één rol is verplicht
- Alle drie de rollen tegelijk zijn toegestaan
- Rolkeuze is zichtbaar in de navigatie/profielbalk
- Rolkeuze heeft in v1 geen effect op zichtbare functionaliteit (voorbereiding op v2)
---
## PBI-02 — Productbeheer
**Prioriteit:** 1 — Kritiek
**Omschrijving:** Het Scrum Team kan producten aanmaken, bekijken, bewerken en archiveren. Een product heeft een naam, beschrijving en koppeling naar een git-repository.
### Stories
**S-02-01: Product aanmaken**
Als Product Owner wil ik een nieuw product aanmaken met naam, beschrijving en git-repo URL, zodat ik een werkruimte heb voor de Product Backlog.
Acceptatiecriteria:
- Naam is verplicht en uniek per gebruiker
- Beschrijving is optioneel (vrije tekst)
- Git-repo URL is optioneel maar wordt gevalideerd als geldige URL
- Product is direct zichtbaar in de productenlijst na aanmaken
**S-02-02: Product bewerken**
Als Product Owner wil ik de naam, beschrijving en git-repo URL van een product kunnen aanpassen, zodat de informatie actueel blijft.
Acceptatiecriteria:
- Alle velden zijn bewerkbaar
- Wijzigingen worden opgeslagen zonder de pagina te verlaten
- Lege naam geeft validatiefout
**S-02-03: Product archiveren**
Als Product Owner wil ik een product kunnen archiveren, zodat het niet meer in het overzicht verschijnt maar de data bewaard blijft.
Acceptatiecriteria:
- Gearchiveerde producten verschijnen niet in de standaardlijst
- Er is een optie om gearchiveerde producten te tonen
- Archiveren is omkeerbaar (product kan worden hersteld)
**S-02-04: Productenlijst bekijken**
Als gebruiker wil ik een overzicht zien van alle actieve producten, zodat ik snel naar het juiste product kan navigeren.
Acceptatiecriteria:
- Lijst toont naam, beschrijving (ingekort) en git-repo link
- Klikken op een product opent de Product Backlog van dat product
- Lege staat toont een prompt om een product aan te maken
---
## PBI-03 — Product Backlog
**Prioriteit:** 1 — Kritiek
**Omschrijving:** Het Scrum Team kan de Product Backlog beheren via een gesplitst scherm: links de PBI's, rechts de bijbehorende stories. Items kunnen aangemaakt, bewerkt, geprioriteerd en gerangschikt worden via drag-and-drop (dnd-kit).
### Stories
**S-03-01: PBI aanmaken**
Als Product Owner wil ik een PBI aanmaken in de Product Backlog, zodat ik nieuwe functionaliteit kan definiëren.
Acceptatiecriteria:
- PBI heeft een titel (verplicht) en omschrijving (optioneel)
- PBI krijgt een prioriteit (1 t/m 4)
- Nieuw PBI verschijnt onderaan de lijst voor de gekozen prioriteit
- Aanmaken via knop in de navigatiebar van het linkerpaneel
**S-03-02: PBI bewerken**
Als Product Owner wil ik de titel, omschrijving en prioriteit van een PBI kunnen aanpassen.
Acceptatiecriteria:
- Dubbelklikken of via contextmenu opent bewerkingsmodus
- Alle velden zijn inline bewerkbaar
- Prioriteitswijziging herplaatst het PBI visueel
**S-03-03: PBI verwijderen**
Als Product Owner wil ik een PBI kunnen verwijderen, zodat irrelevante items de backlog niet vervuilen.
Acceptatiecriteria:
- Verwijderen vereist bevestiging
- Verwijderen van een PBI verwijdert ook alle bijbehorende stories (cascade)
- Actie is niet ongedaan te maken; bevestigingsdialoog waarschuwt hiervoor
**S-03-04: PBI prioriteit instellen**
Als Product Owner wil ik per PBI een prioriteit kunnen instellen (1 t/m 4), zodat de volgorde van de backlog de businesswaarde weerspiegelt.
Acceptatiecriteria:
- Prioriteit is instelbaar via dropdown of inline label
- PBI's worden gegroepeerd per prioriteit in de lijst
- Visuele scheiding per prioriteitsgroep (kleurband of scheidingslijn)
**S-03-05: PBI volgorde aanpassen via drag-and-drop**
Als Product Owner wil ik de volgorde van PBI's binnen dezelfde prioriteit kunnen aanpassen via drag-and-drop, zodat ik fijnmazige prioritering kan doen.
Acceptatiecriteria:
- Drag-and-drop werkt vloeiend (60fps) via dnd-kit
- Volgorde wordt direct opgeslagen na loslaten
- Drag over prioriteitsgrens wisselt de prioriteit van het PBI
- Visuele placeholder toont de doelpositie tijdens het slepen
**S-03-06: PBI filteren**
Als gebruiker wil ik PBI's kunnen filteren op prioriteit of status, zodat ik me kan focussen op het relevante werk.
Acceptatiecriteria:
- Filteropties beschikbaar in de navigatiebar van het linkerpaneel
- Filter werkt realtime (geen herlaadactie)
- Actief filter is duidelijk zichtbaar; eenvoudig te wissen
**S-03-07: Gesplitst scherm Product Backlog**
Als gebruiker wil ik de Product Backlog bekijken als gesplitst scherm (PBI's links, stories rechts), zodat ik snel kan navigeren tussen PBI's en hun stories.
Acceptatiecriteria:
- Scherm is standaard 50/50 verdeeld
- De splitter is horizontaal versleepbaar
- Elk paneel heeft een eigen navigatiebar met acties
- Selecteren van een PBI links toont de bijbehorende stories rechts
- Geselecteerde PBI is visueel gemarkeerd
---
## PBI-04 — Story-beheer
**Prioriteit:** 1 — Kritiek
**Omschrijving:** Stories kunnen worden aangemaakt, bewerkt, geprioriteerd en gerangschikt binnen een PBI. Stories worden weergegeven als blokken van circa 10% schermbreedte, gerangschikt op prioriteit.
### Stories
**S-04-01: Story aanmaken**
Als Product Owner wil ik een story aanmaken binnen een PBI, zodat ik de functionaliteit kan uitwerken in uitvoerbare eenheden.
Acceptatiecriteria:
- Story heeft een titel (verplicht), omschrijving (optioneel) en prioriteit
- Aanmaken via navigatiebar van het rechterpaneel
- Nieuwe story verschijnt als blok rechts, in de juiste prioriteitsgroep
**S-04-02: Story weergave als blokken**
Als gebruiker wil ik stories zien als compacte blokken (~10% schermbreedte), zodat ik snel een overzicht heb van alle stories per PBI.
Acceptatiecriteria:
- Elk blok toont: storytitel, prioriteit, status
- Blokken zijn gerangschikt op prioriteit (hoog naar laag, links naar rechts)
- Elke nieuwe prioriteitsgroep heeft een visuele scheiding (kleurband of lijn)
- Blokken zijn klikbaar voor detail/bewerking
**S-04-03: Story prioriteit instellen**
Als Product Owner wil ik per story een prioriteit instellen, zodat de Developer weet wat als eerste opgepakt moet worden.
Acceptatiecriteria:
- Prioriteit instelbaar via het storyblok (dropdown of label)
- Prioriteitswijziging herplaatst het blok in de juiste groep
**S-04-04: Story volgorde aanpassen via drag-and-drop**
Als Product Owner wil ik de volgorde van stories binnen dezelfde prioriteit aanpassen via drag-and-drop, zodat ik de uitvoeringsvolgorde kan finetunen.
Acceptatiecriteria:
- Drag-and-drop werkt via dnd-kit tussen en binnen prioriteitsgroepen
- Volgorde wordt direct opgeslagen
- Slepen over een prioriteitsgrens wijzigt de prioriteit
**S-04-05: Story bewerken**
Als Product Owner wil ik de titel, omschrijving en prioriteit van een story kunnen aanpassen.
Acceptatiecriteria:
- Bewerkbaar via klikken op het storyblok
- Wijzigingen opgeslagen zonder paginaverversing
**S-04-06: Story verwijderen**
Als Product Owner wil ik een story kunnen verwijderen.
Acceptatiecriteria:
- Verwijderen vereist bevestiging
- Cascade verwijdering van gekoppelde taken
- Niet ongedaan te maken; waarschuwing in dialoog
---
## PBI-05 — Todo-lijst
**Prioriteit:** 2 — Hoog
**Omschrijving:** Gebruikers kunnen een snelle todo-lijst bijhouden voor ongeplande of kortstondige taken. Een todo-item kan worden gepromoveerd naar een PBI of story.
### Stories
**S-05-01: Todo-item aanmaken**
Als gebruiker wil ik snel een todo-item aanmaken zonder het aan een product te koppelen, zodat ik losse gedachten kan vastleggen zonder de planningsflow te onderbreken.
Acceptatiecriteria:
- Todo heeft alleen een titel (verplicht)
- Aanmaken via een snel-invoerveld (Enter om op te slaan)
- Todo's zijn zichtbaar in een aparte todo-sectie of zijpaneel
**S-05-02: Todo-item afvinken**
Als gebruiker wil ik een todo-item kunnen afvinken, zodat ik bij kan houden wat klaar is.
Acceptatiecriteria:
- Afgevinkte items zijn visueel doorgestreept
- Afgevinkte items blijven zichtbaar maar kunnen worden gearchiveerd
**S-05-03: Todo promoveren naar PBI**
Als Product Owner wil ik een todo-item promoveren naar een PBI in een bestaand product, zodat losse ideeën in de formele backlog terechtkomen.
Acceptatiecriteria:
- Promoten opent een dialoog om product en prioriteit te kiezen
- Het todo-item wordt omgezet naar een PBI en verdwijnt uit de todo-lijst
- De PBI-titel is gelijk aan de todo-titel (bewerkbaar in dialoog)
**S-05-04: Todo promoveren naar story**
Als Product Owner wil ik een todo-item promoveren naar een story binnen een bestaand PBI, zodat ik snel nieuwe stories kan toevoegen vanuit losse notities.
Acceptatiecriteria:
- Promoten opent een dialoog om product, PBI en prioriteit te kiezen
- Todo wordt omgezet naar een story en verdwijnt uit de todo-lijst
---
## PBI-06 — Sprint Backlog & Sprint Planning
**Prioriteit:** 2 — Hoog
**Omschrijving:** Het Scrum Team kan een Sprint aanmaken met een Sprint Goal, stories uit de Product Backlog naar de Sprint Backlog slepen, en de volgorde bepalen.
### Stories
**S-06-01: Sprint aanmaken**
Als Scrum Master wil ik een nieuwe Sprint aanmaken met een Sprint Goal, zodat het Scrum Team een duidelijk doel heeft voor de komende Sprint.
Acceptatiecriteria:
- Sprint heeft een Sprint Goal (verplicht, vrije tekst)
- Sprint is gekoppeld aan een product
- Er kan maar één actieve Sprint per product zijn
**S-06-02: Sprint Backlog scherm (gesplitst)**
Als gebruiker wil ik de Sprint Backlog kunnen beheren via een gesplitst scherm (Sprint Backlog links, stories per PBI rechts), zodat ik snel stories kan toevoegen aan de Sprint.
Acceptatiecriteria:
- Links: Sprint Backlog met geselecteerde stories in volgorde
- Rechts: stories uit de Product Backlog, gegroepeerd per PBI
- Splitter is horizontaal versleepbaar
- Elk paneel heeft eigen navigatiebar
**S-06-03: Story naar Sprint slepen**
Als Developer wil ik een story vanuit de Product Backlog naar de Sprint Backlog kunnen slepen, zodat ik bepaal wat we deze Sprint gaan oppakken.
Acceptatiecriteria:
- Drag-and-drop werkt via dnd-kit tussen rechterpaneel en linkerpaneel
- Story verschijnt in de Sprint Backlog op de gesleepte positie
- Story in de Product Backlog krijgt visuele markering "In Sprint"
- Een story kan maar aan één actieve Sprint gekoppeld zijn
**S-06-04: Volgorde stories in Sprint bepalen**
Als Developer wil ik de volgorde van stories in de Sprint Backlog kunnen aanpassen via drag-and-drop, zodat ik de uitvoeringsvolgorde bepaal.
Acceptatiecriteria:
- Drag-and-drop werkt binnen de Sprint Backlog
- Volgorde wordt direct opgeslagen
- Volgorde is onafhankelijk van de prioriteit in de Product Backlog
**S-06-05: Story uit Sprint verwijderen**
Als Developer wil ik een story uit de Sprint Backlog kunnen verwijderen (terugplaatsen in de Product Backlog), zodat we de Sprint scope kunnen aanpassen.
Acceptatiecriteria:
- Story verdwijnt uit de Sprint Backlog
- Story is weer beschikbaar in de Product Backlog
- Actie vereist geen bevestiging (is niet destructief)
---
## PBI-07 — Sprint Planning (taken per story)
**Prioriteit:** 2 — Hoog
**Omschrijving:** Tijdens Sprint Planning worden stories opgedeeld in taken. Hetzelfde gesplitste scherm wordt gebruikt: stories links, taken rechts. Taken kunnen geprioriteerd en gerangschikt worden.
### Stories
**S-07-01: Sprint Planning scherm**
Als Developer wil ik een Sprint Planning scherm zien met stories links en taken rechts, zodat ik per story taken kan aanmaken en rangschikken.
Acceptatiecriteria:
- Links: stories in de Sprint Backlog in volgorde
- Rechts: taken van de geselecteerde story
- Selecteren van een story links toont de bijbehorende taken rechts
- Gesplitst scherm is horizontaal versleepbaar
**S-07-02: Taak aanmaken**
Als Developer wil ik een taak aanmaken onder een story, zodat ik het uitvoerbare werk kan definiëren.
Acceptatiecriteria:
- Taak heeft een titel (verplicht), omschrijving (optioneel) en prioriteit
- Aanmaken via navigatiebar van het rechterpaneel
- Nieuwe taak verschijnt onderaan de takenlijst van de story
**S-07-03: Taak prioriteit instellen**
Als Developer wil ik per taak een prioriteit instellen, zodat de uitvoeringsvolgorde duidelijk is.
Acceptatiecriteria:
- Prioriteit instelbaar via taakregel (dropdown of label)
- Taken gegroepeerd en gerangschikt op prioriteit
**S-07-04: Taak volgorde aanpassen via drag-and-drop**
Als Developer wil ik de volgorde van taken binnen een story kunnen aanpassen via drag-and-drop, zodat de uitvoeringsvolgorde precies klopt.
Acceptatiecriteria:
- Drag-and-drop via dnd-kit binnen de takenlijst
- Volgorde direct opgeslagen na loslaten
**S-07-05: Taakstatus bijhouden**
Als Developer wil ik de status van een taak kunnen bijhouden (To Do, In Progress, Done), zodat de voortgang van de Sprint zichtbaar is.
Acceptatiecriteria:
- Status is instelbaar via de UI (dropdown of knoppen)
- Statuswijziging is direct zichtbaar in het Sprint Planning scherm
- Story toont een voortgangsindicator op basis van taakstatussen
---
## PBI-08 — Claude Code integratie
**Prioriteit:** 2 — Hoog
**Omschrijving:** Claude Code kan via een REST API (en later MCP) stories en taken ophalen, de volgorde beoordelen, een implementatieplan opstellen, tests uitvoeren en committen. Elk resultaat wordt vastgelegd in de story.
### Stories
**S-08-01: REST API — story ophalen**
Als Developer (via Claude Code) wil ik de hoogst geprioriteerde open story van een product kunnen ophalen via een API-endpoint, zodat Claude Code weet wat er gedaan moet worden.
Acceptatiecriteria:
- Endpoint: `GET /api/products/:id/next-story`
- Retourneert: story-id, titel, omschrijving, acceptatiecriteria, gekoppelde taken
- Authentiseerd via API-token
- Geeft 404 als er geen open stories zijn
**S-08-02: REST API — eerste 10 taken ophalen**
Als Developer (via Claude Code) wil ik de eerste 10 taken van de Sprint Backlog kunnen ophalen, zodat Claude Code de volgorde kan beoordelen en zo nodig aanpassen.
Acceptatiecriteria:
- Endpoint: `GET /api/sprints/:id/tasks?limit=10`
- Retourneert taken in huidige volgorde met id, titel, prioriteit, status
- Claude Code kan de volgorde aanpassen via een apart endpoint
**S-08-03: REST API — taakvolgorde aanpassen**
Als Developer (via Claude Code) wil ik de volgorde van taken kunnen aanpassen via de API, zodat Claude Code een optimale uitvoeringsvolgorde kan bepalen.
Acceptatiecriteria:
- Endpoint: `PATCH /api/stories/:id/tasks/reorder`
- Accepteert een geordende lijst van taak-id's
- Volgorde wordt direct weerspiegeld in de UI
**S-08-04: Implementatieplan vastleggen**
Als Developer (via Claude Code) wil ik een implementatieplan kunnen schrijven naar een story, zodat de ontwerpbeslissingen traceerbaar zijn.
Acceptatiecriteria:
- Endpoint: `POST /api/stories/:id/log`
- Veld: `type: "implementation_plan"`, `content: string`
- Log-entry verschijnt in de story-activiteitenlog in de UI
**S-08-05: Teststatus vastleggen**
Als Developer (via Claude Code) wil ik de uitkomst van testruns kunnen vastleggen in een story, zodat kwaliteitsbewijs per story bewaard blijft.
Acceptatiecriteria:
- Endpoint: `POST /api/stories/:id/log`
- Veld: `type: "test_result"`, `content: string`, `status: "passed" | "failed"`
- Teststatus zichtbaar in de story-activiteitenlog
**S-08-06: Commit-hash vastleggen**
Als Developer (via Claude Code) wil ik de commit-hash na een succesvolle commit kunnen vastleggen in een story, zodat code en planning direct gekoppeld zijn.
Acceptatiecriteria:
- Endpoint: `POST /api/stories/:id/log`
- Veld: `type: "commit"`, `hash: string`, `message: string`
- Commit-hash is klikbaar en linkt naar de git-repo (indien geconfigureerd)
**S-08-07: Story activiteitenlog in UI**
Als gebruiker wil ik per story een activiteitenlog zien met alle vastgelegde implementatieplannen, testresultaten en commits, zodat ik de volledige uitvoeringsgeschiedenis op één plek heb.
Acceptatiecriteria:
- Log toont alle entries in chronologische volgorde
- Elk type entry heeft een eigen visuele stijl (plan, test, commit)
- Log is zichtbaar in de story-detailweergave
- Log is read-only in de UI (schrijven gebeurt via API)
---
## PBI-09 — Infrastructuur & deployment
**Prioriteit:** 1 — Kritiek
**Omschrijving:** De app is deployable op Vercel + Neon (cloud) én volledig lokaal draaibaar zonder externe afhankelijkheden.
### Stories
**S-09-01: Cloud deployment (Vercel + Neon)**
Als Developer wil ik de app deployen op Vercel met een Neon PostgreSQL-database, zodat de app beschikbaar is via een URL.
Acceptatiecriteria:
- `next build` slaagt zonder fouten
- Database-migraties worden uitgevoerd via Prisma
- Environment variables zijn gedocumenteerd in `.env.example`
**S-09-02: Lokale modus**
Als Developer wil ik de app lokaal kunnen draaien met een Neon PostgreSQL-database, zodat de lokale setup overeenkomt met productie.
Acceptatiecriteria:
- `npm run dev` start de app lokaal zonder Vercel of Neon account
- Database wordt aangemaakt via `prisma db push`
- README bevat stap-voor-stap instructies voor lokale setup
**S-09-03: API-token authenticatie**
Als Developer wil ik een API-token kunnen genereren in de app, zodat Claude Code veilig kan communiceren met de REST API.
Acceptatiecriteria:
- Gebruiker kan een API-token aanmaken in de instellingenpagina
- Token wordt eenmalig getoond en daarna niet meer zichtbaar
- Token kan worden ingetrokken
- Alle API-endpoints vereisen een geldig token via `Authorization: Bearer`
---
## Backlog — v2 kandidaten (niet in v1)
| PBI | Omschrijving |
|---|---|
| Daily Scrum scherm | Voortgang per story bijhouden tijdens de Sprint |
| Sprint Review scherm | Demo en feedback vastleggen per story |
| Sprint Retrospective scherm | Reflectie vastleggen per Sprint |
| Meerdere gebruikers per Scrum Team | Uitgebreide auth met rol-gebaseerde permissies |
| Automatische statusupdate na commit | Story op Done zetten via API-aanroep |
| Velocity tracking | Statistieken over meerdere Sprints |
| Notificaties / reminders | Push of e-mailmeldingen |
| Timeline / kalenderweergave | Sprint-planning op kalender |
| Definition of Done per product configureerbaar | Nu vaste instelling; later flexibel |
| Integratie GitHub Issues / Linear | Import/export van PBI's en stories |
---
*Dit document dient als testdata voor de eerste implementatie van de datastructuur.*
*Versie 0.1 — te updaten na Sprint 1 Review.*