Scrum4Me/docs/backlog/index.md
Janpeter Visser 7e45bbdbc0
docs: AI-optimized docs restructure (Phases 1–8) (#61)
* docs(dialog-pattern): add generic entity-dialog spec

Introduceert docs/patterns/dialog.md als bron-of-truth voor elke
create/edit/detail-dialog in Scrum4Me, ongeacht het achterliggende
dataobject. Bevat 14 secties: uitgangspunten, stack, component-
architectuur, layout, validatie, drielaagse demo-policy, submission,
dialog-gedrag, theming, footer, triggers/URL-state, per-entiteit
profile-template, out-of-scope, en een verificatie-checklist.

Registreert het patroon in CLAUDE.md "Implementatiepatronen"-tabel
zodat Claude (en mensen) de spec verplicht raadplegen voor elke
nieuwe dialog.

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

* docs(dialog-pattern): convert task spec + add pbi/story entity-profiles

Reduceert docs/scrum4me-task-dialog.md van 507 naar ~140 regels: alle
gedeelde regels verhuisd naar docs/patterns/dialog.md, dit document
bevat nu alleen Task-specifieke velden, URL-pattern, status-veld,
server actions, triggers en bewuste out-of-scope-keuzes.

Voegt twee nieuwe entity-profielen toe voor bestaande dialogen:
- docs/scrum4me-pbi-dialog.md (PbiDialog: state-based, code+title-rij,
  PbiStatusSelect, geen delete in v1)
- docs/scrum4me-story-dialog.md (StoryDialog: state-based, header met
  status/priority badges, inline activity-log, demo-readonly-fallback,
  inline-delete-confirm i.p.v. AlertDialog)

Beide profielen documenteren expliciet de "Bekende gaps t.o.v.
generieke spec" zodat opvolgende PR's de afwijkingen kunnen
rechtzetten of bewust kunnen accorderen.

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

* Added pdevelopment docs

* docs(plans): add docs-restructure plan for AI-optimized lookup

Audit of existing 39 doc files (~10.700 lines) and a phased restructure
proposal aimed at minimising the tokens an AI agent has to read to find
the right reference. Captures resolved decisions on language (English),
ADR template (Nygard default with MADR escape-hatch), index generator
(node script), and folder taxonomy. Proposal status — fase 1 to follow.

* docs(adr): add ADR scaffolding (templates, README, meta-ADR)

Set up docs/adr/ as the canonical home for architecture decisions:

- templates/nygard.md — default four-section format (Status, Context,
  Decision, Consequences) for one-way-door decisions.
- templates/madr.md — MADR v4 with YAML front-matter and explicit
  Considered Options for decisions where rejected alternatives matter.
- README.md — naming convention (NNNN-kebab-case), template-selection
  guidance (Nygard default; MADR for auth, queue mechanics, agent
  integration), status lifecycle, and ADR roster.
- 0000-record-architecture-decisions.md — meta-ADR establishing the
  practice itself, in Nygard format.

Backfilling existing implicit decisions (base-ui-over-radix, float
sort_order, demo-user three-layer policy, etc.) is fase 6 of the
docs-restructure plan.

* feat(docs): add docs index generator + initial INDEX.md

scripts/generate-docs-index.mjs walks docs/**/*.md, parses YAML
front-matter (or first H1 fallback) and a Nygard-style ## Status
section, then writes docs/INDEX.md with grouped tables for ADRs,
Specs, Plans (with archive subsection), Patterns, and Other.

Pure Node 20 (no external deps); idempotent — running it twice
produces byte-identical output. Excludes adr/templates/, the ADR
README, INDEX.md itself, and any *_*.md sidecar file.

Wire-up:
- package.json: docs:index → node scripts/generate-docs-index.mjs

Initial run indexed 35 docs across the existing structure; the
generated INDEX.md is committed so the table is reviewable in the
PR before hooking generation into a pre-commit step.

* chore: ignore Obsidian vault and personal sidecar files

Add .obsidian/ (Obsidian vault config) and _*.md (personal sidecar
notes) to .gitignore so the docs/ tree can serve as canonical source
of truth while still being usable as an Obsidian vault for personal
authoring. The docs index generator already excludes the same _*.md
pattern from INDEX.md.

* docs(plans): add PBI bulk-create spec for docs-restructure

Machine-parseable spec for an executor that calls the scrum4me MCP
(create_pbi → create_story → create_task) to seed the docs-restructure
work into the DB.

- Section 1 (Context) is the PBI description; serves as task-context
  via mcp__scrum4me__get_claude_context.
- Section 2 lists the 6 resolved decisions (English, MD3+styling
  merged, solo-paneel merged, .Plans archived, Nygard ADR default,
  node index script).
- Section 3 records what already shipped on this branch so the
  executor doesn't duplicate the ADR scaffolding or index generator.
- Section 4 carries the structured YAML graph: 1 PBI, 8 stories
  (one per phase), 39 tasks. product_id is REPLACE_ME — fill before
  running.
- YAML validated with PyYAML; field schema sanity-checked.

* docs(junk-cleanup): remove stub patterns/test.md

* docs(junk-cleanup): archive .Plans/ to docs/plans/archive/

* docs(front-matter): add YAML front-matter to docs/ root

* docs(front-matter): add YAML front-matter to patterns/

* docs(front-matter): add YAML front-matter to plans + agent files

* docs(index): regenerate INDEX.md after front-matter pass

* docs(naming): drop scrum4me- prefix from doc filenames

* docs(naming): lowercase API.md and MD3 filenames

* docs(naming): rename plan file to kebab-case ASCII

* docs(naming): rename middleware.md to proxy.md (next 16)

* docs(naming): polish CLAUDE.md doc-index after renames

* docs(taxonomy): scaffold topical folders under docs/

* docs(taxonomy): move spec files into docs/specs/

* docs(taxonomy): move design/api/qa/backlog/assets into folders

* docs(taxonomy): move agent-instruction-audit into decisions/

* docs(split): break architecture.md into 6 topical files

* docs(split): merge solo-paneel-spec into specs/functional.md

* docs(split): merge md3-color-scheme into design/styling

* docs(trim): extract branch/commit rules into runbook

* docs(trim): extract MCP integration into runbook

* docs(adr): add 0001-base-ui-over-radix

* docs(adr): add 0002-float-sort-order

* docs(adr): add 0003-one-branch-per-milestone

* docs(adr): add 0004-status-enum-mapping

* docs(adr): add 0005-iron-session-over-nextauth

* docs(adr): add 0006-demo-user-three-layer-policy

* docs(adr): add 0007-claude-question-channel-design

* docs(adr): add 0008-agent-instructions-in-claude-md + update README index

* docs(index): regenerate after ADR 0001-0008

* docs(glossary): add docs/glossary.md

* chore(docs): regenerate INDEX.md in pre-commit hook

* docs(readme): link INDEX + glossary + agent instructions

* feat(docs): add doc-link checker script

* chore(docs): wire docs:check-links and docs npm scripts

* ci(docs): block merge on broken doc links

* docs(links): fix broken cross-references after restructure

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 03:21:59 +02:00

76 KiB
Raw Permalink 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 specs/functional.md#solo-panel.

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

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

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

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

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