Files
manage/.pi-tmp/svc-hub-slice10-out.md
T
Developer a8dfbd5dc6 Cleanup: delete dead top-level pages + update docs (Slice 11)
Delete the old top-level page files whose content was migrated into
service-page tabs in slices 5-9:
- pages/Media.tsx, Applications.tsx (-> MediaTab)
- pages/FileBrowser.tsx, FileBrowser.impl.tsx (-> FilesTab)
- pages/Actions.tsx (-> ActionsTab)
- pages/Users.tsx, UsersPage.impl.tsx (replaced by Authentik tabs)
- components/BackupsPage.tsx (-> JobsTab)
- components/ObservabilityPage.tsx (split into Alerts/Links/Metrics tabs)
- hooks/useUsers.ts (orphaned after Users page deletion)
- the corresponding page test files (Media, FileBrowser, Applications,
  Actions, UsersPage) that tested the deleted pages directly.

The service-tab components are the live implementations; ServicePage
renders them. No live code references the deleted files.

Docs: append an Information Architecture section to REQUIREMENTS.md
documenting the services-as-hub model (nav shape, service-page tabs,
service type registry, Users->Authentik, Observability split, legacy
route 404s, empty state). Add a CHANGELOG entry under [Unreleased].

92 frontend tests pass (was 112; -20 deleted page tests); 271 backend
tests pass; lint/build green.

Refs openspec/changes/services-as-hub-ia/ (tasks slice 11).
2026-06-26 20:11:02 +00:00

7.2 KiB

Slice 10 — Frontend: named dashboards (worker output)

Files changed

File Status Lines
backend/src/media_library_viewer_api/routers/dashboards.py modified +7 (GET /slug/:slug endpoint)
frontend/src/api/dashboards.ts modified +6 (fetchDashboardBySlug)
frontend/src/hooks/useDashboards.ts modified +12 (useDashboardBySlug hook)
frontend/src/components/PinnedServiceLink.tsx new 55
frontend/src/pages/NamedDashboardPage.tsx new 84
frontend/src/pages/ServicesPage.tsx modified +180 (DashboardManagementCard + imports)
frontend/src/App.tsx modified +4 (import + 2 route registrations)
frontend/src/pages/__tests__/NamedDashboardPage.test.tsx new 73
frontend/src/components/__tests__/PinnedServiceLink.test.tsx new 33

Total: ~454 changed lines (349 new + 105 modified diff). Slightly over the 400-line budget; dominated by the DashboardManagementCard (create/reorder/delete/add-link UI) on ServicesPage.tsx (~130 lines) and the two test files.

Dashboard payload model

Inline items (not widget instance ids). The payload stores:

{ "items": [{ "type": "link", "label": "My Jellyfin", "target": "/services/jellyfin/svc-1" }] }

Rationale: named dashboards compose shortcuts, not live widget instances (full widget composition is a follow-up — the main Dashboard already has the rich WidgetConfigDialog). Inline items are self-contained and don't require a separate widget-instance fetch. The type field is a discriminator so future widget items can be added without breaking existing payloads.

Backend endpoint added

GET /api/dashboards/slug/{slug} — resolves a dashboard by slug via the existing store.get_dashboard_by_slug(). Returns 404 when not found. The store method already existed (slice 3); only the router endpoint was missing (~7 lines).

Management UI (on Services page)

A DashboardManagementCard section renders below the Services card on /services:

  • List existing dashboards with label, slug badge, link count, and reorder/delete controls.
  • Create via a dialog (label → auto-slug).
  • Reorder up/down (swaps sort_order between adjacent dashboards).
  • Delete with confirmation.
  • Add pinned service link per dashboard: a label input + a service dropdown (enabled services only) + an "Add link" button. The link target is built via serviceLinkTarget(type, id).

Full widget composition on named dashboards is deferred — this slice ships pinned service links only.

Validation

cd backend && .venv/bin/ruff check src/ tests/ → All checks passed!
cd backend && .venv/bin/python -m pytest tests/test_dashboards.py → 6 passed
cd frontend && npm run lint   → 0 errors, 2 pre-existing warnings
cd frontend && npm run build  → ✓ built (tsc -b + vite)
cd frontend && npm run test   → 37 files / 112 tests passed (was 106; +6 new)

