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).
4.9 KiB
Slice 1 — Shared primitives (worker output)
Implemented all 6 sub-tasks of Slice 1 (mobile-responsive-parity). No page-level files touched.
Files changed
| File | Status | Lines |
|---|---|---|
frontend/src/hooks/useIsMobile.ts |
new | 31 |
frontend/src/components/ui/mobile-card.tsx |
new | 112 |
frontend/src/components/ui/__tests__/mobile-card.test.tsx |
new | 73 |
frontend/src/components/ui/sheet-form.tsx |
new | 101 |
frontend/src/components/ui/__tests__/sheet-form.test.tsx |
new | 96 |
frontend/src/components/HoverEditButton.tsx |
modified | +24 / -8 |
frontend/src/components/__tests__/HoverEditButton.test.tsx |
modified | +19 |
frontend/src/index.css |
modified | +16 |
frontend/src/App.tsx |
modified | +2 / -13 |
Total: ~492 lines (79 tracked diff + 413 new files). Slightly over the 400-line budget; the overrun is entirely test coverage (mobile-card 73 + sheet-form 96 + hover-edit 19 = 188 test lines) which is the safety net for all 9 downstream page slices. Implementation-only lines are ~304, within budget. Flagging for parent decision; I did not trim tests since they guard later slices.
Validation
cd frontend && npm run lint → 0 errors, 2 pre-existing warnings (UsersPage.impl.tsx, unrelated)
cd frontend && npm run build → ✓ built in 3.49s (tsc -b + vite)
cd frontend && npm run test → 83 passed (25 files)
useIsMobile— no dedicated test (it's a thin matchMedia wrapper exercised by App.tsx integration); the page-slice tests will assert <768px/≥768px behavior.MobileCardRow— 4 tests (primary+fields render, onRowClick fires, actions slot, non-interactive mode).SheetForm— 5 tests (title+children, onSave, onCancel, isPending disables+labels, close-X calls onCancel).HoverEditButton— 4 tests (existing 2 + default mobile=always tokens + legacy mobile=hover tokens).
Deviations from design
MobileCardRowkey strategy: design pseudocode usedMobileCardRowProps<T>withrows: TData[](a typo —TDataundefined). Implemented asrows: T[](correct generic). Also added an optionalclassNameprop on the outer container — minor additive convenience, not a behavior change.MobileCardRowfield rendering: design said "key/value stack"; I used a<dl>withgrid-cols-[auto_1fr]so labels align across rows. Same semantics, cleaner alignment.HoverEditButtondefault class: addedmobile-touch-targetto the button so it meets 44px below md out of the box (consistent with spec R6). Design did not name this class explicitly here but R6/R9 require it on all interactive elements; this primitive is reused by later slices so it should be compliant by default.SheetFormside: usedside="bottom"withh-[100dvh]for a true full-screen mobile form. Design said "side=bottom or side=right, full screen"; bottom is the more native mobile form factor and avoids thesm:max-w-smcap on side=right from the Sheet primitive.SheetFormclose button: design said "title + close X"; I render the X via the Cancel handler (X = cancel) rather than Radix'sonOpenChange(false), so dirty-state confirm logic (spec R4.5) can be centralized in the consumer'sonCancel. Documented in the component docstring.
No other deviations. All shadcn primitives, cn(), lucide-react icons, and existing code style (tabs, data-slot where relevant) matched.
skill_resolution
none — no project/user SKILL.md paths were injected by the parent, and no .atl/skill-registry.md was provided. The task was self-contained against the OpenSpec design/tasks docs.
Residual risks
- jsdom doesn't honor
@media: theuseIsMobilehook returns whateverwindow.matchMediareports in jsdom (default false). Per-page breakpoint tests in later slices will need to mockmatchMediaor use the existing resize pattern. Not a Slice 1 blocker. h-[100dvh]iOS Safari: per design risk note; needs manual verification on a real iOS device during Slice 10 (manual cross-route pass). The flex-column layout avoids the sticky-inside-transform pitfall.HoverEditButtonconsuming pages (Actions, Settings) use.rail-edithover CSS (&:hover .rail-edit { opacity: 1 }). With the new default (mobile="always"), the button is visible below md and hover-revealed at md+ — desktop behavior unchanged because the md:-prefixed classes take over at ≥768px. No migration needed on those pages for Slice 1; they keep working as-is.
Review findings
No blockers. One item for the parent reviewer to confirm:
- Total diff ~492 lines exceeds the 400-line slice budget by ~92 lines, entirely due to additive tests. Acceptable for a foundational primitives slice, but the parent may prefer to split or trim test prose.
Manual notes
git statusconfirms nothing is staged; all changes are unstaged/untracked, ready for the parent to review and commit.- The
swap-paneuntracked file at repo root is pre-existing and unrelated; not touched.