Files
manage/openspec/changes/archive/configurable-dashboard-widgets/apply-progress.md
Developer ca8927834e chore(openspec): archive completed changes
Move finished change directories to openspec/changes/archive/:
- configurable-dashboard-widgets
- decommission-monitoring-poller
- service-registry
- unify-tasks-on-services

All associated implementation has been merged to main.
2026-06-23 19:38:34 +00:00

9.9 KiB

Apply Progress: Configurable Dashboard Widgets

Change: configurable-dashboard-widgets
Apply run: PR 1 / Slice 1 — Backend CRUD and default seeding
Date: 2026-06-19

Completed tasks (Slice 1)

All Slice 1 tasks are marked - [x] in tasks.md:

  • 1.1 Create widget Pydantic models
  • 1.2 Create backend widget registry
  • 1.3 Implement widgets router (CRUD + metadata)
  • 1.4 Extend SettingsStore for dashboard_widgets
  • 1.5 Register widgets router in main.py
  • 1.6 Add backend tests for registry, CRUD, and seeding
  • 1.7 Verify backend slice

Files changed

New files

  • backend/src/media_library_viewer_api/models/widgets.py — Pydantic models: WidgetInstance, WidgetInstanceInput, WidgetTypeInfo, WidgetDataResponse, plus credential-key/secret-value validators.
  • backend/src/media_library_viewer_api/widgets/__init__.py — Package marker.
  • backend/src/media_library_viewer_api/widgets/registry.py — Closed WIDGET_REGISTRY for six Phase 1 widget types, source-type listing, type metadata, and lightweight config-schema validation.
  • backend/src/media_library_viewer_api/routers/widgets.py — REST endpoints for /api/widgets/sources, /types, /instances, and instance CRUD.
  • backend/tests/test_widgets.py — 12 tests covering registry, CRUD, validation, and seeding.

Modified files

  • backend/src/media_library_viewer_api/services/settings_store.py — Added dashboard_widgets table, index, CRUD helpers, default seeding, and refactored ensure_defaults() to seed widgets independently of machine seeding.
  • backend/src/media_library_viewer_api/main.py — Registered widgets_router.

Verification

Commands run:

cd backend
.venv/bin/python -m ruff check .              # All checks passed
PYTHONPATH=src .venv/bin/python -m pytest     # 185 passed, 2 warnings
cd ../frontend
npm run lint                                  # 2 pre-existing warnings, 0 errors
npm run build                                 # Built successfully

Focused widget test output: 12 passed.

Deviations from design

  • None significant for Slice 1. The implementation follows the design's backend CRUD layout.
  • Used HTTP_422_UNPROCESSABLE_CONTENT instead of the deprecated HTTP_422_UNPROCESSABLE_ENTITY.

Completed tasks (Slice 2)

All Slice 2 tasks are marked - [x] in tasks.md:

  • 2.1 Add observability URL settings (grafana_url, prometheus_url)
  • 2.2 Create source adapters (jellyfin, backups, grafana, prometheus, ssh_task, static)
  • 2.3 Add per-widget data endpoint (GET /api/widgets/instances/{id}/data)
  • 2.4 Extract shared backup/Jellyfin dashboard helpers into domain/dashboard.py
  • 2.5 Add adapter + data endpoint tests

Files changed (Slice 2)

New files

  • backend/src/media_library_viewer_api/widgets/sources.pyWidgetSource protocol and six source adapters.
  • backend/src/media_library_viewer_api/domain/dashboard.py — Shared dashboard helpers (_map_sessions_to_activity_rows, build_backup_dashboard_summary).

Modified files

  • backend/src/media_library_viewer_api/config.py — Added grafana_url and prometheus_url settings.
  • backend/src/media_library_viewer_api/routers/widgets.py — Added GET /api/widgets/instances/{id}/data.
  • backend/src/media_library_viewer_api/routers/dashboard.py — Delegated to shared domain/dashboard.py helpers.
  • backend/tests/test_widgets.py — Added adapter and data endpoint tests.
  • docker-compose.yml, docker-compose.dev.yml, .env.example — Wired GRAFANA_URL and PROMETHEUS_URL for the new adapters.

Verification (Slice 2)

cd backend
.venv/bin/python -m ruff check .              # All checks passed
PYTHONPATH=src .venv/bin/python -m pytest     # 200 passed, 2 warnings
cd ../frontend
npm run lint                                  # 2 pre-existing warnings, 0 errors
npm run build                                 # Built successfully

Focused widget test output: 27 passed.

Deviations from design (Slice 2)

  • Adapters currently call get_settings_store() internally for backups/ssh_task sources. The router-level endpoint uses FastAPI DI, but adapter unit tests patch get_settings_store to inject a test store. A future refactor can pass store and settings explicitly into adapter.fetch() for cleaner testability.

Completed tasks (Slice 3)

