Scrum4Me/docs/specs/dialogs/pbi.md
Madhura68 97dc4ee553 feat(pbi-dialog): conform aan dialog-pattern + DemoTooltip + dirty-guard
Story 3 van PBI "Alle dialogen conform docs/patterns/dialog.md".

- lib/schemas/pbi.ts — gedeeld zod-schema (createPbiSchema/updatePbiSchema)
- actions/pbis.ts — returnen nu code+fieldErrors (422) en code: 403 voor
  auth/demo errors
- PbiDialog adopt useDirtyCloseGuard, useDialogSubmitShortcut,
  entityDialog* layout-classes; submit-knop + Annuleren in DemoTooltip
- isDemo-prop toegevoegd, pbi-list geeft 'm door
- docs/specs/dialogs/pbi.md — "Bekende gaps" weggewerkt; alleen bewuste
  uitsluitingen blijven

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 07:23:14 +02:00

126 lines
5.7 KiB
Markdown

---
title: "PbiDialog Profiel"
status: active
audience: [ai-agent, contributor]
language: nl
last_updated: 2026-05-04
---
# 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 `useActionState` (Server Actions / native React), niet `react-hook-form`. Dit is een toegestaan alternatief volgens de generieke spec § 2. Field-errors komen uit het action-result als `result.fieldErrors: Record<string, string[]>` met `result.code === 422`; een lokale `fieldError(field)`-helper levert het eerste bericht op.
### Dirty-tracking handmatig
Omdat we geen `react-hook-form` gebruiken, zetten we `dirty` op `true` bij de eerste `onChange` op het form (en bij wijzigingen van de hidden-state-velden `priority`/`status`). De useDirtyCloseGuard hook gebruikt dit boolean om Esc/Cancel-sluiting te beschermen.
### `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, ... })`
---
## Bewust NIET in v1 (PBI-specifiek)
- ❌ **Geen delete-knop / `deletePbiAction`-trigger vanuit deze dialog** — PBI's worden niet vernietigend verwijderd vanuit de UI; wijzig de status naar `done` of archiveer via een ander mechanisme. `deletePbiAction` bestaat in de codebase voor server-side cleanup maar wordt niet vanuit deze dialog aangeroepen.
- ❌ **Geen char-counter / markdown-hint** op description — PBI-descriptions zijn doorgaans kort en richtinggevend; auto-grow en markdown-rendering horen op StoryDialog/TaskDialog.
---
## 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/design/styling.md` — MD3-tokens, status- en priority-kleuren