From 708f8bcd6f2fd786694af7be82ea3a31e8b5e5a7 Mon Sep 17 00:00:00 2001 From: root Date: Mon, 13 Apr 2026 21:25:52 +0500 Subject: [PATCH] fix: ai-jobs page empty list + missing Review button + no results in modal MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three bugs: 1. +page.server.ts returned an unawaited Promise — SvelteKit awaits it on the server anyway so data.jobs arrived as a plain AIJob[] on the client, not a Promise. The $effect calling .then() on an array silently failed, leaving jobs=[] and the table empty. Fixed by awaiting in the load fn. 2. +page.svelte $effect updated to assign data.jobs directly (plain array) instead of calling .then() on it. 3. handlers_textgen.go: final UpdateAIJob (payload+status write) used r.Context() which is cancelled when the SSE client disconnects. If the browser navigated away mid-job, results were silently dropped and the payload stayed as the initial {pattern} stub with no results array. Fixed by using context.Background() for the final write, matching the pattern already used in handlers_image.go. --- backend/internal/backend/handlers_textgen.go | 4 +++- ui/src/routes/admin/ai-jobs/+page.server.ts | 3 +-- ui/src/routes/admin/ai-jobs/+page.svelte | 4 ++-- 3 files changed, 6 insertions(+), 5 deletions(-) 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 ─────────────────────────────────────