M13: Claude job queue — 'Voer uit'-knop + worker presence (ST-1111) (#18)
* feat(ST-1111.1): add ClaudeJob model and state-machine enum Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(ST-1111.2): add ClaudeJob status API mappers Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(ST-1111.3): add enqueue/cancel ClaudeJob server actions with idempotency + NOTIFY Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(ST-1111.4): forward ClaudeJob events on solo SSE stream + initial state Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(ST-1111.6): add 'Voer uit' + cancel buttons to task detail dialog Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(ST-1111.7): add job status pill with spinner on solo task cards Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test(ST-1111.8): cover job-status mappers and enqueue/cancel actions Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * docs(ST-1111.9): document Claude job queue architecture and agent flow Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(ST-1111.10a): add ClaudeWorker presence model Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(ST-1111.10c): forward worker presence events on solo SSE + initial count Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(ST-1111.10d): show worker presence indicator and gate 'Voer uit' on connected workers Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
1cb5772edd
commit
73087e9705
18 changed files with 921 additions and 27 deletions
|
|
@ -20,7 +20,7 @@
|
|||
import { useEffect, useRef } from 'react'
|
||||
import { flushSync } from 'react-dom'
|
||||
import { useSoloStore } from '@/stores/solo-store'
|
||||
import type { RealtimeEvent, RealtimeStatus } from '@/stores/solo-store'
|
||||
import type { ClaudeJobEvent, JobState, RealtimeEvent, RealtimeStatus } from '@/stores/solo-store'
|
||||
|
||||
const BACKOFF_START_MS = 1_000
|
||||
const BACKOFF_MAX_MS = 30_000
|
||||
|
|
@ -35,6 +35,11 @@ export function useSoloRealtime(productId: string | null) {
|
|||
useEffect(() => {
|
||||
const setStatus = useSoloStore.getState().setRealtimeStatus
|
||||
const handleEvent = useSoloStore.getState().handleRealtimeEvent
|
||||
const handleJobEvent = useSoloStore.getState().handleJobEvent
|
||||
const initJobs = useSoloStore.getState().initJobs
|
||||
const setWorkers = useSoloStore.getState().setWorkers
|
||||
const incrementWorkers = useSoloStore.getState().incrementWorkers
|
||||
const decrementWorkers = useSoloStore.getState().decrementWorkers
|
||||
|
||||
if (!productId) {
|
||||
// Geen actief product (gebruiker zit niet op /solo) — stream uit
|
||||
|
|
@ -84,10 +89,39 @@ export function useSoloRealtime(productId: string | null) {
|
|||
scheduleIndicator('open')
|
||||
})
|
||||
|
||||
source.addEventListener('claude_jobs_initial', (e) => {
|
||||
if (!e.data) return
|
||||
try {
|
||||
initJobs(JSON.parse(e.data) as JobState[])
|
||||
} catch {
|
||||
// ignore malformed payload
|
||||
}
|
||||
})
|
||||
|
||||
source.addEventListener('workers_initial', (e) => {
|
||||
if (!e.data) return
|
||||
try {
|
||||
const { count } = JSON.parse(e.data) as { count: number }
|
||||
setWorkers(count)
|
||||
} catch {
|
||||
// ignore malformed payload
|
||||
}
|
||||
})
|
||||
|
||||
source.onmessage = (e) => {
|
||||
if (!e.data) return
|
||||
try {
|
||||
const payload = JSON.parse(e.data) as RealtimeEvent
|
||||
const raw = JSON.parse(e.data) as RealtimeEvent | ClaudeJobEvent | { type: string }
|
||||
if ('type' in raw) {
|
||||
if (raw.type === 'claude_job_enqueued' || raw.type === 'claude_job_status') {
|
||||
handleJobEvent(raw as ClaudeJobEvent)
|
||||
return
|
||||
}
|
||||
if (raw.type === 'worker_connected') { incrementWorkers(); return }
|
||||
if (raw.type === 'worker_disconnected') { decrementWorkers(); return }
|
||||
return
|
||||
}
|
||||
const payload = raw as RealtimeEvent
|
||||
// Animatie A: kanban-move animeren via View Transitions API. Voor
|
||||
// task UPDATE-events wrap'en we de store-update in een view
|
||||
// transition. flushSync forceert React om synchroon te renderen
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue