diff --git a/backend/internal/backend/handlers_textgen.go b/backend/internal/backend/handlers_textgen.go index f81c063..6cde3a3 100644 --- a/backend/internal/backend/handlers_textgen.go +++ b/backend/internal/backend/handlers_textgen.go @@ -313,6 +313,8 @@ func (s *Server) handleAdminTextGenChapterNames(w http.ResponseWriter, r *http.R } // Mark job as done in PB, persisting results so the Review button works. + // Use context.Background() — r.Context() may be cancelled if the SSE client + // disconnected before processing finished, which would silently drop results. if jobID != "" && s.deps.AIJobStore != nil { status := domain.TaskStatusDone if jobCtx.Err() != nil { @@ -321,7 +323,7 @@ func (s *Server) handleAdminTextGenChapterNames(w http.ResponseWriter, r *http.R resultsJSON, _ := json.Marshal(allResults) finalPayload := fmt.Sprintf(`{"pattern":%q,"slug":%q,"results":%s}`, req.Pattern, req.Slug, string(resultsJSON)) - _ = s.deps.AIJobStore.UpdateAIJob(r.Context(), jobID, map[string]any{ + _ = s.deps.AIJobStore.UpdateAIJob(context.Background(), jobID, map[string]any{ "status": string(status), "items_done": chaptersDone, "finished": time.Now().Format(time.RFC3339), diff --git a/ui/src/routes/admin/ai-jobs/+page.server.ts b/ui/src/routes/admin/ai-jobs/+page.server.ts index f0db9d1..c37bff7 100644 --- a/ui/src/routes/admin/ai-jobs/+page.server.ts +++ b/ui/src/routes/admin/ai-jobs/+page.server.ts @@ -6,8 +6,7 @@ export type { AIJob }; export const load: PageServerLoad = async () => { // Parent layout already guards admin role. - // Stream jobs so navigation is instant; list populates a moment later. - const jobs = listAIJobs().catch((e): AIJob[] => { + const jobs = await listAIJobs().catch((e): AIJob[] => { log.warn('admin/ai-jobs', 'failed to load ai jobs', { err: String(e) }); return []; }); diff --git a/ui/src/routes/admin/ai-jobs/+page.svelte b/ui/src/routes/admin/ai-jobs/+page.svelte index ab95460..a8c8b6a 100644 --- a/ui/src/routes/admin/ai-jobs/+page.svelte +++ b/ui/src/routes/admin/ai-jobs/+page.svelte @@ -9,9 +9,9 @@ let jobs = $state([]); - // Resolve streamed promise on load and on server reloads (invalidateAll) + // data.jobs is a plain AIJob[] (resolved on server); re-sync on invalidateAll $effect(() => { - data.jobs.then((resolved) => { jobs = resolved; }); + jobs = data.jobs; }); // ── Live-poll while any job is in-flight ─────────────────────────────────────