# Configurable per-service Overview (change 4)

## Files changed (10 files, ~310 lines)

| File | Status | Lines |
|------|--------|-------|
| `backend/src/media_library_viewer_api/services/settings_store.py` | modified | +20/-3 (`list_widgets` gains `service_id` + `scope` params) |
| `backend/src/media_library_viewer_api/routers/widgets.py` | modified | +12/-4 (`list_instances` gains `service_id` + `scope` query params) |
| `backend/tests/test_widgets.py` | modified | +36 (filter test) |
| `frontend/src/api/widgets.ts` | modified | +8/-1 (`fetchWidgetInstances` accepts `serviceId?` + `scope?`) |
| `frontend/src/hooks/useWidgets.ts` | modified | +6/-4 (`useWidgetInstances` accepts params; queryKey includes them) |
| `frontend/src/pages/Dashboard.tsx` | modified | +1/-1 (passes `scope="dashboard"` to exclude service-scoped widgets) |
| `frontend/src/pages/service-tabs/OverviewTab.tsx` | **new** | 67 |
| `frontend/src/pages/service-tabs/__tests__/OverviewTab.test.tsx` | **new** | 79 |
| `frontend/src/pages/service-tabs/index.ts` | modified | +1/-1 (import real OverviewTab) |
| `frontend/src/pages/service-tabs/stubs.tsx` | **deleted** | -19 |

## Backend filter shape

`GET /api/widgets/instances` now accepts:

- `?service_id=X` — filter to widgets for service X
- `?scope=dashboard` — only NULL service_id widgets (main dashboard)
- `?scope=service` — only non-NULL service_id widgets

`SettingsStore.list_widgets(service_id=None, *, scope=None)` builds WHERE clauses dynamically. No-args returns all (backward-compatible).

## OverviewTab structure

`OverviewTab({ instance })`:

- Fetches `useWidgetInstances(instance.id)` (scoped to this service).
- Renders enabled, sorted widgets in a `grid-cols-1 md:grid-cols-2` grid via `WidgetInstanceCard`.
- "Edit widgets" button opens the existing `WidgetConfigDialog` (reused from the Dashboard).
- Empty state: "No widgets on this overview yet" + "Add widgets" button.
- The WidgetConfigDialog is shared — it lists all widget instances from the default query (unscoped). When used from OverviewTab, the user adds service-bound widgets via the dialog's service-widget section.

## Config dialog integration

Reuses the existing `WidgetConfigDialog` as-is. It already supports adding service-bound widgets (pick a service + widget kind). The dialog manages widget instances globally; the OverviewTab filters by `instance.id`. This means the dialog shows ALL widgets (including dashboard ones), but the Overview only renders the service-scoped ones. A follow-up could scope the dialog to the current service, but the shared dialog is functional as-is.

## Validation

```
cd backend && .venv/bin/ruff check .                    → All checks passed!
cd backend && .venv/bin/python -m pytest tests/         → 272 passed, 2 warnings
cd frontend && npm run lint                             → 0 errors, 0 warnings
cd frontend && npm run build                            → ✓ built (tsc + vite)
cd frontend && npm run test                             → 36 files / 121 tests passed
```

## Deviations

1. **WidgetConfigDialog is unscoped.** It lists all widget instances. The OverviewTab filters by `instance.id` at render time, but the dialog shows everything. Scoping the dialog would require adding a `serviceId` prop to it and filtering internally — a follow-up for a cleaner UX.
2. **stubs.tsx deleted.** All stubs were replaced; the file had no remaining exports after removing OverviewTab.
3. **ServicePage tests updated.** Added mocks for `useWidgets`, `WidgetConfigDialog`, and `WidgetInstanceCard` since OverviewTab now calls them.

## skill_resolution

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

## Residual risks

- WidgetConfigDialog is shared and unscoped — adding a widget from the OverviewTab's edit button could add a dashboard widget that doesn't show on this overview.
- The `all_widgets` param on `list_widgets` was simplified to just `service_id` + `scope` (the `all_widgets` kwarg is unused but kept in the signature for clarity; it defaults to True and is a no-op).
- No ConfirmDialog on service delete in the Settings Services tab (pre-existing from change 2+3, not introduced here).

```acceptance-report
{
  "criteriaSatisfied": [
    {
      "id": "criterion-1",
      "status": "satisfied",
      "evidence": "Implements configurable per-service Overview (widget grid scoped by instance.id) + backend filter params (?service_id= + ?scope=) + Dashboard scope fix + tests. No scope widening: 10 files, ~310 lines. 272 backend + 121 frontend tests pass; lint/build green both sides."
    }
  ],
  "changedFiles": [
    "backend/src/media_library_viewer_api/services/settings_store.py",
    "backend/src/media_library_viewer_api/routers/widgets.py",
    "backend/tests/test_widgets.py",
    "frontend/src/api/widgets.ts",
    "frontend/src/hooks/useWidgets.ts",
    "frontend/src/pages/Dashboard.tsx",
    "frontend/src/pages/service-tabs/OverviewTab.tsx",
    "frontend/src/pages/service-tabs/__tests__/OverviewTab.test.tsx",
    "frontend/src/pages/service-tabs/index.ts",
    "frontend/src/pages/service-tabs/stubs.tsx"
  ],
  "testsAddedOrUpdated": [
    "backend/tests/test_widgets.py",
    "frontend/src/pages/service-tabs/__tests__/OverviewTab.test.tsx",
    "frontend/src/pages/__tests__/ServicePage.test.tsx"
  ],
  "commandsRun": [
    {
      "command": "cd backend && .venv/bin/ruff check .",
      "result": "passed",
      "summary": "All checks passed"
    },
    {
      "command": "cd backend && .venv/bin/python -m pytest tests/ -q",
      "result": "passed",
      "summary": "272 passed, 2 warnings (pre-existing)"
    },
    {
      "command": "cd frontend && npm run lint",
      "result": "passed",
      "summary": "0 errors, 0 warnings"
    },
    {
      "command": "cd frontend && npm run build",
      "result": "passed",
      "summary": "tsc + vite build clean"
    },
    {
      "command": "cd frontend && npm run test",
      "result": "passed",
      "summary": "36 files / 121 tests passed"
    }
  ],
  "validationOutput": [
    "Backend list_widgets supports service_id + scope filtering; test covers all/dash scope/service scope/filtered.",
    "Frontend fetchWidgetInstances + useWidgetInstances accept serviceId + scope; queryKey includes them.",
    "Dashboard uses scope=dashboard to exclude service-scoped widgets.",
    "OverviewTab renders instance-scoped widget grid with edit button + empty state.",
    "stubs.tsx deleted (all stubs replaced)."
  ],
  "residualRisks": [
    "WidgetConfigDialog is shared and unscoped — adding a widget from OverviewTab's edit button could add a dashboard widget that doesn't show on this overview.",
    "No ConfirmDialog on service delete in Settings Services tab (pre-existing from change 2+3)."
  ],
  "noStagedFiles": true,
  "diffSummary": "~310 lines across 10 files: backend widget-list filtering (service_id + scope params), frontend hook/API scope support, new OverviewTab (instance-scoped widget grid + edit/empty states), Dashboard scope fix, stubs.tsx deleted, ServicePage test mocks updated.",
  "reviewFindings": [
    "no blockers"
  ],
  "manualNotes": "Nothing is staged. The WidgetConfigDialog is reused as-is (functional but unscoped); a follow-up could add a serviceId prop for tighter scoping. The all_widgets kwarg on list_widgets is unused but kept for API clarity."
}
