Scrum4Me/docs/backlog/index.md

74 KiB
Raw Blame History

title status audience language last_updated
Scrum4Me — Implementatie Backlog active
maintainer
contributor
nl 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

Backlog

M0: Foundation

  • 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
  • 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
  • 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
  • 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
  • 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
  • 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
  • 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
  • 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

  • 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
  • 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
  • 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
  • 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
  • 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
  • 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
  • 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
  • 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)
  • 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
  • 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

  • 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
  • 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
  • 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
  • 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
  • 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
  • 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
  • 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
  • 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
  • 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
  • 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

  • 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
  • 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
  • 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
  • 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
  • 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
  • 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
  • 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
  • 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"
  • 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
  • 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
  • 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
  • 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
  • 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 solo-paneel-spec.md.

  • 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
  • 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
  • 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
  • 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
  • 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
  • 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
  • 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
  • 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
  • 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
  • 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
  • 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

  • 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
  • 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)
  • 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
  • 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
  • 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
  • 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
  • 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
  • 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
  • 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
  • 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.

  • 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
  • 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
  • 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
  • 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
  • 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
  • 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
  • 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
  • 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
  • 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
  • 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
  • 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)
  • 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
  • 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
    • 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
    • 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

  • 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
  • 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
  • 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
  • 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
  • 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
  • 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
  • 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
  • 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
  • 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
  • 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
  • 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
  • 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. 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.

  • 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
  • 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
  • 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
  • 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
  • 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
  • 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
  • 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
  • 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
  • ST-709 Prompt implement_next_story

    • Workflow: get_claude_context → plan → log_implementation → per task in_progress/done → tests → log_test_resultlog_commit
    • Done when: prompt zichtbaar in MCP-clients met argument product_id
  • 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).

  • 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
  • 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
  • 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
  • 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
  • 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
  • ST-806 Documentatie + acceptatietest

    • 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)
    • 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

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.

  • 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
  • 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"
  • 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
  • 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
  • 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
  • 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
  • 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

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: getIronSessionsession.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
    • app/login/page.tsx: knop "Inloggen via mobiel" naast bestaande wachtwoord-form (MD3-tokens uit docs/design/styling.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

    • 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/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

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 />
    • components/shared/notifications-bell.tsx — Bell-icon (Lucide) met badge in NavBar (links van avatar); MD3-tokens uit docs/design/styling.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.jsoncrons entry: { path: '/api/cron/expire-questions', schedule: '0 4 * * *' } (dagelijks; Vercel Hobby-plan staat alleen daily crons toe)
    • lib/env.ts + .env.exampleCRON_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

    • docs/api/rest-contract.md: secties "SSE — Notifications" + "Cron — Expire questions" met curl-voorbeelden
    • 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

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

  • Alle M0M6 tasks afgerond
  • Volledige Lars-flow succesvol doorlopen (ST-612)
  • Alle 7 API-endpoints getest via curl (ST-403 t/m ST-409)
  • Demo-gebruiker kan inloggen en heeft geen schrijfrechten (ST-604)
  • App lokaal opzetbaar via README zonder extra hulp (ST-611)
  • CI/CD actief — falende build blokkeert merge (ST-610)
  • Beveiligingsreview API geslaagd (ST-609)
  • Geen bekende blocker-bugs