fix(header): show sync activity only while a job is actually running

The header's sync indicator spun "fetching history" whenever any channel
still lacked full history — i.e. perpetually, even when the scheduler was
idle between its periodic runs. It now spins only while a channel-sync job
(backfill or RSS poll) is actually running; otherwise pending deep-history
work shows as the calm, static "N without full history" link, and recent
work queued for the next run shows a static "N queued". This makes the
header coherent with the Scheduler dashboard's live state.

Backend exposes running_job_ids() from the scheduler activity and a derived
sync_active flag on /api/sync/my-status.
This commit is contained in:
npeter83 2026-06-19 02:57:45 +02:00
parent 7fd335aa90
commit cbe3e7c9b5
7 changed files with 48 additions and 18 deletions

View file

@ -13,6 +13,7 @@ from app.sync.runner import (
run_rss_poll,
)
from app.sync.subscriptions import import_subscriptions
from app.scheduler import running_job_ids
router = APIRouter(prefix="/api/sync", tags=["sync"])
@ -170,6 +171,9 @@ def my_status(
"quota_used_today": quota.units_used_today(db),
"quota_remaining_today": quota.remaining_today(db),
"paused": state.is_sync_paused(db),
# True only while a job that advances this view (channel sync) is actually running,
# so the header shows live activity rather than a perpetual spinner over pending work.
"sync_active": bool({"backfill", "rss_poll"} & running_job_ids()),
}

View file

@ -272,6 +272,13 @@ def _is_running(job_id: str) -> bool:
return bool(_activity.get(job_id, {}).get("running"))
def running_job_ids() -> set[str]:
"""Ids of jobs executing right now (any trigger). Lets non-admin surfaces — e.g. the
header's sync indicator — reflect real activity instead of just pending-work counts."""
with _activity_lock:
return {jid for jid, a in _activity.items() if a.get("running")}
def trigger_job(job_id: str, actor_id: int | None = None) -> str:
"""Run a job immediately in a background thread, independent of its interval schedule.
The wrapper handles its own DB session, activity tracking and pause-skip, so the live