diff --git a/package.json b/package.json index 723e48d..4efc5b3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "netfelix-audio-fix", - "version": "2026.04.14.22", + "version": "2026.04.14.23", "scripts": { "dev:server": "NODE_ENV=development bun --hot server/index.tsx", "dev:client": "vite", diff --git a/server/api/execute.ts b/server/api/execute.ts index 843221a..cdcb8c9 100644 --- a/server/api/execute.ts +++ b/server/api/execute.ts @@ -88,6 +88,10 @@ async function handOffToJellyfin(itemId: number): Promise { const result = await upsertJellyfinItem(db, fresh, rescanCfg, { source: "webhook" }); log(`Post-job verify for item ${itemId}: is_noop=${result.isNoop}`); + // Nudge connected clients so the Done column re-polls and promotes + // the card from ✓ to ✓✓ (or flips it back to Review if jellyfin + // disagreed). + emitPlanUpdate(itemId); } catch (err) { warn(`Post-job verification for item ${itemId} failed: ${String(err)}`); } @@ -169,6 +173,18 @@ function emitJobProgress(jobId: number, seconds: number, total: number): void { for (const l of jobListeners) l(line); } +/** + * Emit when a review_plan mutates asynchronously (after the job already + * finished). Right now the only producer is handOffToJellyfin — the + * verified=1 write that lands ~15s after a job completes. Without this + * the UI would keep showing ✓ indefinitely until the user navigates + * away and back, since we'd never fire job_update again for that item. + */ +function emitPlanUpdate(itemId: number): void { + const line = `event: plan_update\ndata: ${JSON.stringify({ itemId })}\n\n`; + for (const l of jobListeners) l(line); +} + /** Parse "Duration: HH:MM:SS.MS" from ffmpeg startup output. */ function parseFFmpegDuration(line: string): number | null { const match = line.match(/Duration:\s*(\d+):(\d+):(\d+)\.(\d+)/); diff --git a/src/features/pipeline/PipelinePage.tsx b/src/features/pipeline/PipelinePage.tsx index 243a3c3..281ae75 100644 --- a/src/features/pipeline/PipelinePage.tsx +++ b/src/features/pipeline/PipelinePage.tsx @@ -64,6 +64,12 @@ export function PipelinePage() { } scheduleReload(); }); + // plan_update lands ~15s after a job finishes — the post-job jellyfin + // verification writes verified=1 (or flips the plan back to pending). + // Without refreshing here the Done column would never promote ✓ to ✓✓. + es.addEventListener("plan_update", () => { + scheduleReload(); + }); es.addEventListener("job_progress", (e) => { setProgress(JSON.parse((e as MessageEvent).data)); });