78 lines
3.1 KiB
Markdown
78 lines
3.1 KiB
Markdown
---
|
|
title: "ST-1111 — Voer uit-knop met Claude Code job queue"
|
|
status: active
|
|
audience: [maintainer, contributor]
|
|
language: nl
|
|
last_updated: 2026-05-03
|
|
applies_to: [ST-1111]
|
|
---
|
|
|
|
# ST-1111 — 'Voer uit'-knop met Claude Code job queue
|
|
|
|
**Story:** Als developer wil ik op het solo-scherm per task een 'Voer uit'-knop, zodat ik mijn lokale Claude Code-sessie kan inschakelen om de taak uit te voeren.
|
|
|
|
**Branch:** `feat/M13-claude-job-queue`
|
|
|
|
---
|
|
|
|
## Sub-tasks en commits
|
|
|
|
| Task | Commit |
|
|
|---|---|
|
|
| ST-1111.1 DB: ClaudeJob model + enum + migration | `5274e1e` |
|
|
| ST-1111.2 API: ClaudeJob status mappers | `a1b1f69` |
|
|
| ST-1111.3 Server actions: enqueue + cancel | `9d9fb4b` |
|
|
| ST-1111.4 SSE: ClaudeJob events op solo-stream + initial state | `ece0aa9` |
|
|
| ST-1111.5 MCP-tools (mcp repo — aparte PR) | — |
|
|
| ST-1111.6 UI: 'Voer uit' + cancel in TaskDetailDialog | `b9c65eb` |
|
|
| ST-1111.7 UI: status-pill op SoloTaskCard | `dace427` |
|
|
| ST-1111.8 Tests: mappers + actions | `2c2a246` |
|
|
| ST-1111.9 Docs | dit bestand |
|
|
|
|
---
|
|
|
|
## Architectuur
|
|
|
|
### State machine
|
|
|
|
```
|
|
QUEUED → CLAIMED → RUNNING → DONE
|
|
→ FAILED
|
|
→ CANCELLED (cancel-knop of server action)
|
|
CLAIMED → QUEUED (stale cleanup, >30min, via wait_for_job)
|
|
```
|
|
|
|
### NOTIFY-pijplijn
|
|
|
|
Omdat `claude_jobs` geen row-trigger heeft (zoals `tasks` en `stories`), stuurt de **server action** zelf `pg_notify` via `prisma.$executeRaw`:
|
|
|
|
```ts
|
|
await prisma.$executeRaw`SELECT pg_notify('scrum4me_changes', ${JSON.stringify(payload)}::text)`
|
|
```
|
|
|
|
Voordeel: expliciete controle over het payload-shape (met `type` i.p.v. `entity`). Nadeel: MCP-tools in de `mcp`-repo moeten hun eigen NOTIFY-aanroep hebben bij `update_job_status`.
|
|
|
|
### SSE-routing
|
|
|
|
De bestaande `/api/realtime/solo`-route herkent nu twee payload-shapes:
|
|
- `entity: 'task'|'story'` — bestaande trigger-events
|
|
- `type: 'claude_job_enqueued'|'claude_job_status'` — nieuwe job-events
|
|
|
|
Job-events worden gefilterd op `user_id + product_id`. Bij connect stuurt de route een `claude_jobs_initial`-event met alle actieve + recente (vandaag) jobs.
|
|
|
|
### Idempotency
|
|
|
|
`enqueueClaudeJobAction` weigert als `claude_jobs WHERE task_id=X AND status IN (QUEUED, CLAIMED, RUNNING)` bestaat. De client ontvangt `{ error, jobId }` zodat de UI naar de actieve job kan linken in plaats van een nieuw venstertje te openen.
|
|
|
|
---
|
|
|
|
## Beslissingen
|
|
|
|
**Waarom geen DB-trigger voor NOTIFY?**
|
|
De MCP-server claimt jobs via raw SQL (FOR UPDATE SKIP LOCKED); die schrijft ook direct naar de DB. Een trigger zou clean zijn, maar de MCP-tools moeten hoe dan ook hun eigen NOTIFY-payload bouwen voor `update_job_status`. Applicatie-NOTIFY houdt de payloads consistent en expliciet.
|
|
|
|
**Waarom `cancelled` verwijderd uit de store?**
|
|
Geannuleerde jobs zijn terminaal; het pill-element zou "Geannuleerd" tonen tot de gebruiker een refresh doet. In plaats daarvan wist `handleJobEvent` de entry bij `status === 'cancelled'` zodat de kaart teruggaat naar de "Voer uit"-staat.
|
|
|
|
**Auto-clear DONE/FAILED?**
|
|
Niet geïmplementeerd in v1. De pill blijft staan totdat de SSE-connectie herstart (refresh, tab-hidden+visible). Acceptabel voor de eerste iteratie.
|