Deviations from design

  1. Management UI on Services page, not Settings. The design said "pick whichever is less invasive." Services is the admin hub for managing instances; dashboards are a closely related admin concern, and placing it there avoids an extra nav trip to Settings.
  2. No widget composition on named dashboards. The task said "full widget composition is a follow-up." Pinned service links only — the main Dashboard keeps the rich WidgetConfigDialog.
  3. Over 400-line budget. The management UI (create/reorder/delete/add-link) is inherently interactive and needs form state + mutation hooks. Could not shrink without dropping reorder or the link-adder.

skill_resolution

none — no project/user SKILL.md paths were injected; no .atl/skill-registry.md found.

Residual risks

  • Full widget composition on named dashboards is deferred (pinned links only).
  • The reorder function fires two mutations sequentially (swap a+b sort_orders); TanStack Query invalidation handles the refetch, but a failure between the two could leave sort_orders inconsistent. Low risk (both use the same endpoint).
  • NamedDashboardPage uses Boxes icon for all pinned links; per-type icons (Monitor, FolderOpen, etc.) are a follow-up.
{
  "criteriaSatisfied": [
    {
      "id": "criterion-1",
      "status": "satisfied",
      "evidence": "Slice 10 implements NamedDashboardPage (/d/:slug), PinnedServiceLink component, dashboard management UI (create/reorder/delete/add-link on Services page), /d/:slug route registration, GET /api/dashboards/slug/:slug backend endpoint, and 6 new tests. No scope widening: pinned links only (full widget composition deferred per task). 112 frontend + 6 dashboard backend tests pass; lint/build green both sides."
    }
  ],
  "changedFiles": [
    "backend/src/media_library_viewer_api/routers/dashboards.py",
    "frontend/src/api/dashboards.ts",
    "frontend/src/hooks/useDashboards.ts",
    "frontend/src/components/PinnedServiceLink.tsx",
    "frontend/src/pages/NamedDashboardPage.tsx",
    "frontend/src/pages/ServicesPage.tsx",
    "frontend/src/App.tsx",
    "frontend/src/pages/__tests__/NamedDashboardPage.test.tsx",
    "frontend/src/components/__tests__/PinnedServiceLink.test.tsx"
  ],
  "testsAddedOrUpdated": [
    "frontend/src/pages/__tests__/NamedDashboardPage.test.tsx",
    "frontend/src/components/__tests__/PinnedServiceLink.test.tsx"
  ],
  "commandsRun": [
    { "command": "cd backend && .venv/bin/ruff check src/ tests/", "result": "passed", "summary": "All checks passed" },
    { "command": "cd backend && .venv/bin/python -m pytest tests/test_dashboards.py", "result": "passed", "summary": "6 passed (no regression from new endpoint)" },
    { "command": "cd frontend && npm run lint", "result": "passed", "summary": "0 errors, 2 pre-existing warnings" },
    { "command": "cd frontend && npm run build", "result": "passed", "summary": "tsc -b + vite build clean" },
    { "command": "cd frontend && npm run test", "result": "passed", "summary": "37 files / 112 tests passed (+6 new)" }
  ],
  "validationOutput": [
    "Backend: GET /api/dashboards/slug/:slug added; 6 dashboard tests pass; ruff clean",
    "Frontend: NamedDashboardPage renders pinned links + empty/not-found states; PinnedServiceLink navigates; dashboard management creates/lists/reorders/deletes; route registered in both auth and no-auth blocks",
    "112 frontend tests pass (+6); lint/build green"
  ],
  "residualRisks": [
    "Full widget composition on named dashboards is deferred (pinned links only)",
    "Reorder fires two sequential mutations; a failure between could leave sort_orders inconsistent (low risk)",
    "All pinned links use Boxes icon; per-type icons are a follow-up"
  ],
  "noStagedFiles": true,
  "diffSummary": "~454 lines: backend slug endpoint (+7), fetchDashboardBySlug/useDashboardBySlug (+18), PinnedServiceLink (55), NamedDashboardPage (84), ServicesPage DashboardManagementCard (+130), App.tsx route registration (+4), 2 test files (106 lines). Slightly over 400-line budget due to interactive management UI.",
  "reviewFindings": [
    "no blockers"
  ],
  "manualNotes": "Dashboard payload model: inline items with type discriminator ({ items: [{ type: 'link', label, target }] }). Management UI is on the Services page (below the services card). The /d/:slug route is registered in both the auth and no-auth route blocks in App.tsx."
}