Files
manage/openspec/changes/services-as-hub-ia/tasks.md
T
Developer fe028b0e6f Plan services-as-hub IA rework (OpenSpec change)
Reorganize the app around services as the hub. Operational content
(Media, Files, Actions, Users, Backups) moves into type-specific tabs on
the service page. Top nav shrinks to Main Dashboard + named dashboards +
conditional per-type entries (appear when configured) + Services + Settings.

Decisions (D1-D17): conditional type entries; instance switcher for
multi-instance; Files/Actions into ssh_tasks tabs; Backups = new service
type; Users -> Authentik (in scope); Observability split per type (no
aggregate); main dashboard special at /; named dashboards = widgets +
pinned service links; each named dashboard = top entry; Authentik =
directory source (OIDC unchanged); Messaging -> Authentik users via SMTP;
Jellyseerr absorbed into Jellyfin config; standard tab skeleton
(Overview | content | Widgets | Config); Overview = health + metrics;
routing /services/:type/:id + /d/:slug; legacy routes 404; empty-state
CTAs.

Authentik directory client + endpoint included. 12 chained PRs forecast
(backend types -> frontend shell -> content tabs -> dashboards ->
cleanup -> verify).
2026-06-26 17:16:52 +00:00

10 KiB
Raw Blame History

Tasks — Services as hub IA

Change: services-as-hub-ia Phase: tasks Date: 2026-06-26

Review workload forecast

Field Value
Estimated changed lines ~45006000
Chained PRs recommended Yes (12 slices)
Chain strategy stacked-to-main
Slice order 13 backend → 4 shell → 59 content tabs → 10 dashboards → 11 cleanup → 12 verify

Each slice is committed separately. Every slice must leave cd backend && .venv/bin/ruff check . && .venv/bin/python -m pytest and cd frontend && npm run lint && npm run build && npm run test green. Every touched page gains a Vitest case at the new route and asserts the old route 404s (where applicable).


Slice 1 — Backend: new service types + Jellyseerr absorption

Goal: Registry reflects the new world. No frontend change yet.

  • 1.1 Add backups integration

    • Files: backend/src/media_library_viewer_api/integrations/backups.py (new), integrations/registry.py
    • Details: BackupsConfig (ingestion_label: str = "default"), no secrets, widget kind summary (move BackupsWidgetSource adapter to bind the service_id). Register in SERVICE_DEFINITIONS.
  • 1.2 Add authentik integration

    • Files: integrations/authentik.py (new), registry.py
    • Details: AuthentikConfig (base_url: ServiceBaseUrl, timeout_seconds), secret api_token (required). No widget kinds yet.
  • 1.3 Absorb jellyseerr into JellyfinConfig

    • Files: integrations/jellyfin.py, integrations/jellyseerr.py (delete), integrations/registry.py, integrations/__init__.py
    • Details: Add optional jellyseerr_url, jellyseerr_api_key to JellyfinConfig. Delete the jellyseerr integration module and registry entry. Update tests.
  • 1.4 Jellyseerr migration

    • Files: services/settings_store.py (ensure_defaults)
    • Details: On startup, migrate existing jellyseerr rows into paired jellyfin instances per the design. Log a warning for unpaired drops.
  • 1.5 Tests

    • Update backend/tests/test_services.py, test_widgets.py for the new types and the migration. Assert registry contains 8 types (alertmanager, authentik, backups, grafana, jellyfin, nextcloud, prometheus, ssh_tasks).

Slice 2 — Backend: Authentik directory client + endpoint

  • 2.1 AuthentikClient

    • Files: clients/authentik.py (new)
    • Details: users(search, page, page_size) -> {items, total} against the Authentik directory API. Reuse the requests-session pattern from clients/jellyseerr.py. Tests: tests/test_authentik_client.py.
  • 2.2 Directory endpoint

    • Files: routers/authentik_users.py (new), main.py (register router)
    • Details: GET /api/services/authentik/{service_id}/users proxies to the client, resolving the service record via the existing dependency. Tests cover not-configured + unreachable + paginated-success.

Slice 3 — Backend: route cleanup + backups attribution

  • 3.1 Remove Users router

    • Files: routers/users.py, routers/users_impl.py (delete), main.py, dependencies.py
    • Details: Delete the Jellyfin-backed user directory + message-compose router and its deps. Update test_api.py to drop the corresponding tests.
  • 3.2 Backups service attribution

    • Files: routers/backups.py, services/settings_store.py
    • Details: backup report endpoint accepts optional ?service_id=; first-wins association to an enabled backups instance when omitted. Dashboard summary
      • poller continue to work.
  • 3.3 Named dashboards backend

    • Files: models/dashboards.py (new), routers/dashboards.py (new), services/settings_store.py (table + CRUD)
    • Details: named_dashboards table (id, slug, label, sort_order, payload JSON of widget+link placements). Endpoints: GET/POST/PUT/DELETE /api/dashboards. Tests in tests/test_dashboards.py.

Slice 4 — Frontend: top-nav generation + service-page skeleton

