Commit graph

371 commits

Author SHA1 Message Date
Scrum4Me Agent
8bccb56b21 feat(ST-bsjoqjnr): UserQuestion model + PLAN_CHAT enum-waarde
- Voeg PLAN_CHAT toe aan ClaudeJobKind enum
- Voeg UserQuestionStatus enum toe (pending, answered)
- Voeg UserQuestion model toe met idea_id, user_id, question, answer, status
- Koppel user_questions relatie aan Idea model
- Migratie: 20260505120000_add_user_question_plan_chat

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-05 17:26:22 +02:00
Janpeter Visser
881ee007e5
Merge pull request #101 from madhura68/feat/story-abeu63oz
ST-1207: Admin: productenbeheer & ledenbeheer (/admin/products)
2026-05-05 14:55:19 +02:00
Scrum4Me Agent
b9e6e725b6 feat(ST-abeu63oz): admin products-actions (create, update, archive, delete, addMember, removeMember)
- Zod-schema adminProductSchema (name, description, repo_url, definition_of_done, auto_pr, owner_user_id)
- adminCreateProductAction: owner-validatie, prisma.product.create
- adminUpdateProductAction: zelfde schema zonder owner_user_id
- adminArchiveProductAction: toggle archived-vlag
- adminDeleteProductAction: hard delete (cascade)
- adminAddMemberAction: upsert ProductMember
- adminRemoveMemberAction: deleteMany ProductMember
2026-05-05 14:54:08 +02:00
Janpeter Visser
9861495dbd
Merge pull request #100 from madhura68/feat/story-xmwvqru1
ST-1206: Admin: ClaudeJobs beheer (/admin/jobs)
2026-05-05 14:48:30 +02:00
Scrum4Me Agent
788920b790 feat(ST-xmwvqru1): admin jobs-actions (cancelJob, deleteJob)
- lib/session.ts: isAdmin: boolean toegevoegd
- lib/auth-guard.ts: requireAdmin() toegevoegd
- actions/admin/jobs.ts: cancelJobAction (CUID-validatie, eindstatus-check → CANCELLED),
  deleteJobAction (hard delete) — beide 'use server', revalidatePath('/admin/jobs')
2026-05-05 14:47:11 +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
Janpeter Visser
64b8c7f5d7
Merge pull request #98 from madhura68/feat/story-l9kkxh2m
ST-1204: Password-reset-flow & CLI-bootstrap
2026-05-05 14:44:27 +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
5fd56e3f67 feat(ST-111ci8t4): admin user-actions (delete, updateRoles, setMustResetPassword)
- lib/session.ts: isAdmin: boolean toegevoegd aan SessionData
- lib/auth-guard.ts: requireAdmin() toegevoegd (redirect /dashboard bij !isAdmin)
- actions/admin/users.ts: deleteUserAction (zelfbescherming), updateUserRolesAction
  (Zod z.nativeEnum, eigen ADMIN-rol-beveiliging, transactie), setMustResetPasswordAction
  — alle drie 'use server', revalidatePath('/admin/users')
