docs(taxonomy): move spec files into docs/specs/
This commit is contained in:
parent
e56a038b4d
commit
2e47bda28e
14 changed files with 37 additions and 36 deletions
128
docs/specs/dialogs/pbi.md
Normal file
128
docs/specs/dialogs/pbi.md
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
---
|
||||
title: "PbiDialog Profiel"
|
||||
status: active
|
||||
audience: [ai-agent, contributor]
|
||||
language: nl
|
||||
last_updated: 2026-05-03
|
||||
---
|
||||
|
||||
# PbiDialog Profiel
|
||||
|
||||
> Volgt **`docs/patterns/dialog.md`** (de generieke spec voor élke entity-dialog in Scrum4Me).
|
||||
> Dit document beschrijft alleen de PBI-specifieke afwijkingen en keuzes — alle gedeelde regels (layout, motion, demo-policy, foutcodes, validatie, theming, dialog-gedrag) staan in de generieke spec en worden hier niet herhaald.
|
||||
|
||||
> **Belangrijk:** als een regel in dit profiel botst met de generieke spec, wint de generieke spec. Documenteer hier de afwijking + reden, of pas de generieke spec aan.
|
||||
|
||||
---
|
||||
|
||||
## Velden
|
||||
|
||||
| Veld | Type | Mode | Validatie |
|
||||
|---|---|---|---|
|
||||
| `code` | `string \| null` | beide | optional, max 30 chars, mono-font, placeholder `auto` op create (server kent dan zelf een code toe) |
|
||||
| `title` | `string` (required) | beide | trim, 1-200 chars |
|
||||
| `priority` | `int` (1-4, P1 = hoogste) | beide | int 1-4, default 2 (kan via `defaultPriority`-prop bij create) |
|
||||
| `status` | `PbiStatusApi` enum | beide | enum, default `'ready'` |
|
||||
| `description` | `string \| null` | beide | optional, max 2000 chars, plain textarea (geen markdown rendering binnen de dialog) |
|
||||
|
||||
`PbiStatusApi` enum (lowercase, mapped via `lib/task-status.ts`): zie `<PbiStatusSelect>` voor de waarden.
|
||||
|
||||
### Veld-specifiek gedrag
|
||||
|
||||
- **Code + Titel** in één rij (`grid-cols-[6rem_1fr]`)
|
||||
- **Prioriteit + Status** in één rij (`grid-cols-2`)
|
||||
- **Prioriteit** via `<PrioritySelect>` (gedeelde primitive, géén segmented buttons in deze dialog)
|
||||
- **Status** via `<PbiStatusSelect>` (PBI-specifieke wrapper rond gedeelde select)
|
||||
- **Description** is `<Textarea rows={3} resize-none>` — géén auto-grow, géén markdown-hint, géén char-counter (afwijking van generieke spec; rationale: PBI-descriptions zijn doorgaans kort en richtinggevend)
|
||||
|
||||
---
|
||||
|
||||
## URL- of state-pattern
|
||||
|
||||
- **Gekozen:** state-based (`state: PbiDialogState | null` prop, gerendeerd binnen `PbiList`)
|
||||
- **Reden:** PBI-dialog leeft altijd binnen `PbiList` op de product-backlog-pagina; deep-linking is niet vereist en zou een tweede edit-flow toevoegen.
|
||||
- **State-shape:**
|
||||
```ts
|
||||
type PbiDialogState =
|
||||
| { mode: 'create'; productId: string; defaultPriority?: number }
|
||||
| { mode: 'edit'; pbi: PbiDialogPbi; productId: string }
|
||||
```
|
||||
- **Sluiten:** `onClose()` callback uit de parent — `setState(null)` in `PbiList`.
|
||||
|
||||
---
|
||||
|
||||
## Status-veld
|
||||
|
||||
- **Default bij create:** `'ready'` (PBI-default state)
|
||||
- **Geen verberging in create-mode** — anders dan TaskDialog wordt status hier wél getoond bij create, omdat een PBI zonder expliciete status onhandig is voor backlog-grooming
|
||||
|
||||
---
|
||||
|
||||
## Server actions
|
||||
|
||||
| Actie | Locatie | Form-binding | Revalidatie |
|
||||
|---|---|---|---|
|
||||
| `createPbiAction` | `actions/pbis.ts` | via `useActionState` + `<form action>` (FormData) | server-side `revalidatePath` op product-backlog |
|
||||
| `updatePbiAction` | `actions/pbis.ts` | idem | idem |
|
||||
| ~~`deletePbiAction`~~ | **(ontbreekt)** | n.v.t. | n.v.t. |
|
||||
|
||||
Beide acties moeten de drielaagse demo-policy volgen (zie § Bekende gaps).
|
||||
|
||||
---
|
||||
|
||||
## Speciale gedragingen
|
||||
|
||||
### Form-state via `useActionState`
|
||||
|
||||
PbiDialog gebruikt het `useActionState` + `useFormStatus`-patroon (Server Actions / native React), niet `react-hook-form`. Dit is een toegestaan alternatief volgens de generieke spec § 2. Field-errors worden gemapt via een lokale `fieldError(field)`-helper die `result.error` als `Record<string, string[]>` interpreteert wanneer 'm geen string is.
|
||||
|
||||
### `key`-prop op `<form>`
|
||||
|
||||
Het `<form>`-element heeft `key={isEdit ? pbi!.id : 'create'}` — dit reset native form-state (defaultValues) wanneer de dialog tussen create en edit wisselt of wanneer een ander record bewerkt wordt.
|
||||
|
||||
### Hidden inputs voor server-binding
|
||||
|
||||
`priority` en `status` worden via `<input type="hidden">` doorgegeven aan de Server Action (de UI-controls zijn JS-state, niet directe form-fields).
|
||||
|
||||
---
|
||||
|
||||
## Triggers
|
||||
|
||||
- **Create-trigger:** `+ PBI`-knop in `PanelNavBar` van `PbiList` → `setPbiDialogState({ mode: 'create', ... })`
|
||||
- **Edit-trigger:** edit-icoon op een PBI-rij in `PbiList` → `setPbiDialogState({ mode: 'edit', pbi, ... })`
|
||||
|
||||
---
|
||||
|
||||
## Bekende gaps t.o.v. generieke spec
|
||||
|
||||
> Deze items wijken af van `docs/patterns/dialog.md` en horen in een vervolg-PR rechtgezet (niet onderdeel van de huidige docs-introductie).
|
||||
|
||||
- ❌ **Geen `<DemoTooltip>`** rond submit-knop — laag 3 van de drielaagse demo-policy ontbreekt voor PBI-create/update. Dat betekent dat een demo-user de knop kan klikken; de server action blokkeert nog steeds (laag 2), maar de UX is suboptimaal.
|
||||
- ❌ **Geen delete-knop / `deletePbiAction`** — alleen create + update. Of dat bewust is (PBI's worden nooit verwijderd, alleen status veranderd) of een gat, moet expliciet worden besloten en in dit profiel vastgelegd.
|
||||
- ❌ **Geen dirty-close-guard** — Esc / backdrop / Cancel sluiten direct, ook met onopgeslagen wijzigingen. Generieke spec § 8.1 vereist een AlertDialog bij `isDirty`.
|
||||
- ❌ **Geen Cmd/Ctrl+Enter shortcut** — alleen klik op submit-knop.
|
||||
- ❌ **Geen char-counter / markdown-hint** op description — bewust weggelaten omdat PBI-descriptions kort zijn, maar verdient expliciete bevestiging.
|
||||
- ⚠️ **Layout wijkt af** van de generieke responsive-tabel: `sm:max-w-md` i.p.v. de `max-w-[50vw]` / `90vw` / full-screen-progressie uit § 4.
|
||||
|
||||
---
|
||||
|
||||
## Bewust NIET in v1
|
||||
|
||||
Specifiek voor PbiDialog (boven op de algemene out-of-scope-lijst in `docs/patterns/dialog.md` § 13):
|
||||
|
||||
- ❌ Inline aanmaken van child-stories binnen de PBI-dialog (gebeurt via StoryDialog vanuit `StoryPanel`)
|
||||
- ❌ Bulk-status-update over meerdere PBI's
|
||||
- ❌ PBI-templates / kopiëren
|
||||
|
||||
---
|
||||
|
||||
## Referenties
|
||||
|
||||
- `components/backlog/pbi-dialog.tsx` — implementatie
|
||||
- `actions/pbis.ts` — server actions
|
||||
- `components/shared/priority-select.tsx` — gedeelde priority-control
|
||||
- `components/shared/pbi-status-select.tsx` — PBI-status-select
|
||||
- `lib/task-status.ts` — `PbiStatusApi`-mapper
|
||||
- `docs/patterns/dialog.md` — generieke spec (bron-of-truth)
|
||||
- `docs/architecture.md` — datamodel `Pbi`
|
||||
- `docs/styling.md` — MD3-tokens, status- en priority-kleuren
|
||||
171
docs/specs/dialogs/story.md
Normal file
171
docs/specs/dialogs/story.md
Normal file
|
|
@ -0,0 +1,171 @@
|
|||
---
|
||||
title: "StoryDialog Profiel"
|
||||
status: active
|
||||
audience: [ai-agent, contributor]
|
||||
language: nl
|
||||
last_updated: 2026-05-03
|
||||
---
|
||||
|
||||
# StoryDialog Profiel
|
||||
|
||||
> Volgt **`docs/patterns/dialog.md`** (de generieke spec voor élke entity-dialog in Scrum4Me).
|
||||
> Dit document beschrijft alleen de Story-specifieke afwijkingen en keuzes — alle gedeelde regels (layout, motion, demo-policy, foutcodes, validatie, theming, dialog-gedrag) staan in de generieke spec en worden hier niet herhaald.
|
||||
|
||||
> **Belangrijk:** als een regel in dit profiel botst met de generieke spec, wint de generieke spec. Documenteer hier de afwijking + reden, of pas de generieke spec aan.
|
||||
|
||||
---
|
||||
|
||||
## Velden
|
||||
|
||||
| Veld | Type | Mode | Validatie |
|
||||
|---|---|---|---|
|
||||
| `code` | `string \| null` | beide | optional, max 30 chars, mono-font, placeholder `auto` op create |
|
||||
| `title` | `string` (required) | beide | trim, 1-200 chars |
|
||||
| `priority` | `int` (1-4, P1 = hoogste) | beide | int 1-4, default 2 (overschrijfbaar via `defaultPriority`-prop bij create) |
|
||||
| `description` | `string \| null` | beide | optional, plain textarea, placeholder `Als… wil ik… zodat…` (user-story-template) |
|
||||
| `acceptance_criteria` | `string \| null` | beide | optional, plain textarea, placeholder `- Gegeven… Als… Dan…` (Gherkin-template) |
|
||||
| `status` | `StoryStatus` enum | alleen edit | read-only badge in header, niet bewerkbaar in deze dialog |
|
||||
|
||||
`StoryStatus` enum: `OPEN | IN_SPRINT | DONE` (uppercase in DB).
|
||||
|
||||
### Veld-specifiek gedrag
|
||||
|
||||
- **Code + Titel** in één rij (`grid-cols-[6rem_1fr]`)
|
||||
- **Prioriteit** via `<PrioritySelect>` (gedeelde primitive)
|
||||
- **Description** als `<Textarea rows={3} resize-none>` — géén auto-grow, géén markdown-hint binnen de dialog (afwijking van generieke spec; rationale: stories zijn meestal één zin)
|
||||
- **Acceptatiecriteria** idem — géén auto-grow, géén char-counter
|
||||
- **Status** wordt **niet bewerkt** vanuit deze dialog. Status verandert via lijst-acties (sleep naar sprint = IN_SPRINT, taak-completion = DONE). Read-only badge in dialog-header.
|
||||
|
||||
---
|
||||
|
||||
## URL- of state-pattern
|
||||
|
||||
- **Gekozen:** state-based (`state: StoryDialogState | null` prop, gerendeerd binnen `StoryPanel`)
|
||||
- **Reden:** StoryDialog leeft binnen `StoryPanel` met live-store-data (geselecteerde PBI bepaalt zichtbare stories); deep-linking zou parallelle data-fetch-paden vereisen.
|
||||
- **State-shape:**
|
||||
```ts
|
||||
type StoryDialogState =
|
||||
| { mode: 'create'; pbiId: string; productId: string; defaultPriority?: number }
|
||||
| { mode: 'edit'; story: Story; productId: string }
|
||||
```
|
||||
- **Sluiten:** `onClose()` callback uit de parent — `setState(null)` in `StoryPanel`.
|
||||
|
||||
---
|
||||
|
||||
## Status-veld
|
||||
|
||||
- **Niet bewerkbaar in deze dialog** — alleen weergegeven als badge in de header (edit-mode)
|
||||
- **Default bij create:** `OPEN` (server-default, niet expliciet gezet vanuit form)
|
||||
- Status-overgangen lopen via:
|
||||
- `OPEN → IN_SPRINT` — drag-and-drop naar een sprint of `sprint-id` zetten via story-actions
|
||||
- `IN_SPRINT → DONE` — alle taken op `DONE` zetten triggert auto-promotion (zie story-status-logic in `actions/stories.ts`)
|
||||
|
||||
---
|
||||
|
||||
## Server actions
|
||||
|
||||
| Actie | Locatie | Form-binding | Revalidatie |
|
||||
|---|---|---|---|
|
||||
| `createStoryAction` | `actions/stories.ts` | via `useActionState` + `<form action>` (FormData) | server-side `revalidatePath` op product-backlog |
|
||||
| `updateStoryAction` | `actions/stories.ts` | idem | idem |
|
||||
| `deleteStoryAction` | `actions/stories.ts` | aangeroepen vanuit `useTransition` (geen form) | server-side `revalidatePath` |
|
||||
| `getStoryLogsAction` | `actions/stories.ts` | aangeroepen on-mount in edit-mode | n.v.t. (read-only) |
|
||||
|
||||
Alle write-acties zijn drielaags afgedekt (proxy-guard + server-action-check + DemoTooltip op submit-knop).
|
||||
|
||||
---
|
||||
|
||||
## Speciale gedragingen
|
||||
|
||||
### Header-presentatie (afwijking van generieke spec)
|
||||
|
||||
In edit-mode toont de dialog-header **drie elementen** boven op de standaard titel:
|
||||
|
||||
1. Story-titel als dialog-title (groot)
|
||||
2. Story-code als monospace-badge rechtsboven (klein)
|
||||
3. Twee badges direct onder de titel: priority-badge (kleur via `PRIORITY_COLORS`) en status-badge (kleur via `STATUS_COLORS`)
|
||||
|
||||
Generieke spec gaat uit van een sobere header met alleen `headline-small` titel + optioneel een `created_at`-meta-string. StoryDialog wijkt hier bewust van af omdat status + priority belangrijke context zijn voor de gebruiker bij het openen van een story (vaak wisselt iemand vlot tussen meerdere stories).
|
||||
|
||||
### Demo-modus = read-only weergave
|
||||
|
||||
Wanneer `isDemo === true` én `isEdit === true`, wordt het form **vervangen** door een read-only weergave:
|
||||
|
||||
- `description` via gedeelde `<Markdown>`-wrapper (`react-markdown` + `remark-gfm`)
|
||||
- `acceptance_criteria` als plain whitespace-pre-line tekst
|
||||
|
||||
In create-mode is er voor demo-users niets te tonen — de dialog wordt alsnog geopend maar de submit-knop is `disabled` met `<DemoTooltip>`.
|
||||
|
||||
> Dit "read-only-fallback"-patroon is uniek voor StoryDialog tot nu toe. Het zou geadopteerd kunnen worden door andere edit-dialogs zodra demo-flow-vereisten dat rechtvaardigen.
|
||||
|
||||
### Activity-log (StoryLog) inline
|
||||
|
||||
In edit-mode wordt onder het form een `<StoryLog>`-paneel getoond met de chronologische logs van deze story (commit-hashes, status-transitions, etc.). Logs worden lazy-fetched via `getStoryLogsAction(story.id)` zodra de dialog opent.
|
||||
|
||||
Dit is een **read-only side-panel** en valt binnen de uitzondering die de generieke spec § 13 maakt voor `<StoryLog>`-style activity-rendering.
|
||||
|
||||
### Delete-flow (afwijking van generieke spec)
|
||||
|
||||
Generieke spec § 10.4 vereist een **`AlertDialog`** voor delete-confirmatie. StoryDialog gebruikt in plaats daarvan een **inline-confirm** in dezelfde footer-rij:
|
||||
|
||||
```
|
||||
[ Weet je het zeker? Taken worden ook verwijderd. [Verwijderen] [Annuleren] ]
|
||||
```
|
||||
|
||||
Een `AlertDialog` zou een tweede modale laag toevoegen die in deze context onhandig voelt (de dialog zelf is al een interruptive overlay). De inline-confirm is een **bewuste afwijking** van de generieke spec.
|
||||
|
||||
### Form-state via `useActionState`
|
||||
|
||||
Net als PbiDialog gebruikt StoryDialog `useActionState` + `useFormStatus`, niet `react-hook-form`. Dit is een toegestaan alternatief volgens de generieke spec § 2.
|
||||
|
||||
### `key`-prop op `<form>`
|
||||
|
||||
Het `<form>` heeft `key={isEdit ? story!.id : 'create'}` — reset native form-state bij record-wissel of mode-switch.
|
||||
|
||||
---
|
||||
|
||||
## Triggers
|
||||
|
||||
- **Create-trigger:** `+ Story`-knop in `PanelNavBar` van `StoryPanel` → `setStoryDialogState({ mode: 'create', pbiId, productId, defaultPriority: 2 })`
|
||||
- **Edit-trigger:** edit-icoon op een story-card in `StoryPanel` → `setStoryDialogState({ mode: 'edit', story, productId })`
|
||||
- **Empty-state-trigger:** `Maak je eerste story aan`-knop in `EmptyPanel` (zelfde state als create-trigger)
|
||||
|
||||
---
|
||||
|
||||
## Bekende gaps t.o.v. generieke spec
|
||||
|
||||
> Deze items wijken af van `docs/patterns/dialog.md` en horen in een vervolg-PR rechtgezet (niet onderdeel van de huidige docs-introductie).
|
||||
|
||||
- ❌ **Geen dirty-close-guard** — Esc / backdrop / Cancel sluiten direct, ook met onopgeslagen wijzigingen. Generieke spec § 8.1 vereist een AlertDialog bij `isDirty`.
|
||||
- ❌ **Geen Cmd/Ctrl+Enter shortcut** — alleen klik op submit-knop.
|
||||
- ❌ **Geen char-counter / markdown-hint** op description / acceptance_criteria — bewust weggelaten, maar verdient expliciete bevestiging als design-keuze.
|
||||
- ⚠️ **Inline-delete-confirm** in plaats van AlertDialog (zie § Speciale gedragingen). Bewuste afwijking; de generieke spec mag deze variant expliciet toestaan, of dit profile moet als precedent gelden voor toekomstige dialogen.
|
||||
- ⚠️ **Header-layout** met meerdere badges wijkt af van de sobere header in § 4. Bewuste afwijking — context-zwaar bij story-wisselen.
|
||||
- ⚠️ **Layout wijkt af** van de generieke responsive-tabel: `sm:max-w-lg` met eigen `max-h-[90vh]` + `flex flex-col` i.p.v. de exacte `max-w-[50vw]` / `90vw` / full-screen-progressie uit § 4.
|
||||
|
||||
---
|
||||
|
||||
## Bewust NIET in v1
|
||||
|
||||
Specifiek voor StoryDialog (boven op de algemene out-of-scope-lijst in `docs/patterns/dialog.md` § 13):
|
||||
|
||||
- ❌ Status bewerken vanuit de dialog (gebeurt via lijst-acties / drag-and-drop / auto-promotion)
|
||||
- ❌ Inline aanmaken van child-tasks (gebeurt via TaskDialog vanuit `TaskPanel`)
|
||||
- ❌ Bulk-edit over meerdere stories
|
||||
- ❌ Story-templates
|
||||
- ❌ Linking aan externe issues (GitHub / Linear) — staat op v1.1+ roadmap
|
||||
|
||||
---
|
||||
|
||||
## Referenties
|
||||
|
||||
- `components/backlog/story-dialog.tsx` — implementatie
|
||||
- `actions/stories.ts` — server actions (incl. `getStoryLogsAction`)
|
||||
- `components/shared/priority-select.tsx` — gedeelde priority-control
|
||||
- `components/shared/story-log.tsx` — activity-log paneel
|
||||
- `components/shared/demo-tooltip.tsx` — demo-policy laag 3
|
||||
- `components/markdown.tsx` — gedeelde markdown-wrapper
|
||||
- `lib/task-status.ts` — status-enum-mapper
|
||||
- `docs/patterns/dialog.md` — generieke spec (bron-of-truth)
|
||||
- `docs/architecture.md` — datamodel `Story`
|
||||
- `docs/styling.md` — MD3-tokens, status- en priority-kleuren
|
||||
135
docs/specs/dialogs/task.md
Normal file
135
docs/specs/dialogs/task.md
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
---
|
||||
title: "TaskDialog Profiel"
|
||||
status: active
|
||||
audience: [ai-agent, contributor]
|
||||
language: nl
|
||||
last_updated: 2026-05-03
|
||||
---
|
||||
|
||||
# TaskDialog Profiel
|
||||
|
||||
> Volgt **`docs/patterns/dialog.md`** (de generieke spec voor élke entity-dialog in Scrum4Me).
|
||||
> Dit document beschrijft alleen de Task-specifieke afwijkingen en keuzes — alle gedeelde regels (layout, motion, demo-policy, foutcodes, validatie, theming, dialog-gedrag) staan in de generieke spec en worden hier niet herhaald.
|
||||
|
||||
> **Belangrijk:** als een regel in dit profiel botst met de generieke spec, wint de generieke spec. Documenteer hier de afwijking + reden, of pas de generieke spec aan.
|
||||
|
||||
---
|
||||
|
||||
## Velden
|
||||
|
||||
| Veld | Type | Mode | Validatie |
|
||||
|---|---|---|---|
|
||||
| `title` | `string` (required) | beide | trim, 1-120 chars |
|
||||
| `description` | `string \| null` | beide | optional, max 2.000 chars, markdown |
|
||||
| `implementation_plan` | `string \| null` | beide | optional, max 10.000 chars, markdown |
|
||||
| `priority` | `int` (1-4, P1 = hoogste) | beide | int 1-4, default 3 |
|
||||
| `status` | `TaskStatus` enum | alleen edit (default `TO_DO` op create, niet getoond) | enum |
|
||||
| `created_at` | `Date` | alleen edit | read-only metadata in header |
|
||||
|
||||
`TaskStatus` enum: `TO_DO | IN_PROGRESS | REVIEW | DONE`.
|
||||
|
||||
### Veld-specifiek gedrag
|
||||
|
||||
- **Auto-grow textareas** (`description`, `implementation_plan`) via `react-textarea-autosize`. Max 6 regels (description) / 12 regels (implementation_plan), daarna `overflow-y-auto`.
|
||||
- **Karakter-counter** vanaf 75% van de limiet, klein, rechtsonder, `text-muted-foreground`. Bv. `1547 / 2000`.
|
||||
- **Markdown-hint** onder elk textarea: `Markdown ondersteund (lijstjes, **vet**, \`code\`)`.
|
||||
- **Priority** als segmented buttons via `<PrioritySelect>` / `<PrioritySegmented>`. Default P3 (Medium).
|
||||
- **Status** met gekleurde dot:
|
||||
- `TO_DO` — grijs
|
||||
- `IN_PROGRESS` — `status-in-progress` (blauw)
|
||||
- `REVIEW` — paars
|
||||
- `DONE` — `status-done` (groen)
|
||||
- **`created_at` als header-metadata** in edit-mode, naast de titel: `Aangemaakt: 23 apr 2026`. Klein, `muted-foreground`, géén form-veld.
|
||||
|
||||
---
|
||||
|
||||
## URL- of state-pattern
|
||||
|
||||
- **Gekozen:** URL-based (`searchParams`)
|
||||
- **Reden:** TaskDialog wordt geopend vanuit twee context-pagina's (sprint-detail en product-backlog) en moet deep-linkable zijn voor share/refresh-scenario's. Suspense + skeleton voor edit-mode loading is gewenst.
|
||||
- **Routes:**
|
||||
```
|
||||
/sprint/<sprintId>?newTask=1 → create
|
||||
/sprint/<sprintId>?editTask=<taskId> → edit
|
||||
/products/<productId>/backlog?newTask=1 → create
|
||||
/products/<productId>/backlog?editTask=<taskId> → edit
|
||||
```
|
||||
- **Sluiten:** `router.push(<base-route>)` zonder query-params.
|
||||
- **Server-side fetch in edit-mode:** server component fetcht de taak vóór render mét `productAccessFilter(userId)`. Bestaat de taak niet of valt 'm buiten scope → toast + redirect naar de context-route.
|
||||
- Optioneel: `nuqs` als de query-state-handling te omslachtig wordt — pas introduceren als losse refactor-task, niet inline.
|
||||
|
||||
---
|
||||
|
||||
## Status-veld
|
||||
|
||||
Verberg `status` in **create-mode** (default = `TO_DO` is genoeg). Toon alleen in edit-mode als `<Select>` met gekleurde dot per optie.
|
||||
|
||||
---
|
||||
|
||||
## Server actions
|
||||
|
||||
| Actie | Locatie | Context-arg | Revalidatie |
|
||||
|---|---|---|---|
|
||||
| `saveTask` | `app/actions/tasks.ts` | `{ sprintId?: string; productId?: string }` | `revalidatePath('/sprint/<sprintId>')` óf `revalidatePath('/products/<productId>/backlog')` afhankelijk van context |
|
||||
| `deleteTask` | `app/actions/tasks.ts` | idem | idem |
|
||||
|
||||
Beide acties volgen de drielaagse demo-policy + auth-scoping uit `docs/patterns/dialog.md` § 6–7.
|
||||
|
||||
---
|
||||
|
||||
## Speciale gedragingen
|
||||
|
||||
### Triggers (bestaande UI vervangen)
|
||||
|
||||
Deze TaskDialog is de **enige** create/edit-flow voor taken in beide contexten (sprint én backlog). Bestaande inline-edit-paden in `components/sprint/task-list.tsx` en het backlog-equivalent worden vervangen, niet ernaast geplaatst.
|
||||
|
||||
- **Create-trigger:** filled button `+ Nieuwe taak` in tasklist-header → zet `?newTask=1` op huidige route
|
||||
- **Edit-trigger:** klik op de hele rij in de tasklist (geen apart edit-icoon) → zet `?editTask=<id>` op huidige route
|
||||
- **Loading edit-mode:** Suspense met minimale skeleton (3 grijze balken), `200ms`-delay zodat snelle fetches geen flicker tonen
|
||||
|
||||
### Markdown-rendering elders
|
||||
|
||||
`description` en `implementation_plan` worden buiten de dialog (taakdetail, hover-card) gerenderd via de gedeelde `<Markdown>`-wrapper (`react-markdown` + `remark-gfm`). Niet in de dialog zelf.
|
||||
|
||||
---
|
||||
|
||||
## Implementatie-volgorde (suggestie)
|
||||
|
||||
Hergebruik dit als checklist bij het bouwen of refactoren van TaskDialog:
|
||||
|
||||
1. Dependencies in `package.json` (zie `docs/patterns/dialog.md` § 2)
|
||||
2. zod-schema in `lib/schemas/task.ts` — gedeeld door form en action
|
||||
3. `productAccessFilter`-helper checken in `lib/auth/`
|
||||
4. `saveTask` / `deleteTask` in `app/actions/tasks.ts` met auth-scoping + demo-check (laag 2)
|
||||
5. `proxy.ts`-guard voor demo-write-routes (laag 1) — alleen als nog niet aanwezig
|
||||
6. Eventueel ontbrekende MD3-tokens in `app/styles/theme.css` aanvullen
|
||||
7. `<DemoTooltip>` rond submit/delete-knoppen (laag 3)
|
||||
8. TaskDialog — create-mode eerst (minder edge cases)
|
||||
9. Edit-mode toevoegen (status, delete, `created_at`-metadata)
|
||||
10. URL-state via native `searchParams` op beide context-pagina's
|
||||
11. Bestaande task-row trigger refactoren (klikbaar maken naar dialog)
|
||||
12. Suspense + skeleton voor edit-mode + scope-check op fetch
|
||||
13. Dirty-close-guard
|
||||
14. Keyboard shortcuts (Cmd+Enter)
|
||||
|
||||
---
|
||||
|
||||
## Bewust NIET in v1
|
||||
|
||||
Specifiek voor TaskDialog (boven op de algemene out-of-scope-lijst in `docs/patterns/dialog.md` § 13):
|
||||
|
||||
- ❌ Sub-tasks / parent-child relaties tussen taken
|
||||
- ❌ Tags / labels / categorieën op taken
|
||||
- ❌ Due dates / reminders per taak
|
||||
- ❌ Time tracking (uren-registratie) — wel relevant voor inspannings-monitor, eigen feature
|
||||
- ❌ Sharing / collaboration per taak
|
||||
- ❌ Templates voor terugkerende taken
|
||||
|
||||
---
|
||||
|
||||
## Referenties
|
||||
|
||||
- `docs/patterns/dialog.md` — generieke spec (bron-of-truth voor alles wat hier niet beschreven is)
|
||||
- `docs/architecture.md` — datamodel `Task`
|
||||
- `docs/styling.md` — MD3-tokens, status- en priority-kleuren
|
||||
- `lib/task-status.ts` — enum-mapper DB ↔ API
|
||||
658
docs/specs/functional.md
Normal file
658
docs/specs/functional.md
Normal file
|
|
@ -0,0 +1,658 @@
|
|||
---
|
||||
title: "Scrum4Me — Functionele Specificatie"
|
||||
status: active
|
||||
audience: [maintainer, contributor]
|
||||
language: nl
|
||||
last_updated: 2026-05-03
|
||||
---
|
||||
|
||||
# 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 1–2 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 1–2 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 — 3-paneels gesplitst scherm
|
||||
|
||||
**Prioriteit:** v1 — Kritiek
|
||||
**Persona:** Lars, Dina, Remi
|
||||
|
||||
**Omschrijving:**
|
||||
De Product Backlog wordt weergegeven als een 3-paneels gesplitst scherm: PBI's (links) | Stories (midden) | Taken (rechts). De splitters zijn versleepbaar. Selectie cascadeert: klikken op een PBI toont de bijbehorende stories; klikken op een story toont de bijbehorende taken. Elk paneel heeft een eigen navigatiebar met acties.
|
||||
|
||||
**Acceptatiecriteria:**
|
||||
- [ ] Standaard splitverhouding is 20/45/35 (PBI's / Stories / Taken)
|
||||
- [ ] Splitters zijn versleepbaar; positie wordt opgeslagen in een cookie (`sp:backlog-{id}`)
|
||||
- [ ] Selecteren van een PBI toont de bijbehorende stories in het middenpaneel
|
||||
- [ ] Geselecteerd PBI is visueel gemarkeerd (achtergrondkleur of rand)
|
||||
- [ ] Selecteren van een story toont de bijbehorende taken in het rechterpaneel
|
||||
- [ ] Geselecteerde story is visueel gemarkeerd
|
||||
- [ ] Cascade-reset: selecteren van een ander PBI wist de geselecteerde story en taken
|
||||
- [ ] PBI-paneel navigatiebar bevat: [+ PBI aanmaken]
|
||||
- [ ] Stories-paneel navigatiebar bevat: [+ Story aanmaken], [sorteer], [filter status]
|
||||
- [ ] Taken-paneel navigatiebar bevat: [+ Nieuwe taak]
|
||||
- [ ] Lege staat PBI-paneel: prompt om eerste PBI aan te maken
|
||||
- [ ] Lege staat Stories-paneel (geen PBI geselecteerd): instructie om een PBI te selecteren
|
||||
- [ ] Lege staat Stories-paneel (PBI geselecteerd, geen stories): prompt om eerste story aan te maken
|
||||
- [ ] Lege staat Taken-paneel (geen story geselecteerd): instructie om een story te selecteren
|
||||
- [ ] Lege staat Taken-paneel (story geselecteerd, geen taken): prompt om eerste taak aan te maken
|
||||
- [ ] Taak aanmaken opent TaskDialog via `?newTask=1&storyId={id}`
|
||||
- [ ] Taak bewerken opent TaskDialog via `?editTask={id}`
|
||||
|
||||
**Randgevallen:**
|
||||
- Scherm smaller dan 1024px → 3-paneels scherm schakelt over naar 3 tabbladen (PBI's | Stories | Taken)
|
||||
- Mobile tab-navigatie: klikken op PBI schakelt automatisch naar Stories-tab; klikken op story schakelt naar Taken-tab
|
||||
- Mobile ← terug-knop in tab-header op tabs 2 en 3 navigeert naar het vorige tabblad
|
||||
|
||||
---
|
||||
|
||||
### F-05: PBI-beheer
|
||||
|
||||
**Prioriteit:** v1 — Kritiek
|
||||
**Persona:** Lars, Dina, Remi
|
||||
|
||||
**Omschrijving:**
|
||||
Product Backlog Items kunnen worden aangemaakt, bewerkt, geprioriteerd (1–4) 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 (1–4), 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 (1–4, 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 (1–4), 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 (1–4)
|
||||
- [ ] 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 (1–4), 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 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 (1–4), 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".
|
||||
146
docs/specs/personas.md
Normal file
146
docs/specs/personas.md
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
---
|
||||
title: "DevPlanner — User Personas"
|
||||
status: active
|
||||
audience: [maintainer]
|
||||
language: nl
|
||||
last_updated: 2026-05-03
|
||||
---
|
||||
|
||||
# DevPlanner — User Personas
|
||||
|
||||
**Versie:** 0.1 — april 2026
|
||||
**Volgt op:** Concept & Feature Brainstorm v0.3
|
||||
|
||||
---
|
||||
|
||||
## Persona-overzicht
|
||||
|
||||
| Naam | Leeftijd | Situatie | Primaire behoefte |
|
||||
|---|---|---|---|
|
||||
| Lars | 34 | Solo developer, 4 eigen SaaS-projecten naast zijn dagtaak | Overzicht houden zonder planningssysteem te worden |
|
||||
| Dina | 28 | Freelance developer, werkt voor 3 klanten tegelijk | Klantprojecten gescheiden houden en voortgang aantonen |
|
||||
| Remi | 41 | Lead van een klein team (3 personen), geen dedicated PM | Scrum licht toepassen zonder Jira-overhead |
|
||||
|
||||
---
|
||||
|
||||
## Lars
|
||||
|
||||
**Leeftijd:** 34 — **Situatie:** Solo developer met 4 actieve side projects naast een fulltime baan
|
||||
|
||||
### Achtergrond
|
||||
|
||||
Lars werkt overdag als backend developer bij een middelgroot bedrijf en bouwt 's avonds en in het weekend aan zijn eigen projecten: een SaaS-tool voor factuurverwerking, een open-source CLI-utility, een persoonlijk dashboard en een experimenteel AI-project. Hij werkt alleen, heeft geen teamleden en geen deadlines die van buitenaf worden opgelegd. Alles loopt via zijn hoofd of verspreide markdown-bestanden in de repositories zelf.
|
||||
|
||||
### Een typische slechte avond
|
||||
|
||||
Hij opent zijn laptop na het werk en weet niet meer waar hij gebleven was. In welk project zat hij? Wat stond er open? Hij herinnert zich dat er vorige week een bug was gerapporteerd in de factuur-tool, maar hij weet niet meer of hij die heeft gefixt of alleen heeft opgeschreven. Hij besteedt twintig minuten aan het doorzoeken van commit-logs en README's voordat hij kan beginnen. Als Claude Code dan eindelijk een taak oppakt, is de context al kwijt.
|
||||
|
||||
### Hoe hij het nu oplost
|
||||
|
||||
Elke repository heeft een `TODO.md` die hij bijhoudt zolang een project actief is. Als hij een week niet heeft gekeken, is het bestand achterhaald. Prioritering is impliciet — hij werkt aan wat op dat moment het interessantst voelt, niet aan wat het belangrijkst is. Jira heeft hij één keer geprobeerd voor zijn side projects: duurde twee uur om in te richten en stopte na drie weken.
|
||||
|
||||
### Doelen met deze app
|
||||
|
||||
- Elke avond binnen één minuut weten welke taak het meest urgent is per project
|
||||
- Claude Code laten oppakken wat open staat zonder zelf de context te hoeven herstellen
|
||||
- Achteraf kunnen zien wat er gedaan is en hoe (implementatieplan, commit, testresultaat)
|
||||
- Op klantbezoek of bij familie zijn side projects kunnen demonstreren op een geleende laptop, zonder dat hij zijn wachtwoord op een vreemd toetsenbord hoeft te typen — door zijn telefoon (waar hij al ingelogd is) een QR-code op het scherm te laten scannen
|
||||
|
||||
### Frustraties om te vermijden
|
||||
|
||||
- Verplichte velden en formulieren die langer duren dan de taak zelf
|
||||
- Systemen die vragen om updates die hij toch niet bijhoudt (daily standup-achtige inputs)
|
||||
- Alles wat aanvoelt als "project management voor grote teams" — hij is de enige gebruiker
|
||||
|
||||
### Relatie met technologie
|
||||
|
||||
Power user. Leeft in de terminal, gebruikt Claude Code dagelijks, bouwt zijn eigen tooling als iets hem niet bevalt. Wil een app die hij ook via een API kan aansturen.
|
||||
|
||||
---
|
||||
|
||||
## Dina
|
||||
|
||||
**Leeftijd:** 28 — **Situatie:** Freelance full-stack developer, werkt parallel voor drie klanten
|
||||
|
||||
### Achtergrond
|
||||
|
||||
Dina werkt als zelfstandige en heeft op dit moment drie lopende klantprojecten: een e-commerceplatform in onderhoudsfase, een nieuw admin-dashboard voor een logistiek bedrijf en een kleine MVP voor een startup. Elk project heeft zijn eigen ritme, zijn eigen repo en zijn eigen verwachtingen. Ze werkt alleen maar heeft per klant een contactpersoon die wil weten wat er gedaan is.
|
||||
|
||||
### Een typische slechte dag
|
||||
|
||||
Ze heeft drie context-switches voor de lunch. Het logistiek-dashboard heeft een bugmelding binnengekomen, de startup wil een update, en de e-commerce klant heeft een vraag over iets wat twee Sprints geleden geleverd is. Ze heeft geen centraal overzicht — ze zoekt in Slack-threads, e-mails en commit-logs om te reconstrueren wat er wanneer gedaan is. Aan het eind van de dag heeft ze productief werk gedaan maar kan ze niet meer zeggen wat precies.
|
||||
|
||||
### Hoe ze het nu oplost
|
||||
|
||||
Elk klantproject heeft een Notion-pagina met een ruwe takenlijst. Notion is flexible maar heeft geen structuur die Scrum-begrippen begrijpt. Ze heeft geprobeerd Trello te gebruiken maar het mist de hiërarchie die ze nodig heeft (project → feature → taak). Ze voelt dat ze iets robuusters nodig heeft nu het derde project erbij is gekomen.
|
||||
|
||||
### Doelen met deze app
|
||||
|
||||
- Per klant een gestructureerde Product Backlog bijhouden zonder overlap
|
||||
- Snel kunnen antwoorden op "wat heb je de afgelopen week gedaan?" met concrete verwijzingen naar commits en stories
|
||||
- Claude Code gebruiken voor routinetaken zodat zij zich kan richten op complexere beslissingen
|
||||
|
||||
### Frustraties om te vermijden
|
||||
|
||||
- Systemen waarbij klantdata vermengd raakt (ze wil strikte projectscheiding)
|
||||
- Dashboards die statistieken tonen die ze niet nodig heeft (velocity, burndown)
|
||||
- Alles wat ze verplicht dagelijks bij te houden — ze heeft soms een dag vrij of ziek
|
||||
|
||||
### Relatie met technologie
|
||||
|
||||
Comfortabel maar niet fanatiek. Gebruikt VS Code, GitHub en een handvol SaaS-tools. Geen terminal-purist zoals Lars — ze gebruikt liever een goede UI dan een CLI als beide beschikbaar zijn.
|
||||
|
||||
---
|
||||
|
||||
## Remi
|
||||
|
||||
**Leeftijd:** 41 — **Situatie:** Lead developer van een team van drie, zonder dedicated projectmanager
|
||||
|
||||
### Achtergrond
|
||||
|
||||
Remi werkt bij een klein softwarebedrijf met in totaal acht mensen. Zijn team bestaat uit hemzelf en twee juniordevelopers. Ze bouwen interne tools voor twee afdelingen en onderhouden drie legacy-systemen. Er is geen Scrum Master, geen Product Owner en geen projectmanager — Remi doet dat er allemaal bij. Hij heeft Scrum gelezen en wil het toepassen, maar Jira is te zwaar en te duur voor hun schaal. Ze werken nu met een gedeeld Excel-bestand dat niemand consequent bijhoudt.
|
||||
|
||||
### Een typische slechte week
|
||||
|
||||
Ze beginnen maandagochtend zonder duidelijk Sprint-doel. Iedereen werkt aan wat binnenkomt. Woensdag vraagt zijn manager naar de status van het rapportage-dashboard. Remi weet dat er aan gewerkt is maar niet hoe ver het is. Hij moet twee junioren ondervragen en drie feature-branches bekijken om een antwoord te kunnen geven. Vrijdagmiddag hebben ze een "retrospective" die eigenlijk een statusmeeting is.
|
||||
|
||||
### Hoe hij het nu oplost
|
||||
|
||||
Excel voor taakregistratie, Teams-kanalen per project voor communicatie, GitHub Issues voor bugs. Drie systemen die niet met elkaar praten. Hij heeft Linear geprobeerd maar de leercurve was te groot voor de junioren. Hij zoekt iets dat hij in een middag kan inrichten en dat zijn teamleden zonder training kunnen gebruiken.
|
||||
|
||||
### Doelen met deze app
|
||||
|
||||
- Elke Sprint starten met een duidelijk Sprint Goal dat iedereen ziet
|
||||
- Wekelijks in één scherm kunnen zien wat open staat, wat in progress is en wat klaar is
|
||||
- In de toekomst rollen kunnen toewijzen aan teamleden zodat de Product Owner (hijzelf) en de Developers (de junioren) gescheiden rechten hebben
|
||||
|
||||
### Frustraties om te vermijden
|
||||
|
||||
- Alles wat de junioren extra werk geeft zonder directe waarde voor hen
|
||||
- Rapportages en grafieken die hij niet nodig heeft
|
||||
- Systemen waarbij hij afhankelijk is van een externe SaaS die duur wordt bij groei
|
||||
|
||||
### Relatie met technologie
|
||||
|
||||
Ervaren developer, maar kiest bewust voor eenvoud. Wil een tool die hij zelf kan hosten als dat goedkoper uitpakt. Waardeert open source maar heeft geen tijd om een tool te onderhouden die hij zelf gebouwd heeft.
|
||||
|
||||
---
|
||||
|
||||
## Persona-spanningen
|
||||
|
||||
| Spanning | Lars wil | Dina wil | Remi wil | Oplossing |
|
||||
|---|---|---|---|---|
|
||||
| API vs. UI | Alles via API en Claude Code | Goede UI, API is bonus | UI eerst, team moet het kunnen gebruiken | UI is primair; API is volwaardige burgerklasse, niet bijzaak |
|
||||
| Eén vs. meerdere gebruikers | Altijd solo | Solo maar met klantcontext | Team van 3 met rolscheiding | v1 is solo; rolbeheer is v1-fundament voor v2-teamuitbreiding |
|
||||
| Scrum-striktheid | Licht, pragmatisch | Structuur helpt maar geen dogma | Wil Scrum leren toepassen | Scrum-terminologie consistent; events zijn optioneel, niet verplicht |
|
||||
| Zichtbaarheid voortgang | Eigen overzicht, geen rapportages | Klantverantwoording via commits | Teamstatus in één scherm | Activiteitenlog per story volstaat voor alle drie; aparte rapportagelaag is v2 |
|
||||
|
||||
---
|
||||
|
||||
## Primaire persona
|
||||
|
||||
**Lars** is de primaire designtarget voor v1.
|
||||
|
||||
Rationale: Lars vertegenwoordigt de meest veeleisende gebruiker in termen van API-integratie en Claude Code-koppeling — de kern-differentiator van DevPlanner. Als de app goed werkt voor Lars (solo, API-gedreven, meerdere projecten, minimale overhead), werkt het ook voor Dina (zelfde gebruik, lichtere techvoorkeur). Remi's behoeften — teamgebruik en rolscheiding — zijn bewust naar v2 verschoven; het fundament (rolmodel in de datastructuur) wordt in v1 al gelegd.
|
||||
|
||||
Een feature die Lars zou doen stoppen — verplichte velden, trage UI, geen API — wordt niet gebouwd, ook niet als Remi er baat bij zou hebben.
|
||||
Loading…
Add table
Add a link
Reference in a new issue