diff --git a/backend/main.py b/backend/main.py
index c2e6967..c0c612d 100644
--- a/backend/main.py
+++ b/backend/main.py
@@ -552,7 +552,8 @@ async def list_jobs(
cur.execute(
f"""
SELECT id, author, book, chapter, page, submitted_at, status,
- reviewer_name, reviewed_at, mode, ocr_model, original_filename
+ reviewer_name, reviewed_at, mode, ocr_model, original_filename,
+ qdrant_synced_at, updated_at
FROM ocr_jobs {where}
ORDER BY submitted_at DESC
LIMIT %s OFFSET %s
diff --git a/frontend/src/components/JobsPanel.jsx b/frontend/src/components/JobsPanel.jsx
index b1720d7..798d5e8 100644
--- a/frontend/src/components/JobsPanel.jsx
+++ b/frontend/src/components/JobsPanel.jsx
@@ -4,7 +4,7 @@ import { useModels } from '../hooks/useModels'
import { motion, AnimatePresence } from 'framer-motion'
import {
Search, ChevronLeft, ChevronRight, CheckCircle2, Clock,
- FileText, Loader2, Save, RefreshCw, Trash2, Sparkles,
+ FileText, Loader2, Save, RefreshCw, Trash2, Sparkles, Cloud, CloudOff,
} from 'lucide-react'
import axios from 'axios'
@@ -29,6 +29,38 @@ function StatusBadge({ status }) {
)
}
+// A job is synced "in its most recent state" when it was pushed to Qdrant at or
+// after its last update. `updated_at` is bumped by a DB trigger on every change,
+// so any edit after a sync makes the job stale until it's re-synced.
+function isSynced(job) {
+ if (!job.qdrant_synced_at) return false
+ if (!job.updated_at) return true
+ return new Date(job.qdrant_synced_at).getTime() >= new Date(job.updated_at).getTime()
+}
+
+function SyncBadge({ job }) {
+ const synced = isSynced(job)
+ const Icon = synced ? Cloud : CloudOff
+ const title = synced
+ ? 'Synced with Qdrant'
+ : job.qdrant_synced_at
+ ? 'Changed since last Qdrant sync — needs re-sync'
+ : 'Never synced with Qdrant'
+ return (
+
+
+ {synced ? 'Synced' : 'Not synced'}
+
+ )
+}
+
// ─────────────────────────────────────────────────────────────
// Full-screen Job Detail
// ─────────────────────────────────────────────────────────────
@@ -210,6 +242,7 @@ function JobDetail({ jobId, onClose, onReviewed, onDeleted, suggestions = {} })
{job && (
<>
+
-
+
+
{job.book &&
{job.book}
}