Toont per Story onder de gekoppelde PBI: status-badge, taak-rij met
job-status (incl. SKIPPED), branch, pushed_at, pr_url, en bestaande
<StoryLog>-component voor activity-log. PBI-header met PR-link en
gemerged-badge.
Realtime: subscribed op /api/realtime/notifications. Bij story_log-
event waar story_id matcht, of claude_job_status voor dit idea →
router.refresh() (server-render levert nieuwe data).
MD3-tokens overal: bg-status-todo/in-progress/done, bg-surface-
container, bg-muted/60. Geen bg-blue-500.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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>
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>
AFTER INSERT op story_logs emit op scrum4me_changes channel met
entity:'story_log'. Trigger resolved product_id en idea_id via
story → pbi → product/idea zodat SSE-route kan filteren zonder
extra DB-call per event.
Migratie toegepast op Neon productie-DB.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Beschrijft beslissingsboom verify_result × diff-staat × branch-staat
→ JobStatus, met SKIPPED gereserveerd voor al-gemergd werk en FAILED
voor échte fouten. Plus StoryLog-verplichting (log_implementation,
log_commit, log_test_result) en idempotency-protocol vóór schrijven.
PBI-33 batch (5-5 22:22) gedocumenteerd als case-study: drie
protocol-overtredingen die deze runbook + de nieuwe SKIPPED-status
aanpakken.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Italic, neutrale grijstint die SKIPPED visueel onderscheidt van
CANCELLED (zelfde grijstint, maar non-italic) en van FAILED (rood).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- 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>
Reactie op PBI-33 batch waar worker correct detecteerde dat werk al
gemerged was, maar geen passende status had om dat uit te drukken.
SKIPPED is bedoeld voor jobs met verify=EMPTY/DIVERGENT waar de
diff t.o.v. origin/main leeg is — geen FAILED (geen fout), geen DONE
(geen netto-output).
Migratie: ALTER TYPE ClaudeJobStatus ADD VALUE 'SKIPPED'.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Nav-label 'Ideeën' hernoemd naar 'Ideas'; breadcrumb idem
- Grill/Plan tabs disabled (grijs, cursor-not-allowed) zolang er geen
content is; groene stip zodra grill_md resp. plan_md beschikbaar is
- SSE hook roept router.refresh() aan bij job done/failed zodat de
Timeline automatisch de nieuwe GRILL_RESULT/PLAN_RESULT logs toont
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- /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>
- requireAdmin() checkt nu de database i.p.v. session.isAdmin (was altijd undefined)
- loginAction stelt session.isAdmin in op basis van UserRole in de DB
- registerAction stelt session.isAdmin = false expliciet in
- NavBar toont 'Admin'-link conditioneel als roles.includes('ADMIN')
- UserMenu ROLE_LABELS uitgebreid met ADMIN → 'Admin'
- Tests aangepast: prismaUserRole.findFirst mock toegevoegd
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Nieuwe server action in actions/user-questions.ts
- Aanmaken UserQuestion + ClaudeJob PLAN_CHAT in transactie
- Blokkeert als idea.plan_md null is of product ontbreekt
- Idempotency-check: geen dubbele PLAN_CHAT per idee
- pg_notify claude_job_enqueued event voor SSE-realtime
- Rate-limit config uitgebreid met create-user-question
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- 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>
- 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
- 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"
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>
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>
\`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>
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>
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>
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>
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>
components/shared/nav-bar.tsx:
- New nav-link to /ideas with active-state on pathname.startsWith('/ideas')
- Placement: between Insights and Todo's — matches the M12 plan
("direct boven Todo's")
- No icon (existing nav uses text-only links; deviation from plan's
Lightbulb spec for visual consistency with the rest of the nav)
Tests: 546/546 still green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
components/todos/todo-list.tsx:
- TodoCard: new "→ Idee" button next to "→ PBI" + "→ Story" (only shown
for non-demo)
- PromoteIdeaDialog: confirmation modal — no extra inputs needed since
promoteTodoToIdeaAction takes only todoId; title/description carry
over from the todo, status starts as DRAFT
- onPromoteIdea callback wired through TodoCard props
- On success: navigates to /ideas/{new-id} so user lands on the fresh
idea-detail page
Tests: 546/546 still green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>