Goal: Data-driven nav + tab-skeleton ServicePage shell. Content tabs are stubs that say "coming soon" so the rest of the app stays green.

  • 4.1 Service-type → nav-entry map

    • Files: frontend/src/integrations/navEntries.ts (new)
    • Details: Static SERVICE_TYPE_NAV_ENTRIES map (jellyfin→Media, ssh_tasks→[Files, Actions], alertmanager→Alerts, etc.). Helper to filter by configured types.
  • 4.2 Data-driven nav in App.tsx

    • Files: frontend/src/App.tsx
    • Details: Replace static navItems with the memoized list from design. Add useDashboards() and combine with useServiceInstances(). Loading skeleton nav until settled. Legacy routes removed; add 404 catch-all.
  • 4.3 ServicePage tab skeleton + instance switcher

    • Files: frontend/src/pages/ServicePage.tsx, new pages/service-tabs/ directory, pages/ServiceTypePage.tsx (redirect resolver)
    • Details: Refactor ServicePage to render [Overview, ...content, Widgets, Config] from serviceTabs(serviceType). Add /services/:type resolver route. Content tabs are stub components ("coming soon"). Instance switcher dropdown when siblings > 1.
  • 4.4 Empty-state CTAs

    • Files: frontend/src/pages/Dashboard.tsx, pages/ServicesPage.tsx
    • Details: Dashboard shows "Add a service" CTA when no services. Services page strong empty state.
  • 4.5 Tests

    • Nav-generation tests, service-page-skeleton tests, 404-on-legacy-routes tests.

Slice 5 — Frontend: Jellyfin content tabs (Media + Requests)

  • 5.1 MediaTab

    • Files: pages/service-tabs/MediaTab.tsx (lift from pages/Media.tsx)
    • Details: Accept instance prop, pass instance.id to media hooks. Preserve the index build controls + mobile card layout. Delete the old /media route and Applications.tsx wrapper.
  • 5.2 RequestsTab (Jellyseerr enrichment)

    • Files: pages/service-tabs/RequestsTab.tsx
    • Details: Source from the absorbed jellyseerr_url/jellyseerr_api_key on the Jellyfin instance. Render request-management data.
  • 5.3 Tests

    • New tests for MediaTab (instance-scoped), RequestsTab. Delete old Media page tests.

Slice 6 — Frontend: ssh_tasks content tabs (Files + Actions)

  • 6.1 FilesTab

    • Files: pages/service-tabs/FilesTab.tsx (lift from FileBrowser.impl.tsx)
    • Details: Accept instance prop. Delete old /files route + page wrapper.
  • 6.2 ActionsTab

    • Files: pages/service-tabs/ActionsTab.tsx (lift from Actions.tsx)
    • Details: Accept instance prop. Delete old /actions route + page.
  • 6.3 Tests


Slice 7 — Frontend: backups Jobs tab

  • 7.1 JobsTab

    • Files: pages/service-tabs/JobsTab.tsx (lift from components/BackupsPage.tsx)
    • Details: Accept instance prop, scope queries by instance.id. Delete old /backups route + page.
  • 7.2 Tests


Slice 8 — Frontend: Authentik Users + Messaging tabs

  • 8.1 UsersTab

    • Files: pages/service-tabs/UsersTab.tsx, hooks/useAuthentikUsers.ts, api/authentik.ts
    • Details: Directory table + search, sourced from the new endpoint. No Jellyfin/Jellyseerr enrichment.
  • 8.2 MessagingTab

    • Files: pages/service-tabs/MessagingTab.tsx (lift compose UI from UsersPage.impl.tsx)
    • Details: Recipient list sourced from Authentik users. Reuse the mail queue + SMTP settings. Delete the old /users route + UsersPage.
  • 8.3 Tests


  • 9.1 AlertsTab

    • Files: pages/service-tabs/AlertsTab.tsx (lift from ObservabilityPage.tsx)
    • Details: Alertmanager alerts view, instance-scoped. Delete old /observability route + page.
  • 9.2 LinksTab + MetricsTab

    • Files: pages/service-tabs/LinksTab.tsx, pages/service-tabs/MetricsTab.tsx
    • Details: Grafana deep-links; Prometheus status + PromQL explorer. Each instance-scoped.
  • 9.3 Tests


Slice 10 — Frontend: named dashboards

  • 10.1 NamedDashboardPage

    • Files: pages/NamedDashboardPage.tsx, hooks/useDashboards.ts, api/dashboards.ts
    • Details: Render widgets + pinned service links at /d/:slug. CRUD via the new endpoints.
  • 10.2 Pinned service links

    • Files: components/PinnedServiceLink.tsx, integration into the dashboard config dialog
    • Details: Shortcut variant targeting /services/:type/:id or a specific tab.
  • 10.3 Dashboard management UI

    • Files: a new "Manage dashboards" entry on the Services or Settings page
    • Details: Create/rename/reorder/delete named dashboards.
  • 10.4 Tests


Slice 11 — Cleanup + docs

  • 11.1 Delete dead code

    • Files: any remaining top-level page wrappers, unused hooks, stale types.
    • Details: Confirm no references to removed routes/pages remain.
  • 11.2 Update docs/REQUIREMENTS.md

    • Files: docs/REQUIREMENTS.md
    • Details: Rewrite the Information Architecture section. Document the service- type → nav-entry map, the service-page tab skeleton, named dashboards, routing, and the Users→Authentik + Observability-split decisions.
  • 11.3 Update CHANGELOG.md


Slice 12 — Verify

  • 12.1 Cross-route manual pass

    • Details: Walk every service type's page + tabs; walk named dashboards; walk the empty state; confirm legacy routes 404.
  • 12.2 Verify report

    • Files: openspec/changes/services-as-hub-ia/verify-report.md
    • Details: Per-AC evidence (AC1AC10), tool versions, manual notes, residual risks.

Notes

  • Slices 13 are backend-only; slice 4 is the frontend shell turning on the new IA with stubs; 59 replace stubs with real content; 10 adds named dashboards; 1112 close out.
  • Slices 59 are independent and can be reordered or parallelized across branches if useful, but each must merge green with its stub replaced.
  • The frontend content lifts (59) are the bulk of the line count; treat each as a self-contained review-sized PR.