components/shared/nav-bar.tsx:
- New nav-link to /ideas with active-state on pathname.startsWith('/ideas')
- Placement: between Insights and Todo's — matches the M12 plan
("direct boven Todo's")
- No icon (existing nav uses text-only links; deviation from plan's
Lightbulb spec for visual consistency with the rest of the nav)
Tests: 546/546 still green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
components/todos/todo-list.tsx:
- TodoCard: new "→ Idee" button next to "→ PBI" + "→ Story" (only shown
for non-demo)
- PromoteIdeaDialog: confirmation modal — no extra inputs needed since
promoteTodoToIdeaAction takes only todoId; title/description carry
over from the todo, status starts as DRAFT
- onPromoteIdea callback wired through TodoCard props
- On success: navigates to /ideas/{new-id} so user lands on the fresh
idea-detail page
Tests: 546/546 still green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
components/ideas/idea-md-editor.tsx:
- Textarea-based editor with monospace styling for grill_md / plan_md
- kind='plan': live yaml-frontmatter validation as derived state via
useMemo (no setState-in-effect); inline errors with line numbers
- kind='grill': free markdown, no validation
- localStorage draft per (ideaId, kind) — lazy initial-value seeded on
mount; toast notice if drift from server
- Cmd/Ctrl+S keyboard shortcut to save
- Server-action 422 details surface as separate submitErrors state
components/ideas/idea-detail-layout.tsx:
- Grill/Plan tabs flip into edit-mode via "Bewerk" button when:
- grill: status in [GRILLED, PLAN_READY] (M12 grill-keuze 12)
- plan: status === PLAN_READY
- Empty-state offers "Schrijf zelf" when md is null + editable
- Demo always read-only
Tests: 546/546 still green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
components/ideas/idea-row-actions.tsx — replaces T-507 placeholder:
- Grill Me: disabled in GRILLING/PLANNING/PLANNED, requires
product-with-repo + connectedWorkers > 0; tooltip shows specific reason
("Grill loopt al", "Idee heeft een product met repo nodig", "Geen
Claude-worker actief")
- Make Plan: enabled only in GRILLED/PLAN_FAILED/PLAN_READY; same
prerequisites as Grill
- Materialiseer: enabled only in PLAN_READY (no worker needed — synchrone
server-side parser); confirm-dialog before action; navigates to product
backlog PBI anchor on success
- *_FAILED: dedicated "Probeer opnieuw" rotate-icon button
- PLANNED: replaces all three with "Bekijk {PBI-code}" link + open-detail
- Demo: every mutating button wrapped in DemoTooltip with disabled state
- connectedWorkers read directly via useSoloStore (per M12 grill-keuze 16)
Tests: 546/546 still green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
app/(app)/ideas/page.tsx (server-component):
- user_id-only fetch (no productAccessFilter — Idee is privé)
- products fetched with productAccessFilter for filter-dropdown + create-form
components/ideas/idea-list.tsx (client-component):
- Search by title, product-dropdown filter, status multi-chip filter
- Inline create form with title/description/product (optional)
- Native shadcn Table + status badge via getIdeaStatusBadge (T-509)
- Row click navigates to /ideas/[id]
- Sonner toasts for success/error; router.refresh() after mutations
- DemoTooltip + disabled on Nieuw + Archive
- Empty-state + filtered-empty messaging
components/ideas/idea-row-actions.tsx (placeholder for T-508):
- "Open" navigation + "Archive" button only — Grill / Make Plan /
Materialiseer come in T-508 with full disabled-rules
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
PrismaClientValidationError ('Argument \`not\` must not be null') hit at
runtime when notifications-bridge mounted post-M12 schema change.
Although StringNullableFilter typings allow \`not: null\`, the v7 query
engine rejects it.
Removed the WHERE-side filter in 3 places — null-narrowing already
happens client-side via flatMap / Boolean filter:
- components/notifications/notifications-bridge.tsx
- app/api/realtime/notifications/route.ts
- lib/insights/verify-stats.ts (task_id filter)
Idea-questions / idea-jobs will be routed via separate channels in
T-502 + T-507; for now, story-question + task-job paths simply ignore
NULL rows in their post-fetch mapping.
Tests: 479/479 green; tsc clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- new enums IdeaStatus, ClaudeJobKind, IdeaLogType
- new models Idea (with @@unique([user_id, code]) + pbi_id @unique) and IdeaLog
- User.idea_code_counter Int @default(0) for IDEA-{nnn} code generation
- ClaudeJob.task_id nullable; new idea_id + kind fields + index
- ClaudeQuestion.story_id nullable; new idea_id field + index
- existing call sites narrowed to story-questions / task-jobs (idea-paths come in T-502+)
- includes the M12 plan doc copied from /Users/janpetervisser/.claude/plans
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Lighthouse-audit op /products/[id] flagde drie issues; fix in deze PR:
1. **[aria-*] attributes do not match their roles** — pbi-list.tsx had
aria-selected={isSelected} op role="button". aria-selected is alleen
geldig op tab/option/treeitem etc. Voor toggle-buttons is aria-pressed
de juiste attribute.
2. **Touch targets do not have sufficient size** — drie offenders op het
product-backlog scherm (PBI ✎/× iconen, Story ✎ icoon) hadden
~16-18×18px tap-targets via px-1.5/p-0.5. Lighthouse minimum is 24×24
en WCAG AA streeft 44×44. Fix: inline-flex + min-h-7 min-w-7 (28×28px)
met behoud van het kleine icoon — wel grotere clickable area.
3. Dashboard product-card pencil-icoon kreeg dezelfde fix preventief.
Sprint-backlog heeft hetzelfde patroon op meer plekken; bewust nu niet
aangeraakt om PR scope te beperken tot de ge-auditeerde route. Vervolg-PR
indien sprint-page-audit ook flagt.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Statische audit op happy-path-code; 4 categorieën gefixt vóór de Lighthouse-
verificatie die de gebruiker handmatig draait:
1. <main>-landmark op /login en /register (waren <div>); auth-pages krijgen
nu een correcte landmark zodat screen-readers ze kunnen overslaan/nav
2. solo-task-card.tsx: agent-status-pill had role="button" + aria-label maar
GEEN tabIndex en GEEN onKeyDown — keyboard-onbereikbaar. Nu compleet:
tabIndex={0} + Enter/Space-handler
3. Form-label-associaties via htmlFor + id-pairs:
- story-dialog (5): code, title, description, acceptance + priority via labelledby
- task-dialog (3): title, description, implementation_plan
- todo-list PromotePbi/PromoteStory dialogs (6): title, product, pbi, priority
Lighthouse a11y "form-field-multiple-labels" en "label" rules worden
hierdoor groen.
Niet aangeraakt:
- pbi-dialog: htmlFor was al goed gewired
- auth-form: htmlFor was al goed gewired
- Color-contrast: gebruikt MD3-tokens; theoretisch correct (verifieer in
Lighthouse run)
- Heading-hierarchy: nog niet gescand — kan in vervolgronde
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
De dashboard product-card had al een 'Bewerken'-tekstknop, maar het patroon
in de rest van de app (PBI/story/task in cards) is een hover-zichtbaar
pencil-icoon. Vervangen voor consistentie. Product-detail page-header blijft
tekst — daar staat 'Bewerken' tussen andere text-acties zoals "Sprint actief"
en "Instellingen".
Hergebruikt bestaande ProductDialog en setEditingProduct-state — geen wijziging
aan de dialog of action zelf. Demo-block behouden.
Tests: 4 nieuwe (rendert icoon, opent dialog, demo-disabled, geen icoon op
archived).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bottom-fixed nav-bar met 3 lucide-iconen (ListTree/Activity/Settings).
Verbergt Backlog/Solo-tabs als activeProductId null is.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Toont rotate-overlay in portrait, niets in landscape. Kinderen blijven altijd
in DOM — geen unmount zodat SSE-streams overleven bij rotatie.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Hover-zichtbaar ✎-icoon rechts uitgelijnd op iedere taak-rij; opent dezelfde
edit-dialog als een rij-klik (visuele cue, consistent met PBI/story-rijen).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- PBI-rij in Product Backlog-kolom: ✎-icoon rechts uitgelijnd, opent PbiDialog
(rij is nu div role=button i.p.v. nested-button)
- Story-rij in Sprint Backlog-kolom: ✎-icoon vóór de Trash, opent StoryDialog
- SprintStory + PbiWithStories verrijkt met velden die de dialogen lezen
(description / acceptance_criteria / pbi_id / created_at op story; priority /
status / description op PBI)
- pbi.status via pbiStatusToApi → PbiStatusApi (DB UPPER_SNAKE → API lowercase)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Spiegelt het filter-patroon van de Product Backlog-pagina (PbiList) naar de
Sprint-Product-Backlog-kolom: prioriteit + status pills, actieve-filter chips,
localStorage-persistentie. Bestaande collapse-/expand-/alleen-niet-klaar-knoppen
blijven naast de popover staan.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Story 6 zette entityDialogContentClasses op de buitenkant
(flex flex-col p-0 gap-0 max-h-[85vh]) maar de binnenkant van
TaskDetailContent gebruikte nog losse divs zonder shrink-0/flex-1
overflow-y-auto. Resultaat bij lange implementatieplannen: dialog
groeide tot voorbij de viewport, header zat niet vast en footer-margin
(-mx-4 -mb-4) brak omdat parent nu p-0 heeft.
Fix: header in shrink-0 div met px-6 pt-5 pb-4 + border-b; body in
entityDialogBodyClasses (flex-1 overflow-y-auto px-6 py-6 space-y-6);
footer in entityDialogFooterClasses + flex-wrap voor de variabele
job-status-knoppen. Plan-textarea krijgt max-h-[40vh] zodat een lang
plan niet meteen heel het body-gebied opvult.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- TaskDialog: code-input boven titel (font-mono, optional,
placeholder "auto"), CodeBadge in dialog header bij edit
- EditTaskLoader: select code uit DB voor de dialog
- Solo page: vervang inline deriveTaskCode-logica door directe
task.code uit DB
- Sprint page + TaskList + SprintBoardClient: Task-type krijgt
verplicht code-veld; TaskList laat ongebruikte storyCode prop
vallen omdat code-derivatie niet meer nodig is
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Story 5 van PBI "Alle dialogen conform docs/patterns/dialog.md".
- lib/schemas/sprint.ts — gedeelde zod-schemas (create/dates/goal)
- actions/sprints.ts — code+fieldErrors voor 422; code: 403 voor
auth/demo errors
- StartSprintButton dialog: useDirtyCloseGuard, useDialogSubmitShortcut,
entityDialog* layout-classes; DemoTooltip op trigger; veld-niveau
errors via fieldErrors
- SprintHeader's date- en complete-dialogen: zelfde behandeling; date-
dialog krijgt dirty-guard, complete-dialog krijgt DemoTooltip op
bevestigen
- docs/specs/dialogs/sprint.md — entity-profile dat alle drie de modes
documenteert; consolidatie naar één SprintDialog component bewust
uitgesteld
- Sprint-dates tests aangepast aan nieuwe action-shape
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Story 1 van PBI "Alle dialogen conform docs/patterns/dialog.md".
- components/shared/use-dirty-close-guard.tsx — hook + paired AlertDialog
- components/shared/use-dialog-submit-shortcut.ts — Cmd/Ctrl+Enter handler
- components/shared/entity-dialog-layout.ts — MD3-conforme classes voor §4
- TaskDialog refactored om beide hooks + classes te gebruiken (geen
gedragsverandering)
- 8 nieuwe unit-tests
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ProductDialog werd per kaart gerenderd binnen de klikbare card-div.
Hoewel Base UI de dialog portaalt naar document.body, bubblen React
events via de component-tree, niet de DOM-tree — clicks in de dialog
liepen door naar router.push op de kaart.
Fix: dialog-state opheffen naar ProductList; eén ProductDialog buiten
de map() en EditProductButton vervangen door een inline knop met
e.stopPropagation. EditProductButton blijft beschikbaar voor de
product-detailpagina.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Base UI's Menu.Item vuurt geen React-onSelect af bij activatie; alleen
onClick werkt. handleSwitchProduct werd daardoor nooit aangeroepen.
user-menu.tsx en sprint-backlog.tsx gebruiken al onClick — NavBar
miste deze workaround.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- nav-bar: vervang `router.push(/products/{id})` door `router.refresh()` na
setActiveProductAction; voeg success-toast toe. Maakt de actieve-product
switch zichtbaar zonder context-switch naar de detail-page; client-cache
wordt nu correct geinvalideerd.
- product-list (dashboard): integreer EditProductButton naast Activeer/Actief.
Owner én members kunnen editten (per productAccessFilter); demo-modus
rendert disabled+tooltip.
- edit-product-button: optionele isDemo + size + variant props; wraps
DemoTooltip; e.stopPropagation om card-click te voorkomen.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(ST-?): createProductAction + updateProductAction (data-object API)
Voegt data-object-gebaseerde createProductAction(data) en
updateProductAction(id, data) toe aan actions/products.ts voor gebruik
door ProductDialog. Bevat Zod-validatie (incl. github-regex op repo_url),
productAccessFilter voor update, pg_notify bij update, en productMember-
aanleg bij create. FormData-varianten hernoemd naar ...FormAction; callers
bijgewerkt. 9 nieuwe tests groen.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(ST-?): ProductDialog component (create + edit modes)
Voegt components/dialogs/product-dialog.tsx toe op basis van het
entity-dialog-patroon. Gebruikt react-hook-form + zodResolver voor
client-side validatie. Roept createProductAction/updateProductAction
aan en werkt stores/products-store.ts optimistisch bij. Demo-modus
disabled alle velden + submit-knop via DemoTooltip. 7 tests groen.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(ST-?): UI triggers voor ProductDialog op dashboard en product-detail
Voegt NewProductButton toe op het dashboard (vervangt de /products/new
link) en EditProductButton op de product-detail pagina. Bewerken-knop
is alleen zichtbaar voor de product-eigenaar en verborgen in demo-modus.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(test): cast toast via unknown to satisfy strict TS
`toast as { success, error }` direct-cast faalt omdat sonner's toast een
callable + properties is. TS2352. Cast via unknown lost het op zonder
gedrag te wijzigen.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(solo): orderBy taken per PBI-hiërarchie
Voeg pbi.priority en pbi.sort_order toe aan de task.findMany orderBy in de solo-page query zodat taken per PBI gegroepeerd worden vóór story- en task-volgorde.
* feat(solo): previewEnqueueAllAction met blocker-detectie
Voeg previewEnqueueAllAction toe aan actions/claude-jobs.ts: haalt taken op in PBI-volgorde, filtert actieve jobs, detecteert eerste blocker (REVIEW taak of BLOCKED PBI). Retourneert tasks[], blockerIndex en blockerReason. Tests: 7 nieuwe cases voor alle blocker-scenario's en demo-blokkering.
* feat(solo): enqueueClaudeJobsBatchAction met IDOR-check
Voeg enqueueClaudeJobsBatchAction toe: accepteert expliciete taskIds[], verifieert dat alle IDs bij de ingelogde gebruiker horen (IDOR-preventie), slaat taken met actieve jobs over (idempotent), en maakt jobs aan in transactie in opgegeven volgorde. 6 nieuwe tests.
* feat(solo): BatchEnqueueBlockerDialog component
Nieuw dialoogvenster dat gebruiker waarschuwt bij gedetecteerde blocker: toont blockerReason in NL, prefixCount taken vóór blokkade, confirm-knop (disabled met tooltip bij count=0) en annuleer-knop. 7 tests voor rendering, click-handlers en disabled-state.
* feat(solo): preview-then-confirm flow in SoloBoard Voer-alle-uit
Vervang directe enqueueAllTodoJobsAction door previewEnqueueAllAction + BatchEnqueueBlockerDialog. Geen blocker → enqueueClaudeJobsBatchAction direct. Wel blocker → dialog met prefix-enqueue of annuleer. Loading-state op knop tijdens preview en confirm. 5 integratie-tests.
* test(solo): uitgebreide batch-preflight tests met 2 PBI's en 4 taken
Nieuw claude-jobs-batch.test.ts: 10 gevallen voor previewEnqueueAllAction (PBI-volgorde, REVIEW/BLOCKED-detectie, active-job-skip met blockerIndex-shift) en enqueueClaudeJobsBatchAction (happy path, IDOR, active-job-skip, demo).
* feat(schema): add Task.verify_required enum (ALIGNED / ALIGNED_OR_PARTIAL / ANY)
Adds VerifyRequired enum and verify_required field (default ALIGNED_OR_PARTIAL)
to the Task model. Also declares the claude_jobs_status_finished_at_idx index
in the schema to match the live DB. Applied via db execute + migrate resolve.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(ui): add verify_required select to TaskDetailDialog
SoloTask interface, solo page mapping, solo store, PATCH route handler
and TaskDetailDialog all updated to expose the three-level verify gate
(ALIGNED / ALIGNED_OR_PARTIAL / ANY) as a native select. Disabled with
DemoTooltip in demo mode.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat: SoloBoard layout naar SplitPane met cookie-persistentie en tab-collapse
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat: verplaats Live + agent-status indicators naar NavBar
Live-dot (SSE-status) en "Agent verbonden / Geen agent" indicator zijn
verhuisd van de SoloBoard-header naar de NavBar (rechts, voor de
notifications-bell). Data blijft uit useSoloStore komen, gevoed door
SoloRealtimeBridge in de (app)-layout. Indicators tonen alleen op
/products/[id]/solo — buiten die route is de SSE-stream inactief.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat: open SoloRealtimeBridge globaal voor active product
SoloRealtimeBridge gated nu op active-product i.p.v. /solo-pad. Live-dot
en worker-presence werken daardoor op alle (app)-pagina's
(Producten/PB/Sprint/Solo/Todo's). Buiten /solo is de solo-store leeg en
zijn task-events no-ops, dus de stream gedraagt zich automatisch als
lichte presence-stream tot SoloBoard mount.
- realtime-bridge: productId-prop i.p.v. usePathname
- (app)/layout: activeProduct?.id doorgegeven aan bridge
- nav-status-indicators: pathname-check vervangen door hasActiveProduct prop
- nav-bar: hasActiveProduct={!!activeProduct} doorgegeven
- architecture-doc: realtime connection lifecycle bijgewerkt
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat: enqueueAllTodoJobsAction voor batch-queueing van TO_DO-taken
Nieuwe Server Action die alle TO_DO-taken van een product zonder
actieve ClaudeJob in één $transaction als QUEUED jobs aanmaakt en
voor elk een pg_notify('claude_job_enqueued') stuurt zodat de SSE-
stream de UI live bijwerkt.
- Auth + demo-blokkade + product-access via productAccessFilter
- Idempotent: tasks met status QUEUED/CLAIMED/RUNNING worden overgeslagen
- 4 nieuwe tests (happy path, count=0, demo-blokkade, geen toegang)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat: 'Start agents (n)'-knop in Solo header, productname weg
SoloBoard-header toont nu een primary button die het aantal queueable
TO_DO-taken telt (TO_DO zonder actieve ClaudeJob via
claudeJobsByTaskId-store) en bij klik de nieuwe
enqueueAllTodoJobsAction aanroept. Toast geeft het aantal gestarte
agents terug.
- productname-h1 verwijderd (staat al in NavBar-dropdown, dubbel)
- sprintdoel blijft naast de knop
- 'Toon openstaande stories'-link blijft rechts
- demo-modus disabled met DemoTooltip
- batch-pending state voorkomt dubbele klikken
- productName-prop weg uit SoloBoard + page.tsx (was alleen voor h1)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: scope enqueueAllTodoJobsAction op actieve sprint + assignee
De action queue'de eerder ALLE TO_DO-taken van een product, ongeacht
sprint of assignee — terwijl de 'Start agents (n)'-knop in de UI
alleen de taken telt die de gebruiker ziet (actieve sprint, eigen
stories). Daardoor kreeg een klik op de knop veel meer jobs aangemaakt
dan de count suggereerde (62 i.p.v. de getoonde n).
Server-filter komt nu overeen met page.tsx solo-query:
story: { sprint_id: <activeSprint>, assignee_id: userId }
Edge case: geen actieve sprint → success met count=0 (geen error).
Tests aangepast + nieuwe test voor 'geen actieve sprint'-pad.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(db): trigger sync_task_status_from_claude_job promote task naar DONE
Postgres AFTER-trigger op claude_jobs.status zet de bijbehorende
task automatisch op DONE zodra de job DONE wordt — werkt ongeacht
welke client de update doet (MCP-server, Server Action, raw SQL).
Idempotent: WHERE status <> 'DONE' voorkomt no-op updates die de
bestaande notify_task_change-trigger zouden doen vuren. Die laatste
verzorgt de pg_notify naar /api/realtime/solo zodat de UI synct.
- migration: prisma/migrations/20260501110000_sync_task_status_from_claude_job
- doc: nieuwe sectie 'Auto-promote task naar DONE' in architecture.md
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(ui): vul SoloColumn-kolommen volledige paneelhoogte
Buitenste flex-container van SoloColumn miste h-full, waardoor het
kader op content-hoogte bleef hangen i.p.v. de hele pane (binnen
SplitPane) te vullen. Drop-target was daardoor ook beperkt tot het
kleine kader bovenin een lege kolom.
Auto-toegepast door een ClaudeJob-agent op task
cmomoayt10002bortgp27jwma; co-auteurschap hieronder.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs: agent-batch-loop verplichte flow in CLAUDE.md
Na een 'pak de volgende job'-instructie liep de agent één job en sloot
de turn af, waardoor de gebruiker handmatig opnieuw 'wait_for_job'
moest aanroepen voor elke volgende job in de queue.
Voeg een expliciete loop-instructie toe onder de MCP-tools-sectie:
na elke update_job_status moet de agent opnieuw wait_for_job
aanroepen, totdat die na de full block-time terugkomt zonder claim.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat: add plan_snapshot field to ClaudeJob schema
Nullable String? column on claude_jobs captures the task's
implementation_plan at claim time — immutable baseline for drift detection.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* docs: update ClaudeJob lifecycle with plan_snapshot
Document state machine snapshot capture/reset, plan_snapshot field
rationale, and drift-detection baseline semantics.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor: remove duplicate header labels on backlog page
Both the product H1 + description in the page header and the
"Product Backlog" panel-title in the PBI panel duplicated info
already visible in the NavBar. Removed both, keeping the right-aligned
action bars (activate/sprint/settings, plus filters/+PBI) intact.
PanelNavBar component is unchanged — Stories and Taken panels keep
their titles.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* chore(ST-1112): add deps for task dialog
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(ST-1112): add shared zod schema for task dialog
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(ST-1112): add missing MD3 tokens for task dialog
outline-variant, on-error-container, status-review (light + dark)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(ST-1112): add saveTask and deleteTask server actions for TaskDialog
Unified create/edit action (saveTask) replaces separate formData-based
actions for the new TaskDialog. Uses shared zod schema, structured
SaveTaskResult union type, and context-aware revalidatePath for both
sprint and backlog routes.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(ST-1112): add TaskDialog component (create & edit mode)
Builds the full TaskDialog on top of the existing @base-ui/react
Dialog primitive. Covers create mode, edit mode (status field +
created_at metadata + delete), dirty-check AlertDialog, delete
confirm AlertDialog, Cmd+Enter submit, and per-field char counters.
Uses react-hook-form + zodResolver against the shared taskSchema.
Priority and status are extracted to PrioritySegmented and
StatusSelect sub-components using MD3 tokens throughout.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(ST-1112): refactor task-list to open TaskDialog via URL params
Replaces inline create/edit forms with router.push navigation:
- Clicking a task row → ?editTask=<id>
- "+ Taak" button → ?newTask=1&storyId=<storyId>
Removes CreateTaskForm, EditSubmitButton, updateTaskAction, and
createTaskAction from the component. Status toggle and DnD remain
unchanged. Rows now have cursor-pointer and keyboard a11y.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(ST-1112): wire TaskDialog into sprint page via searchParams
Sprint page now reads ?newTask, ?storyId, and ?editTask query params.
For edit mode: fetches the task server-side with productAccessFilter
scope (invalid/foreign IDs redirect to closePath). Renders TaskDialog
when either param is present. closePath is the sprint route without
query params.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(ST-1112): add Suspense skeleton for edit-mode task loading
Extracts task fetch into EditTaskLoader (async server component) so
the sprint board renders immediately while the task loads.
TaskDialogSkeleton shows 3 grey bars during the fetch. Invalid or
out-of-scope task IDs redirect to closePath.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(ST-1112): render description as markdown in task-detail-dialog
Solo task detail now renders description via react-markdown +
remark-gfm with prose styling. Sanitizes script/iframe elements.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(ST-1112): add saveTask/deleteTask server action tests
Covers all three demo-policy layers and cross-tenant scope:
demo blocked (403), unauthenticated blocked, validation 422,
edit cross-tenant forbidden, create cross-tenant forbidden,
and happy-path for both edit and create.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(ST-1112): add updateTaskStatusWithStoryPromotion helper
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(ST-1112): wire story-promotion into saveTask and PATCH /api/tasks/:id
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* docs(ST-1112): add task-dialog doc and architecture note
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* chore: extend allowed tools in settings.local.json
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(ST-1113): add 200ms animation-delay to TaskDialogSkeleton to prevent flicker
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(ST-1114): add DirtyCloseGuard reusable component for dirty-form close confirmation
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(ST-1114): add shared Markdown wrapper, apply to task-detail and story-dialog
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* chore: allow grep -E pattern in settings.local.json
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
useState initializers read localStorage synchronously, which produced
a different render on client (with persisted filterStatus='blocked')
than on server (which has no localStorage and rendered 'all'). The
chip-buttons that surface active filters caused a structural DOM
mismatch next to the Popover trigger, raising a hydration error.
Move the localStorage read into a post-mount useEffect, defaulting
state to the SSR-compatible 'all'/'priority' on first render. Add a
prefsLoaded flag so persist effects skip the initial render and
don't overwrite saved values with defaults.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(ST-1111.1): add ClaudeJob model and state-machine enum
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(ST-1111.2): add ClaudeJob status API mappers
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(ST-1111.3): add enqueue/cancel ClaudeJob server actions with idempotency + NOTIFY
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(ST-1111.4): forward ClaudeJob events on solo SSE stream + initial state
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(ST-1111.6): add 'Voer uit' + cancel buttons to task detail dialog
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(ST-1111.7): add job status pill with spinner on solo task cards
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(ST-1111.8): cover job-status mappers and enqueue/cancel actions
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* docs(ST-1111.9): document Claude job queue architecture and agent flow
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(ST-1111.10a): add ClaudeWorker presence model
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(ST-1111.10c): forward worker presence events on solo SSE + initial count
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(ST-1111.10d): show worker presence indicator and gate 'Voer uit' on connected workers
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(ST-1109.2): add PbiStatus enum and status field to Pbi model
- New PbiStatus enum (READY/BLOCKED/DONE) for PBI lifecycle tracking
- Pbi.status PbiStatus @default(READY)
- Index on (product_id, status) for filter queries
- Migration: 20260429150643_add_pbi_status
- ERD regenerated via prisma generate
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(ST-1109.3): add PBI status API mappers
- pbiStatusToApi / pbiStatusFromApi following same pattern as task/story
- PbiStatusApi type derived from PBI_DB_TO_API
- PBI_STATUS_API_VALUES export for downstream Zod schemas
- Lowercase API surface (ready/blocked/done), DB stays UPPER_SNAKE
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(ST-1109.4): support status in PBI create/update actions
- Optional status field in Zod schemas (lowercase API: ready/blocked/done)
- pbiStatusFromApi() maps to DB enum before persistence
- Status omitted on create => Prisma @default(READY) takes effect
- Update preserves existing status when not provided
- Demo-check unchanged
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(ST-1109.5): auto-mark PBI as DONE when all its stories are DONE on sprint close
Extends completeSprintAction's $transaction with PBI status cascade:
- Pre-transaction: identify PBIs touched by this close (via stories.pbi_id),
fetch each with all its stories
- Skip PBIs already DONE; skip PBIs with 0 stories
- Mark PBI DONE only when every story (post-decision) is DONE — stories
outside the sprint are evaluated against their current DB status
- Promote-only: never demotes a PBI that becomes "incomplete" again
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(ST-1109.6): add Popover primitive (base-ui wrapper)
- Mirrors the Tooltip pattern: render-prop composition, data-slot attrs
- Exports Popover (Root), PopoverTrigger, PopoverContent (Portal+Positioner+Popup)
- MD3 popover/popover-foreground tokens, animated open/close states
- Will be used to consolidate the backlog filter UI in ST-1109.8
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(ST-1109.7): add status select to PBI dialog
- New components/shared/pbi-status-select.tsx mirrors PrioritySelect:
PBI_STATUS_LABELS (NL), PBI_STATUS_COLORS, PbiStatusSelect component
- Reuses existing --status-todo/blocked/done MD3 tokens
- PbiDialog: status state with sync-on-open; default 'ready' for create,
pbi.status for edit; hidden input submits lowercase API value
- Priority + Status sit side-by-side in 2-col grid
- PbiDialogPbi.status is optional; pbi-list.tsx will populate in ST-1109.8
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(ST-1109.8): show PBI status badge and consolidate filters into popover
- Pbi.status (lowercase API) flows from page.tsx via pbiStatusToApi
- Status badge rendered in BacklogCard's badge slot using PBI_STATUS_COLORS
- Two old Select dropdowns replaced by single Popover with three pill-button
sections (Sorteren, Prioriteit, Status) and a "Wis filters" footer
- Filter trigger shows active count "(n)" badge in label
- Active priority/status filters still surface as dismissable chips next to
the trigger for at-a-glance feedback
- onEdit passes the full Pbi (incl. status) so the dialog opens with the
correct current status — closes the data flow loop opened in ST-1109.7
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test(ST-1109.9): cover PBI status mappers and sprint-close cascade
- __tests__/lib/task-status.test.ts: 11 cases incl. round-trip + invalid
input for task/story/pbi mappers; verifies PBI_STATUS_API_VALUES shape
- __tests__/actions/sprints-cascade.test.ts: 8 cases for completeSprintAction:
promote on all-DONE, no promote on partial OPEN, respect out-of-sprint
story status, skip already-DONE PBIs, multi-PBI cascade, 0-story guard,
demo-user block
- Full vitest run: 170/170 green across 21 files
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(ST-1109.10): document PbiStatus enum, sprint-close cascade, and filter UI
- docs/scrum4me-architecture.md: pbis-table updated with status column +
index; PbiStatus enum + Pbi model in the Prisma schema sample;
cascade-on-sprint-close rule documented inline
- docs/scrum4me-styling.md: short note pointing to PBI_STATUS_LABELS /
PBI_STATUS_COLORS in components/shared/pbi-status-select.tsx so future
components don't ad-hoc-copy the color map
- docs/plans/ST-1109-pbi-status.md: in-repo mirror of the approved plan
(per feedback_plan_location memory) with cascade pseudo-code and
end-to-end verification checklist
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(ST-1109.11): persist backlog filters in localStorage
Filters reset op reload was verwarrend. Nu net als sortMode:
- scrum4me:pbi_filter_priority — 'all' | '1' | '2' | '3' | '4'
- scrum4me:pbi_filter_status — 'all' | 'ready' | 'blocked' | 'done'
useState-init met SSR-guard; ongeldige waarden vallen terug op 'all'.
Wis filters reset alle drie de keys correct (sortMode -> 'priority',
beide filters -> 'all'), waardoor de localStorage-staat consistent 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>
* docs(ST-1101..1108): add M11 — Claude question-channel milestone to backlog
Plant acht stories ST-1101..ST-1108 voor het persistente vraag-antwoord-kanaal
tussen Claude (MCP) en de actieve gebruiker. Eerste concrete uitwerking van
de AI-driven dev-flow-richting (strategisch besluit "B" uit overleg na M10).
Beveiligingsuitgangspunt: atomic answer via updateMany WHERE status='open',
demo-blok op write-tools, access-check via productAccessFilter in DB-query én
SSE-filter, cron-endpoint via Bearer-secret, geen vraag/antwoord-tekst in logs.
Hergebruikt bestaande scrum4me_changes-channel (uitgebreid met entity:'question')
en het LISTEN/NOTIFY+ReadableStream-pattern uit M8/M10. Nieuw: user-scoped SSE
op /api/realtime/notifications zodat de bell globaal werkt over producten heen.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(M11): swap demo-active sprint from M10 to M11
M10 is gemerged en afgesloten — M11 wordt de nieuwe demo-actieve milestone
zodat get_claude_context (via MCP) ST-1101 als next-story teruggeeft.
Drie maps in parse-backlog.ts uitgebreid: M11 priority=4, goal omschrijving,
sprint_status='ACTIVE'. M10 → COMPLETED.
Vereist npx prisma db seed na deze commit zodat de live DB de nieuwe
sprint-state weerspiegelt.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(ST-1108): add F-11b — Claude question-channel to functional spec
Voegt feature-omschrijving toe naast bestaande F-11 (Claude Code REST API).
Beschrijft het verloop (Claude → MCP-tool → DB → trigger → SSE → user → answer
→ trigger → Claude polls), acceptatiecriteria (8 items), randgevallen (offline-
Claude, assignee-change, expiry, abuse) en datamodel (claude_questions tabel).
Persona Lars als primair, Dina secundair voor klant-werk.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(M11): drop parser ACTIVE-flip; sprint goes via UI from now on
Bij M9/M10 hebben we de seed-flip (MILESTONE_SPRINT_STATUS pivot) gebruikt om
nieuwe stories als IN_SPRINT in een verse sprint te krijgen. Dat werkt maar
is fragiel:
- npm run seed wist user-data
- de "sprint" die de seed maakt is geen echte planning-actie
- bij multi-product scenario's breekt het model
Vanaf M11 gebruiken we de bestaande Sprint-creatie-UI van Scrum4Me. Stories
voor M11 worden via scripts/insert-milestone.ts (idempotent insert, geen
seed-reset) aan de DB toegevoegd; de gebruiker maakt zelf een Sprint aan in
/products/[scrum4me]/sprint en sleept ST-1101..1108 ernaartoe.
Parser-map M11 dus terug naar COMPLETED zodat een eventuele re-seed niet meer
een fake sprint aanmaakt voor M11-stories.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(ST-1101): add ClaudeQuestion model + notify_question_change trigger
Schema (prisma/schema.prisma):
- Nieuw model ClaudeQuestion: id (cuid), story_id (FK Cascade), task_id?
(FK SetNull), product_id (FK Cascade — gedenormaliseerd uit story.product_id
voor SSE-filter zonder join), asked_by (FK Restrict — Claude-token-houder),
question (Text), options (Json? — string[] voor multi-choice), status
('open'|'answered'|'cancelled'|'expired'), answer (Text?), answered_by
(FK SetNull), answered_at?, created_at, expires_at
- Indexes: (story_id, status), (product_id, status), (status, expires_at)
- Back-relations: User.asked_questions (ClaudeQuestionAsker),
User.answered_questions (ClaudeQuestionAnswerer), Story.claude_questions,
Task.claude_questions, Product.claude_questions
Migratie (20260427224849_add_claude_questions):
- Prisma-gegenereerde DDL voor claude_questions + indexes + 5 FK's
- Toegevoegde notify_question_change() functie + claude_questions_notify trigger
op AFTER INSERT/UPDATE
- Emit op BESTAANDE scrum4me_changes-channel met entity:'question' (i.t.t. M10
dat eigen scrum4me_pairing-channel kreeg) — solo-route in ST-1104 moet
entity='question' wegfilteren om regressie op solo-board te voorkomen
- Trigger leest story.assignee_id voor "wacht op jou"-emphase in payload
- DELETE niet ondersteund — questions gaan naar answered/cancelled/expired
Verification: Node pg-client roundtrip via DATABASE_URL toonde correcte payloads
bij INSERT (op=I, status=open) en UPDATE (op=U, status=answered) met alle FK-IDs
en assignee_id correct uit story-join.
Volgende stap M11: ST-1102 — vier MCP-tools in scrum4me-mcp-repo
(ask_user_question, get_question_answer, list_open_questions, cancel_question).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(ST-1103): add answerQuestion server action
actions/questions.ts:
- answerQuestion(questionId, answer) — auth + Zod + demo-blok + access-check
via productAccessFilter (anyone met product-membership mag antwoorden,
consistent met Scrum self-organizing — niet alleen story-assignee)
- Atomic prisma.claudeQuestion.updateMany WHERE id + status='open' +
expires_at>now → status='answered'; concurrent dubbele submit: één wint
(count=1), rest count=0 met disambiguatie via second findFirst
- revalidatePath('/', 'layout') refresh't NavBar bell-count voor SSR-paths;
realtime updates voor andere clients gaan via SSE in ST-1104/1105
- Begrijpelijke NL-foutmeldingen voor elk faalpad
Tests __tests__/actions/questions.test.ts (6 cases):
- happy: status update + revalidatePath called
- demo-block: error + geen DB-call + geen revalidate
- geen access: error + geen update
- al-answered: race-error 'is al answered'
- expired: race-error 'is verlopen'
- lege answer: Zod-validatie
Quality gates: lint 0 errors, tsc clean, vitest 145/145 (17 files).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(ST-1104): add user-scoped /api/realtime/notifications + filter solo-route
Twee delen:
1. Solo-route filter (1-regel-fix in app/api/realtime/solo/route.ts):
- NotifyPayload uitgebreid met entity:'question'
- shouldEmit returnt direct false bij entity='question'
Voorkomt dat solo-clients M11 question-events ontvangen (geen lekkage naar
het Solo-bord; geen onnodig netwerk-verkeer; loose coupling tussen features).
2. Nieuwe SSE-route app/api/realtime/notifications/route.ts:
- User-scoped (geen ?product_id=); query alle accessible product-IDs één keer
bij connect via productAccessFilter
- LISTEN scrum4me_changes; filter entity='question' && product_id ∈ accessible
- Initial-state-event NA LISTEN actief (race-fix conform M10 ST-1004):
query open vragen voor deze user's accessible products, stuur als event:state
met summary (id, story_code/title, assignee_id, question, options, expires_at)
- Hergebruikt het pg.Client + ReadableStream + heartbeat 25s + hard-close 240s +
abort-cleanup-pattern uit solo-route
Tests __tests__/api/notifications-stream.test.ts:
- 401 zonder iron-session cookie (en geen DB-call)
- Solo-route filter wordt visueel/E2E gedekt in ST-1108-acceptatie
Quality gates: lint 0 errors, tsc clean, vitest 146/146 (18 files).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(ST-1105): add NavBar bell + sheet + answer-modal + Zustand store + SSE hook
UI-volledig voor de Claude vraag-antwoord-flow (M11). Bel-icon links van avatar
in NavBar; klik opent slide-over rechts met openstaande vragen; klik op een vraag
opent een modal voor antwoord. Story-assignee = current user krijgt visuele
"voor jou"-emphase met primary-container accent en error-color badge-ring.
Bestanden:
- stores/notifications-store.ts — Zustand store met init/upsert/remove +
openCount/forYouCount selectors (vereenvoudigd vs solo-store: geen pendingOps,
geen optimistic-echo-onderdrukking)
- lib/realtime/use-notifications-realtime.ts — EventSource hook met state-
event en message-event handling, exponential-backoff reconnect, Page
Visibility pause-resume
- components/notifications/notifications-bridge.tsx — Server Component die
initial open-questions fetcht via productAccessFilter
- components/notifications/notifications-realtime-mount.tsx — tiny client
island dat de store hydrateert + de hook activeert
- components/notifications/notifications-sheet.tsx — shadcn Sheet met item-
lijst, "voor jou"-accent voor assignee-vragen, lege staat
- components/notifications/answer-modal.tsx — Dialog met options-radio of
free-text Textarea (max 4000), char-counter, demo-blok via Tooltip; bij
succes optimistisch remove + sheet blijft open zodat meerdere vragen
achter elkaar te beantwoorden zijn
- components/shared/notifications-bell.tsx — Bell-icon met badge (count >9 → "9+"),
ring-accent als forYouCount > 0, ARIA-label voor screenreaders
Wiring:
- components/shared/nav-bar.tsx — <NotificationsBell /> rechts naast <UserMenu>
- app/(app)/layout.tsx — <NotificationsBridge /> naast <SoloRealtimeBridge />,
user.id (server-side) als prop
base-ui-aanpassingen: SheetTrigger/TooltipTrigger gebruiken render-prop ipv
asChild (geen Radix).
Quality gates: lint 0 errors, tsc clean, vitest 146/146, npm run build groen.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test(ST-1106): add cross-product access-isolation test for notifications SSE
Demo-policy + assignee-emphase zaten al in eerdere stories:
- answerQuestion demo-blok in actions/questions.test.ts (ST-1103)
- AnswerModal demo-tooltip in components/notifications/answer-modal.tsx (ST-1105)
- requireWriteAccess in MCP write-tools (ST-1102)
Deze story voegt expliciet een access-isolation-test toe op de notifications-
SSE-route: productAccessFilter wordt met de echte userId aangeroepen, en
prisma.product.findMany filter't op archived=false + user_id-scope. Dat
garandeert dat een gebruiker geen question-events ontvangt voor producten waar
hij geen membership op heeft.
Story-assignee-emphase blijft visueel-only (NotificationsBell ring-accent +
Sheet primary-container) — toegang werkt product-membership-breed zodat een
team-lid kan invallen als de assignee niet beschikbaar is.
Quality gates: lint 0 errors, tsc clean, vitest 147/147 (was 146).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(ST-1107): add Vercel cron expire-questions (+ M10 pairing cleanup)
POST /api/cron/expire-questions:
- Auth via Authorization: Bearer ${CRON_SECRET} (Vercel injecteert dit
automatisch wanneer de env-var op de project-omgeving staat); 401 als secret
niet matcht of niet is gezet (faal-veilig — geen open endpoint in dev)
- updateMany op claude_questions WHERE status='open' AND expires_at<now →
'expired'
- Bonus: zelfde route ruimt M10 login_pairings op (status='pending' AND
expires_at<now → 'cancelled'). Eén cron-job is goedkoper qua Vercel-budget
en houdt cleanup-strategie centraal — opvolg-actie uit M10 dat geparkeerd was.
Config:
- vercel.json: crons-entry { path: '/api/cron/expire-questions', schedule: '0 */6 * * *' } (4x/dag)
- lib/env.ts: CRON_SECRET als optional in Zod-schema
- .env.example: documentatie + openssl rand-tip
Tests __tests__/api/cron-expire-questions.test.ts (4 cases):
- 401 zonder Authorization-header
- 401 met verkeerde secret
- 401 als CRON_SECRET niet is gezet (faal-veilig)
- 200 met juiste secret: response { expired_questions, expired_pairings, ran_at }
+ beide updateMany WHERE/data correct
Quality gates: lint 0 errors, tsc clean, vitest 151/151.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(ST-1108): document M11 question-channel — API + architecture + pattern
docs/API.md — twee nieuwe secties:
- 'Notifications' met /api/realtime/notifications SSE-endpoint (event-shapes,
filter-rules, voorbeeld)
- 'Cron — Expire questions' met /api/cron/expire-questions (Bearer-auth,
schedule, response-shape, manual curl)
docs/scrum4me-architecture.md — nieuw hoofdstuk 'Vraag-antwoord-kanaal Claude
↔ user' tussen QR-pairing-flow en Projectstructuur:
- Mermaid sequence-diagram (Claude → DB → trigger → SSE → user → answer →
trigger → Claude polls)
- Threat-model-tabel (race, demo-misbruik, cross-product leak, cron-misbruik,
growth, log-leakage)
- Subsectie 'Waarom hergebruik scrum4me_changes-kanaal' met trade-off vs M10's
eigen-kanaal-aanpak
docs/patterns/claude-question-channel.md — herbruikbaar pattern 'Bidirectionele
async-comms tussen MCP-agent en interactieve user' met de vier eindpunten,
vier security-uitgangspunten, channel-strategie-tabel, TTL-richtlijn, en
sjabloon-bestanden per laag (DB / server / client / MCP-tools).
CLAUDE.md — extra rij in Implementatiepatronen-tabel die naar het nieuwe
pattern-doc verwijst.
Acceptatie 6 scenario's:
1. Sync happy path (MCP wait_seconds + UI submit) — handmatig getest tijdens
ST-1105 acceptance-loop met de q-test injection
2. Async happy path — gedekt door get_question_answer-tool in ST-1102 +
list_open_questions
3. Demo-block — actions/questions.test.ts (case 2: demo-user) + AnswerModal
tooltip (visueel)
4. Access-isolation — notifications-stream.test.ts (case 'access-isolation')
5. Expiry — cron-expire-questions.test.ts (case '200 met juiste secret')
6. Race — actions/questions.test.ts (case 'al-answered' via atomic updateMany)
Quality gates: lint 0 errors, tsc clean, vitest 151/151 (19 files), npm run
build groen.
M11 is hiermee feature-compleet. feat/M11-claude-questions heeft 12 commits
lokaal, klaar voor user-acceptatie en PR.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(ST-1107): cron schedule daily — Vercel Hobby allows only 1 run/day
Vercel deploy faalde met:
> Hobby accounts are limited to daily cron jobs.
> This cron expression (0 */6 * * *) would run more than once per day.
Schedule van 4×/dag (0 */6 * * *) naar 1×/dag (0 4 * * * — 04:00 UTC, rustig
tijdstip). Functioneel acceptabel: ClaudeQuestion TTL is 24u, dus daily
cleanup pakt alles dat in de afgelopen 24u verlopen is. Login-pairings TTL
is 2 min — die zijn al onbruikbaar zodra ze expiren, cron is alleen voor
status-housekeeping.
Schedule-referenties consistent bijgewerkt in docs (API.md, architecture,
backlog M11-sectie, plan-doc, pattern-doc) + comment in route.ts. Vermelding
overal dat dit een Hobby-plan-beperking is en Pro fijnmaziger ondersteunt.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>