Scrum4Me/docs/specs/dialogs/idea.md
Madhura68 2f41f8917a docs: idea-dialog profile (M12 T-513)
docs/specs/dialogs/idea.md:
- Velden-table with bron-zod links
- URL/state-pattern: dedicated route /ideas/[id] (afwijking van generieke
  modal-spec — rationale documented)
- 4-tab layout spec
- Full state-machine table with transition triggers + server actions
- Server-action catalog with preconditions + foutcodes
- 3-layer demo-policy (proxy + isDemo-guard + DemoTooltip), incl. wat
  demo WEL mag (download-md is read-only)
- Special behaviors: Cmd/Ctrl+S, localStorage draft (lazy seed),
  useMemo-derived validation, status-badge tokens, connectedWorkers
  via solo-store
- Realtime routing notes
- Test-fixture inventory (90+ cases across 7 test files)

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

9.2 KiB

title status audience language last_updated
IdeaDialog Profiel active
ai-agent
contributor
nl 2026-05-04

IdeaDialog / IdeaDetailLayout Profiel

Volgt docs/patterns/dialog.md (de generieke spec voor élke entity-dialog in Scrum4Me). Dit document beschrijft alleen de Idea-specifieke afwijkingen en keuzes — alle gedeelde regels (layout, motion, demo-policy, foutcodes, validatie, theming) 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 Bron-zod
title string (required) beide trim, 1-200 chars ideaCreateSchema.title
description string | null beide optional, max 4000 chars, plain textarea ideaCreateSchema.description
product_id string | null beide optional cuid; vereist voordat Grill/Make Plan kan starten (M12 grill-keuze 3) ideaCreateSchema.product_id
code string (auto) read-only IDEA-NNN, server-generated via nextIdeaCode(userId) op User.idea_code_counter n.v.t.
status IdeaStatus enum read-only door server gezet via state-machine lib/idea-status.ts canTransition
grill_md string | null edit-tab bewerkbaar in GRILLED | PLAN_READY n.v.t.
plan_md string | null edit-tab bewerkbaar in PLAN_READY + yaml-frontmatter must parse ideaPlanMdFrontmatterSchema
archived boolean read-only via archive-actie n.v.t.
pbi_id string | null read-only gezet door materializeIdeaPlanAction, SetNull als PBI verwijderd n.v.t.

URL- of state-pattern

Afwijking van generieke spec: Idee gebruikt een dedicated route /ideas/[id] ipv een modal-dialog. Reden: het detail-scherm is rijker dan een modal kan dragen (4 tabs incl. md-editor + timeline) en de planningsgeschiedenis is een leesbaar artifact dat verdiend om bookmarkable te zijn.

  • Lijst-create: state-based inline form bovenaan /ideas lijst (IdeaList.showCreate).
  • Detail / edit: route /ideas/[id] met tab-switcher via query-param (?tab=idee|grill|plan|timeline).
  • Geen modal: dus geen Cmd/Ctrl+Enter-submit op de detail-form (alleen op md-editor); Esc doet niets in het detail-scherm.

Tabs (alleen op detail-route)

Tab Content Editable in
idee inline form (title, description, product_id) DRAFT | GRILL_FAILED | GRILLED | PLAN_FAILED | PLAN_READY
grill grill_md markdown render + Bewerk-knop GRILLED | PLAN_READY
plan plan_md markdown render + Bewerk-knop PLAN_READY
timeline UNION van IdeaLog + ClaudeQuestion chronologisch n.v.t. (read-only)

isIdeaEditable, isGrillMdEditable en isPlanMdEditable helpers in lib/idea-status.ts bepalen de exacte regels.


Status-machine

DRAFT ──Grill──▶ GRILLING ─done──▶ GRILLED ──Make Plan──▶ PLANNING ─done──▶ PLAN_READY ──Materialiseer──▶ PLANNED
            │ fail                            │ fail              ▲                                          │
            ▼                                 ▼                   │                                          │
         GRILL_FAILED                      PLAN_FAILED            └─── re-grill / re-plan (append-context)   │
                                                                                                              │
PLANNED ◀── (PBI verwijderd: pbi_id=null, status blijft PLANNED tot Re-link) ────────────────────────────────┘
Van Naar Trigger Server-action
DRAFT GRILLING "Grill" knop startGrillJobAction
GRILLING GRILLED worker → update_idea_grill_md (MCP)
GRILLING GRILL_FAILED worker → update_job_status('failed') (MCP)
GRILLED / PLAN_FAILED / PLAN_READY GRILLING "Grill" knop (re-grill) startGrillJobAction
GRILLED / PLAN_FAILED / PLAN_READY PLANNING "Plan" knop startMakePlanJobAction
PLANNING PLAN_READY worker → update_idea_plan_md (parser ok) (MCP)
PLANNING PLAN_FAILED worker → update_job_status('failed') of parse-fail (MCP)
PLAN_READY PLANNED "Maak PBI" knop materializeIdeaPlanAction
PLANNED (pbi_id=null) PLAN_READY "Plan opnieuw beschikbaar maken" knop relinkIdeaPlanAction
any * archived Archive-knop archiveIdeaAction

Server actions

