Scrum4Me/docs/scrum4me-functional-spec.md
Janpeter Visser 9587ff4ff3
M11: Claude vraagt, gebruiker antwoordt (ST-1101..ST-1108) (#13)
* docs(ST-1101..1108): add M11 — Claude question-channel milestone to backlog

Plant acht stories ST-1101..ST-1108 voor het persistente vraag-antwoord-kanaal
tussen Claude (MCP) en de actieve gebruiker. Eerste concrete uitwerking van
de AI-driven dev-flow-richting (strategisch besluit "B" uit overleg na M10).

Beveiligingsuitgangspunt: atomic answer via updateMany WHERE status='open',
demo-blok op write-tools, access-check via productAccessFilter in DB-query én
SSE-filter, cron-endpoint via Bearer-secret, geen vraag/antwoord-tekst in logs.

Hergebruikt bestaande scrum4me_changes-channel (uitgebreid met entity:'question')
en het LISTEN/NOTIFY+ReadableStream-pattern uit M8/M10. Nieuw: user-scoped SSE
op /api/realtime/notifications zodat de bell globaal werkt over producten heen.

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

* chore(M11): swap demo-active sprint from M10 to M11

M10 is gemerged en afgesloten — M11 wordt de nieuwe demo-actieve milestone
zodat get_claude_context (via MCP) ST-1101 als next-story teruggeeft.

Drie maps in parse-backlog.ts uitgebreid: M11 priority=4, goal omschrijving,
sprint_status='ACTIVE'. M10 → COMPLETED.

Vereist npx prisma db seed na deze commit zodat de live DB de nieuwe
sprint-state weerspiegelt.

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

* docs(ST-1108): add F-11b — Claude question-channel to functional spec

Voegt feature-omschrijving toe naast bestaande F-11 (Claude Code REST API).
Beschrijft het verloop (Claude → MCP-tool → DB → trigger → SSE → user → answer
→ trigger → Claude polls), acceptatiecriteria (8 items), randgevallen (offline-
Claude, assignee-change, expiry, abuse) en datamodel (claude_questions tabel).

Persona Lars als primair, Dina secundair voor klant-werk.

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

* chore(M11): drop parser ACTIVE-flip; sprint goes via UI from now on

Bij M9/M10 hebben we de seed-flip (MILESTONE_SPRINT_STATUS pivot) gebruikt om
nieuwe stories als IN_SPRINT in een verse sprint te krijgen. Dat werkt maar
is fragiel:
- npm run seed wist user-data
- de "sprint" die de seed maakt is geen echte planning-actie
- bij multi-product scenario's breekt het model

Vanaf M11 gebruiken we de bestaande Sprint-creatie-UI van Scrum4Me. Stories
voor M11 worden via scripts/insert-milestone.ts (idempotent insert, geen
seed-reset) aan de DB toegevoegd; de gebruiker maakt zelf een Sprint aan in
/products/[scrum4me]/sprint en sleept ST-1101..1108 ernaartoe.

Parser-map M11 dus terug naar COMPLETED zodat een eventuele re-seed niet meer
een fake sprint aanmaakt voor M11-stories.

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

* feat(ST-1101): add ClaudeQuestion model + notify_question_change trigger

Schema (prisma/schema.prisma):
- Nieuw model ClaudeQuestion: id (cuid), story_id (FK Cascade), task_id?
  (FK SetNull), product_id (FK Cascade — gedenormaliseerd uit story.product_id
  voor SSE-filter zonder join), asked_by (FK Restrict — Claude-token-houder),
  question (Text), options (Json? — string[] voor multi-choice), status
  ('open'|'answered'|'cancelled'|'expired'), answer (Text?), answered_by
  (FK SetNull), answered_at?, created_at, expires_at
- Indexes: (story_id, status), (product_id, status), (status, expires_at)
- Back-relations: User.asked_questions (ClaudeQuestionAsker),
  User.answered_questions (ClaudeQuestionAnswerer), Story.claude_questions,
  Task.claude_questions, Product.claude_questions

Migratie (20260427224849_add_claude_questions):
- Prisma-gegenereerde DDL voor claude_questions + indexes + 5 FK's
- Toegevoegde notify_question_change() functie + claude_questions_notify trigger
  op AFTER INSERT/UPDATE
- Emit op BESTAANDE scrum4me_changes-channel met entity:'question' (i.t.t. M10
  dat eigen scrum4me_pairing-channel kreeg) — solo-route in ST-1104 moet
  entity='question' wegfilteren om regressie op solo-board te voorkomen
- Trigger leest story.assignee_id voor "wacht op jou"-emphase in payload
- DELETE niet ondersteund — questions gaan naar answered/cancelled/expired

Verification: Node pg-client roundtrip via DATABASE_URL toonde correcte payloads
bij INSERT (op=I, status=open) en UPDATE (op=U, status=answered) met alle FK-IDs
en assignee_id correct uit story-join.

Volgende stap M11: ST-1102 — vier MCP-tools in scrum4me-mcp-repo
(ask_user_question, get_question_answer, list_open_questions, cancel_question).

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

* feat(ST-1103): add answerQuestion server action

actions/questions.ts:
- answerQuestion(questionId, answer) — auth + Zod + demo-blok + access-check
  via productAccessFilter (anyone met product-membership mag antwoorden,
  consistent met Scrum self-organizing — niet alleen story-assignee)
- Atomic prisma.claudeQuestion.updateMany WHERE id + status='open' +
  expires_at>now → status='answered'; concurrent dubbele submit: één wint
  (count=1), rest count=0 met disambiguatie via second findFirst
- revalidatePath('/', 'layout') refresh't NavBar bell-count voor SSR-paths;
  realtime updates voor andere clients gaan via SSE in ST-1104/1105
- Begrijpelijke NL-foutmeldingen voor elk faalpad

Tests __tests__/actions/questions.test.ts (6 cases):
- happy: status update + revalidatePath called
- demo-block: error + geen DB-call + geen revalidate
- geen access: error + geen update
- al-answered: race-error 'is al answered'
- expired: race-error 'is verlopen'
- lege answer: Zod-validatie

Quality gates: lint 0 errors, tsc clean, vitest 145/145 (17 files).

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

* feat(ST-1104): add user-scoped /api/realtime/notifications + filter solo-route

Twee delen:

1. Solo-route filter (1-regel-fix in app/api/realtime/solo/route.ts):
   - NotifyPayload uitgebreid met entity:'question'
   - shouldEmit returnt direct false bij entity='question'
   Voorkomt dat solo-clients M11 question-events ontvangen (geen lekkage naar
   het Solo-bord; geen onnodig netwerk-verkeer; loose coupling tussen features).

2. Nieuwe SSE-route app/api/realtime/notifications/route.ts:
   - User-scoped (geen ?product_id=); query alle accessible product-IDs één keer
     bij connect via productAccessFilter
   - LISTEN scrum4me_changes; filter entity='question' && product_id ∈ accessible
   - Initial-state-event NA LISTEN actief (race-fix conform M10 ST-1004):
     query open vragen voor deze user's accessible products, stuur als event:state
     met summary (id, story_code/title, assignee_id, question, options, expires_at)
   - Hergebruikt het pg.Client + ReadableStream + heartbeat 25s + hard-close 240s +
     abort-cleanup-pattern uit solo-route

Tests __tests__/api/notifications-stream.test.ts:
- 401 zonder iron-session cookie (en geen DB-call)
- Solo-route filter wordt visueel/E2E gedekt in ST-1108-acceptatie

Quality gates: lint 0 errors, tsc clean, vitest 146/146 (18 files).

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

* feat(ST-1105): add NavBar bell + sheet + answer-modal + Zustand store + SSE hook

UI-volledig voor de Claude vraag-antwoord-flow (M11). Bel-icon links van avatar
in NavBar; klik opent slide-over rechts met openstaande vragen; klik op een vraag
opent een modal voor antwoord. Story-assignee = current user krijgt visuele
"voor jou"-emphase met primary-container accent en error-color badge-ring.

Bestanden:
- stores/notifications-store.ts — Zustand store met init/upsert/remove +
  openCount/forYouCount selectors (vereenvoudigd vs solo-store: geen pendingOps,
  geen optimistic-echo-onderdrukking)
- lib/realtime/use-notifications-realtime.ts — EventSource hook met state-
  event en message-event handling, exponential-backoff reconnect, Page
  Visibility pause-resume
- components/notifications/notifications-bridge.tsx — Server Component die
  initial open-questions fetcht via productAccessFilter
- components/notifications/notifications-realtime-mount.tsx — tiny client
  island dat de store hydrateert + de hook activeert
- components/notifications/notifications-sheet.tsx — shadcn Sheet met item-
  lijst, "voor jou"-accent voor assignee-vragen, lege staat
- components/notifications/answer-modal.tsx — Dialog met options-radio of
  free-text Textarea (max 4000), char-counter, demo-blok via Tooltip; bij
  succes optimistisch remove + sheet blijft open zodat meerdere vragen
  achter elkaar te beantwoorden zijn
- components/shared/notifications-bell.tsx — Bell-icon met badge (count >9 → "9+"),
  ring-accent als forYouCount > 0, ARIA-label voor screenreaders

Wiring:
- components/shared/nav-bar.tsx — <NotificationsBell /> rechts naast <UserMenu>
- app/(app)/layout.tsx — <NotificationsBridge /> naast <SoloRealtimeBridge />,
  user.id (server-side) als prop

base-ui-aanpassingen: SheetTrigger/TooltipTrigger gebruiken render-prop ipv
asChild (geen Radix).

Quality gates: lint 0 errors, tsc clean, vitest 146/146, npm run build groen.

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

* test(ST-1106): add cross-product access-isolation test for notifications SSE

Demo-policy + assignee-emphase zaten al in eerdere stories:
- answerQuestion demo-blok in actions/questions.test.ts (ST-1103)
- AnswerModal demo-tooltip in components/notifications/answer-modal.tsx (ST-1105)
- requireWriteAccess in MCP write-tools (ST-1102)

Deze story voegt expliciet een access-isolation-test toe op de notifications-
SSE-route: productAccessFilter wordt met de echte userId aangeroepen, en
prisma.product.findMany filter't op archived=false + user_id-scope. Dat
garandeert dat een gebruiker geen question-events ontvangt voor producten waar
hij geen membership op heeft.

Story-assignee-emphase blijft visueel-only (NotificationsBell ring-accent +
Sheet primary-container) — toegang werkt product-membership-breed zodat een
team-lid kan invallen als de assignee niet beschikbaar is.

Quality gates: lint 0 errors, tsc clean, vitest 147/147 (was 146).

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

* feat(ST-1107): add Vercel cron expire-questions (+ M10 pairing cleanup)

POST /api/cron/expire-questions:
- Auth via Authorization: Bearer ${CRON_SECRET} (Vercel injecteert dit
  automatisch wanneer de env-var op de project-omgeving staat); 401 als secret
  niet matcht of niet is gezet (faal-veilig — geen open endpoint in dev)
- updateMany op claude_questions WHERE status='open' AND expires_at<now →
  'expired'
- Bonus: zelfde route ruimt M10 login_pairings op (status='pending' AND
  expires_at<now → 'cancelled'). Eén cron-job is goedkoper qua Vercel-budget
  en houdt cleanup-strategie centraal — opvolg-actie uit M10 dat geparkeerd was.

Config:
- vercel.json: crons-entry { path: '/api/cron/expire-questions', schedule: '0 */6 * * *' } (4x/dag)
- lib/env.ts: CRON_SECRET als optional in Zod-schema
- .env.example: documentatie + openssl rand-tip

Tests __tests__/api/cron-expire-questions.test.ts (4 cases):
- 401 zonder Authorization-header
- 401 met verkeerde secret
- 401 als CRON_SECRET niet is gezet (faal-veilig)
- 200 met juiste secret: response { expired_questions, expired_pairings, ran_at }
  + beide updateMany WHERE/data correct

Quality gates: lint 0 errors, tsc clean, vitest 151/151.

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

* docs(ST-1108): document M11 question-channel — API + architecture + pattern

docs/API.md — twee nieuwe secties:
- 'Notifications' met /api/realtime/notifications SSE-endpoint (event-shapes,
  filter-rules, voorbeeld)
- 'Cron — Expire questions' met /api/cron/expire-questions (Bearer-auth,
  schedule, response-shape, manual curl)

docs/scrum4me-architecture.md — nieuw hoofdstuk 'Vraag-antwoord-kanaal Claude
↔ user' tussen QR-pairing-flow en Projectstructuur:
- Mermaid sequence-diagram (Claude → DB → trigger → SSE → user → answer →
  trigger → Claude polls)
- Threat-model-tabel (race, demo-misbruik, cross-product leak, cron-misbruik,
  growth, log-leakage)
- Subsectie 'Waarom hergebruik scrum4me_changes-kanaal' met trade-off vs M10's
  eigen-kanaal-aanpak

docs/patterns/claude-question-channel.md — herbruikbaar pattern 'Bidirectionele
async-comms tussen MCP-agent en interactieve user' met de vier eindpunten,
vier security-uitgangspunten, channel-strategie-tabel, TTL-richtlijn, en
sjabloon-bestanden per laag (DB / server / client / MCP-tools).

CLAUDE.md — extra rij in Implementatiepatronen-tabel die naar het nieuwe
pattern-doc verwijst.

Acceptatie 6 scenario's:
1. Sync happy path (MCP wait_seconds + UI submit) — handmatig getest tijdens
   ST-1105 acceptance-loop met de q-test injection
2. Async happy path — gedekt door get_question_answer-tool in ST-1102 +
   list_open_questions
3. Demo-block — actions/questions.test.ts (case 2: demo-user) + AnswerModal
   tooltip (visueel)
4. Access-isolation — notifications-stream.test.ts (case 'access-isolation')
5. Expiry — cron-expire-questions.test.ts (case '200 met juiste secret')
6. Race — actions/questions.test.ts (case 'al-answered' via atomic updateMany)

Quality gates: lint 0 errors, tsc clean, vitest 151/151 (19 files), npm run
build groen.

M11 is hiermee feature-compleet. feat/M11-claude-questions heeft 12 commits
lokaal, klaar voor user-acceptatie en PR.

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

* fix(ST-1107): cron schedule daily — Vercel Hobby allows only 1 run/day

Vercel deploy faalde met:
> Hobby accounts are limited to daily cron jobs.
> This cron expression (0 */6 * * *) would run more than once per day.

Schedule van 4×/dag (0 */6 * * *) naar 1×/dag (0 4 * * * — 04:00 UTC, rustig
tijdstip). Functioneel acceptabel: ClaudeQuestion TTL is 24u, dus daily
cleanup pakt alles dat in de afgelopen 24u verlopen is. Login-pairings TTL
is 2 min — die zijn al onbruikbaar zodra ze expiren, cron is alleen voor
status-housekeeping.

Schedule-referenties consistent bijgewerkt in docs (API.md, architecture,
backlog M11-sectie, plan-doc, pattern-doc) + comment in route.ts. Vermelding
overal dat dit een Hobby-plan-beperking is en Pro fijnmaziger ondersteunt.

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

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 11:38:23 +02:00

36 KiB
Raw Blame History

Scrum4Me — Functionele Specificatie

Versie: 0.2 — april 2026 Volgt op: Brainstorm v0.3, Personas v0.1


MVP-scopeverklaring

v1 is een desktop-first fullstack webapplicatie waarmee een solo developer of klein Scrum Team meerdere softwareprojecten hiërarchisch kan plannen (product → PBI → story → taak), Sprints kan beheren via gesplitste schermen met drag-and-drop, en Claude Code kan integreren voor geautomatiseerde implementatieflows waarbij elk resultaat wordt vastgelegd in de story. Een Product Owner kan Developers (via gebruikersnaam) aan een product backlog koppelen; zij krijgen dan schrijfrechten op stories, taken en sprints van dat product. De app is deployable op Vercel + Neon én volledig lokaal draaibaar.

Expliciet buiten scope voor v1

  • Uitnodigingsflow voor teams — Developers toevoegen via gebruikersnaam is beschikbaar; e-mailuitnodiging of link-gebaseerde onboarding komt in v2
  • E-mailverificatie bij registratie — gebruikersnaam/wachtwoord volstaat voor v1
  • Daily Scrum, Sprint Review en Sprint Retrospective schermen — v2
  • Automatische statusupdate van stories na commit — handmatige update via UI in v1
  • Tijdregistratie, urenverantwoording en burndown-charts — buiten positionering
  • Integratie met externe tools (GitHub Issues, Linear, Jira) — v2
  • Notificaties en reminders — v2
  • Native mobiele app — web-first; een toekomstige mobiele variant richt zich uitsluitend op taken afvinken
  • Responsive layout voor schermen smaller dan 1024px — desktop-first in v1

Feature-specificaties

F-01: Authenticatie

Prioriteit: v1 — Kritiek Persona: Lars, Dina, Remi

Omschrijving: Gebruikers kunnen een account aanmaken en inloggen met gebruikersnaam en wachtwoord. Er is een ingebouwde demo-gebruiker met alleen leesrechten. Geen e-mailverificatie vereist in v1.

Acceptatiecriteria:

  • Registratie vereist gebruikersnaam (uniek, min. 3 tekens) en wachtwoord (min. 8 tekens)
  • Dubbele gebruikersnaam geeft duidelijke foutmelding bij registratie
  • Inloggen met incorrecte combinatie geeft generieke foutmelding (geen onderscheid naam/wachtwoord)
  • Na inloggen wordt de gebruiker doorgestuurd naar het dashboard
  • Sessie blijft actief totdat de gebruiker uitlogt
  • Demo-gebruiker kan inloggen met vaste credentials (zichtbaar op de loginpagina)
  • Demo-gebruiker kan niets aanmaken, aanpassen of verwijderen
  • Alle schrijfknoppen zijn zichtbaar maar uitgeschakeld voor de demo-gebruiker, met tooltip "Niet beschikbaar in demo-modus"
  • Uitlogknop is altijd zichtbaar in de navigatie

Randgevallen:

  • Gebruiker probeert beschermde route te bezoeken zonder sessie → redirect naar /login
  • Demo-gebruiker probeert via de API te schrijven → 403 Forbidden

Data:

  • Opgeslagen: users (id, username, password_hash, role[], is_demo, created_at)
  • Sessie: JWT of server-side sessie met user_id en is_demo vlag

F-01b: Inloggen via mobiel (QR-pairing)

Prioriteit: v1 — Belangrijk Persona: Lars (publieke demo-laptops), Dina (klantapparatuur)

Omschrijving: Een gebruiker kan op een (publieke of gedeelde) desktop inloggen zonder zijn wachtwoord te typen, door op zijn al-ingelogde mobiele apparaat een QR-code te scannen die de desktop toont. Na een expliciete tap op "Bevestig" op de mobiel raakt de desktop binnen 12 seconden ingelogd. De flow is bedoeld om typen op vreemde toetsenborden, shoulder-surfing en autofill-history te vermijden.

Verloop:

  1. Op het login-scherm klikt de desktop-gebruiker op "Inloggen via mobiel". De server maakt een eenmalige pairing-rij aan (status pending, vervalt na 2 minuten) met twee gescheiden geheimen: mobileSecret (voor de mobiel) en desktopToken (HttpOnly cookie voor de desktop).
  2. De desktop toont een QR-code. De code bevat een URL met mobileSecret in het URL-fragment (#s=…) — dit fragment wordt door browsers nooit naar servers gestuurd, dus belandt niet in access logs of analytics.
  3. De gebruiker scant met zijn telefoon. De OS-camera opent de URL in de mobiele Scrum4Me-tab. Een Client Component leest het fragment en POST't mobileSecret in de body naar de approve-endpoint.
  4. De mobiele bevestigingspagina toont "Inloggen op {browser-omschrijving} ({IP}) als {jouw-gebruikersnaam}?" met een Bevestig- en Annuleer-knop. Na een tap op Bevestig wordt de pairing approved (status approved, vervaltijd verlengd naar 5 minuten); de desktop ontvangt dit binnen 12 seconden via een SSE-stream die geauthenticeerd is met het HttpOnly desktop-cookie.
  5. De desktop claimt de sessie atomisch (eenmalig consumeerbaar), krijgt zijn iron-session cookie en wordt naar /dashboard doorgestuurd.

Acceptatiecriteria:

  • Knop "Inloggen via mobiel" zichtbaar op /login naast het wachtwoord-formulier
  • QR-code vernieuwt automatisch na 2 minuten via een "Vernieuwen"-knop
  • Mobiele bevestigingspagina toont browser/UA en best-effort IP van de desktop
  • Demo-gebruiker kan niet als approver fungeren — duidelijke foutmelding "Niet beschikbaar in demo-modus"
  • Paired-sessie heeft een eigen TTL van 8 uur (korter dan reguliere wachtwoord-login) en is herkenbaar via een paired-vlag in het session-payload
  • Een tweede claim met dezelfde pairing geeft 410 Gone (one-time use)
  • mobileSecret komt nergens in een GET-URL voor (alleen in URL-fragment of POST-body); desktopToken staat alleen in een HttpOnly cookie met Path=/api/auth/pair, Max-Age=120, SameSite=Lax
  • In nginx/Vercel access logs is geen secret-materiaal terug te vinden (acceptatietest)

Randgevallen:

  • QR vervalt voordat mobiel scant → mobiele pagina toont "Pairing verlopen, vraag een nieuwe QR-code op"; desktop toont "Vernieuwen"-knop
  • Pairing approved maar desktop claimt niet binnen 5 minuten → atomic update faalt; pairing-rij wordt automatisch genegeerd; gebruiker start opnieuw
  • Gebruiker scant een phishing-QR vanaf een willekeurige website → mobiele bevestiging toont onbekende UA/IP; expliciete bevestiging vereist; de gebruiker kan annuleren
  • Gebruiker is op de mobiel niet ingelogd → middleware-guard van /m/pair redirectt naar /login met return-URL
  • Gebruiker logt zichzelf uit op de mobiel terwijl de pairing nog pending is → approve faalt op auth-check

Data:

  • Nieuw: login_pairings (id, secret_hash, desktop_token_hash, status, user_id?, desktop_ua?, desktop_ip?, created_at, expires_at, approved_at?, consumed_at?)
  • Postgres-trigger op login_pairings publiceert via pg_notify('scrum4me_pairing', …)
  • Sessie-payload: nieuwe optionele paired: boolean en pairedExpiresAt: number

F-02: Roltoewijzing

Prioriteit: v1 — Fundament voor v2 Persona: Remi (v2), Lars en Dina (impliciet)

Omschrijving: Een gebruiker kan bij registratie of in instellingen één of meerdere Scrum-rollen aannemen: Product Owner, Scrum Master, Developer. De rol Developer is relevant voor teambeheer: alleen gebruikers met de rol Developer kunnen aan een product backlog worden gekoppeld door de eigenaar.

Acceptatiecriteria:

  • Gebruiker kan bij registratie of achteraf in instellingen rollen selecteren
  • Minimaal één rol is verplicht
  • Alle drie de rollen tegelijk zijn toegestaan
  • Geselecteerde rollen zijn zichtbaar in de profielbalk
  • Alleen gebruikers met de rol Developer kunnen als teamlid aan een product backlog worden gekoppeld
  • Demo-gebruiker heeft een vaste rol (Developer) die niet gewijzigd kan worden

Data:

  • Opgeslagen: user_roles[] als array op het gebruikersobject (enum: PRODUCT_OWNER, SCRUM_MASTER, DEVELOPER)

F-02b: Gebruikersprofiel

Prioriteit: v1 Persona: Lars, Dina, Remi

Omschrijving: Een gebruiker kan een profielfoto, korte omschrijving (bio) en uitgebreide beschrijving toevoegen via de instellingenpagina. De profielfoto wordt server-side verwerkt met Sharp en opgeslagen als WebP bytea in PostgreSQL. Het profiel is niet publiek zichtbaar in v1 maar vormt de basis voor v2-teamweergaven.

Acceptatiecriteria:

  • Gebruiker kan een foto uploaden (JPEG, PNG of WebP, max 12 MB)
  • Validatie op MIME-type en bestandsgrootte vóór verwerking — ongeldige bestanden worden geweigerd met een foutmelding
  • Server converteert afbeelding naar WebP, maximaal 700×700 px (fit inside, niet uitrekken)
  • Profielfoto wordt weergegeven in de instellingenpagina na upload
  • Gebruiker kan een korte omschrijving invoeren (max 160 tekens)
  • Gebruiker kan een uitgebreide beschrijving invoeren (max 2000 tekens)
  • Opslaan van bio-velden is los van de foto-upload (apart formulier)
  • Demo-gebruiker ziet de profiel-sectie niet (uitgeschakeld)

Implementatie:

  • POST /api/profile/avatar — upload + Sharp-verwerking + opslag als bytea
  • GET /api/profile/avatar?v=<timestamp> — serveert avatar met Cache-Control: private, max-age=3600; timestamp in de URL zorgt voor cache-invalidatie na upload
  • updateProfileAction Server Action — slaat bio en bio_detail op

Data:

  • users.bio — VarChar(160), nullable
  • users.bio_detail — VarChar(2000), nullable
  • users.avatar_data — Bytes (bytea), nullable; altijd WebP na verwerking
  • users.updated_at — wordt bijgewerkt bij elke wijziging; gebruikt als versienummer in de avatar-URL

F-02c: Product Backlog-overzicht in instellingen

Prioriteit: v1 Persona: Lars, Dina, Remi

Omschrijving: De instellingenpagina toont een gecombineerde lijst van alle product backlogs waarbij de gebruiker betrokken is: producten waarvan hij/zij eigenaar is, en producten waarbij hij/zij als Developer is toegevoegd. Vanuit deze lijst kan een team-lidmaatschap worden beëindigd.

Acceptatiecriteria:

  • Alle actieve (niet-gearchiveerde) producten van de ingelogde gebruiker zijn zichtbaar met badge "Eigenaar"
  • Alle producten waarbij de gebruiker als Developer is toegevoegd zijn zichtbaar met badge "Developer" en de naam van de eigenaar
  • Klikken op een productnaam navigeert naar de product backlog
  • Bij een Developer-lidmaatschap is een "Verlaten"-knop zichtbaar met bevestigingsstap
  • Na verlaten verdwijnt het product uit de lijst
  • Eigenaar-producten hebben geen verlaat-actie
  • Lege staat toont een link naar "Product aanmaken"
  • Demo-gebruiker ziet de lijst maar heeft geen verlaat-knop

F-03: Productbeheer

Prioriteit: v1 — Kritiek Persona: Lars (meerdere eigen projecten), Dina (per klant), Remi (per team-product)

Omschrijving: Gebruikers kunnen producten aanmaken, bewerken en archiveren. Een product is het hoogste niveau in de hiërarchie en bevat een naam, beschrijving, git-repo URL en de Definition of Done. Alle andere entiteiten (PBI's, stories, taken) horen bij een product.

Acceptatiecriteria:

  • Product aanmaken vereist een naam (uniek per gebruiker, verplicht)
  • Beschrijving is optioneel (vrije tekst, max. 1000 tekens)
  • Git-repo URL is optioneel; wordt gevalideerd als geldige URL bij invullen
  • Definition of Done is een vaste tekst, instelbaar per product (verplicht bij aanmaken, max. 500 tekens)
  • Product verschijnt direct in de productenlijst na aanmaken
  • Naam en alle andere velden zijn bewerkbaar na aanmaken
  • Archiveren is omkeerbaar; gearchiveerde producten zijn standaard verborgen
  • Productenlijst toont: naam, beschrijving (ingekort tot 80 tekens), git-repo link (indien aanwezig)
  • Klikken op een product opent de Product Backlog van dat product
  • Lege staat toont een duidelijke prompt om een eerste product aan te maken

Randgevallen:

  • Gebruiker probeert naam leeg te maken bij bewerken → validatiefout, opslaan geblokkeerd
  • Git-repo URL zonder https:// → validatiefout met suggestie

Data:

  • Opgeslagen: products (id, user_id, name, description, repo_url, definition_of_done, archived, created_at, updated_at)

F-04: Product Backlog — gesplitst scherm

Prioriteit: v1 — Kritiek Persona: Lars, Dina, Remi

Omschrijving: De Product Backlog wordt weergegeven als een gesplitst scherm: links de PBI's gerangschikt op prioriteit en volgorde, rechts de stories van het geselecteerde PBI. De splitter is horizontaal versleepbaar. Elk paneel heeft een eigen navigatiebar met acties (aanmaken, filteren, verwijderen).

Acceptatiecriteria:

  • Standaard splitverhouding is 40/60 (PBI's / stories)
  • Splitter is versleepbaar; positie wordt lokaal opgeslagen (localStorage)
  • Selecteren van een PBI links toont de bijbehorende stories rechts
  • Geselecteerd PBI is visueel gemarkeerd (achtergrondkleur of rand)
  • Linkerpaneel navigatiebar bevat: [+ PBI aanmaken], [filter], [verwijderen]
  • Rechterpaneel navigatiebar bevat: [+ Story aanmaken], [filter], [verwijderen]
  • Lege staat links: prompt om eerste PBI aan te maken
  • Lege staat rechts (geen PBI geselecteerd): instructie om een PBI te selecteren
  • Lege staat rechts (PBI geselecteerd, geen stories): prompt om eerste story aan te maken

Randgevallen:

  • Scherm smaller dan 768px → gesplitst scherm schakelt over naar tabbladen (PBI's / Stories)

F-05: PBI-beheer

Prioriteit: v1 — Kritiek Persona: Lars, Dina, Remi

Omschrijving: Product Backlog Items kunnen worden aangemaakt, bewerkt, geprioriteerd (14) en gerangschikt via drag-and-drop binnen dezelfde prioriteitsgroep. PBI's worden gegroepeerd per prioriteit met een visuele scheiding.

Acceptatiecriteria:

  • PBI aanmaken vereist een titel (verplicht, max. 200 tekens)
  • Omschrijving is optioneel (vrije tekst, max. 2000 tekens)
  • Prioriteit is verplicht (1 = Kritiek, 2 = Hoog, 3 = Middel, 4 = Laag)
  • PBI's worden gegroepeerd per prioriteit; elke groep heeft een visueel label en scheidingslijn
  • Binnen een prioriteitsgroep is de volgorde instelbaar via drag-and-drop (dnd-kit)
  • Slepen over een prioriteitsgrens wijzigt de prioriteit van het PBI
  • Volgorde en prioriteit worden direct opgeslagen na loslaten
  • PBI bewerken (titel, omschrijving, prioriteit) via inline bewerkingsmodus
  • PBI verwijderen vereist bevestiging; cascade-verwijdering van gekoppelde stories en taken
  • Bevestigingsdialoog vermeldt expliciet dat stories en taken ook verwijderd worden
  • Filter op prioriteit werkt realtime; actief filter is visueel zichtbaar en eenvoudig te wissen
  • Drag-and-drop placeholder toont de doelpositie tijdens het slepen

Randgevallen:

  • Prioriteitsgroep is leeg na verplaatsing → groep verdwijnt uit de weergave
  • PBI met stories verwijderen → bevestigingsdialoog toont het aantal gekoppelde stories

Data:

  • Opgeslagen: pbis (id, product_id, title, description, priority (14), sort_order, created_at, updated_at)

F-06: Story-beheer

Prioriteit: v1 — Kritiek Persona: Lars, Dina, Remi

Omschrijving: Stories worden weergegeven als compacte blokken (~10% schermbreedte) in het rechterpaneel van de Product Backlog, gerangschikt op prioriteit. Ze kunnen worden aangemaakt, bewerkt, geprioriteerd en gerangschikt via drag-and-drop.

Acceptatiecriteria:

  • Story aanmaken vereist een titel (verplicht, max. 200 tekens)
  • Omschrijving is optioneel (vrije tekst, max. 2000 tekens)
  • Acceptatiecriteria zijn optioneel (vrije tekst, max. 2000 tekens)
  • Prioriteit is verplicht (14, zelfde schaal als PBI's)
  • Stories worden weergegeven als blokken van ~10% schermbreedte, horizontaal gerangschikt
  • Blokken tonen: storytitel (ingekort), prioriteit-badge, status-badge
  • Elke prioriteitsgroep heeft een visuele scheiding (gekleurde band of scheidingslijn)
  • Volgorde binnen een prioriteitsgroep is instelbaar via drag-and-drop (dnd-kit)
  • Slepen over een prioriteitsgrens wijzigt de prioriteit van de story
  • Klikken op een storyblok opent de story-detailweergave (slide-over of modal)
  • Story verwijderen vereist bevestiging; cascade-verwijdering van gekoppelde taken
  • Stories die aan een Sprint gekoppeld zijn tonen een badge "In Sprint [naam]"

Randgevallen:

  • Storytitel is langer dan past in het blok → afgekapt met ellipsis, volledige titel zichtbaar bij hover

Data:

  • Opgeslagen: stories (id, pbi_id, product_id, title, description, acceptance_criteria, priority (14), sort_order, status (OPEN | IN_SPRINT | DONE), sprint_id?, created_at, updated_at)

F-07: Story-activiteitenlog

Prioriteit: v1 — Kern van Claude Code-integratie Persona: Lars

Omschrijving: Elke story heeft een activiteitenlog die alle door Claude Code vastgelegde stappen toont: implementatieplannen, testresultaten en commits. De log is zichtbaar in de story-detailweergave en is read-only vanuit de UI.

Acceptatiecriteria:

  • Log toont alle entries in chronologische volgorde (oudste eerst)
  • Elk entry-type heeft een eigen visuele stijl:
    • implementation_plan: blauw, icoon document
    • test_result (passed): groen, icoon vinkje
    • test_result (failed): rood, icoon kruis
    • commit: paars, icoon git-branch
  • Commit-entries tonen de hash (ingekort, bijv. a1b2c3d) en commit-bericht
  • Commit-hash is klikbaar als de git-repo URL is ingesteld op het product; opent in nieuw tabblad
  • Log is leeg bij nieuwe stories → lege staat toont "Nog geen activiteit vastgelegd"
  • Log is niet bewerkbaar via de UI

Data:

  • Opgeslagen: story_logs (id, story_id, type (IMPLEMENTATION_PLAN | TEST_RESULT | COMMIT), content, status? (PASSED | FAILED), commit_hash?, commit_message?, created_at)

F-08: Todo-lijst

Prioriteit: v1 — Hoog Persona: Lars (snelle vastlegging), Dina (losse klantnotities)

Omschrijving: Een snelle todo-lijst voor taken die aan een specifiek product zijn gekoppeld. Todo-items kunnen worden afgevinkt en gepromoveerd naar een PBI of story in dat product. Zowel de UI als de REST API vereisen een product_id bij aanmaken — zodat Claude Code altijd werkt binnen de context van de actieve product backlog.

Acceptatiecriteria:

  • Todo aanmaken via snel-invoerveld (Enter om op te slaan); product (dropdown) en titel verplicht
  • Todo aanmaken ook mogelijk via REST API: POST /api/todos (body: { "title": string, "product_id": string }) — zodat Claude Code bevindingen kan vastleggen binnen de actieve product backlog
  • Todo-lijst is zichtbaar als apart scherm of persistent zijpaneel
  • Todo afvinken markeert het als afgerond (visueel doorgestreept)
  • Afgevinkte todo's blijven zichtbaar; kunnen worden gearchiveerd via "Archiveer afgeronde items"
  • Todo promoveren naar PBI: dialoog pre-selecteert het gekoppelde product (bewerkbaar), vraagt prioriteit; todo verdwijnt na promotie
  • Todo promoveren naar story: dialoog pre-selecteert het gekoppelde product (bewerkbaar), vraagt PBI en prioriteit; todo verdwijnt na promotie
  • Titel van het todo-item is vooringevuld in de promotiedialoog (bewerkbaar)
  • Promotie is niet ongedaan te maken; dialoog waarschuwt hiervoor

Randgevallen:

  • Geen producten aangemaakt → promotie-dialoog toont melding "Maak eerst een product aan"
  • Promoveren naar story zonder PBI's in het product → dialoog toont melding "Maak eerst een PBI aan"

Data:

  • Opgeslagen: todos (id, user_id, product_id, title, done, archived, created_at, updated_at)

F-09: Sprint aanmaken en beheren

Prioriteit: v1 — Hoog Persona: Lars, Dina, Remi

Omschrijving: Het Scrum Team kan een Sprint aanmaken met een Sprint Goal. Per product kan er één actieve Sprint zijn. Stories worden via een gesplitst scherm vanuit de Product Backlog naar de Sprint Backlog gesleept.

Acceptatiecriteria:

  • Sprint aanmaken vereist een Sprint Goal (verplicht, max. 500 tekens)
  • Sprint is gekoppeld aan een product
  • Er kan maar één actieve Sprint per product tegelijk zijn
  • Sprint Backlog scherm is gesplitst: Sprint Backlog links, stories per PBI rechts
  • Rechterpaneel toont alle PBI's inklapbaar, met hun stories eronder
  • Stories die al in de Sprint zitten zijn visueel gemarkeerd en niet opnieuw sleepbaar
  • Story naar Sprint slepen via drag-and-drop (dnd-kit) van rechts naar links
  • Story in de Sprint Backlog is herrangschikbaar via drag-and-drop
  • Story uit Sprint verwijderen via contextmenu of verwijderknop → story keert terug in Product Backlog
  • Sprint Goal is bewerkbaar na aanmaken
  • Sprint afronden zet alle stories op DONE of terug op OPEN (keuze per story in afronden-dialoog)

Randgevallen:

  • Gebruiker probeert tweede Sprint aan te maken terwijl er al een actieve Sprint is → foutmelding met link naar actieve Sprint
  • Story wordt uit Sprint verwijderd terwijl er taken aan hangen → taken blijven bestaan maar worden losgekoppeld van de Sprint

Data:

  • Opgeslagen: sprints (id, product_id, sprint_goal, status (ACTIVE | COMPLETED), created_at, completed_at?)

F-10: Sprint Planning — taken aanmaken

Prioriteit: v1 — Hoog Persona: Lars, Remi

Omschrijving: In het Sprint Planning scherm worden stories uit de Sprint Backlog opgedeeld in taken. Het scherm is gesplitst: stories links, taken van de geselecteerde story rechts. Taken kunnen worden geprioriteerd en gerangschikt via drag-and-drop.

Acceptatiecriteria:

  • Sprint Planning scherm toont Sprint Backlog stories links in volgorde
  • Selecteren van een story links toont de bijbehorende taken rechts
  • Taak aanmaken vereist een titel (verplicht, max. 200 tekens)
  • Omschrijving is optioneel (max. 1000 tekens)
  • Prioriteit is verplicht (14)
  • Taken zijn gerangschikt op prioriteit en volgorde; volgorde instelbaar via drag-and-drop (dnd-kit)
  • Taakstatus is instelbaar via de UI: TO_DO | IN_PROGRESS | DONE
  • Story toont een voortgangsindicator (bijv. "2/5 taken Done")
  • Taak verwijderen vereist bevestiging

Randgevallen:

  • Story heeft geen taken → lege staat rechts met prompt om eerste taak aan te maken
  • Alle taken van een story zijn Done → story-voortgang toont 100% maar story-status wijzigt niet automatisch

Data:

  • Opgeslagen: tasks (id, story_id, sprint_id, title, description, priority (14), sort_order, status (TO_DO | IN_PROGRESS | DONE), created_at, updated_at)

F-11: Claude Code REST API

Prioriteit: v1 — Kern-differentiator Persona: Lars

Omschrijving: Een REST API waarmee Claude Code stories en taken kan ophalen, de taakvolgorde kan beoordelen en aanpassen, en implementatieplannen, testresultaten en commits kan vastleggen. Alle endpoints zijn beveiligd via een API-token.

Acceptatiecriteria:

Endpoints:

  • GET /api/products — lijst van actieve producten waarvoor de tokengebruiker eigenaar of teamlid is
  • GET /api/products/:id/next-story — hoogst geprioriteerde open story van de actieve Sprint
  • GET /api/sprints/:id/tasks?limit=10 — eerste N taken in huidige volgorde
  • PATCH /api/stories/:id/tasks/reorder — accepteert geordende lijst van taak-id's
  • POST /api/stories/:id/log — vastleggen van implementatieplan, testresultaat of commit
  • PATCH /api/tasks/:id — status bijwerken (TO_DO → IN_PROGRESS → DONE) en/of implementation_plan opslaan
  • POST /api/todos — todo aanmaken vanuit Claude Code (body: { "title": string, "product_id": string })

Authenticatie:

  • Alle endpoints vereisen Authorization: Bearer <token> header
  • Ontbrekend of ongeldig token geeft 401 Unauthorized
  • Demo-gebruiker API-token geeft 403 op alle schrijf-endpoints

Responsformaat:

  • Alle responses zijn JSON
  • Foutresponses bevatten { "error": "omschrijving" }
  • GET /api/products/:id/next-story geeft 404 als er geen open stories zijn

Data:

  • Opgeslagen: api_tokens (id, user_id, token_hash, label, created_at, revoked_at?)

F-11b: Vraag-antwoord-kanaal Claude ↔ user

Prioriteit: v1 — Verdiept de Claude-integratie (richting B uit strategisch overleg) Persona: Lars (primair), Dina (idem voor klant-werk)

Omschrijving: Wanneer Claude Code tijdens het implementeren van een story een keuze niet uit de acceptance-criteria kan afleiden, post hij een gestructureerde vraag naar Scrum4Me via een MCP-tool. De Scrum4Me-app toont een notificatie-badge voor iedereen met toegang tot het product. Een gebruiker beantwoordt de vraag in de UI; Claude leest het antwoord (sync via een polling-tool of in een latere sessie) en gaat door zonder te raden of te wachten in de Claude Code-sessie.

Verloop:

  1. Claude heeft een vraag → roept MCP-tool ask_user_question aan met { story_id, question, options?, wait_seconds? }. Tool schrijft een rij naar claude_questions met status open, vervaltijd 24 u.
  2. Postgres-trigger emit op het bestaande scrum4me_changes-kanaal met entity: 'question'. De Scrum4Me-app heeft een user-scoped SSE-stream die filter't op product-toegang.
  3. NavBar-bell krijgt een badge met de count van open vragen voor deze gebruiker. Story-assignee ziet een visuele "wacht op jou"-emphase.
  4. Klik op bell → slide-over met lijst → klik op item → modal met de volledige vraag, story-context-link en (optionele) keuze-opties. Submit verstuurt het antwoord via Server Action.
  5. Trigger fired opnieuw, alle SSE-clients zien het item verdwijnen. Claude's tool-poller (als wait_seconds was meegegeven) krijgt het antwoord direct terug; anders haalt Claude het later op via get_question_answer.

Acceptatiecriteria:

  • Claude kan via MCP een vraag stellen (ask_user_question); demo-tokens krijgen permission-denied
  • Bell-icon in NavBar toont badge met aantal open vragen voor de ingelogde gebruiker
  • Iedere gebruiker met product-toegang kan antwoorden; story-assignee krijgt visuele markering
  • Demo-gebruiker kan vragen lezen maar de Verstuur-knop is uitgeschakeld met tooltip
  • Optionele wait_seconds (max 600) laat de MCP-tool blijven pollen; bij timeout retourneert hij status: 'pending'
  • Concurrent dubbele submit op zelfde vraag: één wint via atomic updateMany, ander krijgt foutmelding "al beantwoord"
  • Vragen ouder dan 24 u worden via een Vercel cron op expired gezet
  • Cross-product-isolatie: een gebruiker ziet alleen vragen van producten waar hij toegang toe heeft

Randgevallen:

  • Claude vraagt iets en is daarna offline (Claude Code-sessie afgesloten) → vraag blijft in DB; volgende sessie roept list_open_questions of get_question_answer op
  • Story-assignee verandert nadat de vraag is gesteld → de vraag blijft beantwoordbaar door iedereen met product-toegang; visuele emphase volgt de actuele assignee
  • Vraag verloopt voordat iemand antwoord geeft → cron zet 'm op expired; Claude's get_question_answer retourneert status: 'expired'
  • Phishing/abuse: alleen geverifieerde Claude-tokens kunnen vragen stellen; Scrum4Me-gebruikers zien alleen vragen van hun eigen producten

Data:

  • Nieuw: claude_questions (id, story_id, task_id?, product_id, asked_by, question, options?, status, answer?, answered_by?, answered_at?, created_at, expires_at)
  • Postgres-trigger op claude_questions publiceert via pg_notify('scrum4me_changes', ...)
  • Nieuwe MCP-tools in scrum4me-mcp: ask_user_question, get_question_answer, list_open_questions, cancel_question

F-12: API-tokenbeheer

Prioriteit: v1 — Vereiste voor Claude Code-integratie Persona: Lars

Omschrijving: Gebruikers kunnen API-tokens aanmaken, labelen en intrekken via de instellingenpagina. Een token wordt eenmalig in klare tekst getoond en daarna niet meer.

Acceptatiecriteria:

  • Gebruiker kan een token aanmaken met een optioneel label (bijv. "Claude Code — laptop")
  • Token wordt eenmalig getoond na aanmaken in een kopieerbaar veld
  • Na sluiten van het dialoog is de tokennwaarde niet meer opvraagbaar
  • Tokenoverzicht toont: label, aanmaakdatum, status (actief / ingetrokken)
  • Gebruiker kan een token intrekken; ingetrokken tokens werken direct niet meer
  • Maximaal 10 actieve tokens per gebruiker

Randgevallen:

  • Gebruiker sluit aanmaakmeldingsdialoog per ongeluk → token bestaat al, maar waarde is verloren; gebruiker moet een nieuw token aanmaken

F-13: Lokale en cloud deployment

Prioriteit: v1 — Kritiek Persona: Lars (lokaal, geen vendor lock-in), Dina (cloud)

Omschrijving: De app is deployable op Vercel + Neon PostgreSQL en lokaal draaibaar met een Neon-database. Eén codebase, PostgreSQL via Prisma.

Acceptatiecriteria:

  • npm run dev start de app lokaal met Neon-database
  • DATABASE_URL in .env.local verwijst naar Neon connection string
  • npx prisma db push initialiseert het schema
  • next build slaagt voor Vercel-deployment zonder aanpassingen
  • .env.example documenteert alle vereiste en optionele environment variables
  • README bevat stap-voor-stap instructies voor zowel lokale als cloud setup

Navigatiestructuur

/ (landingspagina — app-uitleg, Scrum-samenvatting, gebruikershandleiding, API-overzicht)
/login
/register

/dashboard                          (productenlijst)
/products/new                       (product aanmaken)
/products/:id                       (Product Backlog — gesplitst scherm)
/products/:id/sprint                (Sprint Backlog — gesplitst scherm)
/products/:id/sprint/planning       (Sprint Planning — gesplitst scherm)
/todos                              (todo-lijst)
/settings                           (profiel, account, product backlogs, rollen, API-tokens)
/settings/tokens                    (API-tokenbeheer)

Datamodel (schets)

Entiteit Sleutelvelden Relaties / opmerkingen
users id, username, password_hash, is_demo, bio?, bio_detail?, avatar_data?, created_at Profielvelden optioneel; avatar opgeslagen als WebP bytea
user_roles id, user_id, role (enum) Meervoudige rollen per gebruiker
api_tokens id, user_id, token_hash, label, revoked_at Max. 10 actief per gebruiker
products id, user_id, name, description, repo_url, definition_of_done, archived Hoogste niveau in de hiërarchie
pbis id, product_id, title, description, priority (14), sort_order Geordend binnen prioriteitsgroep
stories id, pbi_id, product_id, title, description, acceptance_criteria, priority, sort_order, status, sprint_id? Status: OPEN / IN_SPRINT / DONE
story_logs id, story_id, type, content, status?, commit_hash?, commit_message?, created_at Aangemaakt via API; read-only in UI
sprints id, product_id, sprint_goal, status (ACTIVE / COMPLETED), created_at, completed_at? Max. 1 actieve Sprint per product
tasks id, story_id, sprint_id, title, description, implementation_plan?, priority, sort_order, status Status: TO_DO / IN_PROGRESS / DONE; implementation_plan door MCP
todos id, user_id, product_id, title, done, archived, created_at Gekoppeld aan product backlog; verplicht in UI en API
product_members id, product_id, user_id, created_at Many-to-many; alleen Developers; eigenaar via products.user_id

Niet-functionele vereisten

Vereiste Waarde
Platform Desktop-first web (minimale breedte 1024px); toekomstige mobiele variant beperkt tot taken afvinken
Authenticatie Gebruikersnaam + wachtwoord; geen e-mailverificatie in v1
Drag-and-drop dnd-kit (useDraggable / useDroppable); 60fps vereist bij lijsten tot 100 items
API-beveiliging Bearer token op alle /api/* routes
Offline support Niet vereist voor v1
Taal Nederlands voor UI-teksten; Engels voor code, API en technische documentatie
Toegankelijkheid Keyboard-navigatie voor alle primaire acties; WCAG AA voor de sprint planning flow
Database Prisma ORM; PostgreSQL via Neon
Deployment Vercel (cloud) of lokaal via npm run dev
Analytics Vercel Analytics via @vercel/analytics/next in de root layout
Sessiebeheer Server-side sessie of JWT; geen opslag van gevoelige data in localStorage

Sleutel-user-flows

Flow 1: Lars start zijn avond

Startpunt: Dashboard (na inloggen)

  1. Lars ziet zijn productenlijst — vier producten, elk met naam en beschrijving
  2. Hij klikt op "Factuur-tool"
  3. Product Backlog opent — PBI's links, stories rechts
  4. Hij ziet drie open PBI's; klikt op "Betalingsverwerking"
  5. Vijf stories verschijnen rechts als blokken; twee hebben badge "In Sprint"
  6. Hij navigeert naar Sprint Planning
  7. Selecteert een story links, ziet de bijbehorende taken rechts
  8. Maakt een nieuwe taak aan: "Stripe webhook valideren" → prioriteit 1
  9. Opent terminal, start Claude Code
  10. Claude haalt de taak op via API, stelt implementatieplan op
  11. Plan verschijnt direct in de story-activiteitenlog in de app

Resultaat: Lars heeft in minder dan vijf minuten overzicht en Claude Code is aan het werk.


Flow 2: Claude Code volledige cyclus

Startpunt: Claude Code in terminal

  1. claude devplanner next — haalt hoogst geprioriteerde open story op via GET /api/products/:id/next-story
  2. Claude toont storytitel en acceptatiecriteria
  3. Claude haalt eerste 10 taken op via GET /api/sprints/:id/tasks?limit=10
  4. Claude beoordeelt volgorde en past aan via PATCH /api/stories/:id/tasks/reorder
  5. Claude stelt implementatieplan op → POST /api/stories/:id/log (type: IMPLEMENTATION_PLAN)
  6. Claude voert implementatie uit
  7. Claude draait tests → POST /api/stories/:id/log (type: TEST_RESULT, status: PASSED)
  8. Claude maakt commit → POST /api/stories/:id/log (type: COMMIT, hash, message)
  9. Lars opent de story in de app — ziet plan, testresultaat en commit-hash in de activiteitenlog
  10. Lars zet de story handmatig op DONE via de UI

Resultaat: Volledige traceerbaarheid van beslissing tot commit, zonder extra handmatige invoer.


Flow 3: Todo promoveren naar story

Startpunt: Todo-lijst

  1. Lars heeft een todo: "Voeg rate limiting toe aan de API"
  2. Hij klikt op "Promoveren → Story"
  3. Dialoog opent: product (vooringevuld met laatste product), PBI (dropdown), prioriteit
  4. Hij kiest product "Factuur-tool", PBI "Beveiliging", prioriteit 2
  5. Bevestigen → todo verdwijnt, story is aangemaakt
  6. Lars navigeert naar de Product Backlog → story staat in de juiste prioriteitsgroep

Resultaat: Losse gedachte is in drie stappen onderdeel van de formele Product Backlog.


Actief Product Backlog

Concept

Een gebruiker kan één product als "actief" markeren. Dit actieve product wordt in de NavBar centraal getoond en bepaalt welke tabs (Product Backlog, Sprint, Solo) navigeerbaar zijn. Het actieve product wordt opgeslagen in user.active_product_id in de database — niet in een cookie.

Menugedrag

  • Producten — altijd bereikbaar, toont alle producten van de gebruiker
  • Product Backlog — alleen klikbaar als er een actief product is
  • Sprint — alleen klikbaar als er een actief product is én een actieve sprint bestaat; anders tooltip "Geen actieve sprint"
  • Solo — alleen klikbaar als er een actief product is
  • Todo's — altijd bereikbaar

In het midden van de NavBar staat een dropdown met de naam van het actieve product. Via deze dropdown kan de gebruiker wisselen tussen producten of naar "Producten beheren" navigeren.

Activeren

  • Dashboard: elke productrij toont een "Activeer"-knop (verborgen voor het al actieve product). Het actieve product krijgt een "Actief"-badge. Klikken → actief product instellen + navigeer naar Product Backlog.
  • Product Backlog header: als dit product nog niet actief is, staat er een "Activeer"-knop in de header.

Demo-gebruikers zien de knoppen maar krijgen een toast "Niet beschikbaar in demo-modus" bij het klikken.

Edge cases

  • Archiveren: wanneer een eigenaar een product archiveert, wordt active_product_id voor alle leden die dit product actief hadden automatisch op null gezet (atomisch via $transaction).
  • Product verlaten: wanneer een lid het product verlaat, wordt hun active_product_id gecleard.
  • Lid verwijderen: wanneer een eigenaar een lid verwijdert, wordt dat lid's active_product_id gecleard.
  • Stale referentie: als bij een request active_product_id verwijst naar een gearchiveerd of onbereikbaar product (bijv. toegang ingetrokken in een andere sessie), cleared de layout de referentie server-side en redirect naar /dashboard met de toast "Je actieve product is niet meer beschikbaar".