From 3a7861df440d0f8f86c5a065adedace702c10e0e Mon Sep 17 00:00:00 2001 From: Madhura68 Date: Sun, 26 Apr 2026 16:03:43 +0200 Subject: [PATCH] =?UTF-8?q?chore:=20mark=20completed=20tasks=20as=20done?= =?UTF-8?q?=20in=20backlog=20(ST-001=E2=80=93ST-510,=20ST-605,=20ST-608)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 4.6 --- docs/scrum4me-backlog.md | 173 ++++++++++++++++++++++++++------------- 1 file changed, 117 insertions(+), 56 deletions(-) diff --git a/docs/scrum4me-backlog.md b/docs/scrum4me-backlog.md index be9e556..8fcb8ca 100644 --- a/docs/scrum4me-backlog.md +++ b/docs/scrum4me-backlog.md @@ -18,7 +18,8 @@ De MVP is klaar wanneer Lars — de primaire persona — de volledige cyclus kan | 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-312 | +| 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 | @@ -29,35 +30,35 @@ De MVP is klaar wanneer Lars — de primaire persona — de volledige cyclus kan ### M0: Foundation -- [ ] **ST-001** Project scaffolding +- [x] **ST-001** Project scaffolding - `create-next-app` met TypeScript strict, Tailwind CSS, App Router; installeer shadcn/ui, Zustand, dnd-kit, iron-session, bcrypt, Zod; configureer path aliases (`@/`) - Done when: `npm run dev` start zonder fouten; `npm run lint` geeft geen errors; shadcn `Button` rendert op een testpagina -- [ ] **ST-002** Prisma v7 setup + `prisma.config.ts` +- [x] **ST-002** Prisma v7 setup + `prisma.config.ts` - Installeer Prisma v7 + `@prisma/adapter-pg`; schrijf `prisma.config.ts` met `DATABASE_URL` via Zod-gevalideerde env; schrijf `lib/prisma.ts` singleton - Done when: `npx prisma db push` slaagt; Prisma Client importeerbaar in een testbestand zonder fouten -- [ ] **ST-003** Database schema migratie (volledige initiële migratie) +- [x] **ST-003** Database schema migratie (volledige initiële migratie) - Schrijf het volledige `schema.prisma` op basis van het architectuurdocument: `User`, `UserRole`, `ApiToken`, `Product`, `Pbi`, `Story`, `StoryLog`, `Sprint`, `Task`, `Todo`; alle enums, indexes, cascade deletes - Done when: `npx prisma migrate dev --name init` slaagt; alle tabellen zichtbaar in DB-client; `npx prisma validate` geeft geen fouten -- [ ] **ST-004** Seed met testdata +- [x] **ST-004** Seed met testdata - Schrijf `prisma/seed.ts` op basis van het Product Backlog document (devplanner-product-backlog.md); seed één gebruiker, één product (Scrum4Me zelf), alle PBI's en stories als testdata; voeg demo-gebruiker toe - Done when: `npx prisma db seed` slaagt; DB bevat alle PBI's en stories uit het backlog-document; demo-gebruiker aanwezig -- [ ] **ST-005** Environment variabelen + `lib/env.ts` +- [x] **ST-005** Environment variabelen + `lib/env.ts` - Schrijf Zod-schema voor alle env vars (`DATABASE_URL`, `DIRECT_URL`, `SESSION_SECRET`, `NODE_ENV`); exporteer gevalideerd `env` object; schrijf `.env.example` met instructies - Done when: app gooit een begrijpelijke fout bij ontbrekende env var; `.env.example` volledig gedocumenteerd -- [ ] **ST-006** Authenticatie — registratie en inloggen +- [x] **ST-006** Authenticatie — registratie en inloggen - Schrijf `lib/auth.ts` (registreer met bcrypt hash, verifieer bij inloggen); schrijf `lib/session.ts` (iron-session config); implementeer `/register` en `/login` pagina's met Server Actions; sla `{ userId, isDemo }` op in sessiecookie - Done when: registreren → ingelogde sessie → redirect `/dashboard`; inloggen met verkeerde credentials geeft generieke foutmelding; sessie blijft actief na paginaverversing -- [ ] **ST-007** Route-beveiliging via `proxy.ts` +- [x] **ST-007** Route-beveiliging via `proxy.ts` - Schrijf `proxy.ts` die sessiecookie-aanwezigheid controleert; redirect naar `/login` bij alle `/dashboard`, `/products/*`, `/todos`, `/settings/*` routes zonder sessiecookie; authenticated users worden van `/login` en `/register` doorgestuurd naar `/dashboard`; volledige sessievalidatie gebeurt server-side in de app layout - Done when: directe navigatie naar `/dashboard` zonder sessie redirect naar `/login`; ingelogde gebruiker op `/login` redirect naar `/dashboard` -- [ ] **ST-008** Navigatieshell + dashboard-layout +- [x] **ST-008** Navigatieshell + dashboard-layout - Schrijf `app/(app)/layout.tsx` met navigatiebalk (logo, productenlink, todolink, instellingen, uitlogknop); implementeer uitlog Server Action; implementeer `/dashboard` als lege productenlijstpagina met "Maak je eerste product aan" lege staat; zet demo-badge zichtbaar als `isDemo === true` - Done when: volledige auth-flow (register → login → dashboard → logout → login) werkt end-to-end; demo-gebruiker ziet badge in navigatie @@ -65,43 +66,43 @@ De MVP is klaar wanneer Lars — de primaire persona — de volledige cyclus kan ### M1: Producten & Product Backlog -- [ ] **ST-101** Product aanmaken +- [x] **ST-101** Product aanmaken - `/products/new` pagina met formulier (naam, beschrijving, repo URL, definition of done); `createProduct` Server Action met Zod-validatie; uniekheidscontrole op naam per gebruiker; redirect naar `/products/[id]` na aanmaken - Done when: product aangemaakt en zichtbaar op dashboard; dubbele naam geeft inline validatiefout; lege naam blokkeert submit -- [ ] **ST-102** Productenlijst op dashboard +- [x] **ST-102** Productenlijst op dashboard - Haal actieve producten op via Prisma Server Component; toon naam, beschrijving (ingekort 80 tekens), repo-link; lege staat met CTA; klikken opent Product Backlog - Done when: twee producten zichtbaar na aanmaken; gearchiveerd product niet zichtbaar in standaardlijst -- [ ] **ST-103** Product bewerken en archiveren +- [x] **ST-103** Product bewerken en archiveren - Bewerkformulier (naam, beschrijving, repo URL, DoD) via Server Action; archiveerknop met bevestigingsdialoog; hersteloptie voor gearchiveerde producten; "toon gearchiveerd"-filter op dashboard - Done when: naam bijwerken persisteert; archiveren verbergt product; herstel maakt het weer zichtbaar -- [ ] **ST-104** Gesplitst scherm layout component (`SplitPane`) +- [x] **ST-104** Gesplitst scherm layout component (`SplitPane`) - Bouw herbruikbaar `` 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 +- [x] **ST-105** Navigatiebar-component per paneel - Bouw herbruikbaar `` 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 +- [x] **ST-106** PBI aanmaken en weergeven - Linkerpaneel van `/products/[id]`: haal PBI's op gegroepeerd op prioriteit en sort_order; "PBI aanmaken" knop opent inline formulier (titel, prioriteit); `createPbi` Server Action; nieuw PBI verschijnt onderaan de juiste prioriteitsgroep - Done when: PBI aangemaakt en zichtbaar in juiste prioriteitsgroep; lege staat toont prompt -- [ ] **ST-107** PBI prioriteitsgroepen met visuele scheiding +- [x] **ST-107** PBI prioriteitsgroepen met visuele scheiding - Render PBI's gegroepeerd per prioriteit (1–4) 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 +- [x] **ST-108** PBI bewerken en verwijderen - Inline bewerkingsmodus via dubbelklik of contextmenu (titel, omschrijving, prioriteit); `updatePbi` Server Action; verwijderen met bevestigingsdialoog inclusief waarschuwing cascade; `deletePbi` Server Action - Done when: titelbewering opgeslagen zonder paginaverversing; verwijderen cascade-verwijdert stories (verifieerbaar in DB) -- [ ] **ST-109** PBI selecteren → stories laden +- [x] **ST-109** PBI selecteren → stories laden - Klikken op PBI in linkerpaneel toont bijbehorende stories rechts via `useSelectionStore`; geselecteerd PBI visueel gemarkeerd; lege staat rechts als geen stories - Done when: klikken op PBI A toont stories van A rechts; klikken op PBI B schakelt direct over -- [ ] **ST-110** PBI filter +- [x] **ST-110** PBI filter - Filterknop in linkerpaneel navigatiebar; dropdown voor prioriteit (1–4, 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 @@ -109,43 +110,43 @@ De MVP is klaar wanneer Lars — de primaire persona — de volledige cyclus kan ### M2: Stories & Drag-and-drop -- [ ] **ST-201** `usePlannerStore` Zustand-store +- [x] **ST-201** `usePlannerStore` Zustand-store - Schrijf `stores/planner-store.ts` met `pbiOrder`, `storyOrder`, `taskOrder`; `init*`, `reorder*`, `rollback*` actions; TypeScript strict types - Done when: store importeerbaar in een Client Component; `initPbis` vult order; `reorderPbis` muteert order; `rollbackPbis` herstelt vorige staat -- [ ] **ST-202** `useSelectionStore` Zustand-store +- [x] **ST-202** `useSelectionStore` Zustand-store - Schrijf `stores/selection-store.ts` met `selectedPbiId`, `selectedStoryId`, setters en `clearSelection` - Done when: selectie in linkerpaneel via store zichtbaar in rechterpaneel zonder prop drilling -- [ ] **ST-203** dnd-kit setup + PBI drag-and-drop +- [x] **ST-203** dnd-kit setup + PBI drag-and-drop - Installeer dnd-kit; wrap linkerpaneel in `DndContext` + `SortableContext`; implementeer `useSortable` per PBI-rij; `onDragEnd`: bereken nieuwe `sort_order` via float-gemiddelde; optimistisch updaten via `usePlannerStore`; `reorderPbisAction` Server Action; rollback bij fout - Done when: PBI versleepbaar binnen prioriteitsgroep; volgorde opgeslagen na loslaten; UI rollback bij gesimuleerde server-fout -- [ ] **ST-204** PBI drag-and-drop over prioriteitsgrens +- [x] **ST-204** PBI drag-and-drop over prioriteitsgrens - Uitbreiding ST-203: slepen over een prioriteitsgrens wijzigt `priority` van het PBI; `sort_order` wordt onderaan de doelgroep geplaatst; `updatePbiPriority` Server Action - Done when: PBI naar prioriteit 2 slepen vanuit prioriteit 3 wijzigt zowel prioriteit als volgorde -- [ ] **ST-205** Story aanmaken en weergeven als blokken +- [x] **ST-205** Story aanmaken en weergeven als blokken - Rechterpaneel van Product Backlog: haal stories op voor geselecteerd PBI; render als blokken (~10% schermbreedte, horizontaal); elk blok toont titel (ingekort), prioriteitsbadge, statusbadge; "Story aanmaken" knop; `createStory` Server Action - Done when: drie stories zichtbaar als blokken; nieuw blok verschijnt in juiste prioriteitsgroep -- [ ] **ST-206** Story prioriteitsgroepen met visuele scheiding +- [x] **ST-206** Story prioriteitsgroepen met visuele scheiding - Groepeer story-blokken per prioriteit; gekleurde band of scheidingslijn per groep; blokken horizontaal gerangschikt per rij; nieuwe rij bij overloop - Done when: stories van vier prioriteiten correct gescheiden weergegeven -- [ ] **ST-207** Story drag-and-drop (horizontaal, binnen en tussen groepen) +- [x] **ST-207** Story drag-and-drop (horizontaal, binnen en tussen groepen) - dnd-kit horizontale `SortableContext` per prioriteitsgroep; `onDragEnd`: herrangschikking via float-gemiddelde in `storyOrder`; slepen naar andere groep wijzigt prioriteit; optimistisch via `usePlannerStore`; `reorderStoriesAction` Server Action; rollback bij fout - Done when: story versleepbaar binnen groep en naar andere groep; volgorde en prioriteit persistent na loslaten -- [ ] **ST-208** Story detail-modal / slide-over +- [x] **ST-208** Story detail-modal / slide-over - Klikken op storyblok opent slide-over of modal met titel, omschrijving, acceptatiecriteria, statusbadge, activiteitenlog (leeg bij nieuwe story); bewerkformulier voor titel/omschrijving/acceptatiecriteria; `updateStory` Server Action - Done when: klikken op blok opent detail; bewerken persisteert; sluiten keert terug naar backlog -- [ ] **ST-209** Story verwijderen +- [x] **ST-209** Story verwijderen - Verwijderknop in story-detail of contextmenu; bevestigingsdialoog met waarschuwing cascade (taken); `deleteStory` Server Action; blok verdwijnt optimistisch uit het rechterpaneel - Done when: story verwijderd incl. cascade-taken (verifieerbaar in DB); blok direct verdwenen uit UI -- [ ] **ST-210** Story filter in rechterpaneel +- [x] **ST-210** Story filter in rechterpaneel - Filterknop in rechterpaneel navigatiebar; filter op status (OPEN, IN_SPRINT, DONE) en prioriteit; realtime; actief filter als badge; wissbaar - Done when: filter op OPEN verbergt IN_SPRINT stories @@ -153,55 +154,55 @@ De MVP is klaar wanneer Lars — de primaire persona — de volledige cyclus kan ### M3: Sprint Backlog & Sprint Planning -- [ ] **ST-301** `useSprintStore` Zustand-store +- [x] **ST-301** `useSprintStore` Zustand-store - Schrijf `stores/sprint-store.ts`; `initSprint`, `addStoryToSprint`, `removeStoryFromSprint`, `reorderSprintStories`, `rollbackSprint` - Done when: store beheert sprint-story-volgorde onafhankelijk van planner-store -- [ ] **ST-302** Sprint aanmaken +- [x] **ST-302** Sprint aanmaken - "Sprint starten" knop op productpagina (zichtbaar als geen actieve Sprint); modal met Sprint Goal invoerveld; `createSprint` Server Action; max. 1 actieve Sprint per product afgedwongen in service-laag - Done when: Sprint aangemaakt met Goal; tweede sprint aanmaken terwijl eerste actief is geeft foutmelding -- [ ] **ST-303** Sprint Backlog scherm — layout +- [x] **ST-303** Sprint Backlog scherm — layout - `/products/[id]/sprint` pagina; `SplitPane` met Sprint Backlog links (stories in Sprint op volgorde) en rechts de Product Backlog stories gegroepeerd per PBI (inklapbaar); Sprint Goal zichtbaar bovenaan; lege staat links met instructie - Done when: pagina rendert correct; Sprint Goal zichtbaar; beide panelen tonen juiste data -- [ ] **ST-304** Story vanuit Product Backlog naar Sprint slepen +- [x] **ST-304** Story vanuit Product Backlog naar Sprint slepen - dnd-kit drag vanuit rechterpaneel naar linkerpaneel; `onDragEnd`: `addStoryToSprint` in store; story krijgt badge "In Sprint" in Product Backlog; `addStoryToSprintAction` Server Action (zet `sprint_id` + status `IN_SPRINT`); rollback bij fout - Done when: story gesleept naar Sprint verschijnt links en toont "In Sprint" badge rechts; persistent na herlaad -- [ ] **ST-305** Sprint Backlog story volgorde aanpassen +- [x] **ST-305** Sprint Backlog story volgorde aanpassen - dnd-kit verticale `SortableContext` in linkerpaneel; herrangschikking via float-gemiddelde in `useSprintStore`; `reorderSprintStoriesAction` Server Action - Done when: volgorde in Sprint Backlog persistent na loslaten en na paginaverversing -- [ ] **ST-306** Story uit Sprint verwijderen +- [x] **ST-306** Story uit Sprint verwijderen - Verwijderknop per story in Sprint Backlog; `removeStoryFromSprintAction` Server Action (wist `sprint_id`, zet status terug op `OPEN`); story verdwijnt links en badge verdwijnt rechts - Done when: verwijderen persistent; story beschikbaar in Product Backlog rechterpaneel -- [ ] **ST-307** Sprint Planning scherm — layout +- [x] **ST-307** Sprint Planning scherm — layout - `/products/[id]/sprint/planning` pagina; `SplitPane` met Sprint Backlog stories links (op volgorde) en taken van geselecteerde story rechts; Sprint Goal zichtbaar; lege staat rechts als geen story geselecteerd - Done when: pagina rendert; story selecteren links toont taken rechts -- [ ] **ST-308** Taak aanmaken +- [x] **ST-308** Taak aanmaken - "Taak aanmaken" knop in rechterpaneel navigatiebar; inline formulier (titel, omschrijving, prioriteit); `createTask` Server Action; voortgangsindicator per story (bijv. "0/0 Done") - Done when: taak aangemaakt en zichtbaar in takenlijst; voortgangsindicator toont "0/1 Done" -- [ ] **ST-309** Taak drag-and-drop (verticaal) +- [x] **ST-309** Taak drag-and-drop (verticaal) - dnd-kit verticale `SortableContext` in rechterpaneel; herrangschikking via float-gemiddelde in `usePlannerStore.taskOrder`; `reorderTasksAction` Server Action - Done when: taken versleepbaar; volgorde persistent na loslaten -- [ ] **ST-310** Taakstatus bijhouden +- [x] **ST-310** Taakstatus bijhouden - Status-toggle per taak (TO_DO → IN_PROGRESS → DONE) via klikbare badge of dropdown; `updateTaskStatus` Server Action; voortgangsindicator op story updatet optimistisch - Done when: taak op DONE zetten verhoogt teller in voortgangsindicator; persistent na herlaad -- [ ] **ST-311** Taak bewerken en verwijderen +- [x] **ST-311** Taak bewerken en verwijderen - Inline bewerken van titel, omschrijving en prioriteit; `updateTask` Server Action; verwijderen met bevestiging; `deleteTask` Server Action - Done when: titelwijziging persisteert; verwijderde taak verdwijnt uit lijst -- [ ] **ST-312** Sprint afronden +- [x] **ST-312** Sprint afronden - "Sprint afronden" knop op Sprint-pagina; dialoog toont per story de status en vraagt: "Markeer als Done of terug naar Backlog?"; `completeSprint` Server Action zet Sprint op COMPLETED, verwerkt keuzes per story - Done when: Sprint afgerond; stories correct verplaatst naar DONE of OPEN; nieuwe Sprint aanmaakbaar -- [ ] **ST-313** Sprint Board — drie-panelen layout (vervangt ST-303 + ST-307) +- [x] **ST-313** Sprint Board — drie-panelen layout (vervangt ST-303 + ST-307) - **Doel:** `/products/[id]/sprint` wordt één scherm met drie panelen van links naar rechts: Product Backlog · Sprint Backlog · Taken. De losse `/sprint/planning` route wordt verwijderd (redirect → `/sprint`). - **Panelen:** - *Links — Product Backlog:* PBIs met stories gegroepeerd en inklapbaar; stories die al in sprint zijn grijs/disabled; klikken of slepen voegt story toe aan Sprint Backlog (midden) @@ -216,45 +217,105 @@ De MVP is klaar wanneer Lars — de primaire persona — de volledige cyclus kan --- +### 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** `` herbruikbare component + - Wrapper rond shadcn `Avatar`; props: `userId`, `username`, `size` ('xs' | 'sm' | 'md' | 'lg'), `className`; `` 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 `` + 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 `` als geen cookie of cookie ongeldig + - **`/products/[id]/solo` page.tsx:** Server Component; haalt active sprint op (404 → empty state ``); haalt taken op via `Task.findMany` met `where: { sprint_id, story: { assignee_id: session.userId } }` + count ongeclaimde stories parallel; geeft data door aan ``; zet `lastProductId` cookie bij elk bezoek + - **Empty state:** `` met titel, uitleg, link naar productpagina + - **``:** 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) + - **`` 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 + - **``:** drop target per status (`TO_DO` / `IN_PROGRESS` / `DONE`); header met statuskleur via MD3 tokens (`bg-status-todo/15` etc.); count en lege staat + - **``:** 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` + - **``** shadcn `Dialog`: header met taaktitel + statusbadge (MD3 tokens); sectie *Beschrijving* (read-only, volg bestaand patroon); sectie *Implementatieplan* met `