# Apply Progress: Runtime Service Registry

**Change:** `service-registry`
**Apply run:** PRs #7–#10 (Slices 1–4a)
**Date:** 2026-06-19

## Slices 1–3 (MERGED)

- Slice 1 (#7): backend service foundation — encryption, integrations registry,
  services + service_task_runs tables, `/api/services*` CRUD.
- Slice 2 (#8): backend widget rebind — service_id + widget_kind, ServiceRecord
  adapters, built-ins, SSH run logging, retired old widget registry.
- Slice 3 (#9): frontend services runtime — types/API/hooks, frontend registry,
  ServicePage, route swap, reconciled widget components + config dialog.

## Slice 4a — Cleanup + services admin UI + docs (this PR)

### Completed tasks

- [x] Removed addon pages (`/addons/:addonId`, `AddonPage.tsx`, `addons/*`) —
      superseded by service pages.
- [x] Removed `grafana_url` / `prometheus_url` from `config.py`, both compose
      files, `.env.example`, and README. (Frontend `VITE_GRAFANA_URL` /
      `VITE_PROMETHEUS_URL` deep-link vars retained.)
- [x] Added a **Services page** (`/services`) with create/list/delete and a nav
      entry, so service pages are reachable and services are configurable in the
      tool itself.
- [x] Registered `/services` route in both route trees + sidebar nav.
- [x] Updated `docs/REQUIREMENTS.md` (service registry section) and added
      `CHANGELOG.md` with the breaking-upgrade note.

### Decision resolved mid-slice

"Full machine migration" was scoped into **4a (cleanup) + 4b (Jellyfin/Jellyseerr
migration)** because removing machine-level Jellyfin/Jellyseerr fields is deeply
coupled to the Media/Users/Files pages (load-bearing) and there is no
`jellyseerr` service definition yet. 4a ships the safe cleanup + the services
admin UI; 4b does the machine-app-field migration as its own reviewable change.

### Files changed (Slice 4a)

- Backend: `config.py` (removed grafana_url/prometheus_url).
- Compose/env/docs: `docker-compose.yml`, `docker-compose.dev.yml`,
  `.env.example`, `README.md`, `docs/REQUIREMENTS.md`, `CHANGELOG.md` (new).
- Frontend: new `pages/ServicesPage.tsx`; `App.tsx` (routes + nav); removed
  `pages/AddonPage.tsx`, `addons/*`.

### Verification (Slice 4a)

```bash
cd backend
.venv/bin/ruff check .                                      # clean
PYTHONPATH=src .venv/bin/python -m pytest                   # 222 passed
cd ../frontend
npm run lint                                                # 0 errors
npm run build                                               # success
npm run test                                                # 70 passed
```

## Slice 4b — Jellyfin/Jellyseerr → services migration (in progress)

### Completed (backend, this PR)

- [x] Added `jellyseerr` service definition (`integrations/jellyseerr.py`) and
      registered it (6 service types total).
- [x] Added `user_id` to the Jellyfin service config.
- [x] `dependencies.py`: new `_request_jellyfin_service_id` + `_service_record`
      (decrypt-on-read). Rewrote `get_jellyfin_client`, `get_jellyseerr_client`,
      and `get_user_id` to resolve against the service registry via the
      `jellyfin_service_id` query param (first enabled instance as fallback).
- [x] SSH/Files transport (`get_ssh_client`) unchanged — still uses
      `machine_id`.
- [x] Updated service-registry tests for 6 types.

### Selection model (decided)

Split query params: `?jellyfin_service_id=` selects the Jellyfin/Jellyseerr
instance; `?machine_id=` selects SSH/Files transport. Pages that need both pass
both.

### Remaining (frontend, next PR)

- Thread `jellyfinServiceId` through Media / Applications / Dashboard / Users:
  list `jellyfin` service instances instead of `useMonitoringSettings()`
  Jellyfin machines; pass `jellyfin_service_id` to Jellyfin API calls.
- Files page keeps `machine_id`.
- Settings UI: remove machine-level Jellyfin/Jellyseerr fields.
- Remove machine app fields from `settings_store.py` + `routers/settings.py`
  once the UI no longer writes them.

### Frontend half (this PR)

- [x] `api/client.ts`: Jellyfin-backed calls (`fetchCounts`, `fetchLibraries`,
      `fetchActivity`, `fetchUsers`, Media status/build/stop/force-stop, and
      `queryMedia`) now send `jellyfin_service_id` instead of `machine_id`.
- [x] `hooks/useDashboard.ts`, `hooks/useUsers.ts`, `hooks/useMedia.ts`: renamed
      the selector param to `jellyfinServiceId`.
- [x] `pages/Media.tsx` + `pages/Applications.tsx`: select a `jellyfin` service
      instance via `useServiceInstances("jellyfin")` and persist
      `jellyfin_service_id` in the URL.
- [x] Dashboard (widget-based) and Users (default-instance) need no selector
      change.
- [x] Updated Applications + Media tests for the new hook/param.

### Deferred (explicit follow-up)

- Remove machine-level Jellyfin/Jellyseerr fields from `settings_store.py`,
  `routers/settings.py`, and the Settings UI. Low urgency now that the runtime
  reads from services; the machine fields are simply unused for Jellyfin.

### Verification (backend half)

```bash
cd backend
.venv/bin/ruff check .                                      # clean
PYTHONPATH=src .venv/bin/python -m pytest                   # 222 passed
cd ../frontend
npm run lint && npm run build && npm run test               # green (unchanged)
```
