# Typed Scheduler and qBittorrent Polling Implementation Plan

> This plan implements the approved design in `docs/superpowers/specs/2026-07-14-scheduled-actions-design.md`.

**Goal:** Move qBittorrent speed collection into a backend-owned typed scheduler while adding per-service controls, run history, stale-data status, and a manual run action.

**Scope:** qBittorrent speed polling only. The scheduler registry is extensible, but arbitrary widgets, SSH tasks, and other integrations remain out of scope.

## Delivery slices

### Slice 1 — Contracts and persistence

- [ ] Add validated qBittorrent config fields to `backend/src/media_library_viewer_api/integrations/qbittorrent.py`:
  - `polling_enabled` default `true`;
  - `poll_interval_seconds` default `15`, range `5..300`;
  - `sample_retention_seconds` default `1800`, range `60..86400`;
  - `sample_max_rows` default `1200`, range `60..1200`.
- [ ] Add migration/default normalization tests for existing qBittorrent records.
- [ ] Extend `QbittorrentSampleStore` to prune by retention timestamp and capped row count.
- [ ] Add a generic scheduler storage concern with run records, indexes, pruning, and cascade deletion by service ID.
- [ ] Add typed backend models for scheduler status, run records, samples, and manual-run responses.

**Acceptance:** Existing service records validate without edits; qBittorrent samples remain isolated by service; scheduler records cannot contain secrets; deletion removes service-owned samples and runs.

### Slice 2 — Typed scheduler core

- [ ] Create a scheduler action protocol and registry.
- [ ] Implement a single-worker, lifespan-managed scheduler coordinator with responsive stop behavior.
- [ ] Add qBittorrent speed sampling as the first registered action.
- [ ] Extract external polling from `QbittorrentWidgetSource` into a reusable sampler/action helper.
- [ ] Implement immediate startup execution with deterministic staggering.
- [ ] Implement fixed-delay, no-overlap execution and bounded exponential backoff.
- [ ] Reconcile enabled services/config changes on each cycle.
- [ ] Add safe structured logs and Prometheus metrics.
- [ ] Start/stop the scheduler in `main.py` alongside the existing mail queue and backup poller.

**Acceptance:** With no frontend open, enabled qBittorrent services append samples; one slow service cannot create overlapping runs or a backlog; shutdown joins the worker; a successful manual or scheduled run resets backoff.

### Slice 3 — Read-only APIs

- [ ] Add `backend/src/media_library_viewer_api/routers/scheduler.py`.
- [ ] Add status, paginated runs, manual-run, and read-only samples endpoints.
- [ ] Keep service configuration writes on the existing service-instance API.
- [ ] Change the qBittorrent speed widget adapter to read samples only.
- [ ] Add stale-data calculation and safe error truncation.
- [ ] Add API tests for disabled/missing services, stale data, pagination, manual runs, backoff, and authentication.

**Acceptance:** Opening or refreshing a speed widget never contacts qBittorrent and never appends a sample; API responses expose timestamps and status but no credentials.

### Slice 4 — Frontend controls and history

- [ ] Add scheduler TypeScript types, API functions, and React Query hooks.
- [ ] Add qBittorrent schedule controls to the existing schema-driven service editor.
- [ ] Add status/backoff/stale-data presentation and a `Run now` action.
- [ ] Add user-selectable chart windows.
- [ ] Add a paginated run-history table with safe error details.
- [ ] Keep UI refreshes separate from sampler cadence.
- [ ] Add frontend tests for validation, disabled state, stale warning, manual-run reset, chart-window selection, and run-history rendering.

**Acceptance:** Operators can configure, inspect, and manually trigger qBittorrent polling from the service surface without opening the dashboard; the chart remains useful during outages and identifies stale data.

### Slice 5 — Documentation and operational verification

- [ ] Update `docs/REQUIREMENTS.md` with scheduler requirements and the one-worker constraint.
- [ ] Update deployment/runbook documentation with scheduler startup, shutdown, and replica guidance.
- [ ] Add migration/recovery notes for sample and run-history retention.
- [ ] Run backend tests, frontend tests, lint, and build.
- [ ] Verify a headless collection scenario against a mocked qBittorrent service.
- [ ] Verify project-map artifacts after files are added.

## Suggested file map

### Backend

| File | Change |
| --- | --- |
| `backend/src/media_library_viewer_api/integrations/qbittorrent.py` | Schedule config schema and defaults |
| `backend/src/media_library_viewer_api/services/qbittorrent_store.py` | Duration/cap pruning and sample queries |
| `backend/src/media_library_viewer_api/services/service_data.py` | Register scheduler run concern |
| `backend/src/media_library_viewer_api/services/scheduler.py` | Worker lifecycle, reconciliation, timing, backoff |
| `backend/src/media_library_viewer_api/services/scheduler_actions.py` | Typed registry and qBittorrent action |
| `backend/src/media_library_viewer_api/services/scheduler_store.py` | Run-record persistence and pruning |
| `backend/src/media_library_viewer_api/models/scheduler.py` | Response/request models |
| `backend/src/media_library_viewer_api/routers/scheduler.py` | Status, history, samples, manual-run API |
| `backend/src/media_library_viewer_api/widgets/sources.py` | Make qBittorrent speed reads side-effect free |
| `backend/src/media_library_viewer_api/main.py` | Start/stop scheduler |
| `backend/tests/test_scheduler.py` | Scheduler lifecycle/timing/backoff tests |
| `backend/tests/test_scheduler_api.py` | Endpoint and auth tests |
| `backend/tests/test_service_data.py` | Migration/cascade coverage |
| `backend/tests/test_widgets.py` | Read-only qBittorrent widget coverage |

### Frontend

| File | Change |
| --- | --- |
| `frontend/src/types/scheduler.ts` | Scheduler status/run/sample types |
| `frontend/src/api/scheduler.ts` | Typed endpoint wrappers |
| `frontend/src/hooks/useScheduler.ts` | Queries and manual-run mutation |
| `frontend/src/pages/ServicesPage.tsx` | qBittorrent schedule controls/status surface |
| `frontend/src/pages/ServicePage.tsx` or qBittorrent service tab | Status, chart window, history surface |
| `frontend/src/widgets/QbittorrentSpeedWidget.tsx` | Read-only sample window and stale warning |
| `frontend/src/integrations/registry.ts` | Schedule metadata/config exposure if needed |
| `frontend/src/types/index.ts` | Shared exports |

## Risks and mitigations

- **Duplicate polling:** widget adapter becomes read-only; only scheduler action calls qBittorrent.
- **Multiple backend workers:** document and log the one-worker constraint; do not silently duplicate work.
- **Unbounded storage:** prune by both duration and row cap; test pruning under rapid polling.
- **Credential leakage:** reuse existing secret resolution and sanitize run errors/log fields.
- **Scheduler shutdown races:** use a stop event, per-action lock, and bounded joins; test lifespan shutdown.
- **Config changes during a run:** let the current run finish, then reconcile on the next cycle.
- **Stale but useful data:** return samples plus explicit stale status rather than blanking the chart.

## Verification commands

```bash
cd backend && PYTHONPATH=src pytest
cd frontend && npm test
cd frontend && npm run lint
cd frontend && npm run build
```

Do not begin implementation until the final module names, retry cap/jitter, and chart-window response shape are confirmed during the implementation pass.
