fix(PBI-59): map jobs_initial SSE payload by job_id, not id (#155)

De server-route stuurt JobPayload[] (met `job_id`), maar de client deed
`initJobs(jobs, ...)` waardoor alle entries in activeJobs `id: undefined`
kregen — wat React-key warnings opleverde:

  Each child in a list should have a unique "key" prop.

Fix: SSE jobs_initial niet meer als overwrite gebruiken; SSR-fetch heeft
de volledige JobWithRelations al in de store gezet. We reconcileren nu
per job met upsertJob (status/branch/error/summary updaten van bekende
jobs, onbekende jobs als partials toevoegen — zelfde gedrag als gewone
'message' SSE events).

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Janpeter Visser 2026-05-07 20:22:07 +02:00 committed by GitHub
parent 00dbbb4f94
commit 883534a521
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -18,7 +18,6 @@ interface JobStatusPayload {
} }
export default function useJobsRealtime() { export default function useJobsRealtime() {
const initJobs = useJobsStore(s => s.initJobs)
const upsertJob = useJobsStore(s => s.upsertJob) const upsertJob = useJobsStore(s => s.upsertJob)
useEffect(() => { useEffect(() => {
@ -32,10 +31,23 @@ export default function useJobsRealtime() {
es = new EventSource('/api/realtime/jobs') es = new EventSource('/api/realtime/jobs')
es.addEventListener('jobs_initial', (event) => { es.addEventListener('jobs_initial', (event) => {
// De server stuurt JobPayload[] (met `job_id`), niet JobWithRelations[].
// Daarom geen initJobs-overwrite — de SSR-fetch heeft de volledige
// shape al in de store geplaatst. We reconcileren alleen status/branch
// van bekende jobs en pushen onbekende jobs (nieuw aangemaakt tussen
// SSR en SSE-connect) als partials.
try { try {
const jobs = JSON.parse(event.data) const payload = JSON.parse(event.data)
if (Array.isArray(jobs)) { if (!Array.isArray(payload)) return
initJobs(jobs, useJobsStore.getState().doneJobs) for (const p of payload as JobStatusPayload[]) {
if (!p.job_id) continue
upsertJob({
id: p.job_id,
status: p.status as ClaudeJobStatus,
branch: p.branch ?? null,
error: p.error ?? null,
summary: p.summary ?? null,
})
} }
} catch { } catch {
// malformed JSON // malformed JSON
@ -75,5 +87,5 @@ export default function useJobsRealtime() {
if (reconnectTimer) clearTimeout(reconnectTimer) if (reconnectTimer) clearTimeout(reconnectTimer)
es?.close() es?.close()
} }
}, [initJobs, upsertJob]) }, [upsertJob])
} }