2026-05-05 14:38:42 +02:00
Scrum4Me Agent
b760ec625e feat(ST-l9kkxh2m): CLI-script scripts/create-admin.ts voor admin-bootstrap
- Maakt user aan als die niet bestaat, anders upgrade bestaande user
- Upsert ADMIN in user_roles (idempotent)
- Helder foutbericht als argumenten ontbreken (process.exit(1))
- package.json scripts: "create-admin": "tsx scripts/create-admin.ts"
2026-05-05 14:34:10 +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
Janpeter Visser
71281038ff
Merge pull request #97 from madhura68/feat/story-nma6ylbl
ST-1203: Database-schema, sessie-uitbreiding & admin-routing
2026-05-05 14:19:21 +02:00
Scrum4Me Agent
19c458287a feat(ST-nma6ylbl): Prisma-migratie ADMIN enum + must_reset_password + CANCELLED-status
- Role enum: ADMIN toegevoegd (na DEVELOPER)
- User model: must_reset_password Boolean @default(false) toegevoegd (na bio_detail)
- ClaudeJobStatus: CANCELLED was al aanwezig, geen wijziging nodig
- Migratie SQL: ALTER TYPE buiten transactie geplaatst (IF NOT EXISTS)
- prisma migrate deploy toegepast, prisma generate gedraaid
- lint 0 errors, 546 tests groen, build succesvol
2026-05-05 14:18:10 +02:00
Janpeter Visser
d2601b6e9b
Merge pull request #96 from madhura68/fix/idea-pbi-link-route
fix(m12): broken /backlog route on PBI-link after materialize
2026-05-05 14:12:17 +02:00
b25c3c5482 fix(m12): drop bogus /backlog#pbi-{code} route on PBI-link
Three places linked to \`/products/[id]/backlog#pbi-{pbi_code}\` after
materializing or in the planned-state link-card. That route doesn't
exist (product backlog lives at \`/products/[id]\` directly), and the
hash was double-prefixed (\`#pbi-PBI-32\`) since pbi_code already starts
with PBI-. Result: 404 for the user.

Fix: route to \`/products/[id]\` without anchor. The new PBI is the most
recent so visible near the top. Per-PBI anchor scrolling is a follow-up
once we add \`id="pbi-{id}"\` attributes to pbi-list rows.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 14:06:24 +02:00
Janpeter Visser
96bda7da00
Merge pull request #95 from madhura68/fix/idea-timeline-auto-refresh
fix(m12): IdeaTimeline auto-refresh on new idea-questions
2026-05-05 13:45:18 +02:00
Janpeter Visser
c6db766ff7
Merge pull request #94 from madhura68/fix/idea-timeline-hydration-locale
fix(m12): hydration mismatch on IdeaTimeline timestamps
2026-05-05 13:45:02 +02:00
Janpeter Visser
fe880d1d05
Merge pull request #93 from madhura68/fix/m12-bell-loses-idea-questions-on-reconnect
fix(m12): bell loses idea-questions on SSE reconnect
2026-05-05 13:44:47 +02:00
5793afc709 fix(m12): IdeaTimeline auto-refresh on new idea-questions
The /ideas/[id]?tab=timeline page is server-rendered: questions are a
prop snapshot. Without a router.refresh, new questions only show after a
manual page reload — and during a grill-session that's every ~20s.

Fix: in use-notifications-realtime, after dispatching idea-question
events to idea-store + re-syncing the bell, call \`router.refresh()\`.
This re-runs the server-component for whichever page the user is on; on
/ideas/[id] it pulls the new question. On other pages it's a no-op (no
visible state change).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 13:44:15 +02:00
4a86910e66 fix(m12): hydration mismatch on IdeaTimeline timestamps
\`new Date(...).toLocaleString()\` zonder expliciete locale gebruikt de
default-locale van runtime: server (Node) levert nl-NL formaat
(\`05/05/2026, 13:21:51\`), browser CSR levert en-US (\`5/5/2026, 1:21:51 PM\`).
React detecteert dat als hydration-mismatch en regenereert de tree.

Fix: pass \`'nl-NL'\` met expliciete date/time-style. Server en client
produceren nu identieke output.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 13:29:43 +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
Janpeter Visser
4daa564811
Merge pull request #92 from madhura68/fix/m12-idea-question-answering
fix(m12): answer idea-questions — inline + bell support
2026-05-05 13:06:57 +02:00
9e8f33b96e fix(m12): user can answer idea-questions — inline + bell support
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>
2026-05-05 13:05:39 +02:00
Janpeter Visser
2893573004
Merge pull request #91 from madhura68/feat/m12-ideas
M12 — Idea entity + Grill/Plan jobs
2026-05-05 11:58:25 +02:00
02a7f59897 docs: regenerate erd.svg with M12 Idea + IdeaLog models
Auto-generated by prisma generate after schema sync.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 11:55:51 +02:00
Janpeter Visser
452a38726b
Merge pull request #90 from madhura68/docs/pr-merge-conflict-faq
docs(runbook): merge-conflict FAQ voor PR-per-batch flow
2026-05-04 22:14:48 +02:00
492b71beb9 fix: drop \__test__\ export from actions/ideas.ts (use-server-only-fns)
Next.js 'use server' files only allow exports of async functions. The
\`export const __test__ = { canTransition }\` line at the bottom of
actions/ideas.ts threw a runtime error on every page load that imported
the file.

Tests already import canTransition directly from lib/idea-status; the
__test__ helper was vestigial. Removed.

Tests: 546/546 still green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 21:54:10 +02:00
7269e9732d docs: M12 backlog entry + mcp-integration runbook for idea-jobs (M12 T-517)
docs/backlog/index.md:
- New M12 row in milestone-overview
- Full M12 section with 10 stories (8 done, ST-1197 extern + ST-1201 in
  progress); each story lists its task IDs

docs/runbooks/mcp-integration.md:
- wait_for_job payload contract documented per kind discriminator
  (TASK_IMPLEMENTATION vs IDEA_GRILL vs IDEA_MAKE_PLAN)
- Per-kind agent behavior table
- 5 new MCP-tools documented: get_idea_context, update_idea_grill_md,
  update_idea_plan_md, log_idea_decision; plus extended ask_user_question
  contract (story_id|idea_id xor)
- Batch-loop step 2 expanded to switch on kind

docs/INDEX.md auto-regenerated (83 docs).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 21:45:54 +02:00
6721003572 ui: nav entry "Ideeën" between Insights and Todo's (M12 T-515)
components/shared/nav-bar.tsx:
- New nav-link to /ideas with active-state on pathname.startsWith('/ideas')
- Placement: between Insights and Todo's — matches the M12 plan
  ("direct boven Todo's")
- No icon (existing nav uses text-only links; deviation from plan's
  Lightbulb spec for visual consistency with the rest of the nav)

Tests: 546/546 still green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 21:43:51 +02:00
7595474fcc ui: "→ Idee" promote button in TodoCard + PromoteIdeaDialog (M12 T-514)
components/todos/todo-list.tsx:
- TodoCard: new "→ Idee" button next to "→ PBI" + "→ Story" (only shown
  for non-demo)
- PromoteIdeaDialog: confirmation modal — no extra inputs needed since
  promoteTodoToIdeaAction takes only todoId; title/description carry
  over from the todo, status starts as DRAFT
- onPromoteIdea callback wired through TodoCard props
- On success: navigates to /ideas/{new-id} so user lands on the fresh
  idea-detail page

Tests: 546/546 still green.

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

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 21:41:00 +02:00
1ba9feac1a ui: idea-timeline + pbi-link-card + download-md-button (M12 T-512)
components/ideas/idea-timeline.tsx:
- Chronological merge of IdeaLog + ClaudeQuestion (sorted desc by created_at)
- Per-entry icon by log-type (DECISION/NOTE/GRILL_RESULT/PLAN_RESULT/
  STATUS_CHANGE/JOB_EVENT) + question-status label
- MD3-tokens, vertical timeline rail (border-left + dots)
- Question entries show options + answer (border-left highlight)
- Metadata expansion via <details> for log entries

components/ideas/idea-pbi-link-card.tsx:
- PLANNED + pbi present: green status-done card with PBI link
- PLANNED + pbi removed (FK SetNull): blocked-color banner with
  "Plan opnieuw beschikbaar maken" → relinkIdeaPlanAction
- Demo blocked on relink

components/ideas/download-md-button.tsx:
- Calls downloadIdeaMdAction → builds Blob + anchor + click()
- Filename: {idea.code}-{kind}.md
- Demo MAY use it (read-only)

components/ideas/idea-detail-layout.tsx:
- Replaces inline placeholders with extracted components
- Md tabs gain Download (.md) button + Edit button row

Tests: 546/546 still green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 21:39:33 +02:00
9d3a993f2a ui: idea-md-editor with yaml-validate + wire into detail tabs (M12 T-511)
components/ideas/idea-md-editor.tsx:
- Textarea-based editor with monospace styling for grill_md / plan_md
- kind='plan': live yaml-frontmatter validation as derived state via
  useMemo (no setState-in-effect); inline errors with line numbers
- kind='grill': free markdown, no validation
- localStorage draft per (ideaId, kind) — lazy initial-value seeded on
  mount; toast notice if drift from server
- Cmd/Ctrl+S keyboard shortcut to save
- Server-action 422 details surface as separate submitErrors state

components/ideas/idea-detail-layout.tsx:
- Grill/Plan tabs flip into edit-mode via "Bewerk" button when:
  - grill: status in [GRILLED, PLAN_READY] (M12 grill-keuze 12)
  - plan: status === PLAN_READY
- Empty-state offers "Schrijf zelf" when md is null + editable
- Demo always read-only

Tests: 546/546 still green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 21:37:49 +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
a1d3a83af5 ui: full IdeaRowActions with disabled-rules + tooltips (M12 T-508)
components/ideas/idea-row-actions.tsx — replaces T-507 placeholder:
- Grill Me: disabled in GRILLING/PLANNING/PLANNED, requires
  product-with-repo + connectedWorkers > 0; tooltip shows specific reason
  ("Grill loopt al", "Idee heeft een product met repo nodig", "Geen
  Claude-worker actief")
- Make Plan: enabled only in GRILLED/PLAN_FAILED/PLAN_READY; same
  prerequisites as Grill
- Materialiseer: enabled only in PLAN_READY (no worker needed — synchrone
  server-side parser); confirm-dialog before action; navigates to product
  backlog PBI anchor on success
- *_FAILED: dedicated "Probeer opnieuw" rotate-icon button
- PLANNED: replaces all three with "Bekijk {PBI-code}" link + open-detail
- Demo: every mutating button wrapped in DemoTooltip with disabled state
- connectedWorkers read directly via useSoloStore (per M12 grill-keuze 16)

Tests: 546/546 still green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 21:32:06 +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
006d803a16 ui: idea-status badge helper (M12 T-509)
lib/idea-status-colors.ts: getIdeaStatusBadge(status) → { label, classes,
pulse? }. Reuses existing --status-*-tokens (in-progress / blocked / review
/ done) — no new tokens needed in theme.css.

Mapping (per docs/plans/M12-ideas.md state machine):
- DRAFT          → surface-variant (neutral)
- GRILLING       → in-progress + pulse
- GRILL_FAILED   → blocked
- GRILLED        → review (waiting for next step)
- PLANNING       → in-progress + pulse
- PLAN_FAILED    → blocked
- PLAN_READY     → review
- PLANNED        → done

CLAUDE.md hardstop respected — only MD3-tokens, no arbitrary Tailwind colors.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 21:29:02 +02:00
8cc4e0aeb7 realtime: idea-store + extend notifications hook for idea events (M12 T-503)
stores/idea-store.ts (Zustand):
- jobByIdea, ideaStatuses, openQuestionsByIdea
- handleIdeaJobEvent: derives optimistic ideaStatus (queued/claimed/running →
  grilling/planning; failed → grill_failed/plan_failed; done = no-op since
  the server-side update_idea_*_md is source-of-truth)
- handleIdeaQuestionEvent: list-based, removes on non-open
- setIdeaStatus / setJobStatus / clearForIdea optimistic helpers
- connectedWorkers NOT duplicated — UI reads useSoloStore(s.connectedWorkers)

lib/realtime/use-notifications-realtime.ts:
- Single SSE serves both bell-questions and idea-state. Adds dispatcher
  branches: idea-job payloads → idea-store; idea-question payloads (idea_id
  set) → idea-store; story-questions → existing notifications-store path.

Tests: 7/7 idea-store cases (queued→grilling, failed→*_failed, done no-op,
question-list management, clearForIdea isolation).
Full suite: 546/546 green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 20:02:22 +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
a1d1f99216 proxy: add /ideas to protectedRoutes; verify demo-guard for /api/ideas (M12 T-501)
- 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>
2026-05-04 19:56:41 +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
6904de9f2b actions: promoteTodoToIdeaAction (M12 T-499)
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>
2026-05-04 19:52:37 +02:00
6fee0394c5 actions: materializeIdeaPlanAction + relinkIdeaPlanAction (M12 T-498)
actions/ideas.ts:
- materializeIdeaPlanAction(id):
  - guard: status===PLAN_READY, plan_md present, product linked, demo-403
  - parsePlanMd → 422 with line-info on fail
  - Prisma.\$transaction:
    - SELECT max(code) for PBI/Story/Task within product
    - INSERT PBI with sort_order = lastPbi+1 within priority
    - per story: INSERT (sequential ST-NNN), per task: INSERT (T-N)
    - UPDATE idea SET pbi_id, status=PLANNED
    - INSERT IdeaLog{PLAN_RESULT, metadata}
  - returns 409 on P2002 (concurrent-materialize race)
- relinkIdeaPlanAction(id):
  - guard: status===PLANNED && pbi_id===null (PBI manually deleted via SetNull FK)
  - reverts to PLAN_READY + IdeaLog{NOTE}

Tests: 39 cases total (8 new for materialize + relink): happy creates entities,
status-mismatch-422, parse-fail-422 with details, demo-403, P2002→409,
relink happy + invalid-precondition guards.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 19:51:18 +02:00
33cbb6c2f4 actions: idea-job triggers + cancel (M12 T-497)
actions/ideas.ts:
- startGrillJobAction(id) — DRAFT/GRILLED/GRILL_FAILED/PLAN_READY → GRILLING;
  validates product+repo_url, idempotency check (active job 409),
  worker-count check (15s freshness), atomic $transaction creates ClaudeJob
  + flips idea.status + IdeaLog{JOB_EVENT}, manual pg_notify
- startMakePlanJobAction(id) — GRILLED/PLAN_FAILED/PLAN_READY → PLANNING;
  same shape via shared startIdeaJob helper
- cancelIdeaJobAction(id) — finds active QUEUED|CLAIMED|RUNNING job for idea,
  reverts status: grill→DRAFT/GRILLED based on grill_md presence;
  plan→GRILLED/PLAN_READY based on plan_md presence

Tests: 31 cases incl. happy path, demo-403, no-product/no-repo-422,
no-worker-422, idempotency-409, status-mismatch-422, cancel revert paths,
404 no-active-job.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 19:49:27 +02:00
5f410d3b10 actions: ideas CRUD + grill_md/plan_md edit + download (M12 T-496)
actions/ideas.ts (strikt user_id-only, geen productAccessFilter):
- createIdeaAction(input) — atomic nextIdeaCode + idea.create in $transaction
- updateIdeaAction(id, input) — guards on isIdeaEditable
- archiveIdeaAction / unarchiveIdeaAction
- deleteIdeaAction — refuses when pbi_id linked
- updateGrillMdAction — only in GRILLED|PLAN_READY; logs IdeaLog{NOTE}
- updatePlanMdAction — only in PLAN_READY; runs parsePlanMd; 422 with details on fail
- downloadIdeaMdAction — read-only, demo allowed

Added rate-limit configs: create-idea, edit-idea-md, start-idea-job,
materialize-idea.

Tests: 19 cases covering auth (401), demo (403), zod (422), status guards
(422), 404 cross-user-scope, plan-md parse-fail with details.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 19:47:30 +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
dd935c22d3 prompts: embedded grill + make-plan prompts for IDEA_* jobs (M12 T-495)
- lib/idea-prompts/grill.md: interview-loop prompt; uses ask_user_question MCP
  one-question-at-a-time; stop-condition (title + scope + 3+ AC + 1+ risk);
  ends with update_idea_grill_md
- lib/idea-prompts/make-plan.md: single-pass planning prompt; reads grill_md +
  repo; produces strict yaml-frontmatter format consumable by parsePlanMd;
  forbids ask_user_question (twijfels → re-grill via UI)

Both in Dutch, consistent with rest of scrum4me content. No external
anthropic-skills dependency: scrum4me owns these prompts.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 19:41:56 +02:00
dfee518996 lib: idea-code generator + plan_md yaml-frontmatter parser (M12 T-494)
- lib/idea-code.ts: pure formatIdeaCode helper (client-safe, no prisma)
- lib/idea-code-server.ts: atomic nextIdeaCode via Prisma row-lock,
  accepts optional TransactionClient for combining with idea.create
- lib/idea-plan-parser.ts: parsePlanMd extracts ---yaml---/body, runs
  yaml.parse + ideaPlanMdFrontmatterSchema, returns line-info on failure;
  CRLF-tolerant
- adds yaml@^2.8.4 dependency
- 8 unit tests (parser happy/missing/yaml-error/zod-error/empty-stories/CRLF;
  formatIdeaCode pad-3 + overflow)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 19:40:39 +02:00
bba3f11269 lib: idea schemas + status mappers + transition guards (M12 T-493)
- lib/schemas/idea.ts: ideaCreateSchema, ideaUpdateSchema, ideaPlanMdFrontmatterSchema
  (yaml-frontmatter contract for materialize-step parser)
- lib/idea-status.ts: bidirectional DB↔API mapping, canTransition state-machine
  guard, isIdeaEditable + isGrillMdEditable + isPlanMdEditable helpers
- includes auto-regen docs/erd.svg from prisma generate

Tests: 26 cases (status round-trip, transitions valid/invalid, schema validation
edge-cases, priority bounds, verify-enum).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 19:38:52 +02:00
86fb97456e db: M12 migration — ideas + idea_logs + check-constraints + pg_notify update (T-492)
- new tables ideas + idea_logs with FKs (User/Product/Pbi cascade rules per plan)
- claude_jobs.task_id nullable; new idea_id FK + kind enum + index
  + check-constraint: exactly_one(task_id, idea_id)
- claude_questions.story_id nullable; new idea_id FK + index
  + check-constraint: exactly_one(story_id, idea_id)
- notify_question_change trigger: handles null story_id; idea_id added to payload

Verified against dev DB: tables created, both check-constraints active
(neither-set insert correctly rejected with errcode 23514),
trigger replaced.

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