Commit graph

109 commits

Author SHA1 Message Date
Scrum4Me Agent
4a929b1962 feat(ideas): secondary_products meeladen in IdeaDto en alle queries
Voegt IdeaProduct schema toe (dependency van story-qtkvz6ly), breidt
IdeaWithProduct type en IdeaDto interface uit met secondary_products array,
en laadt de relatie mee in findMany/findFirst in page.tsx en REST GET.

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

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

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

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

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

ACTIVE_JOB_STATUSES bewust ongewijzigd — SKIPPED is afgerond.

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

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

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-05 17:40:31 +02:00
Janpeter Visser
384a7ecd4a
Merge pull request #99 from madhura68/feat/story-111ci8t4
ST-1205: Admin: gebruikersbeheer (/admin/users)
2026-05-05 14:44:55 +02:00
Scrum4Me Agent
31edfa8194 feat(ST-111ci8t4): /admin/users pagina met tabel, role-editor en delete-dialog
- app/(app)/admin/layout.tsx: admin-sidebar met links (Gebruikers/Claude Jobs/Producten)
- app/(app)/admin/page.tsx: redirect naar /admin/users
- app/(app)/admin/users/page.tsx: server component, query users+roles, geeft userId door
- components/admin/users-table.tsx: client component met UsersTable, RoleBadge,
  RolesDialog (checkboxes, eigen ADMIN-rol geblokkeerd), DeleteDialog (confirm),
  ResetToggle — alles via useTransition + server actions
2026-05-05 14:44:04 +02:00
Scrum4Me Agent
a19ae89e37 feat(ST-l9kkxh2m): /reset-password pagina + resetPasswordAction + hashPassword
- lib/auth.ts: hashPassword(password) geëxporteerd (bcrypt, rounds=12)
- actions/auth.ts: resetPasswordAction met Zod-validatie (min 8, superRefine gelijkheid),
  prisma.user.update (password_hash + must_reset_password=false), redirect /dashboard
- app/(auth)/reset-password/page.tsx: server guard (userId check + must_reset_password check)
- app/(auth)/reset-password/reset-form.tsx: client form (nieuw wachtwoord + bevestiging)
- __tests__/actions/auth.test.ts: 3 tests voor resetPasswordAction
2026-05-05 14:30:59 +02:00
bec4c05e80 fix(m12): bell loses idea-questions on SSE reconnect
The notifications-realtime hook (PR #92) does close+connect on every
idea-question 'open' event, but the SSE state-event-handler in
/api/realtime/notifications only re-fetched story-questions. Result:
each reconnect wiped idea-questions from the bell within ~500ms, even
though the bridge had loaded them on initial page-render.

Symptom: clicking an idea-question in the bell sometimes hit a
\"question gone\" race because the close+connect after the live event
emptied them out.

Fix: SSE initial-state now does a Promise.all on
- story-questions (productAccessFilter, existing path)
- idea-questions (idea.user_id === session.userId, M12 strict-private)
and sends merged + sorted state with discriminated \`kind\` field.

This mirrors the bridge's own initial fetch (PR #92), so a bridge-mount
and an SSE-reconnect now produce identical state.

Tests: 546/546 still green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 13:14:48 +02:00
1362996a2b ui: /ideas/[id] detail page with 4-tab layout (M12 T-510)
app/(app)/ideas/[id]/page.tsx (server-component):
- user_id-only fetch with notFound() on miss (anti-enumeration)
- Parallel fetch: idea+product+pbi, products list, recent logs (100),
  questions (50)

components/ideas/idea-detail-layout.tsx (client-component):
- Header: code + title + status-badge + product-link + IdeaRowActions
- PBI-link card when PLANNED (or Re-link banner when pbi removed —
  T-512 wires the action)
- URL-based tab switcher (?tab=idee|grill|plan|timeline) — bookmarkable
- Idee-tab: inline edit form with isIdeaEditable guard, dirty-tracking +
  Reset/Save buttons
- Grill/Plan-tabs: read-only md preview (T-511 will add the editor)
- Timeline-tab: chronological merge of IdeaLog + ClaudeQuestion entries
  (T-512 will polish the styling and component-split)

Tests: 546/546 still green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 21:35:48 +02:00
2eb0f33068 ui: /ideas list page + IdeaList table + row-actions skeleton (M12 T-507)
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>
2026-05-04 21:30:56 +02:00
0e2808ac88 realtime: route idea-jobs + idea-questions to /notifications channel (M12 T-502)
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>
2026-05-04 20:00:05 +02:00
4b234dc300 api: REST endpoints for ideas (M12 T-500)
- 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>
2026-05-04 19:55:49 +02:00
4d2e4b0b4b fix: drop \{ not: null }\ filters — Prisma 7 rejects them at runtime
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>
2026-05-04 19:44:48 +02:00
300e426a4e schema: add Idea + IdeaLog models, extend ClaudeJob/Question for ideas (M12 T-491)
- 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>
2026-05-04 19:25:07 +02:00
0f40bc1c70 fix(privacy): NODE_ENV-guard 4 debug-routes (before-launch privacy review)
Privacy/PII review-pass van Server Actions, API-routes, debug-paths en
Sentry config:

 Sentry sendDefaultPii: false in alle drie configs (server/edge/client)
 Geen wachtwoord/email/token in console-logs
 Pair-id-logs zijn metadata-only (5-min TTL, geen secret)

⚠️ Vier debug-routes hadden geen auth-guard:
- /api/debug/realtime-stream — rauwe pg_notify-stream zonder filtering
- /api/debug/emit-test-notify — anonieme test-emit op het kanaal
- /debug-env — lekt env-var-metadata (hostnames, lengtes, pooled-flag)
- /debug-realtime — UI op dezelfde rauwe pg_notify-stream

Allemaal gemarkeerd als TIJDELIJK met VERWIJDEREN-comments uit M8.
Voor v1 launch: NODE_ENV-guard die in productie 404 retourneert. Lokaal
dev blijft alles werken voor debugging.

Toekomstige cleanup: kunnen worden verwijderd zodra M8-realtime stabiel
draait in productie en niemand ze meer nodig heeft.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 14:16:49 +02:00
Janpeter Visser
04181e54cb
Merge pull request #87 from madhura68/feat/a11y-audit-fixes
fix(a11y): static accessibility fixes (v1-readiness #4 — code-side)
2026-05-04 14:08:58 +02:00
31ff70b71a fix(a11y): static accessibility findings (v1-readiness #4 — code-side)
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>
2026-05-04 13:58:34 +02:00
a0a10001d5 feat(rate-limit): per-user mutation-rate-limiting (v1-readiness #3)
lib/rate-limit.ts: 11 nieuwe scope-configs + enforceUserRateLimit(scope, userId)
helper. Returnt { error, code: 429 } shape voor consistent foutbeleid.

Toegepast op de high-value mutation-paths:
- actions/pbis.ts createPbiAction
- actions/stories.ts createStoryAction
- actions/tasks.ts saveTask (alleen create-path) + createTaskAction
- actions/todos.ts createTodoAction
- actions/sprints.ts createSprintAction
- actions/products.ts createProductAction + createProductFormAction
- actions/api-tokens.ts createApiTokenAction
- actions/questions.ts answerQuestion
- actions/claude-jobs.ts enqueueClaudeJobAction + enqueueClaudeJobsBatchAction
- app/api/profile/avatar/route.ts POST
- app/api/stories/[id]/log/route.ts POST

Limits zijn ruim genoeg voor normaal gebruik, eng genoeg voor abuse-loops:
create-task 100/min, create-todo 60/min, create-pbi 30/min, create-product
5/min, create-token 10/uur, etc. Per-user scope (geen globale block).

Niet aangeraakt: reorder/status-toggle (intra-session frequent, lage abuse),
update/delete (laag-volume), cron-routes (CRON_SECRET-gated).

Consumer-tweaks: 'success' in result narrowing waar TS de bredere union niet
meer accepteerde. Tests: 9 nieuwe op rate-limit-helper.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 13:48:59 +02:00
b327fbdf09 feat(ST-1138): mobile Solo-pagina + verify TaskDetailDialog (T-331/T-332/T-333)
- 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>
2026-05-04 10:52:41 +02:00
5b42740461 feat(ST-1137): mobile Product Backlog-pagina (T-328/T-329/T-330)
- 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>
2026-05-04 10:14:20 +02:00
0a3dc401b7 feat(ST-1136): mobile Settings-pagina + LogoutButton (T-325/T-326/T-327)
- app/(mobile)/m/settings/page.tsx — read-only account-info, product-selector
  (hergebruikt ActivateProductButton + setActiveProductAction met redirectTo
  /m/products/[id]/solo), QR-pairing-instructie, logout
- components/mobile/logout-button.tsx — AlertDialog "Uitloggen?" met bevestig
  + annuleer; demo-user mag uitloggen (geen demo-block)
- Tests: LogoutButton render + open + bevestig (logoutAction) + annuleer

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 10:12:19 +02:00
7b32fc60e6 feat(ST-1134): (mobile) route group + auth-guard helper + manifest (T-321)
- lib/auth-guard.ts (nieuw): requireSession() — gedeelde auth+paired-expiry
  guard, hergebruikt door (app)/layout.tsx
- (app)/layout.tsx: refactor naar requireSession() (gedraagt zich identiek)
- (mobile)/layout.tsx (nieuw): minimal layout met LandscapeGuard +
  MobileTabBar; geen NavBar/StatusBar/MinWidthBanner/bridges
- /m/pair filesystem-move van (app)/ naar (mobile)/ — URL onveranderd
- public/manifest.json: orientation landscape
- Tests: requireSession-helper (3 paden)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 09:55:18 +02:00
6a76bc0f8c feat(sprint): edit-icoon op PBI (Product Backlog) en story (Sprint Backlog)
- 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>
2026-05-04 09:19:49 +02:00
081a0a51c3 feat(codes): UI toont en accepteert code voor taken
- 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>
2026-05-04 08:36:52 +02:00
b05c4d241b feat(dialogs): gedeelde primitives — useDirtyCloseGuard, useDialogSubmitShortcut, layout-classes
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>
2026-05-04 07:14:07 +02:00
4ff50cb87e feat(landing): rewrite around local-first proposition + architecture diagram
Reframe the landing page around what makes Scrum4Me unique: code execution
stays on the developer's own hardware (laptop, NAS or VM). The Vercel UI and
Neon DB are a metadata coordination layer; source code never leaves the local
worker. Adds a mermaid-rendered architecture diagram (two-zone: Scrum4Me-stack
vs Jouw kant) with light/dark variants generated via mmdc.

Page changes (app/page.tsx):
- Hero: H1 "Plannen in de cloud. Uitvoeren op je eigen machine." + new
  subhead; CTA "Hoe het werkt" replaces "Demo bekijken" (anchors to
  #architectuur).
- New section §3 "Architectuur" with light/dark SVG and 4 callout-cards
  (Vercel · Neon · Lokale worker · GitHub) honestly describing what each
  component stores or runs.
- Feature grid: 6 cards (set C) — combines Sprint Board + Solo Paneel,
  adds Lokale Claude-agents, Realtime updates, Async vraagkanaal, Todo's.
  LIVE callout removed (folded into Realtime card).
- "Twee manieren"-section replaced by a Quickstart with concrete git-clone
  snippet for the MCP-server.
- Handleiding: 9 → 10 steps; MCP recommended in step 8; new step 9
  "Story laten uitvoeren" describing the Voer-uit / job-queue flow;
  step 1 mentions QR-pairing as alternative login.
- Footer: adds link to madhura68/scrum4me-mcp alongside the app repo.

Tooling:
- New docs/diagrams/architecture.mmd (mermaid source).
- New npm script "diagrams" generates light + dark SVG via mmdc; output
  committed to public/diagrams/. No prebuild hook (manual regenerate, like
  prisma generate / docs:index).
- Plan + grilling outcomes captured in docs/plans/landing-local-first.md.

Tracked under Scrum4Me story cmoq2qoik0001qa175iynfnaa
(PBI Marketing & Landingspagina, cmoq2q50s0000qa174rmrjove).

Verified: npm run lint (0 new errors), npm test (379/379), npm run build OK.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 20:24:10 +02:00
Scrum4Me Agent
1b94f32954 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>
2026-05-03 17:39:05 +02:00
Scrum4Me Agent
ae66c21109 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>
2026-05-03 17:28:47 +02:00
Janpeter Visser
0ce6076a5c
Solo batch-enqueue: per-PBI volgorde + blocker-dialog (#65)
* 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).
2026-05-03 13:55:13 +02:00
ced0a8a4c0
Verify-gate uitbreiden: DIVERGENT/PARTIAL vereist agent-acknowledgement (#53)
* 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>
2026-05-02 17:45:19 +02:00
d93c91c386
fix(insights): integrate all 5 sections in /insights + add missing components (#50)
The /insights page was rendering only Sprint Health (2 charts).
PRs #47/#48/#49 delivered helpers + tests for Velocity, Backlog health
and Agent throughput, but Velocity and Backlog never produced their
UI components, and none of the four new sections were wired into
page.tsx. Result: user sees 2 charts where 5 sections were promised.

This PR fills the gaps:

- New `app/(app)/insights/components/velocity-chart.tsx` — Recharts
  grouped BarChart with optional ReferenceLine for the average. Empty
  state when <2 completed sprints.
- New `app/(app)/insights/components/backlog-health.tsx` — counters
  (stories sans AC / tasks sans plan / stuck>7d) + stuck-tasks table
  with severity-coded days-stuck cell.
- `app/(app)/insights/page.tsx` rewritten as 5 sections:
  Sprint Health, Plan-quality (donut + alignment-trend), Agent
  throughput, Velocity, Backlog health. Helpers run in one
  Promise.all so the page renders in a single tick.

Tests: 314/314 green, tsc clean, lint 0 errors.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 17:23:03 +02:00
739037a60b
Agent throughput: jobs per dag stacked bar + KPI-strip (#49)
* 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>
2026-05-02 16:30:06 +02:00
ce94fb48c3
Foundation: route, recharts, sprint-dates migration, chart-colors helper (#46)
* feat(ST-1201): add Sprint start_date/end_date + claude_jobs index migration

- Sprint model: optionele start_date en end_date (DATE) voor burndown x-as
- CREATE INDEX claude_jobs(status, finished_at) voor agent-throughput-queries
- Bestaande sprints houden NULL; burndown skipt die

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

* feat(ST-1202): add lib/chart-colors.ts + vitest coverage

MD3-token-to-CSS-var mappings for STATUS, PRIORITY, VERIFY, JOB_STATUS
and SERIES_COLORS; all 5 tests pass.

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

* feat(ST-1203): add Insights link to NavBar

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

* feat(ST-1204): move Insights NavBar link between Solo and Todo's

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

* feat(ST-1205): add sprint start_date/end_date UI + server actions

- createSprintAction + updateSprintDatesAction: Zod date validation
  with end_date >= start_date cross-check
- start-sprint-button: date inputs in create dialog
- sprint-header: date display button + edit dialog with updateSprintDatesAction
- sprint page: select start_date/end_date for SprintHeader prop
- Demo blokkade via bestaande isDemo checks
- 6 tests groen (validation + demo guard)

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

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-02 15:58:15 +02:00
6c6c8b96b7
fix(realtime): force-destroy pg socket on cleanup timeout (SSE leak) (#44)
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>
2026-05-01 20:04:22 +02:00
0454eede74
feat(insights): port unique files from closed bundle-PRs (#41)
Re-introduce the 3 unique files from closed PRs #37 and #40 that
overlap-merged with already-landed sub-PRs (#34, #35, #36, #38, #39):

- app/(app)/insights/page.tsx — Server Component dat alle helpers
  parallel aanroept en de 5 sectie-Cards rendert (Sprint Health,
  Plan-quality, Agent throughput, Velocity, Backlog health)
- app/(app)/insights/components/sprint-info-strip.tsx — chips per
  active sprint met productname + goal + dagen-over + taakcount
- app/(app)/insights/components/alignment-trend.tsx — Recharts
  LineChart die % ALIGNED jobs per sprint over laatste 5 sprints toont
- lib/insights/verify-stats.ts — TrendPoint type + getAlignmentTrend
  helper (uitgebreid van PR #38)

Plus dependency: recharts (was in package.json van #37/#40 die we
sloten).

Tests: 290/290 groen, tsc clean, lint clean.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 16:44:53 +02:00
8c0941804c
Component: VerifyResultDonut + Top-5 tabellen (#39)
* chore: voeg recharts toe aan dependencies

Vereist door PlanQualityCard component.

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

* feat: getVerifyResultStats helper (lib/insights/verify-stats.ts)

Aggregeert verify_result counts en top-5 EMPTY/DIVERGENT jobs over de laatste N dagen.

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

* feat: PlanQualityCard — verify_result donut + top-5 EMPTY/DIVERGENT tabellen

PieChart (donut) met ALIGNED/PARTIAL/EMPTY/DIVERGENT verdeling, MD3-kleuren.
Twee tabellen rechts met Next.js Link deeplinks naar TaskDetailDialog.
Empty-state met link naar Plan-verify gating story.

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

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-01 16:41:25 +02:00
2539361784
Component: SprintStatusDonut (Recharts PieChart) (#36)
* chore: voeg recharts toe aan dependencies

Vereist door SprintStatusDonut component.

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

* feat: SprintStatusDonut + getSprintStatusBreakdown helper

PieChart (donut) met TO_DO/IN_PROGRESS/DONE verdeling over alle active sprints.
REVIEW wordt samengevoegd in IN_PROGRESS. MD3 status-kleuren via CSS-variabelen.

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

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-01 16:41:18 +02:00
1df1ea48ad
Component: BurndownChart (Recharts LineChart) (#35)
* feat: getBurndownData helper + computeBurndownDays (lib/insights/burndown.ts)

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>

* chore: voeg recharts toe aan dependencies

Vereist door BurndownChart component in lib/insights.

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

* feat: BurndownChart component (Recharts LineChart, ideal vs remaining)

LineChart met two series: ideal (grijs, dashed) en remaining (status-in-progress blauw).
ResponsiveContainer 100% x 240px, empty-state bij lege days-array.

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

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-01 16:41:15 +02:00
9794a9baef
M13: Veilige Claude-agent-workflow (Scrum4Me-side) (#26)
* feat: add pushed_at field to ClaudeJob schema

Nullable DateTime column to record when the agent's feature branch was
pushed to origin. Enables the UI to show a 'pushed' state independently
of DONE status.

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

* feat: GitHub-link op DONE-card + pushed_at doorvoer

- lib/job-status-url.ts: getBranchUrl(repoUrl, branch) → GitHub tree URL
- JobState + ClaudeJobEvent: pushed_at? veld toegevoegd
- realtime/solo/route.ts: pushed_at in Prisma-select, JobPayload en mapping
- SoloBoardProps + TaskDetailDialog: repoUrl prop doorgevoerd
- task-detail-dialog: "Open op GitHub"-link als done + pushed_at + branch + repoUrl
- 3 unit-tests voor getBranchUrl; totaal 261 tests groen

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

* feat: add VerifyResult enum, verify_only on Task, verify_result on ClaudeJob

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

* feat: add verify_result+pushed_at to JobState, VerifyResultApi type, SSE payload

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

* feat: verify_only field on SoloTask, PATCH route saves verify_only

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

* feat: TaskDetailDialog — verify_result display + verify_only checkbox

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

* test: verify_only PATCH + verify_result dialog render + store fix

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

* docs: document VerifyResult enum, verify_only task field, pushed_at in architecture

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

* feat(M13): cron /api/cron/cleanup-agent-artifacts — hard-delete FAILED/CANCELLED jobs >7 days

* feat(M13): add auto_pr field to Product schema + migration

* feat(M13): auto_pr toggle in product settings — server action + UI component + tests

* feat(M13): add pr_url to ClaudeJob schema + migration

* feat(M13): UI — 'Open PR' link on DONE-card; pr_url in JobState + SSE + task-dialog

* feat(M13): add retry_count migration + regen erd

- Migration ALTER TABLE claude_jobs ADD COLUMN retry_count INT DEFAULT 0
  (schema.prisma was reeds bijgewerkt in eerdere commits)
- docs/erd.svg geregenereerd voor de complete M13-schema-wijzigingen
  (verify_result, verify_only, pushed_at, pr_url, auto_pr, retry_count)

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

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-01 13:42:18 +02:00
3bb87f17ba
Solo Paneel header refactor + agent-workflow hardening (#24)
* 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>
2026-05-01 11:41:35 +02:00
794f7afd2e
feat: plan_snapshot field on ClaudeJob + architecture doc (#23)
* 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>
2026-04-30 19:43:47 +02:00
8877ea469d
feat(M14): 3-pane backlog — generic SplitPane, BacklogStore, SSE realtime, card-grid TaskPanel (#22)
* feat(split-pane): refactor to generic n-pane SplitPane with cookie persistence

New API: panes[], defaultSplit[], cookieKey, tabLabels. Supports arbitrary
number of panes with n-1 draggable dividers and JSON cookie persistence.
Replaces TriplePane; mobile renders tabs.

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

* feat(split-pane): migrate callers to new panes[] API

Backlog page and sprint board now use generic SplitPane.
TriplePane removed; sprint board uses 3-pane with defaultSplit=[28,35,37].

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

* test(split-pane): add unit tests for 2/3-pane, cookie-restore, mobile tabs

Added jsdom + @testing-library/react devDeps for component testing.
7 cases: render, divider count, cookie restore, invalid cookie fallback,
mobile tab render/switch, and no-dividers-on-mobile.

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

* feat(backlog): add BacklogStore Zustand store with applyChange reducer

State: pbis, storiesByPbi, tasksByStory. setInitialData for server
hydration; applyChange(entity, op, data) handles I/U/D for SSE events.

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

* feat(backlog): server-fetch tasks + hydrate BacklogStore on page load

Page now fetches tasks parallel to stories and groups by story_id.
BacklogHydrationWrapper calls setInitialData on mount so the store
is ready for downstream SSE consumers.

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

* feat(backlog): add EmptyPanel shared component, replace inline empty states

EmptyPanel takes title?, message, and optional action with DemoTooltip.
Replaces duplicate inline empty-state markup in pbi-list and story-panel.

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

* feat(backlog): add TaskPanel with sortable rows and TaskDialog wiring

Reads selectedStoryId + tasksByStory from stores. DnD reorder via
reorderTasksAction. Row click → ?editTask, + button → ?newTask&storyId.
DemoTooltip on drag handles and + button.

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

* feat(backlog): wire TaskPanel + TaskDialog into backlog page

3-pane SplitPane [20,45,35]. searchParams for newTask/editTask.
TaskDialog and EditTaskLoader render on ?newTask and ?editTask.

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

* test(backlog): add TaskPanel tests for render states and click handlers

7 cases: no-story empty, no-tasks empty+action, tasks render, + button
router.push, row click router.push, demo disabled button, demo disabled handles.

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

* feat(backlog): migrate PbiList to store-driven via useBacklogStore

Removes pbis prop; reads from useBacklogStore(s => s.pbis) so SSE
updates reflect in real-time without prop drilling.

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

* feat(backlog): migrate StoryPanel to store-driven + selectStory on click

Removes storiesByPbi prop; reads from useBacklogStore. Card click now
dispatches selectStory(id) + shows isSelected highlight. Edit moved to
inline pencil button. page.tsx drops pbis/storiesByPbi props.

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

* test(backlog): add 3-pane integration tests for click-cascade flow

Covers: empty states, PBI→stories, story→tasks, cascade-reset,
isSelected highlight. localStorage mocked for sort-mode persistence.

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

* feat(ST-1115): SSE backlog realtime — endpoint, hook, hydration mount, tests

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

* feat(ST-1116): mobile auto-switch tabs + back button in BacklogSplitPane

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

* docs(ST-1116): update functional-spec (3-pane backlog + mobile) and architecture (backlog SSE + backlog-store)

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

* feat(ST-1117): TaskPanel card-grid — BacklogCard + rectSortingStrategy, tests updated

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

* fix(tests): correct PbiStatusApi type and remove duplicate mock keys

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

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-30 18:16:07 +02:00
6cd98129f2
M14: TaskDialog (create/edit) + story auto-promotion (#21)
* 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>
2026-04-30 16:55:20 +02:00
c6fdd45d98
chore: debug-realtime tooling for SSE pipeline diagnostics (#20)
* chore(debug): add /debug-realtime page + bare SSE endpoint

Tijdelijke debug-tooling voor M8-acceptance op Vercel preview.

- app/api/debug/realtime-stream/route.ts — geen auth, geen filtering;
  dropt elke pg_notify-event op scrum4me_changes rauw door als SSE
- app/debug-realtime/page.tsx — open zonder login op de root, toont
  binnenkomende events in een simpele <table>

Doel: isoleren of de SSE + Postgres LISTEN-pipe op Vercel überhaupt
events laat zien, los van iron-session, productfilter of solo-store.
Als ook deze niets binnen krijgt: probleem zit in pg connection of
Vercel function lifecycle. Als deze wel events toont: probleem zit
hoger in de stack (filter, store, hook).

VERWIJDEREN voordat de PR uit draft gaat.

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

* chore(debug): extend /debug-realtime with stats, emit-button and filters

Bouwt de basale luister-tabel uit met diagnostische tooling om de
SSE+LISTEN-pipe stress-vrij te kunnen valideren.

Toegevoegd:
- POST /api/debug/emit-test-notify — vuurt een handmatige pg_notify
  op scrum4me_changes met een synthetic payload (debug:true) zonder
  een echte DB-UPDATE te doen. Isoleert de SSE-route van Prisma/triggers.
- DebugRealtimeClient: stats-grid (status, reconnects, total events,
  since last event met >30s rood-warning, largest gap, first-event-
  time), emit-button, reset-stats, filters op type en entity
  (incl. "debug only").
- Type/entity kolom in de tabel met kleuring per type.

Geen impact op productie- of solo-flow. Tijdelijke testtooling;
verwijderen wanneer we deze pagina niet meer nodig hebben.

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

* chore(debug): add Layer 2 — mini Zustand-store + dispatch toggles

Test of SSE-event → store → render-pipeline werkt buiten de Solo
Paneel context. Mirrort het patroon van solo-store maar minimaal.

- debug-store.ts: kleine Zustand-store met tasks + applyEvent +
  applyCount/skipCount-tellers
- store-panel.tsx: rendert store-state in een tabel met statuskleuring
- client.tsx: drie layer-toggles (dispatch / flushSync / startView-
  Transition) + lift dispatch in onmessage. Zo kunnen we elke
  combinatie isoleren

Bevestigd: alle drie de toggles werken op het bare /debug-realtime
endpoint. Volgende laag is Server Action revalidation.

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

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 20:35:40 +02:00
73087e9705
M13: Claude job queue — 'Voer uit'-knop + worker presence (ST-1111) (#18)
* 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>
2026-04-29 19:51:48 +02:00
1cb5772edd
M12 / ST-1110: Demo gebruiker read-only (#17)
* feat(ST-1110.3): add proxy.ts demo-guard for non-GET API routes

* feat(ST-1110.3+4): demo-guard proxy + block demo in QR-pairing

- proxy.ts: gebruik unsealData ipv getIronSession (middleware-compatibel)
- pair/start: isDemo-check via cookies() guard
- pair/claim: check pairing.user.is_demo na DB-read; 403 + clearPairCookie

* feat(ST-1110.5): unify demo write-button pattern to disabled+tooltip

Convert all !isDemo && <Button> patterns to <DemoTooltip show={isDemo}>
<Button disabled={isDemo}> so demo visitors see app capabilities.
Affects: pbi-list, story-panel, story-dialog, task-list, sprint-backlog,
token-manager, product-list, activate-product-button, leave-product-button,
settings page.

* test(ST-1110.6): proxy demo-guard coverage — 403 for demo+non-GET on /api/*

* docs(ST-1110.7): document three-layer demo-readonly policy and mirror plan
2026-04-29 18:44:14 +02:00
8a9fb9d32b
M12 / ST-1109: PBI krijgt een status (Ready / Blocked / Done) (#16)
* 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>
2026-04-29 17:52:34 +02:00