Commit graph

73 commits

Author SHA1 Message Date
Janpeter Visser
5cb3abbd3d
Sprint: Idee regril mogelijkheid (#144)
* feat(ST-cmovhveef): add PLANNED to GRILL_TRIGGERABLE_FROM and PLANNED→GRILLING transition

- GRILL_TRIGGERABLE_FROM now includes 'PLANNED' in actions/ideas.ts
- ALLOWED_TRANSITIONS PLANNED entry extended with 'GRILLING' in lib/idea-status.ts
- Updated canTransition test to reflect the new re-grill-from-PLANNED behavior

* test(ST-cmovhvef3): add exhaustive re-grill canTransition test covering PLANNED

Adds a loop test that asserts canTransition(status, 'GRILLING') for all
statuses in GRILL_TRIGGERABLE_FROM that support the transition, explicitly
documenting PLANNED as a valid re-grill entry point.

* feat(ST-cmovhvegf): add existingPbi pre-check in materializeIdeaPlanAction

- Adds options.allowAlongside parameter to control behaviour when a PBI
  with executed tasks already exists.
- Returns 409 PBI_HAS_ACTIVE_TASKS:<code> when tasks are DONE/IN_PROGRESS
  and allowAlongside is not set.
- Auto-deletes the old PBI inside the transaction when no tasks have been
  executed (atomic replace).
- Alongside mode (allowAlongside=true) skips deletion and creates a new PBI.

* test(ST-cmovhveh3): add pre-check integration tests for materializeIdeaPlanAction

Three new scenarios in ideas-crud.test.ts:
- auto-vervang: old PBI deleted in transaction when no executed tasks
- conflict-409: returns PBI_HAS_ACTIVE_TASKS:<code> with active tasks
- alongside: skips delete and creates new PBI when allowAlongside=true
Also adds task.count, pbi.findUnique, pbi.delete to prisma mock.

* feat(ST-cmovhveih): remove PLANNED-blokkering in idea-row-actions, add inline Bekijk-PBI button

- Removed grillBlockedReason guard for status==='planned', enabling re-grill from PLANNED
- Removed the early return for PLANNED that hid all standard buttons
- Added conditional 'Bekijk <code>' button at the start of the standard button set,
  visible only when status==='planned' and PBI + product_id are present

* feat(ST-cmovhvej7): add PBI_HAS_ACTIVE_TASKS alongside-dialoog in materialize handler

When materializeIdeaPlanAction returns code 409 with PBI_HAS_ACTIVE_TASKS:<code>,
a confirm dialog offers the user a choice: create new PBI alongside the existing one
or cancel. Alongside=true retries the action; cancel leaves the idea in PLAN_READY.
2026-05-07 15:27:43 +02:00
Janpeter Visser
07749ad9fb
PBI-50: SPRINT_IMPLEMENTATION single-session sprint runner (Scrum4Me-side) (#139)
* PBI-50 F1: SPRINT_BATCH execution-strategy + cross-repo blocker + branch-resume

Schema-migratie + Scrum4Me-side wiring voor de nieuwe SPRINT_IMPLEMENTATION-flow:

- prisma: PrStrategy ADD VALUE 'SPRINT_BATCH'; ClaudeJobKind ADD VALUE
  'SPRINT_IMPLEMENTATION'; nieuwe enum SprintTaskExecutionStatus; ClaudeJob.lease_until
  + status_lease_until index; SprintRun.previous_run_id (self-relation
  SprintRunChain) voor branch-hergebruik bij resume; nieuwe sprint_task_executions
  tabel met frozen plan_snapshot + verify_required_snapshot per task in scope.
- actions/sprint-runs.ts startSprintRunCore: nieuwe blocker-type 'task_cross_repo'
  voor SPRINT_BATCH (pre-flight rejecteert sprints met cross-repo task_url).
  Bij SPRINT_BATCH: één SPRINT_IMPLEMENTATION ClaudeJob (geen per-task loop).
- actions/sprint-runs.ts resumePausedSprintRunAction: SPRINT_BATCH-pad met
  remaining-execution-check; bij onafgemaakt werk → nieuwe SprintRun met
  previous_run_id + run.branch hergebruikt + nieuwe SPRINT_IMPLEMENTATION-job.
  Oude SprintRun → CANCELLED. Bestaande PBI-49 P0 scope-DONE pad ongewijzigd.
- actions/products.ts updatePrStrategyAction: accepteert SPRINT_BATCH.
- components/products/pr-strategy-select.tsx: drie opties met helptekst,
  gebruikt @prisma/client PrStrategy ipv lokaal type.
- components/sprint/sprint-run-controls.tsx: BLOCKER_LABELS + blockerHref
  voor task_cross_repo.

Migratie applied op Neon. Type-check + 532 tests groen.

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

* PBI-50 F5: cross-repo blocker test voor SPRINT_BATCH

- task_cross_repo blocker fires bij task.repo_url ≠ product.repo_url
- happy path: tasks zonder repo_url-override of met match → één
  SPRINT_IMPLEMENTATION-job (niet per-task).

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

* PBI-50 F5: docs/architecture/sprint-execution-modes.md

Vergelijking PER_TASK vs SPRINT_BATCH met trade-offs, datamodel-
toevoegingen (SprintTaskExecution, lease_until, SprintRunChain) en
MCP-tools-matrix per modus. Toegevoegd aan breadcrumb in
docs/architecture.md.

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-05-07 13:05:02 +02:00
Janpeter Visser
77617e89ac
PBI-46: Sprint-niveau jobflow met cascade-FAIL (F1/F2/F4 Scrum4Me) (#136)
* ST-1243: F1 schema + propagateStatusUpwards-helper voor sprint-flow

Schema-uitbreidingen voor de sprint-niveau jobflow (PBI-46):
- TaskStatus, StoryStatus, PbiStatus, SprintStatus krijgen FAILED
- Nieuwe enums: SprintRunStatus, PrStrategy
- Nieuw SprintRun-model dat per-task ClaudeJobs groepeert
- ClaudeJob.sprint_run_id koppeling + index
- Product.pr_strategy (default SPRINT)
- Bijhorende Prisma-migratie

propagateStatusUpwards vervangt updateTaskStatusWithStoryPromotion en
herevalueert de keten Task → Story → PBI → Sprint → SprintRun bij elke
task-statuswijziging. Bij FAILED cancelt het sibling-jobs in dezelfde
SprintRun. PBI-status BLOCKED blijft handmatig en wordt niet overschreven.

Status-mappers + theme krijgen failed-token + label-uitbreidingen.

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

* ST-1244: F2 sprint-runs actions + deprecate per-task enqueues

actions/sprint-runs.ts (nieuw):
- startSprintRunAction met pre-flight (impl_plan / open ClaudeQuestion / PBI BLOCKED|FAILED)
- Maakt SprintRun + ClaudeJobs in PBI→Story→Task volgorde
- resumeSprintAction zet FAILED tasks/stories/PBIs terug en start nieuwe SprintRun
- cancelSprintRunAction breekt lopende SprintRun af zonder cascade

actions/claude-jobs.ts:
- enqueueClaudeJobAction, enqueueAllTodoJobsAction, previewEnqueueAllAction,
  enqueueClaudeJobsBatchAction nu deprecation-stubs (UI-cleanup volgt in F4)
- cancelClaudeJobAction blijft beschikbaar voor losse jobs

Tests bijgewerkt: 11 nieuwe sprint-runs tests, claude-jobs(-batch) tests
herzien naar deprecation-asserties.

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

* ST-1246: F4 UI Start/Resume/Cancel sprint + pr_strategy dropdown

- components/sprint/sprint-run-controls.tsx: knoppen Start Sprint
  (sprintStatus=ACTIVE), Hervat sprint (sprintStatus=FAILED) en
  Annuleer sprint-run (lopende run). Pre-flight blocker-modal toont
  blockers met directe links naar de relevante pagina's.
- components/products/pr-strategy-select.tsx: dropdown SPRINT|STORY in
  product-settings, met optimistic update + sonner-toast op fail.
- actions/products.ts: updatePrStrategyAction (eigenaar-only, demo-block).
- Sprint-page: query op actieve SprintRun + tonen van controls-balk.

Live cascade-visualisatie (T-634) staat als follow-up genoteerd —
huidige sprint-board statusbadges volstaan voor MVP. De Solo-board
"Voer uit"-knoppen zijn niet expliciet verwijderd; ze tonen nu de
deprecation-error van de gestubde actions tot de Solo-flow opnieuw
ontworpen 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-05-06 16:43:57 +02:00
Janpeter Visser
52c610b11c
ST-1242: Verwijder todo-tests, herstel security-test en verifieer volledige build (#134)
* feat(cleanup): verwijder Todo's navlink en todo-referenties uit marketing page [cmotto5ia000nx3178lq6xk8d]

- nav-bar.tsx: Todo's navLink verwijderd; Ideas-link blijft staan
- app/page.tsx: /todos quick-access link, feature-entry en /api/todos API-doc verwijderd

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

* feat(cleanup): verwijder app/(app)/todos/ en components/todos/ [cmottjvzo000cx3172472cu4g]

* test(cleanup): verwijder POST /api/todos import en describe-block uit security.test.ts [cmotto5jn000px317kjqlba89]

- Import 'POST as postTodo' uit verwijderde todos-route verwijderd
- describe('POST /api/todos') sectie (3 tests) verwijderd
- 73 testfiles / 561 tests groen

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

* test(cleanup): verwijder __tests__/api/todos.test.ts en __tests__/actions/todos-promote-idea.test.ts [cmottjw1u000fx317igq27mh5]

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 12:24:29 +02:00
Janpeter Visser
555ed8fe89
feat(ST-qfpqpxzy): DB schema + settings-UI voor min_quota_pct worker-drempel (#118)
- User.min_quota_pct Int @default(20) + ClaudeWorker.last_quota_pct/last_quota_check_at
- Migratie add_worker_quota_gate
- lib/schemas/user.ts: minQuotaPctSchema (int, 1-100)
- actions/settings.ts: updateMinQuotaPctAction met auth/demo/zod-guard
- MinQuotaEditor component met numeric input en DemoTooltip
- Settings-pagina: Worker-instellingen sectie

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 03:45:59 +02:00
Janpeter Visser
be8cd4d02c
feat(ST-vi5iff4s): SoloTask-interface + Prisma-queries uitbreiden met PBI-velden (#116)
pbi_code, pbi_title, pbi_description (nullable) toegevoegd aan SoloTask-interface.
Desktop en mobile solo-page: story.pbi select + mapping via ?. en ?? null.
Test-fixtures bijgewerkt (3 bestanden). 72 testfiles groen, tsc + build slagen.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 03:30:11 +02:00
Janpeter Visser
d819d29b04
feat(ST-d9sl8egw): lib/insights/token-history.ts — sprint-historiek, dag-data & PBI-aggregaat (#115)
Drie functies via prisma.$queryRaw: getSprintTokenHistory (per-sprint
aggregaat), getDayTokenData (dag-totalen met guard op lege sprintId),
getPbiTokenAggregates (per-PBI met guard). Tests voor alle drie.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 03:19:45 +02:00
Janpeter Visser
a2c8bd41af
ST-1216: Insights sprint-widget — token KPI-kaartjes & per-job tabel (#114)
* feat(ST-vmc7vpps): lib/insights/token-stats.ts — sprint KPI + per-job query

SQL-queries voor totale tokens/kosten (KPI) en per-job tabel met
ModelPrice JOIN. Guard op lege sprintId. Tests voor empty guard,
KPI-mapping en null token-data.

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

* feat(ST-vmc7vpps): TokenUsageCard — KPI-kaartjes + sorteerbare per-job tabel

Client-component met drie KPI-strips (totaal tokens, kosten USD, gem. per job)
en sorteerbare tabel op kosten of duur. Nulls als '—', MD3-tokens, geen
hardcoded kleuren.

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

* feat(ST-vmc7vpps): insights page — TokenUsageCard integreren

Voeg getTokenStats + TokenUsageCard imports toe aan insights/page.tsx.
tokenStats apart awaiten na activeSprints (kan niet in dezelfde Promise.all).
TokenUsageCard-sectie toegevoegd na AgentThroughputCard.

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

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 03:19:30 +02:00
ca1a89ca04 test(T-574): cron-cleanup test verwacht SKIPPED in deleteMany filter
Bijgewerkt na uitbreiding van cleanup-criteria met SKIPPED-jobs in
T-572.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 23:12:11 +02:00
fbf58d4e44 fix: admin-navigatie zichtbaar voor ADMIN-rol gebruikers
- 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>
2026-05-05 20:46:27 +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
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
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
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
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
63f5231770 feat(dashboard): pencil-icoon edit-trigger op product-card (todo cmoq3ox51)
De dashboard product-card had al een 'Bewerken'-tekstknop, maar het patroon
in de rest van de app (PBI/story/task in cards) is een hover-zichtbaar
pencil-icoon. Vervangen voor consistentie. Product-detail page-header blijft
tekst — daar staat 'Bewerken' tussen andere text-acties zoals "Sprint actief"
en "Instellingen".

Hergebruikt bestaande ProductDialog en setEditingProduct-state — geen wijziging
aan de dialog of action zelf. Demo-block behouden.

Tests: 4 nieuwe (rendert icoon, opent dialog, demo-disabled, geen icoon op
archived).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 11:21:14 +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
13ab53ab8d feat(ST-1135): UA-redirect bij login — phone naar /m/* (T-322/T-323/T-324)
- lib/user-agent.ts (nieuw): isPhoneUA() — Mobi-substring heuristiek
  (telefoons hebben Mobi, tablets/desktop niet)
- actions/auth.ts loginAction: leest user-agent header na session.save();
  phone-UA + actief product → /m/products/[id]/solo, zonder → /m/settings;
  tablet/desktop/null-UA → /dashboard (ongewijzigd)
- Tests: 7 helper-cases + 6 loginAction-paden incl. demo-user

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 10:09:09 +02:00
479a502dfd feat(ST-1133): entityDialogContentClasses → full-screen op <640px (T-316/T-317/T-318)
Eén edit op de gedeelde constant dekt PbiDialog, StoryDialog, TaskDialog,
TaskDetailDialog (allen renderen DialogContent met dezelfde className).

Toegevoegd: max-sm:w-screen max-sm:h-screen max-sm:max-h-screen
max-sm:max-w-none max-sm:rounded-none. Desktop-classes (sm:max-w-[90vw],
lg:max-w-[50vw]) blijven onveranderd.

Tests: smoke op constant + regressie-vangnet dat de 4 entity-dialogen
entityDialogContentClasses blijven gebruiken.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 10:06:21 +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
47d57a0963 feat(ST-1134): MobileTabBar component (T-320)
Bottom-fixed nav-bar met 3 lucide-iconen (ListTree/Activity/Settings).
Verbergt Backlog/Solo-tabs als activeProductId null is.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 09:52:33 +02:00
e68552bcfd feat(ST-1134): LandscapeGuard component (T-319)
Toont rotate-overlay in portrait, niets in landscape. Kinderen blijven altijd
in DOM — geen unmount zodat SSE-streams overleven bij rotatie.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 09:51:48 +02:00
784791d8f9 feat(sprint-dialogs): conform aan dialog-pattern + entity-profile
Story 5 van PBI "Alle dialogen conform docs/patterns/dialog.md".

- lib/schemas/sprint.ts — gedeelde zod-schemas (create/dates/goal)
- actions/sprints.ts — code+fieldErrors voor 422; code: 403 voor
  auth/demo errors
- StartSprintButton dialog: useDirtyCloseGuard, useDialogSubmitShortcut,
  entityDialog* layout-classes; DemoTooltip op trigger; veld-niveau
  errors via fieldErrors
- SprintHeader's date- en complete-dialogen: zelfde behandeling; date-
  dialog krijgt dirty-guard, complete-dialog krijgt DemoTooltip op
  bevestigen
- docs/specs/dialogs/sprint.md — entity-profile dat alle drie de modes
  documenteert; consolidatie naar één SprintDialog component bewust
  uitgesteld
- Sprint-dates tests aangepast aan nieuwe action-shape

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 07:30:46 +02:00
03a248b0fb feat(product-dialog): conform aan dialog-pattern + entity-profile
Story 2 van PBI "Alle dialogen conform docs/patterns/dialog.md".

- lib/schemas/product.ts — gedeeld zod-schema (Dialog API)
- actions/products.ts — createProductAction/updateProductAction returnen
  nu code+fieldErrors voor 422-validatie en code: 403 voor demo/auth
- ProductDialog adopt useDirtyCloseGuard, useDialogSubmitShortcut,
  entityDialog* layout-classes; 422-fieldErrors mappen naar form.setError
- docs/specs/dialogs/product.md — entity-profile

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 07:18:39 +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
Janpeter Visser
0ee03c6b72
ProductDialog: create + edit met alle velden (#68)
* feat(ST-?): createProductAction + updateProductAction (data-object API)

Voegt data-object-gebaseerde createProductAction(data) en
updateProductAction(id, data) toe aan actions/products.ts voor gebruik
door ProductDialog. Bevat Zod-validatie (incl. github-regex op repo_url),
productAccessFilter voor update, pg_notify bij update, en productMember-
aanleg bij create. FormData-varianten hernoemd naar ...FormAction; callers
bijgewerkt. 9 nieuwe tests groen.

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

* feat(ST-?): ProductDialog component (create + edit modes)

Voegt components/dialogs/product-dialog.tsx toe op basis van het
entity-dialog-patroon. Gebruikt react-hook-form + zodResolver voor
client-side validatie. Roept createProductAction/updateProductAction
aan en werkt stores/products-store.ts optimistisch bij. Demo-modus
disabled alle velden + submit-knop via DemoTooltip. 7 tests groen.

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

* feat(ST-?): UI triggers voor ProductDialog op dashboard en product-detail

Voegt NewProductButton toe op het dashboard (vervangt de /products/new
link) en EditProductButton op de product-detail pagina. Bewerken-knop
is alleen zichtbaar voor de product-eigenaar en verborgen in demo-modus.

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

* fix(test): cast toast via unknown to satisfy strict TS

`toast as { success, error }` direct-cast faalt omdat sonner's toast een
callable + properties is. TS2352. Cast via unknown lost het op zonder
gedrag te wijzigen.

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

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-03 17:56:33 +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
6375ed6949
End-to-end smoke-test: PBI/Story/Task verschijnen zonder refresh (#57)
* fix(backlog-store): make INSERT handlers idempotent to prevent duplicate entries on duplicate SSE-events

* docs(realtime-smoke): add manual smoke-checklist for PBI/Story/Task realtime end-to-end verification

---------

Co-authored-by: Scrum4Me Agent <30029041+madhura68@users.noreply.github.com>
2026-05-02 21:09:37 +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
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
af77553407
feat(insights): add getBacklogHealth helper — stuck tasks + missing AC/plan counts (#48)
Three read-only counters: stories without acceptance_criteria, tasks without
implementation_plan, and top-10 IN_PROGRESS tasks stuck >7 days. All scoped
via productAccessFilter.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-02 16:17:03 +02:00
219d54b3e5
feat(insights): add getVelocity helper — DONE-tasks per completed sprint (#47)
Aggregates task.status=DONE counts across last N completed sprints
(default 5), filtered by productAccessFilter and returned in
chronological order for x-axis rendering.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-02 16:07:57 +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
ddd9b8b39b
feat: getVerifyResultStats helper + 5 Vitest-tests (lib/insights/verify-stats.ts) (#38)
Aggregeert verify_result counts (ALIGNED/PARTIAL/EMPTY/DIVERGENT) en top-5 EMPTY/DIVERGENT
jobs over de laatste N dagen voor de ingelogde gebruiker.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-01 16:41:22 +02:00
fb3e55b9c0
feat: getBurndownData helper + computeBurndownDays (lib/insights/burndown.ts) (#34)
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>
2026-05-01 16:41:12 +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
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