Files
manage/openspec/changes/service-registry/apply-progress.md
T
Developer 10fd4ead4a feat(widgets): rebind widgets to the service registry
PR 2 of 4 for the runtime service registry change.

- dashboard_widgets gains service_id + widget_kind columns (legacy
  addon_id/widget_type kept but unused).
- Source adapters take (service: ServiceRecord | None, widget_kind, config).
  SERVICE_ADAPTERS keyed by service_type; BUILTIN_ADAPTERS for backups/static.
- Backups and static stay as service-less built-ins (service_id nullable),
  exposed via GET /api/widgets/builtin.
- SSH task adapter resolves the task + instance, runs over SSH, and appends a
  service_task_runs history row on success/failure/timeout/error.
- Retire widgets/registry.py; widget metadata now comes from the integrations
  registry + widgets/builtin. Remove /api/widgets/types and /api/widgets/sources.
- Stop default widget seeding (fresh install = empty dashboard).
- Rewrite widget tests around the service-bound + built-in model (26 tests).

Backend-only breaking change; frontend is reconciled in Slice 3. Build/lint
stay green; pytest 222 passed.
2026-06-22 16:42:56 +00:00

3.5 KiB

Apply Progress: Runtime Service Registry

Change: service-registry Apply run: PR 1 + PR 2 / Slice 1 + Slice 2 Date: 2026-06-19

Slice 1 — Backend service foundation (MERGED)

Completed in PR #7. See git history. Summary: Fernet secrets helper, closed integrations/ registry with Pydantic config + widget-config definitions for grafana/prometheus/jellyfin/nextcloud/ssh_tasks, services + service_task_runs tables with cascade delete, /api/services* CRUD, MANAGE_ENCRYPTION_KEY required at startup, 25 tests.

Slice 2 — Backend widget rebind to services (this PR)

Completed tasks

  • 2.1 Add service_id / widget_kind columns to dashboard_widgets (additive ALTER; legacy addon_id/widget_type kept but unused).
  • 2.2 Refactor source adapters to fetch(service, widget_kind, config) with ServiceRecord | None. SERVICE_ADAPTERS keyed by service_type; BUILTIN_ADAPTERS for backups/static. SSH adapter resolves the task + instance, runs, and appends a service_task_runs row (success/failure/ timeout/error).
  • 2.3 Retire old widgets/registry.py (deleted; metadata now comes from integrations/registry + widgets/builtin).
  • 2.4 Update widgets router + models for service-bound + built-in widgets. Removed /api/widgets/types and /api/widgets/sources; added /api/widgets/builtin.
  • 2.5 Rewrite widget tests around the new model.
  • 2.6 Stop default widget seeding (fresh install = empty dashboard).

Decision resolved mid-slice

Backups and static widgets stay as service-less built-ins (service_id nullable), per product decision. The data endpoint resolves built-ins via BUILTIN_ADAPTERS and service-bound widgets via SERVICE_ADAPTERS + a decrypted ServiceRecord.

Files changed (Slice 2)

  • New: widgets/builtin.py (built-in kinds + light config validation).
  • Rewritten: widgets/sources.py (ServiceRecord, new protocol, service + built-in adapters, SSH run logging, _build_ssh_client).
  • Deleted: widgets/registry.py.
  • Modified: models/widgets.py (service_id + widget_kind; BuiltinWidgetKindInfo).
  • Modified: routers/widgets.py (new validation, /builtin, data resolution).
  • Modified: services/settings_store.py (widget columns; no-op seeding).
  • Modified: integrations/base.py (WidgetKind.config_model for Pydantic widget-config validation).
  • Rewritten: tests/test_widgets.py (26 tests).

Verification (Slice 2)

cd backend
.venv/bin/ruff check .                                      # All checks passed
PYTHONPATH=src .venv/bin/python -m pytest                   # 222 passed
cd ../frontend
npm run lint                                                # 0 errors
npm run build                                               # success

Known transient state (resolved by Slice 3)

Slice 2 is a backend-only breaking change to the widget API. Until Slice 3 lands, the frontend still calls the removed /api/widgets/types and /api/widgets/sources endpoints and uses the old widget_type shape, so the dashboard widget config UI is non-functional at runtime. Build/lint stay green. This is the accepted transient state for a stacked backend→frontend rebind.

Remaining work

  • Slice 3: Frontend services runtime (types, API, hooks, frontend service registry, service pages, route swap, remove addon pages, reconcile widget UI).
  • Slice 4: Dashboard picker on services, settings rework, remove grafana_url/prometheus_url env vars, docs + changelog.