Drie functies via prisma.$queryRaw: getSprintTokenHistory (per-sprint
aggregaat), getDayTokenData (dag-totalen met guard op lege sprintId),
getPbiTokenAggregates (per-PBI met guard). Tests voor alle drie.
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* 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>
- requireAdmin() checkt nu de database i.p.v. session.isAdmin (was altijd undefined)
- loginAction stelt session.isAdmin in op basis van UserRole in de DB
- registerAction stelt session.isAdmin = false expliciet in
- NavBar toont 'Admin'-link conditioneel als roles.includes('ADMIN')
- UserMenu ROLE_LABELS uitgebreid met ADMIN → 'Admin'
- Tests aangepast: prismaUserRole.findFirst mock toegevoegd
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Two gaps discovered during the first live grill-session of IDEA-002:
the agent posted a question, but the user had no UI to answer it.
1. Idea-questions only appeared on the Timeline-tab as read-only entries
2. Notifications-bell fetched + handled story-questions only
This fix:
**Inline answer-form in IdeaTimeline** (components/ideas/idea-timeline.tsx)
- Open questions now render an AnswerForm directly under the question text
- Multi-choice options become clickable buttons (one-click submit); free-text
fallback via collapsed details/textarea
- Plain free-text questions render textarea + Verzend
- Calls existing answerQuestion server-action; toast + router.refresh on success
**Notifications-bell extended for idea-questions**
- stores/notifications-store.ts: NotificationQuestion → discriminated union
(kind: 'story' | 'idea'); forYouCount treats idea-questions as always-for-you
(idea is strictly user_id-only — only the owner sees them)
- components/notifications/notifications-bridge.tsx: parallel fetch of
story-questions (productAccessFilter) + idea-questions (idea.user_id ===
session.userId); merged + sorted by created_at
- components/notifications/notifications-sheet.tsx: renders idea_code/title
for kind='idea'
- components/notifications/answer-modal.tsx: header + open-link branch on
kind (idea → /ideas/[id]?tab=timeline; story → existing /sprint link)
- lib/realtime/use-notifications-realtime.ts: idea-question events also
trigger close+reconnect on 'open' (loads fresh detail) and remove(id) on
non-open — same pattern story-questions already use
- components/shared/notifications-bell.tsx: badge counts idea-questions as
for-you regardless of assignee
**Security gap closed (actions/questions.ts answerQuestion)**
Before: accepted any answer if user has product-access.
After: idea-questions require idea.user_id === session.userId; story-
questions keep the existing productAccessFilter path. (Prisma 7 rejects
\`{ not: null }\` in WHERE; routing happens app-level after a single fetch.)
Tests: 546/546 still green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Idea-jobs and idea-questions are user-private (M12 grill-keuze 8) — they
flow through /api/realtime/notifications, not /api/realtime/solo.
app/api/realtime/notifications/route.ts:
- Pre-fetch user's idea-ids → accessibleIdeaIds Set (avoids per-event DB lookup)
- New IdeaJobPayload type (claude_job_enqueued/_status with kind=IDEA_*)
- New QuestionPayload narrows: story_id and idea_id mutually exclusive (DB
check-constraint enforces it)
- Routing: idea-jobs filtered on user_id; idea-questions on accessibleIdeaIds;
story-questions on accessibleProductIds (existing path)
app/api/realtime/solo/route.ts:
- JobPayload extended with optional kind + idea_id
- shouldEmit filters out kind=IDEA_GRILL/IDEA_MAKE_PLAN — they don't belong
on the product/sprint Solo Paneel
Tests: 539/539 green; notifications-stream test mock updated for idea.findMany.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- proxy.ts: /ideas added to protectedRoutes — unauthenticated users get
redirected to /login when navigating to /ideas or /ideas/[id]
- existing demo-guard catch-all (\`/api/* + non-GET\`) already blocks
POST/PATCH/DELETE /api/ideas* with 403 — confirmed via 3 new tests
- server-action endpoints (start-grill / start-make-plan / materialize /
promote-to-idea) carry their own \`session.isDemo\` checks inside
actions/ideas.ts and actions/todos.ts (defense in depth)
Tests: 9/9 in proxy demo-guard suite (added 3 idea cases).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- app/api/ideas/route.ts: GET (list with archived/product_id/status filters,
user_id-scope), POST (creates DRAFT with auto IDEA-NNN code, 201)
- app/api/ideas/[id]/route.ts: GET (idea + recent logs), PATCH
(ideaUpdateSchema, isIdeaEditable guard)
- lib/idea-dto.ts: API projection — converts Prisma row → DTO with
lowercase status + has_grill_md/has_plan_md flags (md content excluded
from list payloads, fetch via dedicated download action)
Auth: session OR API-token via authenticateApiRequest. Strict user_id
scope (no productAccessFilter — Idee is privé per Q8). 404 (not 403) for
foreign-user reads to prevent enumeration.
Tests: 13 cases (auth-401, demo-403, validation-422, malformed-400,
not-found-404, status-mismatch-422, filter param round-trip, DTO shape).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
actions/todos.ts:
- promoteTodoToIdeaAction(todoId): auth + demo + scope + already-archived
guards. Atomic \$transaction creates DRAFT Idea (with auto IDEA-NNN code)
and archives source Todo + IdeaLog{NOTE}.
- Anders dan Todo→PBI/Story (die de todo deleten): we ARCHIVEREN. De idea
wordt het nieuwe planningsartifact; de archived todo bewaart het
vertrekpunt (zie M12 grill-keuze 12).
Tests: 5 cases — happy, auth-401, demo-403, scope-404, already-archived-422.
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>
- app/(mobile)/m/products/[id]/solo/page.tsx — hergebruikt SoloBoard 1:1 met
desktop. 3-koloms-kanban blijft, NoActiveSprint-fallback ongewijzigd
- T-332 verify-only: TaskDetailDialog regel 383 gebruikt
entityDialogContentClasses → mobile-fullscreen erft automatisch uit ST-1133
- Tests: regressie-vangnet op SoloBoard-hergebruik, requireSession,
NoActiveSprint, en op TaskDetailDialog-className-wiring (geen override)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- app/(mobile)/m/products/[id]/page.tsx — hergebruikt BacklogHydrationWrapper +
BacklogSplitPane + PbiList/StoryPanel/TaskPanel (1:1 zelfde data-fetch als
desktop-page; demo blijft read-only via PbiList/StoryPanel)
- Cookie-key gescheiden: `backlog-${id}-mobile` (beslissing C in
docs/plans/PBI-11-mobile-shell.md) — tab-mode-gebruikers vervuilen de
desktop-split-percentages niet
- closePath en redirect-targets blijven onder /m/products/
- Tab-mode rendert automatisch op <1024px via SplitPane (uit ST-1116)
- Tests: regressie-vangnet op cookie-key, /m/-paden, hergebruik
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>
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>
* 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(insights): add getJobsPerDay helper — agent throughput per day + KPIs
Raw SQL aggregation of claude_jobs by day and status over 14 days with
zero-fill for missing days. KPIs: todayCount, successRate7d, avgDurationSeconds7d.
Optional productId filter.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(insights): add AgentThroughputCard — stacked BarChart + KPI-strip + product filter
KPI strip (jobs today, 7d success rate, 7d avg duration), 14-day stacked
BarChart with JOB_STATUS_COLORS, and URL-bookmarkable product dropdown via
useTransition + router.replace. Empty-state when no activity.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Three read-only counters: stories without acceptance_criteria, tasks without
implementation_plan, and top-10 IN_PROGRESS tasks stuck >7 days. All scoped
via productAccessFilter.
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Aggregates task.status=DONE counts across last N completed sprints
(default 5), filtered by productAccessFilter and returned in
chronological order for x-axis rendering.
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Three SSE-routes (solo, backlog, notifications) each create a long-
running pg.Client that LISTENs on scrum4me_changes. On abrupt close
(Fast Refresh, browser refresh, Vercel function recycle) the
pgClient.end()-await sometimes hangs silently, leaving the underlying
socket connected to Postgres. The connection stays in 'idle' on Neon's
side and after ~10-20 reconnects the connection-pool fills up — new
SSE connects fail with ERR_INCOMPLETE_CHUNKED_ENCODING in the browser.
Fix: shared `closePgClientSafely` helper that races client.end()
against a 2 s timeout; on timeout it force-destroys the underlying
socket so the OS releases the FD and Postgres notices the disconnect.
Validated by direct DB inspection: 18 stale 'idle LISTEN'-connections
were piled up before the fix; after manual pg_terminate_backend cleanup
the SSE-stream stabilised. This change makes the pile-up impossible
going forward.
- new lib/realtime/pg-client-cleanup.ts
- 3 routes use the helper instead of bare `await pgClient.end()`
- 3 unit tests for the helper (timely-end, hang-falls-back-to-destroy,
end-rejection-is-swallowed)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Aggregeert verify_result counts (ALIGNED/PARTIAL/EMPTY/DIVERGENT) en top-5 EMPTY/DIVERGENT
jobs over de laatste N dagen voor de ingelogde gebruiker.
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Server-side aggregatie per active sprint: bouwt time-series met remaining en ideal per dag.
Inclusief 4 Vitest-unit-tests voor de pure computeBurndownDays functie.
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>
* 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>
* 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>