# Tasks: Unify Saved Tasks on SSH Services

**Change:** `unify-tasks-on-services`
**Phase:** tasks
**Date:** 2026-06-19

## Review workload forecast

| Field | Value |
|-------|-------|
| Estimated changed lines | ~600–800 |
| Chained PRs recommended | Yes (2 PRs) |
| Chain strategy | stacked-to-main |

## Slice 1: Backend — shared runner + service-based tasks

**Goal:** One execution path; tasks target ssh_tasks services; one history table.

- [x] **1.1 Add shared `run_saved_task` helper**
  - Files: `backend/src/media_library_viewer_api/services/task_runner.py` (new)
  - Lines: ~90
  - Details: `run_saved_task(store, task, service, *, request_id)` builds the SSH
    client from the service record (promote `_build_ssh_client`), renders the
    command, runs with the service timeout, appends a `service_task_runs` row,
    returns a `TaskRunResult`.
- [x] **1.2 Rename saved_tasks column**
  - Files: `services/settings_store.py` (modify)
  - Lines: ~20
  - Details: `default_machine_id` → `default_service_id` (ALTER TABLE RENAME
    COLUMN on startup; update `_row_to_task`, `_normalize_task_payload`,
    `upsert_task`).
- [x] **1.3 Drop saved_task_runs**
  - Files: `services/settings_store.py` (modify)
  - Lines: ~-60
  - Details: `DROP TABLE IF EXISTS saved_task_runs`; remove `record_task_run`
    and `list_task_runs` (task flavor).
- [x] **1.4 Rewire tasks router**
  - Files: `routers/tasks.py` (modify)
  - Lines: ~70
  - Details: `TaskInput.default_service_id`; `run_task` takes `service_id`
    (override), resolves an ssh_tasks service, calls `run_saved_task`;
    `/api/tasks/{id}/runs` reads `service_task_runs`. Remove
    `_resolve_machine_for_task` and `_client_for_machine`.
- [x] **1.5 Widget delegates to shared helper**
  - Files: `widgets/sources.py` (modify)
  - Lines: ~-40
  - Details: `SshTaskWidgetSource.fetch` calls `run_saved_task` instead of its
    inline run+log block.
- [x] **1.6 Add `list_service_task_runs` by task (if not present)**
  - Files: `services/settings_store.py` (modify)
  - Lines: ~10
  - Details: Confirm `list_service_task_runs(task_id=...)` covers the tasks
    router needs.
- [x] **1.7 Update backend tests**
  - Files: `backend/tests/test_jobs.py`, `test_api.py` (modify)
  - Lines: ~60
  - Details: Update task-run tests to the service model; cover override +
    default + disabled-service paths.
- [x] **1.8 Verify**
  - Run: `cd backend && .venv/bin/ruff check . && PYTHONPATH=src .venv/bin/python -m pytest`

**Slice 1 total:** ~250 changed lines.

## Slice 2: Frontend — Actions page on services

**Goal:** Actions page targets ssh_tasks services; reads service_task_runs.

- [x] **2.1 Update types**
  - Files: `frontend/src/types/index.ts` (modify)
  - Lines: ~15
  - Details: `SavedTask` / `SavedTaskInput` `default_service_id`;
    `SavedTaskRun` aligned to `service_task_runs`.
- [x] **2.2 Update API client**
  - Files: `frontend/src/api/client.ts` (modify)
  - Lines: ~10
  - Details: `runTask(taskId, serviceId?)` sends `service_id`.
- [x] **2.3 Rewire Actions page**
  - Files: `frontend/src/pages/Actions.tsx` (modify)
  - Lines: ~120
  - Details: Task editor "Default service" select lists ssh_tasks services via
    `useServiceInstances("ssh_tasks")`; run dialog "Run on" selects an instance;
    run history reads `service_task_runs`. Remove `useMonitoringSettings`.
- [x] **2.4 Update Actions tests**
  - Files: `frontend/src/pages/__tests__/Actions.test.tsx` (modify)
  - Lines: ~30
  - Details: Mock `useServiceInstances`; update fixtures.
- [x] **2.5 Docs + changelog**
  - Files: `docs/REQUIREMENTS.md`, `CHANGELOG.md` (modify)
  - Lines: ~30
  - Details: Saved-actions section: tasks target ssh_tasks services; local mode
    dropped; breaking-upgrade note.
- [x] **2.6 Verify**
  - Run: `cd frontend && npm run lint && npm run build && npm run test`

**Slice 2 total:** ~200 changed lines.

## Integration and acceptance

- [x] **3.1 Backend full test run** — `PYTHONPATH=src pytest`, all green.
- [x] **3.2 Frontend full build/lint/test**.
- [ ] **3.3 Manual dev-stack check**:
  - Create an ssh_tasks service; create a task with that default; run from
    Actions; see the run in both the Actions history and the service page.
  - Override the target at run time.
  - SSH task widget uses the same history.