All Slice 3 tasks are marked - [x] in tasks.md:

  • 3.1 Add TypeScript widget interfaces (WidgetInstance, WidgetInstanceInput, WidgetTypeInfo, WidgetDataResponse)
  • 3.2 Create widget API client (frontend/src/api/widgets.ts)
  • 3.3 Create widget TanStack Query hooks (frontend/src/hooks/useWidgets.ts)
  • 3.4 Create frontend widget registry (frontend/src/widgets/registry.ts)
  • 3.5 Implement six widget presentational components (frontend/src/widgets/*.tsx)
  • 3.6 Add frontend registry unit test (frontend/src/widgets/registry.test.ts)

Files changed (Slice 3)

New files

  • frontend/src/api/widgets.ts — API functions for widget CRUD, registry metadata, and per-widget data.
  • frontend/src/hooks/useWidgets.ts — TanStack Query hooks for instances, data, sources, types, and mutations.
  • frontend/src/widgets/registry.ts — Closed frontend registry with metadata, refresh intervals, and config fields.
  • frontend/src/widgets/JellyfinWidget.tsx — Renders Jellyfin session activity.
  • frontend/src/widgets/BackupsWidget.tsx — Renders backup dashboard summary.
  • frontend/src/widgets/GrafanaLinkWidget.tsx — Renders a deep-link to Grafana (no iframe).
  • frontend/src/widgets/PrometheusMetricWidget.tsx — Renders PromQL instant query result.
  • frontend/src/widgets/SshTaskWidget.tsx — Renders saved SSH task output.
  • frontend/src/widgets/StaticWidget.tsx — Renders static text.
  • frontend/src/widgets/index.ts — Barrel exports.
  • frontend/src/widgets/registry.test.ts — Vitest unit tests for registry metadata.

Modified files

  • frontend/src/types/index.ts — Added widget TypeScript interfaces.

Verification (Slice 3)

cd backend
.venv/bin/python -m ruff check .              # All checks passed
PYTHONPATH=src .venv/bin/python -m pytest     # 200 passed, 2 warnings
cd ../frontend
npm run lint                                  # 2 pre-existing warnings, 0 errors
npm run build                                 # Built successfully
npm run test -- src/widgets/registry.test.ts  # 3 passed

Deviations from design (Slice 3)

  • Registry unit test is colocated at frontend/src/widgets/registry.test.ts and runs with Vitest, matching the project's existing npm run test setup, instead of frontend/tests/widgets.test.mjs.
  • JellyfinWidget uses SessionActivityPanel directly because NowPlaying does not expose an emptyMessage prop.

Completed tasks (Slice 4)

All Slice 4 tasks are marked - [x] in tasks.md:

  • 4.1 Refactor Dashboard.tsx to render enabled widget instances in sort order
  • 4.2 Create WidgetInstance renderer component
  • 4.3 Create WidgetConfigDialog for add/edit/reorder/delete widgets
  • 4.4 Create addon pages (AddonPage, GrafanaAddonPage, PrometheusAddonPage, SshTasksAddonPage)
  • 4.5 Register /addons/:addonId route in App.tsx
  • 4.6 Update docs/REQUIREMENTS.md with widget system documentation

Files changed (Slice 4)

New files

  • frontend/src/components/WidgetInstance.tsx — Renders a widget instance by looking up its definition and dispatching to the registered component.
  • frontend/src/components/WidgetConfigDialog.tsx — Dashboard widget configuration UI: list, add, edit, delete, reorder, enable/disable.
  • frontend/src/pages/AddonPage.tsx — Route mapper for /addons/:addonId.
  • frontend/src/addons/GrafanaAddonPage.tsx — Grafana addon landing page (deep-link only).
  • frontend/src/addons/PrometheusAddonPage.tsx — Prometheus addon landing page.
  • frontend/src/addons/SshTasksAddonPage.tsx — SSH tasks addon landing page.
  • frontend/src/addons/index.ts — Barrel exports.

Modified files

  • frontend/src/pages/Dashboard.tsx — Replaced hard-coded Jellyfin/Backups sections with widget instance loop; kept Shortcuts section; added "Edit dashboard" button.
  • frontend/src/App.tsx — Registered /addons/:addonId route in both OIDC and non-OIDC route trees.
  • docs/REQUIREMENTS.md — Added Configurable Dashboard Widgets section.

Verification (Slice 4)

cd backend
.venv/bin/python -m ruff check .              # All checks passed
PYTHONPATH=src .venv/bin/python -m pytest     # 200 passed, 2 warnings
cd ../frontend
npm run lint                                  # 2 pre-existing warnings, 0 errors
npm run build                                 # Built successfully
npm run test -- src/widgets/registry.test.ts  # 3 passed

Deviations from design (Slice 4)

  • The "Edit dashboard" button lives in the Shortcuts section action area for now. A future UI pass can move it to a dedicated dashboard header.
  • Machine/task selectors in the config dialog filter to enabled Jellyfin machines / enabled tasks, which is slightly stricter than the design's generic string field.

Remaining work

  • Phase 1 widget system is complete. Future work could include widget grid layout, drag-and-drop reorder, richer Prometheus visualizations, or migrating shortcuts into the widget system.

PR boundary

This slice is PR 1 of 4 in the approved stacked-to-main chain. It is backend-only and leaves the frontend build/lint green.

Actual changed-line count: ~780 added lines across production code and tests (new files: ~597 lines; modified files: ~181 insertions). This is above the nominal ~400-line review budget, but Slice 1 is the smallest coherent backend unit: removing the CRUD router, store helpers, or tests would leave the slice non-functional or unverifiable. If the reviewer prefers a smaller blast radius, the store helpers (~90 lines) could be split into a preceding PR, though that PR would not be independently user-visible.