Commit graph

152 commits

Author SHA1 Message Date
Scrum4Me Agent
0a06b1dc74 feat(ideas): resolve active_product_id en geef door aan IdeaList
Haalt active_product_id op via prisma.user.findUnique en resolveert
het tegen de al opgehaalde toegankelijke productenlijst (AC4). Geeft
het resultaat als prop door aan IdeaList in plaats van hardcoded null.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-15 04:33:47 +02:00
Scrum4Me Agent
2150038066 feat(ideas): voeg activeProductId-prop toe aan IdeaList
IdeaListProps uitgebreid met activeProductId: string | null.
Create-form initialiseert en reset naar het actieve product na aanmaken.
Tests en page.tsx bijgewerkt (page.tsx krijgt echte waarde in volgende taak).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-15 04:27:10 +02:00
Janpeter Visser
3d5c22382c
feat(PBI-91): expliciete schermstaat + draft-zichtbaarheid PB-page (#210)
* docs(ST-1369): plan PBI-91 — expliciete schermstaat + draft-zichtbaarheid

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

* feat(ST-1369): screen-state module — ScreenState + deriveScreenState()

Pure afleidingslaag die de verspreide schermstaat-derivatie van de Product
Backlog page consolideert tot één testbaar ScreenState-model. Nog geen
consumers — die volgen in T-1035/T-1036.

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

* test(ST-1369): unit-tests voor deriveScreenState()

Dekt alle vier de kinds (NO_SPRINT, DRAFT, ACTIVE, EDITING), de building-flag
en de draft-voorrang boven een actieve sprint.

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

* feat(ST-1369): SprintSwitcher op deriveScreenState + draft op trigger (G5)

De trigger-knop toont nu de concept-sprint zodra er een sprint-draft loopt,
niet langer alleen de (disabled) dropdown-regel. Schermstaat-afleiding loopt
via de pure deriveScreenState() i.p.v. losse flags.

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

* feat(ST-1369): NewSprintTrigger achter isActiveProduct-gate (G6)

De "Nieuwe sprint"-knop rendert niet langer op een niet-actief product —
een sprint-draft starten daar was verwarrend. page.tsx geeft de bestaande
isActiveProduct-flag door.

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

* test(ST-1369): component-tests voor draft-op-trigger (G5) en isActiveProduct-gate (G6)

sprint-switcher: trigger toont concept-sprint bij een pending draft, en geen
concept-label zonder draft. new-sprint-trigger: nieuw testbestand — rendert
niet op een niet-actief product, wel op een actief product zonder draft.

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

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 01:45:35 +02:00
Janpeter Visser
2a6386163c
Sprint: Jobs scherm (#209)
* refactor(jobs): extraheer job-mapper naar lib/jobs-mapper.ts + voeg breadcrumb-velden toe

Verplaatst JobWithRelations, JOB_INCLUDE, RawJob, PriceRow, pickDescription,
computeCost en mapJob naar lib/jobs-mapper.ts (zonder 'use server'). Voegt
buildPriceMap helper toe en breidt de types uit met productCode, storyCode en
pbiCode via task->story->pbi en product.code includes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(jobs): voeg GET /api/jobs/[id] route toe + tests

* feat(jobs): useJobsRealtime fetch-on-unknown met dedup-Set

Wanneer een SSE-event een onbekend job_id bevat, haalt de hook de volledige
job op via GET /api/jobs/[id] en upsert die in de store. Een inFlight-Set
voorkomt gelijktijdige dubbele fetches voor hetzelfde job_id. Bekende jobs
blijven de bestaande partial-upsert gebruiken. Zelfde logica in jobs_initial.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(jobs): JobCard breadcrumb + datum-fallback per kind

Voeg productCode/pbiCode/storyCode/startedAt/finishedAt toe aan
JobCardProps; bouw breadcrumb per job-kind en toon finishedAt → startedAt
→ createdAt als datum. JobsColumn geeft de nieuwe velden door.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test(jobs): JobCard breadcrumb + datum-fallback tests

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-15 01:25:20 +02:00
Janpeter Visser
3ad352c10f
Sprint: ll (#206)
* feat(jobs): voeg lib/jobs-time-filter.ts toe met tijdvenster-predikaat

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(user-settings): voeg views.jobs.timeFilter toe aan UserSettingsSchema

Breidt ViewsPrefs uit met een jobs-object (JobsViewPrefs) dat timeFilter
accepteert met waarden '1h' | '24h' | 'all'. ViewsPrefs blijft .strict().

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test(jobs-time-filter): voeg unit-tests toe voor isWithinTimeWindow en UserSettings-schema

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(jobs-time-filter): voeg JobsTimeFilterControl component toe

Nieuw client-component dat views.jobs.timeFilter leest/schrijft
via useUserSettingsStore met pill-stijl (MD3-tokens).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(jobs): wire JobsTimeFilter in jobs page header

Plaatst het tijdfilter-component rechts van de Jobs-kop via justify-between op de header-div.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(jobs): pas tijdvenster-filter toe in JobsColumn

Lees views.jobs.timeFilter uit de store en filter jobs op createdAt via isWithinTimeWindow, als eerste check vóór de bestaande kind/status-filters.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 21:06:59 +02:00
Janpeter Visser
ff22196714
Sprint: Stories en taken krijgen één voorspelbare volgorde gekoppeld aan hun code; drag-and-drop herordening voor stories/taken verdwijnt, priority wordt puur label. (#201)
* feat(code): add parseCodeNumber helper to lib/code.ts

Pure helper that extracts the trailing numeric sequence from a code string
(ST-007 → 7, T-42 → 42). Non-conforming codes fall back to Number.MAX_SAFE_INTEGER
so they sort to the end. Includes 5 unit tests.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(tasks): add code field to BacklogTask type and all task selects

Adds `code: string | null` to BacklogTask interface and includes it in
all Prisma task.findMany selects (backlog API, stories tasks API, page
hydration routes). Updates coerceTaskPayload and test fixtures to match.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(sort-order): derive story/task sort_order from parseCodeNumber(code)

All create paths (createStoryAction, saveTask, createTaskAction,
materializeIdeaPlanAction) and code-edit paths (updateStoryAction, saveTask
update) now set sort_order = parseCodeNumber(code) instead of last+1.
Removes stale last-record queries from create paths.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(sort-order): decouple sprint membership actions from sort_order

createSprintAction and addStoryToSprintAction no longer write sort_order
when adding stories to a sprint. sort_order is derived from code via
parseCodeNumber, so membership should only set sprint_id + status.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor(ordering): remove priority from all story/task orderBy

Story- en taak-ordering is nu puur sort_order asc (created_at als
tiebreaker). PBI-ordering (priority + sort_order) blijft ongewijzigd.

Gewijzigd: backlog/route, pbis/stories/route, claude-context/route,
next-story/route, workspace/route, tasks/route, sprint-runs (query +
in-memory sort), solo-workspace-server, page.tsx (app + mobile + sprint),
store compareStory, actions/sprints story-query, next-story test.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor(dnd): remove drag-and-drop reorder for stories and tasks

- Remove reorderStoriesAction, reorderTasksAction, reorderSprintStoriesAction
- Delete REST route app/api/stories/[id]/tasks/reorder/route.ts
- Remove DnD from backlog story-panel and task-panel (flat list)
- Remove reorder-within-sprint branch from sprint-board-client handleDragEnd
- Switch SortableSprintRow to plain SprintRow using useDraggable (membership drag kept)
- Remove all DnD from task-list (status toggle + edit kept)
- Remove story-order/task-order/sprint-story-order/sprint-task-order mutation types and store handlers
- Remove related tests for deleted reorder route; fix sprint store tests

* feat(backlog): toon code-badge op backlog-taakkaarten

Geeft code={task.code} door aan <BacklogCard> in TaskCard (task-panel.tsx).
BacklogCard rendert de CodeBadge al conditionally — alleen de prop ontbrak.

* feat(migration): backfill story/task sort_order from code numeric suffix

One-time Prisma migration that sets sort_order = trailing numeric part
of code for all existing stories and tasks, consistent with
parseCodeNumber (fallback = Number.MAX_SAFE_INTEGER for non-conforming
codes). PBIs are intentionally excluded.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs+tests(sort-order): update for code-binding order on stories/tasks

- Rewrite docs/patterns/sort-order.md: float-insertion PBI only; story/task
  sort_order = parseCodeNumber(code), never drag/membership mutated
- Update plan-to-pbi-flow.md: sort_order auto, sprint_id param, priority=label
- Update make-plan.md: priority=label, array order = execution order
- Update rest-contract.md: fix sprint-tasks ordering, remove reorder endpoint
- Add ADR-0011: code is bindende volgordesleutel voor stories/taken
- Regenerate docs/INDEX.md via npm run docs
- Remove reorderStoriesAction/reorderTasksAction mocks from backlog tests
- Remove dnd-kit mocks from task-panel test (panel no longer uses dnd)
- Extend materializeIdeaPlanAction test: assert sort_order=parseCodeNumber(code)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 19:02:36 +02:00
Janpeter Visser
d84cdf664f
feat(PBI-67): IDEA_REVIEW_PLAN — iterative multi-model plan review (#199)
* feat(ideas): upload-plan knop — short-circuit van Make-Plan AI-flow

Voegt een 'Upload plan' knop toe in idea-row-actions (verschijnt in zowel
list als idea-detail). Klik → file picker → kies .md → server-side parse +
opslaan; idea-status springt naar PLAN_READY. Vandaaruit de bestaande
'Maak PBI' knop voor materialize.

Server (uploadPlanMdAction):
- Toegestaan vanuit DRAFT, GRILLED, PLAN_FAILED, PLAN_READY
- DRAFT → skip-grill: status gaat direct naar PLAN_READY
- PLAN_READY overschrijft het bestaande plan (consistent met
  updatePlanMdAction, geen confirmation)
- Geblokkeerd in GRILLING/PLANNING (job loopt), PLANNED (al gematerialiseerd)
- Parse-failure → 422 + details (NIET opslaan, zodat een onparseerbaar plan
  nooit in de DB belandt)
- Empty / >100k chars → 422
- Schrijft IdeaLog NOTE met from_status + length
- Rate-limit + demo-guard + ownership-check via loadOwnedIdea (zelfde
  patroon als updatePlanMdAction)

UI (idea-row-actions.tsx):
- Hidden <input type=file accept=".md,.markdown,text/markdown,text/plain">
- FileReader → text → action
- Toast bij success + router.refresh()
- Blocked-tooltip in andere statussen

Tests: 10 nieuwe in __tests__/actions/ideas-crud.test.ts dekkend voor:
happy paths (DRAFT/GRILLED/PLAN_READY-overwrite/PLAN_FAILED), blocks
(PLANNED/GRILLING), validation (empty/oversized/parse-fail), 404.
Full suite groen: 849/849.

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

* Add reviews for Bootstrap-wizard plans v3.2 to v3.4

- Review v3.2: Addressed executor model, fire-and-forget issues, and PAT handling.
- Review v3.3: Improved transaction handling, stale recovery, and ID generation.
- Review v3.4: Finalized GitHub permissions, catalog versioning, and E2E verification queries.
- Updated recommendations for each version to enhance implementation readiness.

* docs(plans): M8 bootstrap-wizard upload-variant v1.4 — backtick-paden

Upload-variant van het volledige technische plan (docs/plans/M8-bootstrap-wizard.md),
bedoeld voor de "Upload plan"-functie. Genereert 1 PBI + 4 Stories + 22 Tasks
via materializeIdeaPlanAction.

v1.4-aanpassingen tov eerdere generatie-iteratie:
- Alle bestandspaden in implementation_plan in backticks (path-extractor matchen)
- Expliciete "Bestanden:" blok per task vóór de stappen
- Alle tasks op verify_required: ALIGNED_OR_PARTIAL (was deels ALIGNED — te strict
  voor ADR-stubs en multi-file edits)

Fixt forward-only: T-963 cancelled_by_self door DIVERGENT verifier-verdict.
Re-upload van dit bestand produceert tasks die door verify_task_against_plan
als ALIGNED of PARTIAL geclassificeerd kunnen worden.

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

* PBI-67: Add review-plan support to Idea model and job config

- Add plan_review_log and reviewed_at fields to Idea model
- Add REVIEWING_PLAN, PLAN_REVIEW_FAILED, PLAN_REVIEWED to IdeaStatus enum
- Add IDEA_REVIEW_PLAN to ClaudeJobKind enum
- Add IDEA_REVIEW_PLAN config to job-config.ts with model=opus, thinking_budget=6000
- Create migration record for schema changes (applied via db push)

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* PBI-67 Phase 2: Add update-idea-plan-reviewed MCP tool

- Create src/tools/update-idea-plan-reviewed.ts: saves review-log and transitions idea status to PLAN_REVIEWED
- Add PLAN_REVIEW_RESULT to IdeaLogType enum (both repos)
- Register tool in src/index.ts
- Update Prisma schemas (both repos): add plan_review_log and reviewed_at fields to Idea model
- Add REVIEWING_PLAN, PLAN_REVIEW_FAILED, PLAN_REVIEWED to IdeaStatus enum (MCP schema)
- Add IDEA_REVIEW_PLAN to ClaudeJobKind enum (MCP schema)
- Tool includes transaction safety and convergence metrics logging

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* feat(PBI-67): IDEA_REVIEW_PLAN Phases 3-6 — server actions, UI components, prompt & tests

- Phase 3: startReviewPlanJobAction, cancelIdeaJobAction, status transitions
  (REVIEWING_PLAN / PLAN_REVIEWED / PLAN_REVIEW_FAILED), status colors,
  job-card/jobs-column filters, idea-list status tabs
- Phase 4: review-plan-job.md prompt (multi-model orchestration with codex
  injection + active plan revision via update_idea_plan_md after each round),
  runbook, 13 unit tests
- Phase 5: ReviewLogViewer component (rounds, convergence, approval, issues),
  idea-detail integration, proper ReviewLog TypeScript types exported from component
- Phase 6.1: wait-for-job discriminator wired (IDEA_REVIEW_PLAN), plan-revision
  step made mandatory in prompt (was previously optional/missing)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 03:35:02 +02:00
Janpeter Visser
d587be2fb3
feat(PBI-79): Product Backlog sprint-membership via vinkjes (#190)
* feat(PBI-79/ST-1333): active-sprint null-contract + clearActiveSprintAction

- lib/user-settings.ts: activeSprints values nullable in Zod-schema.
  Key-aanwezigheid heeft nu betekenis (key+null = bewust geen sprint;
  key ontbreekt = fallback-cascade).
- lib/active-sprint.ts: nieuwe readStoredActiveSprintState helper +
  resolveActiveSprint respecteert expliciet 'cleared' state zonder fallback.
  clearActiveSprintInSettings schrijft null i.p.v. de key te verwijderen.
- actions/active-sprint.ts: nieuwe clearActiveSprintAction met auth +
  membership-check.
- components/shared/sprint-switcher.tsx: '— Geen actieve sprint —'-optie
  in dropdown, disabled wanneer er geen actieve sprint is.
- Tests: nieuwe active-sprint.test.ts (resolver-paden + clear),
  active-sprint-action.test.ts (action-laag), uitbreiding user-settings.test.ts.

Plan: docs/plans/PBI-79-backlog-sprint-workflow.md

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

* feat(PBI-79/ST-1334): user-settings pendingSprintDraft-slot

- lib/user-settings.ts: nieuw workflow.pendingSprintDraft veld met
  compacte intent-shape (pbiIntent + per-PBI storyOverrides).
- actions/sprint-draft.ts: setPendingSprintDraftAction +
  clearPendingSprintDraftAction met product-membership-check + Zod-validatie.
- stores/user-settings/store.ts: setPendingSprintDraft / clearPendingSprintDraft
  optimistic acties + fine-grained mutators upsertPbiIntent / upsertStoryOverride.
  Sprint-draft actions worden dynamisch geïmporteerd zodat jsdom-tests
  zonder DATABASE_URL niet falen.
- Tests: nieuwe sprint-draft.test.ts (action-laag), uitbreiding
  user-settings store-tests (5 nieuwe cases) en schema-tests (4 cases).

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

* feat(PBI-79/ST-1343): sprint-conflicts helper-library

- lib/sprint-conflicts.ts: drie pure/server-side helpers voor eligibility
  + cross-sprint detectie.
  - isEligibleForSprint(story): sprint_id IS NULL en status != DONE
  - partitionByEligibility(prisma, storyIds, excludeSprintId): split in
    eligible / notEligible / crossSprint met reden per story
  - getBlockingSprintMap(prisma, productId, storyIds, excludeSprintId):
    map storyId → { sprintId, sprintName } voor stories in andere OPEN sprint
- Tests: __tests__/lib/sprint-conflicts.test.ts (16 cases) — alle eligibility
  paden + cross-sprint scoping + CLOSED-sprint filtering.

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

* feat(PBI-79/ST-1335): sprint-membership-summary + cross-sprint-blocks endpoints

Twee nieuwe GET-route handlers, beide verplicht gescoped op pbiIds (geen
product-brede aanroepen).

- app/api/products/[id]/sprint-membership-summary/route.ts
  Response: { [pbiId]: { total, inSprint } } via twee prisma.groupBy calls
  (totaal + binnen actieve sprint). Voor state-B tri-state.

- app/api/products/[id]/cross-sprint-blocks/route.ts
  Response: { [storyId]: { sprintId, sprintName } } voor stories in andere
  OPEN sprints. UX-hint voor disabled-vinkjes; commit-acties blijven
  autoritatief.

Tests: 13 cases dekken happy path, 400 zonder pbiIds, 400 zonder sprintId,
404 zonder product-access, auth-fail, en NOT-clause voor excludeSprintId.

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

* feat(PBI-79/ST-1336): product-workspace sprint-membership slice + selectors

Datalaag voor de vinkje-UI van state A′ en state B.

types.ts:
- PbiSummaryEntry, CrossSprintBlock, SprintMembershipSlice toegevoegd.

store.ts:
- Nieuwe slice `sprintMembership` met pbiSummary, crossSprintBlocks,
  pending: { adds[], removes[] }, loadedSummaryForSprintId.
- Acties: setPbiSummary, setCrossSprintBlocks, toggleStorySprintMembership
  (cancel-out logic), resetSprintMembershipPending, fetchSprintMembershipSummary,
  fetchCrossSprintBlocks.
- hydrateSnapshot reset óók de membership-slice.

selectors.ts:
- selectPbiTriState (aggregate-only zolang stories niet geladen; rekent
  pending mee bij loaded PBI's).
- selectStoryEffectiveInSprint (DB ⊕ pending).
- selectStoryIsBlocked (cross-sprint hint).
- selectIsDirty, selectPendingCount.

Tests: 25 cases in nieuwe sprint-membership.test.ts dekken alle selector-
paden, toggle-cancel-out, fetch-helpers, en pbiId-scoping.

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

* feat(PBI-79/ST-1337): state A′ UI — metadata dialog + sticky banner + PbiList ombouw

UI-laag voor de sprint-definitie-flow (state A′).

Nieuw:
- NewSprintMetadataDialog (stap 1): sprint_goal + optionele dates;
  'Verder' schrijft via useUserSettingsStore.setPendingSprintDraft.
- SprintDefinitionBanner (sticky): toont doel + X PBI's / Y stories teller;
  'Annuleren' → AlertDialog confirm → clearPendingSprintDraft;
  'Sprint aanmaken' nog niet aangesloten (wacht op ST-1339).
- NewSprintTrigger: button in page header die de metadata-dialog opent;
  verbergt zichzelf zolang er al een draft loopt.
- SprintDraftBanner: client-wrapper, rendert banner alleen als draft bestaat.

Wijzigingen:
- lib/user-settings.ts: pendingSprintDraft startAt/endAt → z.string().date().
- PbiList: oude selectionMode + selectedIds + NewSprintDialog vervangen door
  hasDraft-afgeleide A′-mode met tri-state vinkjes; togglen muteert
  upsertPbiIntent('all'|'none') en wist storyOverrides per PBI.
- StoryPanel: in A′-mode toont elke story een cherrypick-checkbox die
  upsertStoryOverride('add'/'remove'/'clear') aanroept; cross-sprint-blocked
  stories krijgen disabled-icoon met sprint-naam tooltip.
- app/(app)/products/[id]/page.tsx: StartSprintButton vervangen door
  NewSprintTrigger; SprintDraftBanner gepositioneerd boven split-pane.

Tests: bestaande tests blijven groen (806 cases) — UI-specifieke component
tests volgen later. ST-1339 sluit createSprintWithSelectionAction aan.

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

* feat(PBI-79/ST-1339): createSprintWithSelectionAction + banner wire-up

actions/sprints.ts:
- Nieuwe createSprintWithSelectionAction(productId, metadata, pbiIntent,
  storyOverrides).
- Server-side intent-resolve:
  1. Voor elke PBI met intent='all': fetch child-story-IDs minus
     storyOverrides[pbi].remove.
  2. Plus storyOverrides[*].add (cross-PBI cherrypick toegestaan).
- Eligibility-filter via partitionByEligibility (sprint_id IS NULL + status
  != DONE; stories in andere OPEN sprint → conflicts.crossSprint).
- Transactie wrapt sprint.create + story.updateMany (status='IN_SPRINT') +
  task.updateMany (sprint_id cascade) — alles atomair.
- setActiveSprintInSettings na success.
- Return: { success, sprintId, affectedStoryIds, affectedPbiIds,
  affectedTaskIds, conflicts: { notEligible, crossSprint } } of error.

components/backlog/sprint-definition-banner.tsx:
- 'Sprint aanmaken'-knop sluit aan op createSprintWithSelectionAction;
  toast bij conflicts, success-toast anders, router.refresh() voor SSR
  cycle. Pending draft wordt door de action zelf nog niet expliciet gewist
  — dat gebeurt via revalidatePath en kan in ST-1340 finetuned worden.

Tests: __tests__/actions/create-sprint-with-selection.test.ts (6 cases)
dekken intent-resolve, override-respect, cross-sprint conflict, transactie-
binding van story.status + task.sprint_id, return-shape, en error-pad.

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

* feat(PBI-79/ST-1340): commitSprintMembershipAction + gerichte client-store patches

actions/sprints.ts:
- Nieuwe commitSprintMembershipAction(activeSprintId, adds[], removes[]).
- Eligibility-filter voor adds via partitionByEligibility (sprint_id IS NULL
  en niet DONE; cross-sprint conflicts → notEligible).
- Race-safety voor removes: alleen stories met huidige sprint_id ==
  activeSprintId; rest → conflicts.alreadyRemoved.
- Transactie wrapt twee updateMany-paren (story status mee, task.sprint_id
  cascade). Update-paren overgeslagen wanneer leeg.
- Return: { success, affectedStoryIds, affectedPbiIds, affectedTaskIds,
  conflicts: { notEligible, alreadyRemoved } }.

stores/product-workspace/store.ts:
- applyMembershipCommitResult({ activeSprintId, addedStoryIds,
  removedStoryIds }) patcht entities.storiesById met juiste sprint_id +
  status; ledigt sprintMembership.pending. Geen task-veld omdat
  BacklogTask geen sprint_id-kolom heeft in de store.

Tests: __tests__/actions/commit-sprint-membership.test.ts (8 cases) — happy
path, DONE-conflict, cross-sprint, race-safety voor removes, transactie-
inhoud (status='IN_SPRINT'/'OPEN'), task-cascade, return-shape, auth-fail.

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

* feat(PBI-79/ST-1338): state B vinkjes-UI + 'Sprint opslaan'-knop met teller

State B (actieve sprint geselecteerd, geen draft) hangt nu aan dezelfde
vinkje-UI als state A′, maar muteert de transient pending-buffer in plaats
van de draft.

- PbiList: nieuwe prop activeSprintId. selectionMode = hasDraft ||
  stateBMode. togglePbiInDraft routeert naar upsertPbiIntent (A′) of bulk-
  toggleStorySprintMembership over eligible child-stories (B, skip blocked).
- StoryPanel: idem prop activeSprintId. StoryBlockWithCherrypick muteert
  draft via upsertStoryOverride in A′ of pending buffer via
  toggleStorySprintMembership in B (cross-sprint blocked = disabled).
- SaveSprintButton (nieuw): client component in page header, alleen
  zichtbaar als er een actieve sprint is. Disabled bij clean buffer,
  enabled met teller bij dirty. Klikken calls commitSprintMembershipAction
  → applyMembershipCommitResult gericht in store + toast bij conflicts.
- page.tsx: activeSprintItem.id wordt doorgegeven aan PbiList, StoryPanel
  en SaveSprintButton.

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

* feat(PBI-79/ST-1341+ST-1342): SprintEditDialog metadata-edit + multi-OPEN sprints

ST-1341 (T-946):
- actions/sprints.ts: nieuwe updateSprintAction(sprintId, fields) — JSON
  input, accepteert optionele goal/startAt/endAt; auth + product-access
  check, prisma.sprint.update, revalidatePath. Type-safe return.
- components/backlog/sprint-edit-dialog.tsx: Entity-Dialog-pattern voor
  metadata-edit van een sprint. Velden: sprint_goal, start_date, end_date.
  Link 'Sprint afronden… →' naar bestaande /products/[id]/sprint/[sprintId]
  zodat de completion-flow (per-story DONE/OPEN beslissing + PBI-promotie)
  niet wordt geduplicereerd. useDirtyCloseGuard.

ST-1342 (T-947):
- actions/sprints.ts: OPEN-uniqueness check in createSprintAction
  verwijderd. Een product mag nu meerdere OPEN sprints tegelijk hebben;
  cross-sprint-conflicts per story worden afgevangen door
  partitionByEligibility in de membership-commit-flow.

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

* test(PBI-79/ST-1344): updateSprintAction regression coverage

Audits van de geplande non-regressie-tests laten zien dat alle invarianten
uit het ST-1344 plan reeds gedekt zijn door eerder toegevoegde tests:

- clearActiveSprintAction null-not-delete → __tests__/lib/active-sprint.test.ts
  + __tests__/actions/active-sprint-action.test.ts
- Endpoints rejecten zonder pbiIds (400) → __tests__/api/sprint-membership-summary.test.ts
  + __tests__/api/cross-sprint-blocks.test.ts
- Status-mutaties story.status=IN_SPRINT/OPEN met task.sprint_id cascade
  in dezelfde transactie → __tests__/actions/create-sprint-with-selection.test.ts
  + __tests__/actions/commit-sprint-membership.test.ts
- Cross-sprint conflicts + DONE-eligibility → __tests__/lib/sprint-conflicts.test.ts

Nieuw: __tests__/actions/update-sprint.test.ts (6 cases) dekt
updateSprintAction die nog geen tests had — goal alleen, dates alleen,
null-clear, 403 zonder access, lege goal weigering, leeg fields-object
weigering.

Handmatige E2E checklist (T-949) blijft staan voor menselijke browser-
validatie tijdens PR-review.

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

* fix(PBI-79): PBI-rij selecteert weer in A′/B-modus; vinkje is aparte trigger

Voor PBI-79 maakte het hele PBI-kaartje in selectionMode (state A′ én B)
de toggle. Daardoor:
- klik op rij = bulk-toggle stories (teller liep op);
- geen setActivePbi, dus StoryPanel kreeg geen content.

Fix: in selectionMode wordt onClick = onSelect (PBI activeren → stories
laden) en de tri-state-iconen verhuizen naar een eigen <button> in de
actions-slot met stopPropagation. Toggle gedrag (bulk add/remove in B,
upsertPbiIntent in A′) blijft ongewijzigd via die knop.

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

* fix(PBI-79): cascade-restore alleen als hint-story bij nieuwe PBI hoort

Bug: setActivePbi reset activeStoryId/activeTaskId, maar het cascade-
restore-pad zette daarna een hint-story actief zonder te valideren of die
story bij de nieuw-geselecteerde PBI hoort. Bij PBI-switch bleef daardoor
de task-kolom de taken van de vorige story tonen.

Fix: alleen setActiveStory(hint) als entities.storiesById[hint].pbi_id ===
pbiId. Bij mismatch blijft activeStoryId null en is de task-kolom leeg
totdat de gebruiker een story uit de nieuwe PBI kiest.

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

* feat(PBI-79): sprint-switch auto-select PBI/story + user-settings persist

Bij sprint-switch wordt de sprint-content server-side opgevraagd. Wanneer
de sprint precies één PBI (en die PBI exact één story binnen de sprint)
heeft, worden PBI en story automatisch geselecteerd. Alle drie keuzes
(sprint, pbi, story) worden atomair in user-settings opgeslagen zodat ze
cross-device blijven hangen.

- lib/user-settings.ts: layout krijgt nullable activePbis +
  activeStories per product.
- lib/active-sprint.ts: setActiveSelectionInSettings schrijft de drie
  keys atomair + notify pg_notify.
- actions/active-sprint.ts: switchActiveSprintAction(productId, sprintId)
  doet de server-side auto-select-resolutie (single PBI → single story)
  en returnt { sprintId, pbiId, storyId }.
- components/shared/sprint-switcher.tsx: handleSwitchSprint roept de
  nieuwe action aan en synchroniseert de workspace-store gelijk zodat
  de UI geen flash krijgt voor de SSR-refresh.
- components/backlog/active-selection-hydrator.tsx (nieuw): client-side
  effect dat user-settings.activePbis/activeStories naar workspace-store
  spiegelt; wint van de localStorage hint-restore.
- app/(app)/products/[id]/page.tsx: ActiveSelectionHydrator gemount
  binnen BacklogHydrationWrapper.

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

* docs(PBI-79): plan-update met implementatie-stand + scope-aanpassing

Documenteert wat er sinds de eerste implementatie-pass is gebeurd:
- Tabel van 14 commits met hun rol.
- Twee bugs die tijdens testen boven kwamen (PBI-rij-klik, cascade-restore).
- Nieuwe feature sprint-switch auto-select (server resolveert single-PBI/
  single-story; user-settings persist).

En kondigt scope-aanpassing aan voor de volgende implementatie-ronde:
- pendingSprintDraft wordt session-only (geen server-persist meer).
- useDirtyCloseGuard wist draft op leave-with-confirm.
- Sprint-switcher krijgt concept-entry zolang er een draft loopt.

De rest van het plan beneden blijft van kracht behalve waar deze sectie
het overruled.

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

* feat(PBI-79): pendingSprintDraft session-only + concept-entry + leave-guard

Scope-aanpassing uit plan-revisie: drafts persisten niet meer server-side.

Wijzigingen:
- stores/user-settings/store.ts:
  - hydrate() strip nu workflow.pendingSprintDraft uit serverstate
    (legacy DB-entries blijven harmless aanwezig maar worden niet
    gehydreerd → effectief unreachable voor de UI).
  - setPendingSprintDraft / clearPendingSprintDraft worden lokale-only;
    geen import van sprint-draft-actions, geen server-roundtrip.
  - upsertPbiIntent / upsertStoryOverride blijven via setPendingSprintDraft
    routeren → ook session-only.
- components/shared/sprint-switcher.tsx: leest draft-goal uit user-settings
  store en toont '⚙ Concept — [goal]' als niet-selecteerbare entry
  bovenaan de dropdown zolang er een draft loopt.
- components/backlog/sprint-draft-leave-guard.tsx (nieuw): registreert
  een beforeunload-listener zolang er een draft is. Browser-refresh,
  tab-close en back-navigatie tonen daarmee de standaard confirm. In-app
  route-changes blijven via de banner-Annuleren-knop lopen.
- app/(app)/products/[id]/page.tsx: SprintDraftLeaveGuard gemount naast
  de banner.
- Tests: user-settings store-tests aangepast (geen server-call assert
  meer, hydrate strip-assert toegevoegd; upsert-tests seed nu via
  setPendingSprintDraft i.p.v. legacy hydrate).

setPendingSprintDraftAction + clearPendingSprintDraftAction blijven bestaan
voor eventuele toekomstige opruim-flows, maar worden niet meer aangeroepen
vanuit de UI.

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

* docs(PBI-79): mark scope-aanpassing afgerond + localStorage overzicht

- Drie open punten uit plan-revisie afgevinkt (commit 2a4ee6a).
- Sectie 'Bewust niet geïmplementeerd': server-persist van manuele
  PBI/story-klikken — op vraag van user nu out-of-scope voor deze PR.
- Tabel localStorage-gebruik in de codebase voor toekomstige referentie.

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

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 18:56:46 +02:00
Janpeter Visser
bf7162a5fc
feat(PBI-76): migrate cookie-based prefs to user-settings (Phase 2) (#189)
* feat(PBI-76): extend UserSettings schema with layout

Adds layout.splitPanePositions and layout.activeSprints. These will
hold values currently kept in client-side and server-side cookies
(Phase 2). Two new tests cover the shape.

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

* feat(PBI-76): migrate SplitPane positions to user-settings store

Outside of a drag the store is the source of truth (cross-tab
updates flow in for free). During a drag we keep splits in local
state so mousemove does not round-trip through the store. On
mouseup we persist the final splits via setPref. Removes
document.cookie reads/writes — cookieKey is reused as the
store-key for backwards compat.

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

* feat(PBI-76): resolveActiveSprint reads from User.settings

lib/active-sprint:
- New helpers: getActiveSprintIdFromSettings, setActiveSprintInSettings,
  clearActiveSprintInSettings — all read/write user.settings.layout.activeSprints.
- resolveActiveSprint(productId, userId) — userId now required, falls back
  to first OPEN, then most recent CLOSED sprint.
- Cookie helpers (getActiveSprintIdFromCookie/setActiveSprintCookie/
  clearActiveSprintCookie) removed.

Callers updated to pass session.userId. The cookie-based fallback path
is gone — `actions/active-sprint.ts` and `actions/sprints.ts` will be
updated in the next commit (T-917).

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

* feat(PBI-76): rewrite setActiveSprint callers to use settings

setActiveSprintAction, syncActiveSprintCookieAction, and the two
sprint-creation paths in actions/sprints.ts now write through
setActiveSprintInSettings (which also emits pg_notify for cross-tab
sync) instead of dropping a cookie. The action names keep the
'cookie' suffix in the user-visible API for now — clean rename can
come later.

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

* feat(PBI-76): migration helper v2 — handle legacy cookies

Bumps marker version to 'v2'. buildMigrationPatch now also scans
document.cookie for `sp:*` (split-pane positions) and
`active_sprint_*` (active sprint per product) and lifts them into
layout.splitPanePositions / layout.activeSprints. clearLegacyStorage
replaces clearLegacyLocalStorage and clears both keys and cookies.
clearLegacyLocalStorage stays as a deprecated alias so the bridge
upgrade is a single rename.

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

* test(PBI-76): align tests with new SplitPane and active-sprint flow

- split-pane.test.tsx: seed positions via Zustand store instead of
  document.cookie; mock @/actions/user-settings so the prisma client
  is not transitively initialised in jsdom.
- backlog-split-pane.test.tsx: same action mock.
- sprint-dates.test.ts: add user.findUnique/update + $executeRaw
  mocks because createSprintAction now writes to user-settings.

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

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 21:20:29 +02:00
Janpeter Visser
a1e6ec35e5
feat(PBI-78): cost-analysis widget on insights page (#187)
* feat(PBI-78): cost-analysis data layer (T-902)

- New lib/insights/cost-analysis.ts with 5 query functions:
  getCostKpi, getCostByDay, getCostByModel, getCostByKind, getCacheEfficiency
- Period type: 7d | 30d | 90d | mtd
- Cost formula reused from token-stats.ts (input + output + cache + thinking)
- Cache savings: cache_read_tokens × (input_price - cache_read_price) / 1M
- Plan: docs/plans/PBI-78-cost-analysis-widget.md

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

* feat(PBI-78): cost-analysis UI component (T-903)

- New app/(app)/insights/components/cost-analysis.tsx
- Period selector (7d/30d/90d/MTD) via URL ?period= with useTransition + router.replace
- KPI strip: total cost, avg per day, cache savings, top model
- 2x2 chart grid: daily cost (BarChart), per-model + per-kind (vertical BarCharts), cache efficiency (PieChart)
- Empty state for kpi.jobCount === 0
- Uses MD3 tokens (var(--chart-N), var(--status-done))

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

* feat(PBI-78): wire CostAnalysisCard onto insights page (T-904)

- Parse ?period= from searchParams (default 30d, validates against 7d/30d/90d/mtd)
- Parallel-fetch 5 cost queries via Promise.all alongside existing widgets
- New "Cost analyse" section between Sprint Health and Plan-quality
- Existing TokenUsageCard ("Token gebruik" section) stays as sprint detail

verify (lint+typecheck+test) and build pass.

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

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 12:59:45 +02:00
Janpeter Visser
f8693d126b
refactor(PBI-77): standaardiseer loading-skeletons rond shadcn Skeleton (#186)
* refactor(PBI-77): align TaskDialogSkeleton with entity-dialog-layout (T-899)

Use entityDialogContentClasses/HeaderClasses/BodyClasses/FooterClasses from
components/shared/entity-dialog-layout.ts instead of hand-copied class strings.
Header, body, and footer skeletons now mimic the actual TaskDialog form fields
(code, title, description, plan, priority, status, delete + cancel/save buttons).

Fixes mobile layout-shift: the shared content-classes include max-sm: fullscreen
modifiers that the previous hand-rolled subset missed.

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

* refactor(PBI-77): extract BacklogPageSkeleton from 3 identical loadings (T-900)

The loading.tsx files for products/[id], sprint/[sprintId], and
sprint/[sprintId]/planning were 100% identical hand-rolled animate-pulse markup.
Extract the shape into components/loading/backlog-page-skeleton.tsx using the
shadcn <Skeleton> component, and re-export it from the three loading files.

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

* refactor(PBI-77): dashboard + settings loading on shadcn Skeleton (T-901)

Replace hand-rolled animate-pulse + bg-border divs with the shadcn <Skeleton>
component for consistent bg-muted color and pulse timing across all five
loading.tsx files. No layout-shift: heights, widths, and rounded radii are
preserved.

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

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 12:59:08 +02:00
Janpeter Visser
a0e5867857
feat(PBI-76): user-settings DB-store infrastructure (Phase 0) (#185)
* docs(PBI-76): plan for user-settings DB-store

Persists view/filter prefs in User.settings (Json) instead of
localStorage. SSR-correct hydration, cross-tab sync via
LISTEN/NOTIFY + SSE, cross-device persistence.

Phased: 0=infra, 1=migrate flicker sources, 2=cookie consolidation.

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

* feat(PBI-76): User.settings json column + migration

Adds JSONB column to users table for persistent user prefs.
Idempotent SQL — safe on databases where column already exists.

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

* feat(PBI-76): user-settings types and merge helpers

Zod schema for User.settings shape (views/devTools), deep-merge
helper that replaces arrays and merges nested objects, and a
safe parser that returns defaults on invalid input.

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

* feat(PBI-76): updateUserSettingsAction with notify

Validates patch via Zod, deep-merges with current settings in
a transaction, persists to DB, and emits pg_notify on
scrum4me_changes for cross-tab/cross-device sync. Demo accounts
get 403, unauthenticated 401, invalid input 422.

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

* feat(PBI-76): user-settings zustand store with optimistic flow

Hydrate from prop (SSR-correct), setPref via path with optimistic
update + rollback on server error, applyServerPatch for SSE-driven
cross-tab updates. Demo accounts skip server-write entirely.

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

* feat(PBI-76): SSE route for user-settings

User-scoped /api/realtime/user-settings stream that filters
scrum4me_changes notifications on kind=user_settings and matching
userId. Forwards the patch as a data: event so other tabs can
applyServerPatch without re-fetching settings.

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

* feat(PBI-76): user-settings bridge mounted in app layout

Hydrates the zustand store with the user's persisted settings via
prop (SSR-correct, no flicker). Opens an EventSource to
/api/realtime/user-settings so changes from other tabs/devices
flow into the same store. Demo accounts skip the SSE subscription.

Layout now selects user.settings alongside the other user fields,
no extra DB roundtrip.

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

* test(PBI-76): user-settings lib/action/store coverage

22 vitest cases covering merge semantics (no mutation, array
replace, nested merge), Zod schema strictness, server action
auth/demo/validation paths, and the optimistic store flow
including rollback and demo-mode skip.

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

* chore(PBI-76): sync package-lock to v1.3.3

Lockfile drifted after @prisma/client reinstall during the
schema regenerate. No dependency changes — just the version
field tracking package.json bumped in #184.

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

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 12:44:32 +02:00
Janpeter Visser
1f8cbacb0a
feat: shared backlog filter popover + sprint header polish (v1.3.3) (#184)
- Move sprint switcher into sprint header, centered between title and actions
- Extract BacklogFilterPopover as shared component used by sprint and product backlog
- Add sort options (code/priority/status) with single-pill asc/desc toggle
- Default sprint backlog status filter to OPEN, remove "alleen niet klaar" button
- Persist collapsed state and filter popover open in localStorage
- Fix hydration flicker: defer localStorage read to useEffect with prefsLoaded gate for writes
- Increase sprint switcher text size for readability

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 11:12:04 +02:00
Janpeter Visser
a9b53dedf0
feat(PBI-75): sprint task-edit client-side via workspace-store (#183)
Klik op een taak in het sprint-scherm opent de edit-dialog nu
client-side via setActiveTask op de sprint-workspace-store.
Geen URL-navigatie, geen volledige server re-render — alleen
GET /api/tasks/{id} voor het detail. SSE propageert server-saves
automatisch terug.

- TaskDialog: optionele onClose/onSaved callbacks (closePath
  optional gemaakt — backwards compatible)
- SprintTaskDialogMount: nieuwe client-component die
  selectActiveTask consumeert en TaskDialog rendert
- SprintUrlTaskSync: deeplink (?editTask=<id>) → store
- Sprint page: mounts toegevoegd, editTask searchParam +
  EditTaskLoader-Suspense verwijderd
- TaskList.openEditDialog roept setActiveTask aan ipv router.push
- Vitest integratie-test voor SprintTaskDialogMount

Out-of-scope (follow-up PBIs): newTask-flow, mobile, product-backlog.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 08:21:42 +02:00
Janpeter Visser
3b5cee823c
Load/render workspace alignment (#182)
* docs: plan load render workspace alignment

* fix: normalize workspace status hydration

* fix: avoid duplicate backlog hydration load

* refactor: use sprint store active story

* refactor: migrate solo to workspace store

* chore: stabilize verification ignores
2026-05-10 07:34:58 +02:00
Janpeter Visser
98ee05d458
feat(PBI-74): sprint-workspace-store (Story 9) (#181)
* feat(PBI-74): sprint-workspace-store skelet (Story 9 / T-879)

- stores/sprint-workspace/{types,store,selectors,restore}.ts conform
  product-workspace blueprint
- ContextSlice: activeProduct, activeSprintId, activeStoryId, activeTaskId
- EntitiesSlice: sprintsById, storiesById, tasksById
- RelationsSlice: sprintIdsByProduct, storyIdsBySprint, taskIdsByStory
- LoadingSlice met activeRequestId voor race-safe ensure*Loaded
- SyncSlice: realtimeStatus, lastResyncAt, resyncReason
- Realtime applyRealtimeEvent voor sprint/story/task entities + unknown-event
  fallback, parent-move handling, child-cleanup bij D op sprint/story
- Optimistic mutations: sprint-story-order, sprint-task-order, entity-patch
- LocalStorage hints (storage key sprint-workspace-hints) per product/sprint
- 45 unit-tests groen — verplicht 13 cases uit workspace-store.md §Tests

* feat(PBI-74): sprint hydratie + realtime SSE (Story 9 / T-880)

- app/api/realtime/sprint/route.ts: SSE-stream LISTEN/NOTIFY op
  scrum4me_changes, filter entity ∈ {sprint, story, task} per product_id;
  ready-event, heartbeat 25s, hard-close 240s
- lib/realtime/use-sprint-realtime.ts: client-hook met backoff-reconnect;
  ready-cycle telt; geen close op hidden; setRealtimeStatus
- lib/realtime/use-sprint-workspace-resync.ts: visibility + online triggers
  resyncActiveScopes('visible' | 'reconnect')
- components/sprint/sprint-hydration-wrapper.tsx: hydrateSnapshot via
  useEffect met fingerprint-check; mount realtime + resync
- app/(app)/products/[id]/sprint/[sprintId]/page.tsx: wrap SprintBoardClient
  in SprintHydrationWrapper; bouw SprintWorkspaceTask-shape voor
  tasksByStoryWorkspace en SprintHydrationData voor de wrapper

Schaduw-fase: useSprintStore blijft parallel werken in board components
totdat T-881 die migreert en T-883 de oude store opruimt.

* feat(PBI-74): migreer sprint-board componenten naar workspace-store (Story 9 / T-881)

- TaskList: leest tasks via selectTasksForStory met useShallow; DnD via
  applyOptimisticMutation('sprint-task-order') + settle/rollback
- SprintBacklogLeft: leest stories via selectStoriesForActiveSprint met
  useShallow; props 'stories' verwijderd
- SprintBoardClient: leest sprintStories uit selector i.p.v. lokale state;
  add/remove via direct setState met manuele snapshot-rollback;
  reorder via applyOptimisticMutation('sprint-story-order'); assignee-
  change via store entity-mutation; tasksByStory en sprintStoryIdList
  props weg
- app/(app)/.../sprint/[sprintId]/page.tsx: bouwt SprintHydrationData voor
  wrapper; geeft alleen non-store props door aan SprintBoardClient

useSprintStore wordt nergens meer geïmporteerd — alleen comment-referentie
in SprintHydrationWrapper. Cleanup van het bestand zelf in T-883.

Verify groen (671 tests, typecheck, lint clean).

* feat(PBI-74): read-routes voor sprint-workspace + cache-headers (Story 9 / T-882)

- GET /api/products/[id]/sprints — lijst sprints per product
  (ensureProductSprintsLoaded). force-dynamic, productAccessFilter,
  start_date/end_date naar ISO-date string.
- GET /api/sprints/[id]/workspace — sprint snapshot met sprint-meta,
  stories (incl. taskCount/doneCount/assignee), tasks gegroepeerd per
  story (ensureSprintLoaded). force-dynamic, productAccessFilter via
  product, status-vertaling via taskStatusToApi/storyStatusToApi.

Race-safe loaders (activeRequestId-guard), restore-flow (cascade-restore
via writeProductHint/writeSprintHint/writeStoryHint/writeTaskHint),
resync-laag (useSprintWorkspaceResync visibility + online), unknown-event
filter (isUnknownEntityEvent → resyncActiveScopes('unknown-event')) zijn
allemaal in T-879/T-880 al ingebouwd; T-882 sluit het loop met de
ontbrekende API-endpoints + cache-headers (cache: 'no-store' op fetches,
force-dynamic op routes).

* feat(PBI-74): cleanup oude sprint-store (Story 9 / T-883)

- rm stores/sprint-store.ts — alle componenten lezen nu via
  useSprintWorkspaceStore (T-881 voltooide imports-migratie)
- update SprintHydrationWrapper-comment: schaduw-fase referenties
  verwijderd

Verify: 671 tests groen, typecheck clean, build groen.
Grep useSprintStore = 0.

* docs(PBI-74): update Story 9 status in implementatieplan (T-884)

- Frontmatter: ready-to-execute → in-progress; revision 1 → 2;
  last_updated 2026-05-09 → 2026-05-10
- Stories-tabel: kolom Status toegevoegd (Stories 1-8 DONE via PR #180,
  Story 9 met T-884 op review)
- §Story 9: per-taak status + acceptatie-checklist voor T-884 manuele
  staging-checks
- Aanbeveling-blokje: noteert dat Story 9 vroeger gestart is dan het
  ontwerpdoc adviseerde
2026-05-10 06:53:04 +02:00
Janpeter Visser
5df04feb11
feat(PBI-74): Zustand product-workspace rearchitecture (Stories 1-8) (#180)
* feat(PBI-74): product-workspace store skelet + test-infra (Story 1)

Skelet voor de nieuwe `product-workspace-store` die op termijn de gefragmenteerde
`backlog-store`/`planner-store`/`selection-store`/`product-store` vervangt. Deze
PR levert alleen het skelet + tests; UI-consumers worden in latere stories
omgezet.

- vitest naar jsdom + tests/setup.ts (MemoryStorage, default fetch-stub) — G6/G8
- stores/product-workspace/{types,store,selectors,restore}.ts — immer-middleware,
  alle slices en acties (hydrate, setActive*, ensure*Loaded met activeRequestId-
  guard, applyRealtimeEvent, resyncActiveScopes/loadedScopes, optimistic
  mutations). Restore-wiring in setters volgt in Story 4 (T-857/T-858).
- selectors gebruiken module-level EMPTY refs (G1) en documenteren useShallow-
  vereiste (G2)
- 34 nieuwe unit-tests dekken §Testing setup-checklist uit het ontwerp:
  hydrateSnapshot, selection-cascade, applyRealtimeEvent (I/U/D + parent-move +
  ander-product + unknown-entity → resync), delete-cleanup, race-safe loaders,
  ensureTaskLoaded _detail-flag, resyncActiveScopes ensure-keten, restore-hints
  read/write/clear, optimistic mutation rollback/settle/SSE-echo idempotent
- docs/api/rest-contract.md: audit-sectie met de vier ontbrekende
  ensure*Loaded-endpoints (worden toegevoegd in Story 7 / T-870)

Refs: PBI-74, ST-1318, T-837..T-843
Bron-ontwerp: docs/plans/zustand-store-rearchitecture.md
Implementatieplan: docs/plans/zustand-workspace-store-implementation.md

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

* feat(PBI-74): dual-dispatch hydratie + realtime naar workspace-store (Story 2)

Story 2 — schaduw-fase: BacklogHydrationWrapper en useBacklogRealtime voeden
nu ook de nieuwe product-workspace-store, terwijl de oude useBacklogStore /
useProductStore leidend blijft voor componenten. Story 3 verschuift consumers
één voor één; Story 8 ruimt de oude stores op.

- T-844: BacklogHydrationWrapper roept naast useBacklogStore.setInitialData
  ook useProductWorkspaceStore.hydrateSnapshot aan. Productname-prop optioneel
  toegevoegd voor activeProduct-context.
- T-845: useBacklogRealtime onmessage dispatcht events naar zowel oude store
  (applyChange) als nieuwe store (applyRealtimeEvent). Geen wijziging aan
  reconnect/visibility — Story 5.
- T-846: dev-only logWorkspaceFingerprint helper vergelijkt counts tussen
  oude en nieuwe store na hydrate en na elk realtime-event. console.warn bij
  mismatch; opt-in debug log via NEXT_PUBLIC_DEBUG_WORKSPACE_FINGERPRINT=1.
  Bestand TODO-marked voor verwijdering in Story 8 (T-878).
- T-847: SetCurrentProduct schrijft naast oude useProductStore ook
  useProductWorkspaceStore.setActiveProduct({id, name}); cleanup cleart beide.
  setActiveProduct triggert ensureProductLoaded — fetch-stub tot Story 7
  (T-870) de LIST-endpoints toevoegt.

Verify: lint+typecheck clean, 636/636 tests groen (geen UI-regressie omdat
oude store leidend blijft).

Refs: PBI-74, ST-1319, T-844..T-847

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

* feat(PBI-74): migreer backlog-componenten naar workspace-store (Story 3)

Story 3 verplaatst alle UI-consumers van de oude vier stores
(useBacklogStore/usePlannerStore/useSelectionStore/useProductStore) naar de
nieuwe product-workspace-store. De oude stores blijven nog bestaan voor
hydration-wrapper en realtime-hook (dual-dispatch); Story 8 ruimt ze op.

- T-848 backlog-split-pane.tsx: leest activePbiId/activeStoryId uit
  context-slice (primitives, geen useShallow nodig).
- T-849 pbi-list.tsx: selectVisiblePbis(useShallow); DnD via
  applyOptimisticMutation('pbi-order' + optionele 'entity-patch' bij
  cross-priority drag), met settle/rollback per server-result.
- T-850 story-panel.tsx: selectStoriesForActivePbi(useShallow); DnD via
  applyOptimisticMutation('story-order' + entity-patch bij priority change).
- T-851 task-panel.tsx: selectTasksForActiveStory(useShallow); DnD via
  applyOptimisticMutation('task-order'); detail-view (ensureTaskLoaded +
  isDetail) zit in de task-dialog (apart component, niet in deze lijst).
- T-852 start-sprint-button.tsx: selectActivePbi + selectStoriesForActivePbi
  voor free-story count.
- T-853 set-current-product.tsx: alleen workspace-store.setActiveProduct
  (oude useProductStore-import verwijderd).
- T-854 G1/G2-audit: alle nieuwe selectors gebruiken module-level EMPTY
  refs (G1) en useShallow voor lijsten (G2). Geen 'Maximum update depth'-
  warnings tijdens npm test.
- T-855 tests bijgewerkt: backlog-split-pane.test, task-panel.test,
  integration.test gebruiken nu setState op workspace-store (helpers
  resetWorkspace/setActiveStoryAndTasks/selectPbi/selectStory).

Verify: lint+typecheck clean, 636/636 tests groen. UI-consumers van
oude stores zijn nu nul (uitgezonderd dual-dispatch in hydration-wrapper en
realtime-hook + dev-fingerprint-helper, die in Story 8/T-873/T-878 verdwijnen).

Refs: PBI-74, ST-1320, T-848..T-855

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

* feat(PBI-74): race-safe loaders + restore-hints + URL-prioriteit (Story 4)

- T-856: activeRequestId-guard zat al in store.ts uit Story 1; bevestigd door
  de race-safety test (in-flight ensurePbiLoaded mag niet overschrijven).
- T-857: restore-hint flow toegevoegd in setActiveProduct/setActivePbi/
  setActiveStory. Async chain: await ensureXxxLoaded → guard check →
  readHints → valideer hint via entities.byId → setActiveYyy(hint).
  Geen setTimeout-trick — chain is alleen await-based.
- T-858: writeProductHint/writePbiHint/writeStoryHint/writeTaskHint
  aangeroepen direct na set(...) zodat de hint-persistentie altijd
  consistent is met de in-store selectie.
- T-859: nieuwe components/backlog/url-task-sync.tsx — leest
  ?editTask=&lt;id&gt; uit useSearchParams, schrijft de hint en roept
  setActiveTask aan zodat de URL wint boven een eerder gepersisteerde
  task-hint. Gemount in beide product-pages (desktop + mobile) binnen
  BacklogHydrationWrapper.
- T-860: 6 nieuwe vitest-cases — 4 voor hint-persist per setter, 2 voor de
  restore-flow chain (hint die niet in entities zit wordt genegeerd; hint
  die wel in entities zit wordt toegepast). Bestaande race-safety test
  blijft groen.

Verify: lint+typecheck clean, 642/642 tests groen.

Refs: PBI-74, ST-1321, T-856..T-860

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

* feat(PBI-74): hidden-tab + reconnect resync (Story 5)

Per ontwerp samen in één commit zodat geen vangnet wegvalt zonder vervanging.

- T-861: useBacklogRealtime sluit niet meer op visibilitychange hidden;
  EventSource blijft open zolang browser/netwerk dit toelaten. Reconnect bij
  netwerkfout blijft via backoff. visibilitychange fungeert nog wel als
  re-connect-trigger als de stream tussentijds is gesloten (b.v. 240s
  hard-close server-side).
- T-862: 'ready'-event-handler telt connect-cycles. De eerste 'ready' is de
  initial connect (geen resync). Bij latere 'ready' (post-reconnect) wordt
  resyncActiveScopes('reconnect') aangeroepen om gemiste events op te halen.
- T-863: nieuwe lib/realtime/use-workspace-resync.ts — luistert op
  document.visibilitychange (hidden→visible) en window.online; dispatcht
  resyncActiveScopes('visible') resp. 'reconnect'. Mounted in
  BacklogHydrationWrapper na useBacklogRealtime.
- T-864: 4 nieuwe vitest-cases voor useWorkspaceResync (jsdom): visible→
  visible event, online event, hidden negeren, cleanup-bij-unmount.

Daarnaast lint-cleanup: ongebruikte 'order'-variabelen in pbi-list en
story-panel weggehaald.

Verify: lint+typecheck clean, 646/646 tests groen.

Refs: PBI-74, ST-1322, T-861..T-864

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

* feat(PBI-74): unknown-event fallback tests (Story 6)

T-865 (isUnknownEntityEvent filter) en T-866 (resync-trigger in
applyRealtimeEvent) zijn al in Story 1 geïmplementeerd in store.ts;
deze story breidt de test-coverage uit met expliciete negatieve cases
voor het type-veld noise pattern.

T-867 — 5 nieuwe vitest-cases:
- unknown entity met ANDER product_id → geen resync
- claude_job_status (type) → geen resync
- worker_heartbeat (type) → geen resync
- claude_job_enqueued (type) → geen resync
- payload zonder entity en zonder type → genegeerd
- question-entity (entity-veld, geen type, niet pbi/story/task) → resync trigger

Verify: lint+typecheck clean, 651/651 tests groen.

Refs: PBI-74, ST-1323, T-865..T-867

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

* feat(PBI-74): cache-headers + LIST endpoints (Story 7)

- T-868: cache: 'no-store' was al ingebouwd in fetchJson helper (Story 1).
  Bevestigd door bestaande ensureProductLoaded test die de fetch-init
  controleert.
- T-869: force-dynamic toegevoegd op alle vier nieuwe LIST-endpoints.
- T-870: vier nieuwe routes voor ensure*Loaded:
  - GET /api/products/:id/backlog → ProductBacklogSnapshot
  - GET /api/pbis/:id/stories → BacklogStory[]
  - GET /api/stories/:id/tasks → BacklogTask[]
  - GET /api/tasks/:id (nieuwe handler naast bestaande PATCH) → TaskDetail
    met _detail: true marker
  Auth via authenticateApiRequest (Bearer of iron-session); access-control
  via productAccessFilter (gebruiker is owner of member van het product).
  Statussen worden via taskStatusToApi/storyStatusToApi/pbiStatusToApi
  vertaald naar lowercase API-vorm.
- T-871: SSE-route /api/realtime/backlog stuurt al ready-event direct na
  LISTEN (regel 106) — geen wijziging nodig.

Verify: lint+typecheck clean, 651/651 tests groen.

Refs: PBI-74, ST-1324, T-868..T-871

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

* feat(PBI-74): oude stores opruimen (Story 8)

Workspace-store is nu de enige bron voor product-backlog client-state. De
vier voorgangers en de dual-dispatch-infrastructuur zijn verwijderd.

- T-872: grep over codebase op useBacklogStore/usePlannerStore/
  useSelectionStore/useProductStore is leeg.
- T-873..T-876: stores/{backlog,planner,selection,product}-store.ts deleted.
- T-877: __tests__/realtime/payload-contract.test.ts en
  __tests__/api/backlog-realtime.test.ts deleted — pbi/story/task I|U|D
  payload-handling wordt al gedekt door
  __tests__/stores/product-workspace/store.test.ts (incl. parent-move,
  idempotent inserts, delete-cleanup).
- T-878: lib/realtime/dev-workspace-fingerprint.ts deleted, dual-dispatch
  uit BacklogHydrationWrapper en lib/realtime/use-backlog-realtime.ts
  weggehaald. stores/products-store.ts (lijst van producten ≠ active
  product) blijft ongewijzigd.

Bijwerkingen:
- BacklogPbi en BacklogStory types in components/backlog/story-panel.tsx en
  components/sprint/sprint-backlog.tsx krijgen sort_order zodat ze met de
  workspace-types overeenkomen.
- Server-pages /products/[id]/page.tsx (desktop+mobile) en
  /products/[id]/sprint/[sprintId]/page.tsx selecteren sort_order op story
  en mappen het door in de hydration-payload.

Verify: lint+typecheck clean, 626/626 tests groen (verlies van 25 redundante
oude-store tests; workspace-store tests dekken hetzelfde gedrag).

Refs: PBI-74, ST-1325, T-872..T-878

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

* docs(PBI-74): richtlijn workspace-store + realtime patroon

Documenteert het patroon dat in Stories 1-8 is opgeleverd, zodat een
volgende workspace-store (sprint, of een nieuwe bounded context) hetzelfde
recept volgt.

- docs/patterns/workspace-store.md (nieuw): wanneer een workspace-store, de
  vijf state-slices, selectors-regels (G1/G2), race-safe ensure*Loaded met
  activeRequestId-guard (G4), SSE-hook + applyRealtimeEvent met
  unknown-event filter, hidden-tab + reconnect resync via
  useWorkspaceResync, restore-hint flow met await-chain en URL-prioriteit,
  optimistic mutations (applyOptimisticMutation/rollback/settle), API
  endpoint-vereisten (force-dynamic, cache: no-store), test-setup met
  MemoryStorage + originalActions snapshot + mockImplementation, gotchas
  G1-G8 als comment-template, en het 8-staps migratiepad.
- docs/patterns/zustand-optimistic.md: bijgewerkt voor de nieuwe
  workspace-store API; verwijst voor het bredere patroon naar
  workspace-store.md. Voorbeelden voor pbi-order + entity-patch.
- CLAUDE.md: patterns quickref aangevuld met workspace-store-rij.

Verify: typecheck clean.

Refs: PBI-74

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

* fix(PBI-74): solo + notifications hooks volgen ook hidden-tab/resync patroon

Het uitgangspunt van PBI-74 (robuust tegen gemiste SSE-events, hidden tabs
en onbekende notify-vormen) gold universeel — niet alleen voor
product-workspace. use-solo-realtime en use-notifications-realtime hadden
nog dezelfde bug die use-backlog-realtime in Story 5 al opgelost kreeg:
sluit stream op hidden, geen resync.

Reproductie (zoals gemeld): solo-screen open in tab A, product-backlog
open in tab B; bewerk task-title in tab B → tab A's solo-SSE was gesloten
(hidden) en kreeg het NOTIFY-event nooit. Tab terug naar solo →
EventSource reconnect maar geen resync → oude title persisteert. Postgres
NOTIFY heeft geen replay, dus zonder resync zijn die events permanent
verloren.

Fix in beide hooks (zelfde patroon als Story 5 voor backlog):
- Stream blijft open op visibilitychange hidden — geen close() meer.
- Bij hidden→visible én bij window 'online': router.refresh() zodat de
  server-component opnieuw fetcht en de initial-state-prop ververst (wat
  voor solo de tasks-record reset via initTasks; voor notifications de
  questions-bel-state).
- Bij latere 'ready'-events na reconnect (use-solo-realtime): zelfde
  router.refresh() trigger zodat we niet vertrouwen op alleen het
  visibility-pad.

Verify: lint + typecheck clean, 626/626 tests groen.

Refs: PBI-74

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

* docs: fix broken links in research-repo plan

docs/plans/lees-de-readme-md-validated-book.md beschrijft een research-
repo migratiepad. De links waren geschreven vanuit het research-repo-
perspectief (paden als stores/data-store.ts, ../Scrum4Me/CLAUDE.md,
docs/plans/zustand-store-rearchitecture.md zonder relative-prefix), wat
de doc-link-checker hier laat falen.

- Header-note toegevoegd dat het document voor de research-repo is.
- Interne refs (zustand-store-rearchitecture.md, CLAUDE.md) → relatieve
  paden die in deze repo wél resolven (./zustand-..., ../../CLAUDE.md).
- Research-repo-only refs (stores/data-store.ts,
  hooks/use-event-stream.ts, components/*-select.tsx, etc.) → inline
  code-tags met "(research-repo)" suffix; de link-checker slaat ze over
  en de leesbaarheid blijft.

Verify: npm run docs:check-links → ✓ All doc links valid (118 files).

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 02:25:19 +02:00
Janpeter Visser
d292e445d9
Sprint: Verbeteren debug mode (#179)
* feat(PBI-49): add debugProps helper + Vitest test

Adds lib/debug.ts with debugProps(id, component, file) that returns
data-debug-id and data-debug-label attrs in dev mode, empty object in
production. Adds __tests__/lib/debug.test.ts covering both modes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs(PBI-49): add debug-id pattern doc + CLAUDE.md reference

Adds docs/patterns/debug-id.md documenting the named-component boundary
rule (6 punten), helper-voorbeeld, skip-criteria en motivatie voor
handmatige pad-argumenten. Voegt verwijzing toe aan CLAUDE.md
patterns-tabel.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor(PBI-49): migrate 17 shared/ components to debugProps helper

Replace hardcoded data-debug-id + data-debug-label attribute pairs with
{...debugProps(id, component, file)} spread in all 17 components/shared/
files. Existing debug-ids preserved unchanged.

* feat(PBI-49): add debugProps to backlog/, sprint/, solo/ components

* feat(PBI-49): add debugProps to jobs/ + ideas/ components

* feat(PBI-49): add debugProps to products/ + settings/ + notifications/ components

* feat(PBI-49): add debugProps to admin/ + dashboard/ + dialogs/ + mobile/ + split-pane/

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(PBI-49): use attr(data-debug-id) for debug tooltip in globals.css

* refactor(PBI-49): remove data-debug-label from debugProps helper + test

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor(PBI-49): strip unused component/file args from debugProps in shared/

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(PBI-49): add BEM sub-element data-debug-id to StatusBar, NavBar, PanelNavBar

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(PBI-49): add BEM sub-element data-debug-id to components/sprint/*

- new-sprint-dialog: __submit on submit button
- sprint-backlog: __list on SprintBacklogLeft + SprintBacklogRight scroll areas
- sprint-board-client: root wrapper div (display:contents) + __drag-overlay
- sprint-header: __title on goal button, __dates on dates button, __actions on action cluster
- sprint-run-controls: root on controls div, __start/__cancel on action buttons; __blockers-dialog on dialog content
- start-sprint-button: root on trigger button, __dialog on dialog content, __submit on submit button
- sync-active-sprint-cookie: no debug-id (returns null, side-effect only), comment added

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(PBI-49): add BEM sub-element data-debug-id to components/backlog/*

* feat(PBI-49): add BEM sub-element data-debug-id to components/ideas/*

* feat(PBI-49): add BEM sub-element data-debug-id to components/dashboard/* + components/markdown.tsx

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(PBI-49): add BEM sub-element data-debug-id to new-product-button

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(PBI-49): add BEM sub-element data-debug-id to components/solo/*

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(PBI-49): add BEM sub-elements to nav-status-indicators

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(PBI-49): add BEM sub-element data-debug-id to components/jobs/*

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(PBI-49): add BEM sub-element data-debug-id to components/products/*

* feat(PBI-49): add BEM sub-element data-debug-id to components/notifications/*

- answer-modal: __content (scroll area), __submit (footer)
- notifications-bridge: skip comment (bridge, non-rendering wrapper)
- notifications-realtime-mount: skip comment (returns null)
- notifications-sheet: __header, __items (questions list)
- push-toggle: __switch (button), __label (button text) on subscribed/unsubscribed states

* feat(PBI-49): add BEM sub-element data-debug-id to components/settings/*

- leave-product-button: root only (single-button component)
- min-quota-editor: __input (number input), __save (save button)
- profile-editor: __username (bio/short-description input), __save (submit)
- role-manager: __roles (checkbox list), __add (save button)
- token-manager: __tokens (active tokens list), __generate (create button)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(PBI-49): add BEM sub-element data-debug-id to admin, auth, dialogs, entity-dialog, mobile, split-pane

* docs(PBI-49): add debug-labels BEM pattern doc + CLAUDE.md entry

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-09 22:46:29 +02:00
Janpeter Visser
6756450131
Sprint: rerun jobs (#176)
* feat(PBI-jobs): voeg isDemo-prop door aan JobsBoard en JobDetailPane

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(PBI-jobs): voeg 'Opnieuw starten'-knop toe aan JobDetailPane

Toont een restart-knop voor jobs met status FAILED, CANCELLED of SKIPPED.
Gebruikt useTransition voor loading-state en DemoTooltip voor demo-modus.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test(PBI-jobs): voeg component-test toe voor JobDetailPane restart-knop

Test: knop zichtbaar voor FAILED, verborgen voor DONE, aanroep met juist id, disabled in demo-modus.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs(PBI-jobs): voeg F-14 restart-acceptatiecriteria toe aan functional.md

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-09 17:19:47 +02:00
Janpeter Visser
71319e629d
feat(PBI-71): UX-fix 'lege sprint' + sprint-switch data-refresh (#175)
- StartSprintButton dialog toont 3-state banner: info met accurate vrije-
  stories count + PBI-context, of waarschuwing als geen PBI geselecteerd
  is, of waarschuwing als de geselecteerde PBI 0 vrije stories heeft
- Voeg sprint_id toe aan BacklogStory/Story/SprintStory + select in PB-
  pagina's en sprint-board mappings, zodat de banner accuraat kan tellen
- createSprintAction: revalidatePath met 'layout' flag voor consistency
  met createSprintWithPbisAction (top-nav 'Sprint' link ververst direct)

Sprint-switch data-refresh op alle relevante pagina's:

- BacklogHydrationWrapper: fingerprint-based re-hydratie zodat PB-data
  na router.refresh opnieuw uit nieuwe initialData komt (was: useEffect
  met lege deps draaide alleen 1x)
- SprintBoardClient: key={sprint.id} forceert remount bij sprint-switch
  zodat lokale sprintStories/sprintStoryIds-state vers ge-init wordt
- Solo (desktop + mobile): gebruik resolveActiveSprint(id) ipv eerste
  OPEN-sprint, plus key={sprint.id} op SoloBoard voor remount

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 16:27:24 +02:00
Janpeter Visser
8c63ba377d
feat(PBI-67): model + mode-selectie per ClaudeJob-kind (#169)
* feat(PBI-67/ST-1297): datamodel-velden voor job-model-selectie

Voegt 8 nieuwe optionele velden toe verspreid over Product, Task en
ClaudeJob ten dienste van de override-cascade:

  task.requires_opus → job.requested_* → product.preferred_* → kind-default

Bestaande rijen krijgen NULL (Product/ClaudeJob) of false (Task) en
vallen daarmee terug op de kind-defaults uit de resolver (ST-1298).

Migration is additief: alleen ALTER TABLE ADD COLUMN, geen RENAME of
DROP. Bestaande factories en seed-script blijven werken zonder
aanpassing omdat alle nieuwe velden default-waardes hebben.

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

* feat(PBI-67/ST-1299): job-config snapshot bij enqueue + worker-flag-runbook

T-789: Snapshot van resolved JobConfig in ClaudeJob.requested_*
bij elke job-creatie. Helper in lib/job-config-snapshot.ts laadt
product (preferred_*) en task (requires_opus) en draait de resolver
uit lib/job-config.ts (mirror van scrum4me-mcp/src/lib/job-config.ts —
zelfde matrix, sync-comment in bestand). Toegepast op alle 5
enqueue-locaties:

  - actions/user-questions.ts          (PLAN_CHAT)
  - actions/sprint-runs.ts × 3         (SPRINT_IMPLEMENTATION x2,
                                        TASK_IMPLEMENTATION loop)
  - actions/ideas.ts                   (IDEA_GRILL / IDEA_MAKE_PLAN)

Test-mocks uitgebreid met product.findUnique en task.findUnique zodat
de helper bij unit tests veilig terugvalt op kind-defaults (alle 563
tests groen).

T-790: Sectie 'Config doorgeven aan Claude Code' toegevoegd aan
docs/runbooks/worker-idempotency.md met CLI-flag-mapping en de
verwachte aanroep per kind. Forward-link naar
docs/runbooks/job-model-selection.md (volgt in T-794).

Plus: docs/plans/job-model-selection.md (de approved plan-doc).

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

* feat(PBI-67/ST-1300): cost-attribution voor thinking-tokens + admin UI

T-792: token-stats + token-history rekenen actual_thinking_tokens nu
mee in de totale kosten (tegen input-rate, conform Anthropic billing).
COALESCE-veilig zodat oude rijen 0 bijdragen i.p.v. NaN. Nieuwe export
`getTokenStatsByKind` aggregeert tokens en kosten per ClaudeJob.kind
zodat we relatieve uitgaven van IDEA_GRILL/IDEA_MAKE_PLAN/PLAN_CHAT/
TASK_IMPLEMENTATION/SPRINT_IMPLEMENTATION kunnen zien.

T-793: admin/jobs Kosten-tabel toont:
  - Nieuwe kolom 'Thinking' (aantal verbruikte thinking-tokens)
  - Mismatch-marker (rood) als requested_model afwijkt van actuele
    model_id — duidt op een worker die de CLI-flag niet doorgaf.
    Tooltip toont aangevraagd model. Geen Sentry/log-noise.

Page-level cost-berekening volgt dezelfde formule (input_price ×
thinking_tokens). 563 tests groen.

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

* docs(PBI-67/ST-1301): runbook + CLAUDE.md updates voor model/mode-selectie

T-794: Nieuwe runbook docs/runbooks/job-model-selection.md met
override-cascade, kind-default-matrix, override-voorbeelden,
auditspoor en cost-attribution-formule. 107 regels.

T-795: CLAUDE.md hardstop-bullet voor 'Model/mode per ClaudeJob'
(verwijst naar nieuwe runbook) + patterns-quickref-rij voor
job-config resolver. CLAUDE.md blijft 139 regels (≤ 150).

T-796: docs:check-links groen — 108 files, geen broken links. Twee
externe-repo verwijzingen (scrum4me-mcp/...) ge-de-linked tot plain
text omdat de check-links script de zustertree niet traverseert; de
referenties blijven leesbaar.

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

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 11:20:10 +02:00
Janpeter Visser
a16988b957
Sprint: debug, zichtbaarheid componenten (#165)
* feat(debug-store): Zustand store met hydration-flag voor debug-modus

* feat(status-bar): dev-only debug-toggle via geïsoleerde sub-component

* feat(globals.css): debug-mode overlay CSS voor data-debug-id elementen

* feat(shared): data-debug-id+label op navigatie-componenten

* feat(shared): data-debug-id+label op form/select-componenten

* feat(shared): data-debug-id+label op display-componenten
2026-05-08 08:55:43 +02:00
Janpeter Visser
3842c05ae9
feat: sprint-switcher overal + PBI auto-toevoeging + cleanups (#163)
* refactor: verplaats sprint-switcher van NavBar naar product-header

Sprint-pulldown zit nu in de bestaande balk op de product backlog
(naast Sprint starten / Instellingen) i.p.v. in het midden van de
NavBar. Alleen zichtbaar wanneer het product ook het actieve product
van de gebruiker is.

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

* chore: sync package-lock.json version naar 1.2.0

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

* refactor: centreer sprint-switcher en verwijder badges uit dropdown items

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

* refactor: vervang sprint-status badge door subtle tekst

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

* feat: toon code + titel + status in sprint-switcher dropdown items

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

* fix: cookie-write uit Server Component (Next.js 16 verbiedt dit)

setActiveSprintCookie werd direct aangeroepen in app/(app)/products/[id]/sprint/[sprintId]/page.tsx,
wat in Next.js 16 een runtime-error oplevert ('Cookies can only be modified in a Server Action
or Route Handler'). Vervangen door een client-side bridge die syncActiveSprintCookieAction
aanroept na mount, zodat de active-sprint cookie nog steeds gesynced blijft met de URL.

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

* feat: filter 'toon afgeronde sprints' in sprint-switcher dropdown

Default verbergt de switcher gesloten/gearchiveerde/mislukte sprints
(toont alleen open + de huidige actieve sprint). Toggle bovenaan de
lijst om alle sprints te tonen.

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

* feat: nieuwe sprint wordt direct geselecteerd zonder redirect

createSprintAction zet nu de active-sprint cookie naar de zojuist
aangemaakte sprint, en de StartSprintButton refresht de huidige
pagina i.p.v. te redirecten naar /sprint. Resultaat: gebruiker blijft
op de product backlog en ziet de nieuwe sprint direct geselecteerd
in de sprint-pulldown.

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

* refactor: verplaats Manual en Admin naar user-menu dropdown

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

* feat: voeg geselecteerde PBI automatisch toe aan nieuwe sprint

Bij sprint-aanmaak wordt de pbi_id uit de selection-store als hidden
form-field meegestuurd. Server-side worden alle stories van die PBI
(zonder sprint) en hun taken aan de nieuwe sprint gekoppeld; stories
krijgen status IN_SPRINT met incrementele sort_order.

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

* feat: sprint-switcher op solo- en sprint-board pagina's

Sprint-switcher is nu beschikbaar op de drie hoofdpagina's: product
backlog, solo board en sprint board. Allen renderen 'm in een
gecentreerde balk net onder de NavBar. Sprint-data via gedeelde helper
getSprintSwitcherData.

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

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 02:32:50 +02:00
Janpeter Visser
a4a7ef9b8b
refactor: sprint-switcher van NavBar naar product-header (#162)
* refactor: verplaats sprint-switcher van NavBar naar product-header

Sprint-pulldown zit nu in de bestaande balk op de product backlog
(naast Sprint starten / Instellingen) i.p.v. in het midden van de
NavBar. Alleen zichtbaar wanneer het product ook het actieve product
van de gebruiker is.

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

* chore: sync package-lock.json version naar 1.2.0

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

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 01:05:39 +02:00
Janpeter Visser
4a9db57e94
feat(PBI-63): meerdere sprints per product + EXCLUDED + sprint-switcher (#161)
- Sprint lifecycle: ACTIVE→OPEN, COMPLETED→CLOSED, +ARCHIVED (FAILED behouden)
- TaskStatus: +EXCLUDED (overgeslagen door agent-loop via bestaande TO_DO filter)
- Cookie-gebaseerde actieve sprint per product (lib/active-sprint.ts)
- Route splitsen: /products/[id]/sprint/[sprintId] + /sprint redirect-page
- NavBar: gestapelde product/sprint dropdowns + BUILDING-badge derivatie
- Backlog selectie-modus + nieuwe-sprint-dialog (createSprintWithPbisAction)
- Migratie 20260507210000_sprint_lifecycle: ALTER TYPE RENAME (geen data-rewrite)
- Version bump 1.0.0 → 1.2.0

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 00:15:04 +02:00
Janpeter Visser
7ae8a24372
Sprint: pbi-55 (#156)
* ST-cmovs79lt: Schema + migratie PushSubscription model

Voeg PushSubscription model toe aan prisma/schema.prisma met
snake_case-conventie, relation field op User, en bijbehorende
migratie (push_subscriptions tabel, FK + index op user_id).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* ST-cmovs7e3o: web-push dependency + VAPID env vars feature-gated

Voeg web-push + @types/web-push toe aan package.json.
Registreer NEXT_PUBLIC_VAPID_PUBLIC_KEY, VAPID_PRIVATE_KEY,
VAPID_SUBJECT en INTERNAL_PUSH_SECRET als .optional() in lib/env.ts.
Documenteer alle vier in .env.example en README.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* ST-cmovs7jgr: lib/push-server.ts met sendPushToUser + stale-cleanup

Server-only push-lib met VAPID feature-gate, send naar alle
subscriptions van een user, en automatische cleanup bij 404/410.
Unit tests: success-pad, 410 verwijdert sub, 404 verwijdert sub,
andere errors loggen zonder delete.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* ST-cmovs7ouz: lib/push-client.ts client-side push helpers + stub actions/push.ts

Client-side helpers: isPushSupported, isIOSSafari, isStandalonePWA,
urlBase64ToUint8Array, subscribeToPush, unsubscribeFromPush.
Stub actions/push.ts zodat imports resolven (implementatie volgt
in volgende taak). Unit tests voor urlBase64ToUint8Array.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* ST-cmovs7ut4: actions/push.ts subscribeToPushAction + unsubscribeFromPushAction

Vervangt stub met volledige implementatie: requireUser via getSession,
demo-block, Zod-validatie, upsert met user_id-scoping en user-scoped
deleteMany. Tests (8): idempotentie, demo-block, unauthenticated, invalid input.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* ST-cmovs80c1: POST /api/internal/push/send met constant-time Bearer check

Route: 503 als INTERNAL_PUSH_SECRET uitstaat, 401 bij verkeerd secret
(timingSafeEqual), 400 bij invalid JSON, 422 bij Zod-fout, 204 bij succes.
push-server.ts: env-import vervangen door process.env om SESSION_SECRET
validatie tijdens build te omzeilen. Tests aangepast.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* ST-cmovs862j: Admin test-send route + public/sw.js service worker

POST /api/internal/push/test-send: requireAdmin check (redirect bij
niet-admin), optioneel body met defaults, roept sendPushToUser aan, 204.
public/sw.js: push-handler met showNotification, notificationclick met
same-origin guard, focus bestaand venster of openWindow.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* ST-cmovs8jvq: PushToggle component met 3 states + iOS-banner

Client component met states loading/unsupported/ios-needs-install/
denied/subscribed/unsubscribed. useEffect detecteert initial status,
permission-prompt alleen via user-click. iOS-banner NL, denied-uitleg,
subscribe/unsubscribe knoppen met sonner-toasts.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* ST-cmovs8psg: notifications-sheet + iOS meta-tags in layout

notifications-sheet.tsx: PushToggle onderin met sectie
'Notificatie-instellingen' en visuele scheidslijn.
app/layout.tsx: appleWebApp.capable, statusBarStyle en
mobile-web-app-capable meta-tags toegevoegd via Next.js Metadata API.
manifest.json had al display: standalone.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* ST-cmovs8vxj: docs/patterns/web-push.md pattern-documentatie

Architectuur-diagram, payload-shape, foutcodes, VAPID-config,
iOS-quirks, demo-users blokkade, trigger-voorbeelden (server +
HTTP) en admin-testroute curl-voorbeeld.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-07 21:46:01 +02:00
Janpeter Visser
a268df3680
feat(PBI-59): Sprint.code (SP-N sequentieel per product) (#153)
Voegt een verplicht code-veld toe aan Sprint, sequentieel per product
(consistent met PBI-N, ST-NNN, T-N).

- **Schema** — `Sprint.code String @db.VarChar(30)` + `@@unique([product_id, code])`
- **Migratie** — voegt kolom toe als nullable, backfillt bestaande sprints
  via `ROW_NUMBER() OVER (PARTITION BY product_id ORDER BY created_at)`
  als `SP-N`, en zet daarna NOT NULL + UNIQUE.
- **Generator** — `generateNextSprintCode(productId)` in lib/code-server.ts
  volgt het patroon van story/pbi/task; createSprintAction gebruikt
  `createWithCodeRetry` voor race-bescherming.
- **Seed** — sprint-counter per product (`SP-1`, `SP-2`, ...).

Zichtbaar in:
- Sprint-header (`Product › Sprint actief · SP-3`)
- JobCard + JobDetailPane voor SPRINT_IMPLEMENTATION jobs
- Insights: VelocityChart x-axis (compacter dan goal-truncated),
  AlignmentTrend tooltip, SprintInfoStrip
- actions/jobs-page.ts: `sprintCode` is weer een echte code i.p.v. null

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 20:10:16 +02:00
Janpeter Visser
f166186374
feat(PBI-59): Jobs-pagina UI (vervolg na #149) (#150)
* feat(PBI-58): Vitest-tests voor SoloTaskCard veldmapping en 4-regels layout

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(PBI-59): server action fetchJobsPageData voor jobs-pagina

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(PBI-59): SSE-route /api/realtime/jobs voor user-scoped job-events

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(PBI-59): JobCard component voor jobs-pagina

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(PBI-59): JobDetailPane component voor jobs-pagina

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(PBI-59): API route GET /api/jobs/[id]/sub-tasks voor sprint task executions

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(PBI-59): SprintSubTasksPane component voor jobs-pagina

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(PBI-59): Zustand store useJobsStore voor jobs-pagina

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(PBI-59): useJobsRealtime hook met SSE-verbinding en store-updates

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(PBI-59): JobsBoard 3-kolom SplitPane client component

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(PBI-59): /jobs server page met JobsBoard

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(PBI-59): Jobs nav-link toevoegen aan NavBar

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-07 19:16:20 +02:00
Janpeter Visser
4a63b4b01f
Sprint: UI taken/ (#149)
* feat(PBI-58): Vitest-tests voor SoloTaskCard veldmapping en 4-regels layout

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(PBI-59): server action fetchJobsPageData voor jobs-pagina

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(PBI-59): SSE-route /api/realtime/jobs voor user-scoped job-events

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(PBI-59): JobCard component voor jobs-pagina

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(PBI-59): JobDetailPane component voor jobs-pagina

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(PBI-59): API route GET /api/jobs/[id]/sub-tasks voor sprint task executions

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-07 18:41:19 +02:00
Janpeter Visser
bd7478861b
PBI-58: Developer manual + in-app /manual page (#148)
* docs(PBI-58): add developer manual chapters under docs/manual/

Adds a 7-file English-language manual targeted at new human contributors:
index, overview, statuses & transitions (with mermaid state diagrams),
git workflow, MCP integration, docker, and troubleshooting. The manual
is the *map* — it cross-references existing runbooks/ADRs/architecture
docs rather than duplicating their content.

Regenerates docs/INDEX.md and validates with check-doc-links.mjs.

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

* chore(PBI-58): add markdown rendering deps + manual:build script

Adds mermaid, rehype-slug, rehype-autolink-headings for the in-app
/manual page. Wires manual:build into prebuild so production builds
always regenerate the chapter TOC.

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

* feat(PBI-58): codegen script for in-app manual TOC

scripts/build-manual.mjs walks docs/manual/, parses YAML front-matter,
strips it from the body, and emits lib/manual.generated.ts with a typed
ManualEntry[] containing slug, title, description, filePath, and the
embedded markdown body. Pure Node 20, mirrors generate-docs-index.mjs.

Inlining the markdown at build time keeps runtime serverless functions
free of filesystem reads, which avoids whole-project NFT tracing.

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

* feat(PBI-58): /manual route renders developer manual chapters in-app

Catch-all route at app/(app)/manual/[[...slug]]/page.tsx with
generateStaticParams covering every TOC entry. Server-side
MarkdownView uses react-markdown with remark-gfm, rehype-slug, and
rehype-autolink-headings; mermaid code blocks are routed to a
client-only MermaidBlock that dynamic-imports mermaid on mount.

ManualSidebar (client) reads the typed TOC and highlights the active
chapter via usePathname.

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

* feat(PBI-58): add Manual link to main nav bar

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

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 18:00:10 +02:00
Janpeter Visser
d750676f5e
PBI-56 + ST-1275: PLAN_READY → GRILLING re-grill + SKIPPED status rendering (#147)
* fix(ST-1272): allow PLAN_READY → GRILLING re-grill transition

actions/ideas.ts already lists PLAN_READY in GRILL_TRIGGERABLE_FROM,
but lib/idea-status.ts ALLOWED_TRANSITIONS was missing the
PLAN_READY → GRILLING edge. As a result, clicking Grill on a PLAN_READY
idea returned 422 "Status-transitie ongeldig" while the UI button was
enabled. Mirrors the existing PLANNED → GRILLING re-grill behaviour.

- lib/idea-status.ts: PLAN_READY allows GRILLING in addition to
  PLANNING/PLANNED
- __tests__/lib/idea-status.test.ts: explicit assert for
  PLAN_READY → GRILLING and PLAN_READY added to the regrill loop
  covering every GRILL_TRIGGERABLE_FROM status

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

* feat(ST-1275): render SKIPPED job status in chart-colors and insights

Closing the gap left when ClaudeJobStatus.SKIPPED was added to the schema:
the badge map and case-mapper already covered it, but the chart palette,
the per-day insights aggregator and the stacked-bar chart did not. SKIPPED
jobs (e.g. cmovkur8 manually flipped during the no-op-exit hotfix) now
render with a muted style consistent with cancelled.

- lib/chart-colors.ts: JOB_STATUS_COLORS gains a 'skipped' entry
  (var(--muted-foreground), same intensity as cancelled — neither rood/orange)
- lib/insights/agent-throughput.ts: DayCount + STATUSES + perDay zero-fill
  now include 'skipped'; the SQL terminal_7d filter already counted SKIPPED
- app/(app)/insights/components/agent-throughput.tsx: STACKED_STATUSES and
  the empty-state guard include 'skipped'
- __tests__: chart-colors keys list, job-status round-trip ('all 7 statuses')
  and the insights non-zero filter all account for SKIPPED

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

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 17:36:44 +02:00
Janpeter Visser
e8562d4018
Sprint: inzicht jobs (#146)
* feat(admin/jobs): select token-velden en bereken kostprijs server-side

Voegt model_id, input_tokens, output_tokens, cache_read_tokens en
cache_write_tokens toe aan de ClaudeJob-query en berekent cost_usd per
job via een ModelPrice-lookup. Jobs zonder prijs-entry of zonder
input_tokens krijgen cost_usd: null.

* feat(admin/jobs-table): toggle-buttons en view-state voor status/kosten-weergave

Voegt useState toe, breidt Job-type uit met model_id en cost_usd, extraheert
huidige tabellogica naar StatusTable en voegt CostsTable-stub + toggle-knoppen
toe aan JobsTable.

* feat(admin/jobs-table): CostRow en CostsTable voor kosten-view

Voegt CostRow toe met kolommen ID/Gebruiker/Product/Type/Model/Kosten(USD)/
Aangemaakt/Acties en vervangt de CostsTable-stub door een volledige tabel.
Kostprijs geformatteerd als "$0.0042"; ontbrekende prijs toont "—".
2026-05-07 16:09:17 +02:00
Janpeter Visser
2d27c41d38
fix(textarea): border-border + bg-input-background voor off-focus zichtbaarheid (#143)
Vervangt border-input door border-border en bg-transparent door
bg-input-background in de gedeelde Textarea-component en de lokale
textareaClass in task-dialog.tsx. Dark mode ongewijzigd via dark:bg-input/30.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-07 14:27:11 +02:00
Janpeter Visser
d3e79021c1
PBI-47: schema, pause_context Zod, resumePausedSprintRunAction, PAUSED-banner UI (#137)
Scrum4Me-side counterpart of scrum4me-mcp@f7f5a48 (PBI-9 + PBI-47):

- prisma migration: ClaudeJob.{base_sha,head_sha} + SprintRun.pause_context
- lib/pause-context.ts: Zod schema + parsePauseContext + pauseReasonLabel
  helper; single source of truth for the JSON pause_context shape produced
  by the mcp sprint-run flow (MERGE_CONFLICT pause)
- actions/sprint-runs.ts: resumePausedSprintRunAction — separate from the
  existing FAILED-resume flow, requires SprintRun.status === PAUSED, closes
  the linked ClaudeQuestion, clears pause_context, sets RUNNING/QUEUED based
  on whether a claim is still active
- components/sprint/sprint-run-controls.tsx: PAUSED banner with reason label,
  PR link, conflict-files list (max 5 + "+N more"), Resume button with
  confirm() guard
- app/(app)/products/[id]/sprint/page.tsx: load pause_context from active
  SprintRun and pass through to SprintRunControls

All MD3 tokens (warning-container, on-warning-container, primary). No raw
Tailwind utility colours.

Tests: 532 passing across 72 files (Scrum4Me side).

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 22:17:11 +02:00
Janpeter Visser
77617e89ac
PBI-46: Sprint-niveau jobflow met cascade-FAIL (F1/F2/F4 Scrum4Me) (#136)
* ST-1243: F1 schema + propagateStatusUpwards-helper voor sprint-flow

Schema-uitbreidingen voor de sprint-niveau jobflow (PBI-46):
- TaskStatus, StoryStatus, PbiStatus, SprintStatus krijgen FAILED
- Nieuwe enums: SprintRunStatus, PrStrategy
- Nieuw SprintRun-model dat per-task ClaudeJobs groepeert
- ClaudeJob.sprint_run_id koppeling + index
- Product.pr_strategy (default SPRINT)
- Bijhorende Prisma-migratie

propagateStatusUpwards vervangt updateTaskStatusWithStoryPromotion en
herevalueert de keten Task → Story → PBI → Sprint → SprintRun bij elke
task-statuswijziging. Bij FAILED cancelt het sibling-jobs in dezelfde
SprintRun. PBI-status BLOCKED blijft handmatig en wordt niet overschreven.

Status-mappers + theme krijgen failed-token + label-uitbreidingen.

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

* ST-1244: F2 sprint-runs actions + deprecate per-task enqueues

actions/sprint-runs.ts (nieuw):
- startSprintRunAction met pre-flight (impl_plan / open ClaudeQuestion / PBI BLOCKED|FAILED)
- Maakt SprintRun + ClaudeJobs in PBI→Story→Task volgorde
- resumeSprintAction zet FAILED tasks/stories/PBIs terug en start nieuwe SprintRun
- cancelSprintRunAction breekt lopende SprintRun af zonder cascade

actions/claude-jobs.ts:
- enqueueClaudeJobAction, enqueueAllTodoJobsAction, previewEnqueueAllAction,
  enqueueClaudeJobsBatchAction nu deprecation-stubs (UI-cleanup volgt in F4)
- cancelClaudeJobAction blijft beschikbaar voor losse jobs

Tests bijgewerkt: 11 nieuwe sprint-runs tests, claude-jobs(-batch) tests
herzien naar deprecation-asserties.

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

* ST-1246: F4 UI Start/Resume/Cancel sprint + pr_strategy dropdown

- components/sprint/sprint-run-controls.tsx: knoppen Start Sprint
  (sprintStatus=ACTIVE), Hervat sprint (sprintStatus=FAILED) en
  Annuleer sprint-run (lopende run). Pre-flight blocker-modal toont
  blockers met directe links naar de relevante pagina's.
- components/products/pr-strategy-select.tsx: dropdown SPRINT|STORY in
  product-settings, met optimistic update + sonner-toast op fail.
- actions/products.ts: updatePrStrategyAction (eigenaar-only, demo-block).
- Sprint-page: query op actieve SprintRun + tonen van controls-balk.

Live cascade-visualisatie (T-634) staat als follow-up genoteerd —
huidige sprint-board statusbadges volstaan voor MVP. De Solo-board
"Voer uit"-knoppen zijn niet expliciet verwijderd; ze tonen nu de
deprecation-error van de gestubde actions tot de Solo-flow opnieuw
ontworpen wordt.

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

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 16:43:57 +02:00
Janpeter Visser
ab8c3dca3f
ST-1239: Atomische database-migratie — todos naar ideas + droppen todos-tabel (#132)
* feat(cleanup): verwijder Todo's navlink en todo-referenties uit marketing page [cmotto5ia000nx3178lq6xk8d]

- nav-bar.tsx: Todo's navLink verwijderd; Ideas-link blijft staan
- app/page.tsx: /todos quick-access link, feature-entry en /api/todos API-doc verwijderd

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(cleanup): verwijder app/(app)/todos/ en components/todos/ [cmottjvzo000cx3172472cu4g]

* test(cleanup): verwijder POST /api/todos import en describe-block uit security.test.ts [cmotto5jn000px317kjqlba89]

- Import 'POST as postTodo' uit verwijderde todos-route verwijderd
- describe('POST /api/todos') sectie (3 tests) verwijderd
- 73 testfiles / 561 tests groen

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test(cleanup): verwijder __tests__/api/todos.test.ts en __tests__/actions/todos-promote-idea.test.ts [cmottjw1u000fx317igq27mh5]

* feat(cleanup): verwijder actions/todos.ts en app/api/todos/route.ts; verplaats updateRolesAction naar actions/settings.ts [cmottjvy9000ax3173sgfjcqs]

* feat(db): migratie todos→ideas, counters bijwerken, todos dropt [cmotto5fh000jx317r7c5srvb]

Nieuwe Prisma-migratie die in één transactie actieve todos omzet naar
DRAFT-ideas met unieke IDEA-NNN codes, idea_code_counter per user
bijwerkt, en de todos-tabel dropt.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(schema): verwijder Todo model en relaties uit prisma/schema.prisma [cmottjvwu0008x317fwwodg3i]

* feat(cleanup): vervang open_todos door open_ideas in /api/products/:id/claude-context

Laatste prisma.todo-referentie verwijderd. Endpoint geeft nu open_ideas terug
(ideeën van de gebruiker voor dit product die niet gearchiveerd zijn en nog
niet status PLANNED hebben). Docs bijgewerkt in docs/api.md en
docs/api/rest-contract.md.

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

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 12:25:37 +02:00
Janpeter Visser
c18d17108c
ST-1240: Verwijder backend todo-code (server actions + API route) (#135)
* feat(cleanup): verwijder Todo's navlink en todo-referenties uit marketing page [cmotto5ia000nx3178lq6xk8d]

- nav-bar.tsx: Todo's navLink verwijderd; Ideas-link blijft staan
- app/page.tsx: /todos quick-access link, feature-entry en /api/todos API-doc verwijderd

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(cleanup): verwijder app/(app)/todos/ en components/todos/ [cmottjvzo000cx3172472cu4g]

* test(cleanup): verwijder POST /api/todos import en describe-block uit security.test.ts [cmotto5jn000px317kjqlba89]

- Import 'POST as postTodo' uit verwijderde todos-route verwijderd
- describe('POST /api/todos') sectie (3 tests) verwijderd
- 73 testfiles / 561 tests groen

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test(cleanup): verwijder __tests__/api/todos.test.ts en __tests__/actions/todos-promote-idea.test.ts [cmottjw1u000fx317igq27mh5]

* feat(cleanup): verwijder actions/todos.ts en app/api/todos/route.ts; verplaats updateRolesAction naar actions/settings.ts [cmottjvy9000ax3173sgfjcqs]

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 12:24:48 +02:00
Janpeter Visser
628fbd7e7b
ST-1241: Verwijder frontend todo-UI (page, component, navbar en marketing page) (#133)
* feat(cleanup): verwijder Todo's navlink en todo-referenties uit marketing page [cmotto5ia000nx3178lq6xk8d]

- nav-bar.tsx: Todo's navLink verwijderd; Ideas-link blijft staan
- app/page.tsx: /todos quick-access link, feature-entry en /api/todos API-doc verwijderd

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(cleanup): verwijder app/(app)/todos/ en components/todos/ [cmottjvzo000cx3172472cu4g]

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 12:24:00 +02:00
Janpeter Visser
3a61a8ddc1
Landing v3: van idee tot pull request + 6 nieuwe screenshots (#127)
* feat(landing): tour uitbreiden naar 6 screenshots incl. Ideas/Insights

Vervangt de oude 3 .jpg-screenshots door 6 nieuwe .png's met de huidige
visuele staat van de app. Volgorde toont de hele flow: ideeën binnen →
producten → backlog → sprint → solo → insights.

- Tour-array van 3 naar 6 figures, grid blijft lg:grid-cols-3 (2x2-rijen)
- Intro-tekst aangepast: "Zes weergaven van Scrum4Me — van inkomende
  ideeën tot persoonlijk Kanban-bord en voortgangs-inzichten"
- Bestandsnamen gehernoemd naar lowercase-dash-conventie:
  Sprint.png → sprint.png, Solo.png → solo.png, "Product Backlog.png" →
  product-backlog.png, Producten.png → producten.png, Insights.png →
  insights.png, Ideas-table.png → ideas-table.png,
  Ideas-detail.png → ideas-detail.png (alleen voorbereiding, nog niet in
  tour gebruikt)
- Oude .jpg-bestanden verwijderd

Niet onderdeel: hero/architectuur/handleiding-rewrite voor v3 — die staan
gepland onder ST-1224 en blijven open.

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

* feat(landing): v3 — van idee tot pull request, met procesflow + Idea-laag

Verbreedt de landing-propositie van "executie lokaal" (v2) naar de
volledige cyclus idee → grill → plan → execute → PR. Eert M12 Ideas dat
sinds 2026-05-04 het kernconcept van het product is, plus auto-PR en
Sync-tab.

Hero:
- H1 "Van idee tot pull request — op je eigen hardware."
- Subhead benoemt grill→plan→materialise→agent→PR-cyclus en houdt het
  lokaal-anker uit v2 vast.

Nieuw §3 "Van idee tot pull request":
- 4 procesflow-kaarten (Idee/Grill/Plan/Execute) met status-chips
  (DRAFT/GRILLING/PLAN_READY/DONE→PR) en pijlen tussen kaarten op md+.
- Samenvattende paragraaf over state-machine, materializeIdeaPlanAction
  en auto-PR.

Architectuur:
- docs/diagrams/architecture.mmd Worker-label uitgebreid met
  "jobs: GRILL · PLAN · IMPL"; beide SVG's geregenereerd.
- Callout "Lokale worker" benoemt nu de drie jobsoorten expliciet.

Feature-grid (set D, 6 kaarten):
- Vervang "Hiërarchisch plannen" door "Ideas — Grill & Plan" op positie 1.
- Lokale Claude-agents: + auto-PR/SQUASH-merge zin.
- Realtime updates: + Sync-tab zin.
- Async vraagkanaal: + Grill-vragen zin.

Quickstart:
- Extra regel over UI-route (/ideas → Nieuw idee → Grill me) onder de
  bestaande MCP-installatie code-block.

Scrum-samenvatting:
- Hiërarchie wordt twee-rij-systeem: Idea (DRAFT → GRILLED → PLAN_READY)
  als bovenste rij, daaronder Product → PBI → Story → Taak met
  "materialiseert ↓"-pijl.
- Terminologie krijgt 2 nieuwe tegels (Idea, Grill/Plan).

Handleiding (10 → 12 stappen):
- Nieuwe stappen 3-5 voor Idea-route (vastleggen, grillen, plan + materialiseren),
  visueel gemarkeerd met tertiary-border en chip "Idea-route".
- Oude 7+8 (token + MCP-koppeling) samengevoegd tot stap 10.
- Stap 11 uitgebreid met Sync-tab-volgen.
- Stap 6 hernoemd "opbouwen" → "finetunen".

Plan-doc + grilling-context vastgelegd in docs/plans/landing-v3-idea-flow.md.

Tracked: ST-1224 onder PBI Marketing & Landingspagina.

Verified: lint 0 errors / 7 warnings (alle pre-existing), 564/564 tests,
productie-build slaagt.

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

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 09:05:22 +02:00
Janpeter Visser
31dc429b61
feat(M13 PBI-31 T-519b/T-520b): NavBar stand-by badge + quota-check runbook (#119)
* feat(M13 T-519b): SSE worker_heartbeat + NavBar stand-by badge

Aanvulling op scrum4me-mcp PR #25 (worker_heartbeat MCP-tool).

- app/api/realtime/solo/route.ts: WorkerHeartbeatPayload type +
  isWorkerHeartbeatPayload guard + shouldEmit-routing op user_id.
- stores/solo-store.ts: workerQuotaPct + workerQuotaCheckAt state +
  setWorkerQuota action. Reset bij decrementWorkers naar 0.
- lib/realtime/use-solo-realtime.ts: handle worker_heartbeat-event,
  roep setWorkerQuota.
- components/solo/nav-status-indicators.tsx: stand-by badge wanneer
  workerQuotaPct < minQuotaPct + tooltip met drempel.
- components/shared/nav-bar.tsx + app/(app)/layout.tsx: minQuotaPct
  prop plumbing van User.min_quota_pct naar NavStatusIndicators.

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

* docs(M13 T-520b): pre-flight quota-check sectie in mcp-integration

Documenteert de batch-loop-uitbreiding:
1. get_worker_settings → min_quota_pct
2. bin/worker-quota-probe.sh → pct + reset
3. worker_heartbeat naar server (NavBar stand-by-badge)
4. Sleep tot reset bij low quota; anders wait_for_job

Verwijst naar bin/worker-quota-probe.sh in scrum4me-docker (zie
PR daar).

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

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 04:34:48 +02:00
Janpeter Visser
555ed8fe89
feat(ST-qfpqpxzy): DB schema + settings-UI voor min_quota_pct worker-drempel (#118)
- User.min_quota_pct Int @default(20) + ClaudeWorker.last_quota_pct/last_quota_check_at
- Migratie add_worker_quota_gate
- lib/schemas/user.ts: minQuotaPctSchema (int, 1-100)
- actions/settings.ts: updateMinQuotaPctAction met auth/demo/zod-guard
- MinQuotaEditor component met numeric input en DemoTooltip
- Settings-pagina: Worker-instellingen sectie

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 03:45:59 +02:00
Janpeter Visser
be8cd4d02c
feat(ST-vi5iff4s): SoloTask-interface + Prisma-queries uitbreiden met PBI-velden (#116)
pbi_code, pbi_title, pbi_description (nullable) toegevoegd aan SoloTask-interface.
Desktop en mobile solo-page: story.pbi select + mapping via ?. en ?? null.
Test-fixtures bijgewerkt (3 bestanden). 72 testfiles groen, tsc + build slagen.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 03:30:11 +02:00
Janpeter Visser
a2c8bd41af
ST-1216: Insights sprint-widget — token KPI-kaartjes & per-job tabel (#114)
* feat(ST-vmc7vpps): lib/insights/token-stats.ts — sprint KPI + per-job query

SQL-queries voor totale tokens/kosten (KPI) en per-job tabel met
ModelPrice JOIN. Guard op lege sprintId. Tests voor empty guard,
KPI-mapping en null token-data.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(ST-vmc7vpps): TokenUsageCard — KPI-kaartjes + sorteerbare per-job tabel

Client-component met drie KPI-strips (totaal tokens, kosten USD, gem. per job)
en sorteerbare tabel op kosten of duur. Nulls als '—', MD3-tokens, geen
hardcoded kleuren.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(ST-vmc7vpps): insights page — TokenUsageCard integreren

Voeg getTokenStats + TokenUsageCard imports toe aan insights/page.tsx.
tokenStats apart awaiten na activeSprints (kan niet in dezelfde Promise.all).
TokenUsageCard-sectie toegevoegd na AgentThroughputCard.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 03:19:30 +02:00
Scrum4Me Agent
4a929b1962 feat(ideas): secondary_products meeladen in IdeaDto en alle queries
Voegt IdeaProduct schema toe (dependency van story-qtkvz6ly), breidt
IdeaWithProduct type en IdeaDto interface uit met secondary_products array,
en laadt de relatie mee in findMany/findFirst in page.tsx en REST GET.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 02:20:34 +02:00
678069a3d8 feat(T-563): integreer Sync-tab in IdeaDetailLayout + page-loader
- TabKey union uitgebreid met 'sync'.
- Sync-tab alleen zichtbaar als syncData !== null && idea.status === 'planned'
  (M12 keuze 6: na Materialiseer-actie).
- page.tsx roept loadIdeaSyncData alleen aan bij PLANNED + pbi_id, anders
  null doorgeven aan layout.
- showSync-flag bepaalt of de tab in TAB_KEYS array zit en in de UI
  gerenderd wordt.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 00:21:59 +02:00
f4f02bd0d2 feat(T-561): loadIdeaSyncData server-loader voor Sync-tab
Joint Idea → PBI → Stories → Tasks → ClaudeJobs + StoryLog in één
prisma.findFirst-call. user_id-scope conform M12-keuze 2 (strikt
user_id-only). Filtert ClaudeJob op kind=TASK_IMPLEMENTATION en
neemt laatste 20 story-logs per story.

Returns null als idea geen pbi_id heeft — caller render geen tab.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 00:18:21 +02:00
e1da9aae43 feat(T-560): SSE-route accepteert story_log-payloads
StoryLogPayload type toegevoegd aan NotifyPayload union. In de
notification-handler: idea_id-pad checkt accessibleIdeaIds (M12
user-private), fallback op product_id check accessibleProductIds.
Consistent met question-payload-pattern.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 00:17:38 +02:00
deb70a9e20 feat(T-572): map SKIPPED in lib/job-status + alle terminal-checks
- lib/job-status.ts: SKIPPED ↔ 'skipped' mapping in beide richtingen
- components/shared/job-status.ts: label "Overgeslagen" + neutrale italic styling
- actions/admin/jobs.ts: cancel-guard erkent SKIPPED als eindstatus
- app/api/cron/cleanup-agent-artifacts: SKIPPED ook opruimen na 7d
- lib/insights/agent-throughput: SKIPPED telt mee als terminal

ACTIVE_JOB_STATUSES bewust ongewijzigd — SKIPPED is afgerond.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 23:10:14 +02:00
474a8da053 feat: admin jobs en products pagina's
- /admin/jobs: overzicht van de laatste 100 Claude jobs met cancel/delete
- /admin/products: overzicht van alle producten met archive/delete
- JobsTable component met statusbadges en acties per job
- ProductsTable component met eigenaar, leden/PBI-telling en acties

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-05 20:46:27 +02:00
Scrum4Me Agent
99ae2d7e8f feat(ST-zyb6qlnn): UserQuestions server-side ophalen en als prop doorzenden
- app/(app)/ideas/[id]/page.tsx: userQuestion.findMany + DTO-mapping
- components/ideas/idea-detail-layout.tsx: IdeaUserQuestionDto type +
  userQuestions prop toegevoegd aan Props interface en component-signature

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-05 17:40:31 +02:00