actions/ideas.ts:

Actie Precondition Effect
createIdeaAction(input) auth + niet-demo nieuwe DRAFT-idea + auto-code
updateIdeaAction(id, input) isIdeaEditable(status) update title/description/product_id
archiveIdeaAction(id) / unarchiveIdeaAction(id) scoped on user_id flip archived
deleteIdeaAction(id) pbi_id === null hard delete (cascades naar IdeaLog)
updateGrillMdAction(id, md) isGrillMdEditable(status) update + IdeaLog{NOTE}
updatePlanMdAction(id, md) isPlanMdEditable(status) + parsePlanMd.ok update + IdeaLog{NOTE}
startGrillJobAction(id) product+repo + worker actief + status in GRILL_TRIGGERABLE_FROM enqueue ClaudeJob{kind:IDEA_GRILL}
startMakePlanJobAction(id) idem + status in MAKE_PLAN_TRIGGERABLE_FROM enqueue ClaudeJob{kind:IDEA_MAKE_PLAN}
cancelIdeaJobAction(id) actieve job aanwezig job→CANCELLED + status revert
materializeIdeaPlanAction(id) status===PLAN_READY + plan_md parseable atomic create PBI + stories + tasks; idea→PLANNED
relinkIdeaPlanAction(id) status===PLANNED && pbi_id===null status→PLAN_READY
downloadIdeaMdAction(id, kind) scope (demo OK, read-only) return md-string
promoteTodoToIdeaAction(todoId) (in actions/todos.ts) todo niet archived + niet-demo DRAFT-idea + Todo→archived

Foutcodes: 400 = JSON parse, 401 = auth, 403 = demo, 404 = scope/not-found, 409 = idempotency/race, 422 = validatie/status-mismatch, 429 = rate-limit.


Demo-policy (3-laag)

Laag Wat Waar
1 proxy.ts blokt POST/PATCH/DELETE /api/ideas* proxy.ts catch-all rule
2 session.isDemo guard in elke muteer-actie actions/ideas.ts
3 <DemoTooltip show={isDemo}> rondom muteer-knoppen idea-row-actions.tsx, idea-list.tsx, idea-detail-layout.tsx, download-md-button.tsx (NIET — read-only mag)

Demo-user MAG: lijst zien, idee zien, navigeren tussen tabs, downloaden van md. Demo-user MAG NIET: aanmaken, bewerken, archiveren, Grill, Plan, Materialiseer, Re-link, Promote-from-Todo.


Special behaviors

IdeaMdEditor

  • Cmd/Ctrl+S triggert save (alleen in editor, niet in detail-form).
  • localStorage draft per (idea_id, kind): lazy read-on-mount via useState(() => readSeed(...)) om setState-in-effect te vermijden. Drift met server → toast info bij restore.
  • Live yaml-validate voor plan-kind: useMemo(() => parsePlanMd(value)) → derived state, geen useEffect.
  • Submit-errors los van validation-errors in state — server-side details overschrijven client-side validate als die er zijn.

IdeaPbiLinkCard

  • Drie states: PLANNED+pbi (groene link), PLANNED+pbi-null (oranje banner met Re-link knop), niet-PLANNED (return null).

Status badges

  • Status-tokens via lib/idea-status-colors.tsgetIdeaStatusBadge(status){ label, classes, pulse? }.
  • GRILLING en PLANNINGanimate-pulse om "actief" te signaleren.

Connected workers

  • IdeaRowActions leest useSoloStore(s => s.connectedWorkers) (M12 grill-keuze 16 — geen lift naar gedeelde store voor v1).
  • Zonder worker: Grill / Make Plan disabled met tooltip "Geen Claude-worker actief". Materialiseer is server-side synchroon en heeft géén worker nodig.

Realtime

SSE-stream /api/realtime/notifications levert idea-events (M12 T-502). Routing in lib/realtime/use-notifications-realtime.ts:

  • claude_job_* payloads met kind=IDEA_*useIdeaStore.handleIdeaJobEvent
  • entity:'question' payloads met idea_id set → useIdeaStore.handleIdeaQuestionEvent
  • Story-questions blijven in useNotificationsStore

useIdeaStore houdt optimistic state: jobByIdea, ideaStatuses, openQuestionsByIdea. Voor de detail-pagina is de server-state na router.refresh() source-of-truth — de store is een UI-cache.


Test-fixtures

  • __tests__/actions/ideas-crud.test.ts (39 cases) — alle CRUD + job-trigger + materialize + relink paden
  • __tests__/api/ideas.test.ts (13 cases) — REST-laag
  • __tests__/stores/idea-store.test.ts (7 cases) — Zustand event-handling
  • __tests__/lib/idea-status.test.ts (15+ cases) — status mappers + transition guards
  • __tests__/lib/idea-schemas.test.ts (16+ cases) — zod-validatie
  • __tests__/lib/idea-plan-parser.test.ts (6 cases) — yaml-frontmatter
  • __tests__/proxy/demo-guard.test.ts (9 cases) — incl. 3 idea-cases

Geen Playwright/MSW E2E voor v1 — handmatig E2E-script staat in docs/plans/M12-ideas.md "Verificatie".