Files
manage/openspec/changes/service-registry/apply-progress.md
T
Developer 1da67f38c7 feat(services): frontend services runtime and widget rebind
PR 3 of 4 for the runtime service registry change.

- Add service + new-shape widget TypeScript types; widgets carry service_id
  + widget_kind (service-bound) or null (built-in).
- Add services API client + TanStack Query hooks; reconcile the widget API
  client/hooks to the new endpoints (remove sources/types; add builtin kinds).
- Add closed frontend service registry (integrations/registry.ts) mirroring the
  backend, with resolveWidget(widget, services) mapping a widget to its
  component + refresh interval.
- Add ServicePage at /services/:serviceType/:serviceId with config view,
  empty-on-edit secret inputs + 'set' badges, enable toggle, delete, and the
  service's widget-kind list.
- Register /services/:serviceType/:serviceId in App.tsx.
- Reconcile the six widget components to refreshIntervalMs + description props;
  rewrite WidgetConfigDialog around a service -> widget-kind picker.
- Update Dashboard test; add integrations/registry.test.ts.

Verification: frontend lint 0 errors, build success, 70 tests passed; backend
ruff clean, 222 tests passed.
2026-06-22 18:59:41 +00:00

3.9 KiB
Raw Blame History

Apply Progress: Runtime Service Registry

Change: service-registry Apply run: PR 1 + PR 2 + PR 3 (Slices 13) Date: 2026-06-19

Slice 1 — Backend service foundation (MERGED, PR #7)

Fernet secrets, closed integrations/ registry (Pydantic config + widget-config for grafana/prometheus/jellyfin/nextcloud/ssh_tasks), services + service_task_runs tables with cascade delete, /api/services* CRUD, MANAGE_ENCRYPTION_KEY required at startup.

Slice 2 — Backend widget rebind (MERGED, PR #8)

Widgets carry service_id + widget_kind; adapters take fetch(service: ServiceRecord | None, widget_kind, config); backups + static stay as service-less built-ins; SSH adapter logs to service_task_runs; old widgets/registry.py retired; default seeding removed.

Slice 3 — Frontend services runtime (this PR)

Completed tasks

  • 3.1 Service + new widget TypeScript types (ServiceInstance, ServiceInstanceInput, ServiceTypeInfo, ServiceWidgetKindInfo, SecretFieldInfo, BuiltinWidgetKindInfo; widget gains service_id + widget_kind).
  • 3.2 Services API + hooks (api/services.ts, hooks/useServices.ts). Reconciled api/widgets.ts + hooks/useWidgets.ts to the new shape (removed sources/types; added builtin kinds).
  • 3.3 Closed frontend service registry (integrations/registry.ts) mirroring the backend; resolveWidget(widget, services) maps a widget to its component + refresh interval.
  • 3.4 Service page at /services/:serviceType/:serviceId with config view, empty-on-edit secret inputs + "set" badges, enable toggle, delete, and the service's widget-kind list.
  • 3.5 Route swap: added /services/:serviceType/:serviceId; addon route retained for now (removed in Slice 4 cleanup).
  • 3.6 Reconciled widget components to take refreshIntervalMs + description props; rewrote WidgetConfigDialog around the service → widget-kind picker (pulled 4.1 forward to keep the build whole).
  • 3.7 Registry + Dashboard tests updated; new integrations/registry.test.ts.

Decision resolved mid-slice

Secret edit UX = empty-on-edit + "set" badge (blank = keep existing; typing = replace). Applied on the ServicePage secrets card.

Files changed (Slice 3)

  • New: api/services.ts, hooks/useServices.ts, integrations/registry.ts, integrations/registry.test.ts, pages/ServicePage.tsx.
  • Modified: types/index.ts, api/widgets.ts, hooks/useWidgets.ts, components/WidgetInstance.tsx, components/WidgetConfigDialog.tsx, pages/Dashboard.tsx, pages/__tests__/Dashboard.test.tsx, App.tsx, all six widgets/*.tsx components, widgets/index.ts.
  • Deleted: widgets/registry.ts, widgets/registry.test.ts.

Verification (Slice 3)

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

Deviations / notes

  • WidgetConfigDialog was rewritten in this slice (pulled forward from task 4.1) because the old dialog imported the deleted widget registry and would not compile. The SSH task-output widget keeps a dedicated task picker; other widget configs use a generic schema-driven field editor.
  • Addon pages (/addons/:addonId) are kept compiling but superseded by service pages; Slice 4 removes them and the now-unused machine Jellyfin/Jellyseerr fields + grafana_url/prometheus_url env vars, and writes the changelog.

Remaining work

  • Slice 4: remove addon pages + machine app fields, remove grafana_url/prometheus_url from config + compose, docs + changelog (breaking upgrade note).