Files
manage/.pi-tmp/slice2-worker-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

71 lines
4.8 KiB
Markdown

# Slice 2 — Dashboard mobile layout (worker output)
## Files changed
| File | Status | Lines |
|------|--------|------|
| `frontend/src/pages/Dashboard.tsx` | modified | +128 / -7 |
| `frontend/src/pages/__tests__/Dashboard.test.tsx` | modified | +178 / -2 |
**Total: ~306 changed lines** (299 insertions, 7 deletions). Under the 400-line budget.
## What was implemented
### 2.1 — Single-column grid below md
Below `md` (`isMobile === true`), widgets render inside `MobileWidgetSections` which uses `grid grid-cols-1 gap-4`. At `md` and above, the existing flat `visibleWidgets.map(...)` renders unchanged — no grid wrapper is introduced on desktop (preserving the exact previous behavior).
### 2.2 — Section anchor bar
Below `md`, a horizontally scrollable pill bar (`MobileWidgetSections` anchor bar) groups widgets by section. Clicking a pill calls `document.getElementById(...).scrollIntoView({ behavior: "smooth", block: "start" })`. Each section renders with `scroll-mt-16` so the sticky TopBar doesn't cover the heading.
**Section-to-widget mapping:**
- **Observability** (Activity icon): service-bound widgets whose service_type is `alertmanager`, `prometheus`, or `grafana`.
- **Media** (Monitor icon): service-bound widgets whose service_type is `jellyfin`.
- **Backups** (DatabaseBackup icon): built-in widgets with `widget_kind === "backups"`.
- **Custom** (LayoutDashboard icon): built-in `static`, `ssh_tasks`, `nextcloud`, and any unmatched widget.
Section order: Observability → Media → Backups → Custom. Empty sections are not rendered.
Icons match the existing nav (`App.tsx` `navItems`): Activity for Observability, Monitor for Media, DatabaseBackup for Backups.
### 2.3 — Tests
Extended `Dashboard.test.tsx` with 3 new tests (6 total, all passing):
1. **Mobile renders single column with anchor bar**: verifies Observability/Media/Backups sections appear, Custom does NOT (empty section hidden), all widgets render.
2. **Desktop hides anchor bar**: verifies no section headings or pills at desktop width.
3. **Anchor pill jumps via scrollIntoView**: spies on `Element.prototype.scrollIntoView`, clicks the Media pill, asserts the spy was called.
**matchMedia mock**: Added `setMatchMedia(matches: boolean)` helper that stubs `window.matchMedia` for the `(max-width: 768px)` query. Called in `beforeEach` with `false` (desktop default). Each mobile test calls `setMatchMedia(true)`.
## Validation
```
npm run lint → 0 errors, 2 pre-existing warnings (UsersPage.impl.tsx, unrelated)
npm run build → ✓ built in 855ms (tsc -b + vite)
npm run test → 25 files / 89 tests passed (was 86; +3 new)
```
## Deviations from design
1. **Desktop path preserved as bare map (no grid wrapper)**. The design pseudocode said `grid grid-cols-1 md:grid-cols-*`. The actual existing desktop code has no grid — it's a flat `visibleWidgets.map(...)` inside a `flex flex-col gap-4` parent. The task explicitly said "preserve whatever the current code does" and "do NOT change desktop behavior". Adding a grid wrapper (even `grid-cols-1`) around the desktop path would be a structural change. So the `isMobile` branch renders `MobileWidgetSections` (which has its own `grid grid-cols-1`) on mobile, and the bare map on desktop. Desktop DOM is byte-for-byte identical to before.
2. **Section headings (`<h3>`) on mobile**. The design/spec did not explicitly name section headings, only the anchor bar. I added a subtle `<h3 className="text-sm font-semibold text-muted-foreground">` per section so the sections are visually identifiable after scrolling. This is additive mobile-only markup; desktop is unaffected.
3. **`useServiceInstances()` added to Dashboard**. Required to resolve service-bound widget types for section grouping. TanStack Query dedupes by key, so this shares the cache with `WidgetInstanceCard`'s own `useServiceInstances()` call — no extra network request.
## skill_resolution
`none` — no project/user SKILL.md paths were injected by the parent, and no `.atl/skill-registry.md` was found. The task was self-contained against the OpenSpec design/tasks docs.
## Residual risks
- **jsdom `matchMedia` state is per-test, not reactive**: the `useIsMobile` hook reads `matchMedia` synchronously during `useState` init, then sets up a listener. The test sets `matchMedia` before render. If a test needed to simulate a live resize mid-render, the mock's `addEventListener` is a no-op (no event fires). This is adequate for breakpoint-branch tests but cannot test responsive transitions. Acceptable for this slice.
- **Anchor pill duplicate text**: each section label appears in both the pill and the `<h3>`. Tests use `getAllByText` or `getByRole("button", { name })` to disambiguate. This is a minor testing concern, not a runtime issue.
## Review findings
No blockers identified during self-review. All validation commands green.