docs(T-1015): PB-workflow doc — as-is workflow-states, transitions en diagram
Tweede laag van het Product Backlog page workflow-doc (PBI-88 / ST-1363): de zeven impliciete workflow-states met preconditie en UI-gedrag, de transition-tabel, en een Mermaid stateDiagram-v2. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
cb5150c2ae
commit
bf42609d6d
1 changed files with 75 additions and 0 deletions
|
|
@ -92,3 +92,78 @@ Spiegelbeeld voor de sprint-board: stories/taken *binnen* één sprint. Zelfde s
|
|||
Gebruikersvoorkeuren + cross-tab sync. Voor dit scherm cruciaal: **`workflow.pendingSprintDraft[productId]`** — de concept-sprint die ontstaat bij "Nieuwe sprint". Sinds PBI-79 is die draft **session-only**: `setPendingSprintDraft` / `clearPendingSprintDraft` schrijven alleen lokaal (geen server-roundtrip), en `hydrate()` stript legacy DB-entries weg zodat de draft niet "spookt". `upsertPbiIntent` / `upsertStoryOverride` muteren de draft-selectie.
|
||||
|
||||
Slice-details van het workspace-patroon (`ensure*Loaded`, selectors, optimistic mutations, gotchas) staan in [workspace-store.md](../patterns/workspace-store.md) — hier niet gedupliceerd.
|
||||
|
||||
## Workflow-states (as-is)
|
||||
|
||||
Het scherm kent vandaag **geen expliciete `screenState`**. De zichtbare toestand wordt per render ad-hoc afgeleid uit SSR-flags in `app/(app)/products/[id]/page.tsx` (`isActiveProduct`, `hasOpenSprint`, `isDemo`, plus `sprintItems` / `activeSprintItem` / `buildingSprintIds` uit `getSprintSwitcherData`) en store-flags (`pendingSprintDraft` uit `user-settings`, `sprintMembership.pending` uit `product-workspace`). Daaruit zijn **zeven** herkenbare states te destilleren.
|
||||
|
||||
### 1. `PRODUCT_NOT_ACTIVE`
|
||||
**Preconditie:** `isActiveProduct === false` (`user.active_product_id !== id`).
|
||||
**UI:** `SprintSwitcher` wordt niet gerenderd (`{isActiveProduct && …}`); `ActivateProductButton` zichtbaar. De PBI/Story/Task-panes werken normaal. `NewSprintTrigger` is wél zichtbaar (alleen demo-gated, geen `isActiveProduct`-gate); `SaveSprintButton` niet.
|
||||
|
||||
### 2. `PRODUCT_ACTIVE_NO_SPRINTS`
|
||||
**Preconditie:** `isActiveProduct === true` && `sprintItems.length === 0`.
|
||||
**UI:** `SprintSwitcher` rendert maar valt terug op de "Geen sprints"-tooltip (early return in `sprint-switcher.tsx`). `NewSprintTrigger` enabled. Geen `SaveSprintButton`, geen "Sprint actief →"-link (`hasOpenSprint === false`).
|
||||
|
||||
### 3. `SPRINT_DRAFT_PENDING`
|
||||
**Preconditie:** `user-settings.workflow.pendingSprintDraft[productId]` is truthy (session-only).
|
||||
**UI:** sticky `SprintDefinitionBanner` onder de header (`sprint-draft-banner.tsx`); `NewSprintTrigger` verbergt zichzelf (`if (hasDraft) return null`); `SprintSwitcher` toont een *disabled* "⚙ Concept — [goal]"-item; `SprintDraftLeaveGuard` registreert een `beforeunload`-waarschuwing. De cherrypick-checkboxes in de PBI/Story-panes schakelen naar draft-selectie-modus.
|
||||
|
||||
### 4. `ACTIVE_SPRINT_CLEAN`
|
||||
**Preconditie:** `isActiveProduct` && `activeSprintItem !== null` && `selectIsDirty === false`.
|
||||
**UI:** `SprintSwitcher` toont de actieve sprint-code + status; `SaveSprintButton` zichtbaar maar **disabled** (`disabled={!isDirty || …}`), label "Sprint opslaan"; "Sprint actief →"-link zichtbaar zolang er een open sprint is.
|
||||
|
||||
### 5. `ACTIVE_SPRINT_DIRTY`
|
||||
**Preconditie:** als 4, maar `selectIsDirty === true` — `sprintMembership.pending.adds`/`removes` is niet leeg.
|
||||
**UI:** als 4, maar `SaveSprintButton` **enabled** met teller — label `Sprint opslaan (N)`.
|
||||
|
||||
### 6. `ACTIVE_SPRINT_BUILDING`
|
||||
**Preconditie:** `activeSprintItem` && `buildingSprintIds.includes(activeSprintItem.id)` — er loopt een `SprintRun` met status `QUEUED`/`RUNNING` (`getSprintSwitcherData`).
|
||||
**UI:** als 4/5, maar `SprintSwitcher` toont een gele **"BUILDING"**-badge i.p.v. de sprint-status. Cosmetisch — er worden geen knoppen geblokkeerd.
|
||||
|
||||
### 7. `DEMO_MODE`
|
||||
**Preconditie:** `session.isDemo === true`.
|
||||
**Strikt genomen geen state** maar een **cross-cutting constraint** over alle bovenstaande states: schrijf-knoppen (`ActivateProductButton`, `SaveSprintButton`, `NewSprintTrigger`, `EditProductButton`) zijn verborgen of `disabled`; de `SprintSwitcher` navigeert i.p.v. een server-action aan te roepen; en de server-actions zelf returnen vroeg met `{ error: 'Niet beschikbaar in demo-modus' }`.
|
||||
|
||||
## Transitions (as-is)
|
||||
|
||||
Er is geen centrale overgangs-tabel; elke transitie is een server-action of store-mutatie gevolgd door een re-render:
|
||||
|
||||
| Van → naar | Trigger |
|
||||
|---|---|
|
||||
| `PRODUCT_NOT_ACTIVE` → `PRODUCT_ACTIVE_*` | `setActiveProductAction` (`actions/active-product.ts`) → `revalidatePath('/', 'layout')` |
|
||||
| `PRODUCT_ACTIVE_NO_SPRINTS` / `ACTIVE_SPRINT_*` → `SPRINT_DRAFT_PENDING` | `NewSprintTrigger` → `NewSprintMetadataDialog` → `setPendingSprintDraft` (`user-settings` store, session-only) |
|
||||
| `SPRINT_DRAFT_PENDING` → vorige state | `SprintDefinitionBanner` "Annuleren" → `clearPendingSprintDraft` |
|
||||
| `SPRINT_DRAFT_PENDING` → `ACTIVE_SPRINT_CLEAN` | `SprintDefinitionBanner` "Sprint aanmaken" → `createSprintWithSelectionAction` (`actions/sprints.ts`): maakt de sprint, koppelt de geselecteerde stories, en de nieuwe sprint wordt de actieve sprint |
|
||||
| `ACTIVE_SPRINT_CLEAN` ↔ `ACTIVE_SPRINT_DIRTY` | `toggleStorySprintMembership` (`product-workspace` store) maakt dirty; `commitSprintMembershipAction` via `SaveSprintButton` → `applyMembershipCommitResult` maakt weer clean |
|
||||
| `ACTIVE_SPRINT_*` → andere `ACTIVE_SPRINT_*` | `SprintSwitcher` dropdown → `switchActiveSprintAction` (`actions/active-sprint.ts`) |
|
||||
| `ACTIVE_SPRINT_*` → `PRODUCT_ACTIVE_NO_SPRINTS` (geen actieve sprint) | `SprintSwitcher` "— Geen actieve sprint —" → `clearActiveSprintAction` |
|
||||
| `ACTIVE_SPRINT_CLEAN/DIRTY` ↔ `ACTIVE_SPRINT_BUILDING` | extern: een `SprintRun` gaat naar `QUEUED`/`RUNNING` resp. rondt af — zichtbaar bij de volgende SSR-render |
|
||||
|
||||
Server-actions roepen `revalidatePath` aan; de client doet daarnaast `router.refresh()` (bv. na sprint-creatie). De realtime-laag (SSE → `applyRealtimeEvent`) dekt *externe* wijzigingen — zie de sectie Architectuur-lagen hierboven.
|
||||
|
||||
## State-diagram
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> PRODUCT_NOT_ACTIVE
|
||||
PRODUCT_NOT_ACTIVE --> PRODUCT_ACTIVE_NO_SPRINTS: setActiveProductAction
|
||||
|
||||
PRODUCT_ACTIVE_NO_SPRINTS --> SPRINT_DRAFT_PENDING: Nieuwe sprint / setPendingSprintDraft
|
||||
SPRINT_DRAFT_PENDING --> PRODUCT_ACTIVE_NO_SPRINTS: Annuleren / clearPendingSprintDraft
|
||||
SPRINT_DRAFT_PENDING --> ACTIVE_SPRINT: Sprint aanmaken / createSprintWithSelectionAction
|
||||
|
||||
state ACTIVE_SPRINT {
|
||||
[*] --> CLEAN
|
||||
CLEAN --> DIRTY: toggleStorySprintMembership
|
||||
DIRTY --> CLEAN: commitSprintMembershipAction
|
||||
CLEAN --> BUILDING: SprintRun QUEUED/RUNNING
|
||||
BUILDING --> CLEAN: SprintRun klaar
|
||||
}
|
||||
|
||||
ACTIVE_SPRINT --> SPRINT_DRAFT_PENDING: Nieuwe sprint / setPendingSprintDraft
|
||||
ACTIVE_SPRINT --> ACTIVE_SPRINT: switchActiveSprintAction
|
||||
ACTIVE_SPRINT --> PRODUCT_ACTIVE_NO_SPRINTS: clearActiveSprintAction
|
||||
```
|
||||
|
||||
`DEMO_MODE` staat bewust niet in het diagram: het is geen knoop in de graaf maar een read-only constraint die over álle states heen ligt.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue