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).
This commit is contained in:
Developer
2026-06-26 20:11:02 +00:00
parent ec57eff59a
commit a8dfbd5dc6
45 changed files with 3441 additions and 4711 deletions
+126
View File
@@ -0,0 +1,126 @@
# Slice 7 — Settings mobile forms (worker output)
## Files changed
| File | Status | Lines |
|------|--------|-------|
| `frontend/src/components/ui/sheet-form.tsx` | modified | +4 / -1 |
| `frontend/src/pages/Settings.tsx` | modified | +130 / -43 |
| `frontend/src/pages/__tests__/Settings.test.tsx` | modified | +75 / -0 |
**Total: ~228 changed lines** (209 insertions, 44 deletions). Under the 400-line budget.
## What was implemented
### 7.1 — Machine editor SheetForm below md
Below `md` (`isMobile === true`), the machine editor opens inside a `<SheetForm>` instead of a centered `<Dialog>`. Both editors share the same `machineDialogOpen` state — the SheetForm and Dialog are branched via `isMobile ? <SheetForm> : <Dialog>`, using the exact same open/close/save/cancel flow.
**Open-state strategy:** Unlike ServicePage (open-on-mount), the machine editor SheetForm is **triggered by user action** — the same Edit/Add-machine buttons that open the Dialog on desktop open the SheetForm on mobile. The `machineDialogOpen` state drives both. No navigation needed on close because the Settings page content (tabbed cards, machine list) is always visible behind the sheet.
**Preserved behaviors:**
- **Validate-on-save** — `saveMachineDraft(machineDraft)` is unchanged; the same validation logic runs.
- **SSH test validation** — `validateMachineSSH` + the "Validate SSH + trust host" button render inside the MachineEditor, which is shared between both branches.
- **ConfirmDialog (delete confirmation)** — rendered as a sibling OUTSIDE both the SheetForm and Dialog, so it overlays correctly on both layouts.
- **Save-disabled logic** — added `saveDisabled` prop to SheetForm; wired to the same condition the desktop DialogFooter uses (`!machineDraft.name || (ssh && !host)`).
- **Delete on mobile** — a "Delete machine" button renders inside the SheetForm body (when editing an existing machine), separate from the save bar.
- **Desktop (`md+`)** — the Dialog renders byte-for-byte identical (verified by the 3 existing desktop tests passing unchanged).
### SSH key manager
The SSHKeyManager is an **inline two-panel layout** (not a dialog), and its grid already uses `grid-cols-1 md:grid-cols-[320px_minmax(0,1fr)]` — it already stacks on mobile. No SheetForm conversion was needed or correct for this component. The machine list grid (`grid-cols-1 md:grid-cols-[...]`) also already stacks. No changes needed to either.
### SheetForm enhancement
Added `saveDisabled?: boolean` prop to `SheetForm` (additive, default `false`). This is needed because the machine editor gates save on required fields (name + host for SSH mode). The existing ServicePage consumer does not pass it (defaults to `false`). Non-breaking.
## Validation
```
npm run lint → 0 errors, 2 pre-existing warnings (UsersPage.impl.tsx, unrelated)
npm run build → ✓ built (tsc -b + vite)
npm run test → 27 files / 113 tests passed (was 110; +3 new)
```
## Deviations from design
1. **SSHKeyManager not wrapped in SheetForm.** The task said "both the machine editor dialog AND the SSH-key editor dialog." However, the SSH key manager is an inline two-panel layout (SelectionRailCard + SectionCard), not a dialog. It already stacks responsively (`grid-cols-1 md:grid-cols-[...]`). Wrapping an inline editor in a SheetForm would break its always-visible selection-rail UX. The machine editor (which IS a dialog) was converted to SheetForm as specified.
2. **`saveDisabled` prop added to SheetForm.** The design did not name this prop, but the machine editor requires it to match the desktop DialogFooter's `confirmDisabled` semantics. Additive and non-breaking.
3. **No navigation on close.** Unlike ServicePage (which navigates to `/services` on close), the Settings machine editor just closes the sheet — the page content is always behind it, so there's no stranding risk.
## skill_resolution
`none` — no project/user SKILL.md paths were injected, and no `.atl/skill-registry.md` was found.
## Residual risks
- **Dirty-state outside-click confirm (R4.5)** is still not implemented at the SheetForm level. Same deferred concern as Slice 6 — the SheetForm passes `onOpenChange` straight through. Flag for verify pass.
- **MachineEditor grid on mobile.** The MachineEditor uses `grid-cols-12` with `col-span-12 md:col-span-X` — already responsive (full-width below md). No changes needed.
- **Touch targets on rail rows.** The machine/SSH-key selection rails use `onClick` on `<div>` elements. The 44px touch-target audit is Slice 9, not here.
## Review findings
No blockers. The desktop Dialog is preserved token-identical (verified by 3 existing desktop tests passing unchanged). The SheetForm conversion follows the established ServicePage pattern.
```acceptance-report
{
"criteriaSatisfied": [
{
"id": "criterion-1",
"status": "satisfied",
"evidence": "Slice 7 converts the machine editor Dialog to SheetForm below md, adds saveDisabled to SheetForm (additive, non-breaking), and extends Settings.test.tsx with 3 mobile tests. Desktop Dialog preserved token-identical (3 existing desktop tests pass unchanged). SSHKeyManager already responsive (inline, not a dialog). No backend, no other pages touched, no scope widening."
}
],
"changedFiles": [
"frontend/src/components/ui/sheet-form.tsx",
"frontend/src/pages/Settings.tsx",
"frontend/src/pages/__tests__/Settings.test.tsx"
],
"testsAddedOrUpdated": [
"frontend/src/pages/__tests__/Settings.test.tsx"
],
"commandsRun": [
{
"command": "cd frontend && npm run lint",
"result": "passed",
"summary": "0 errors; 2 pre-existing warnings in UsersPage.impl.tsx (unrelated)"
},
{
"command": "cd frontend && npm run build",
"result": "passed",
"summary": "tsc -b + vite build clean"
},
{
"command": "cd frontend && npm run test",
"result": "passed",
"summary": "27 files / 113 tests passed (3 new mobile tests added)"
},
{
"command": "cd frontend && git diff --cached --stat",
"result": "passed",
"summary": "Empty — no staged files"
}
],
"validationOutput": [
"Machine editor Dialog → SheetForm branch via isMobile, same machineDialogOpen state",
"saveDisabled prop added to SheetForm (additive, default false)",
"Desktop Dialog token-identical (3 existing desktop tests pass unchanged)",
"SSHKeyManager already responsive (grid-cols-1 md:grid-cols-[...] stacks)",
"ConfirmDialog rendered as sibling outside both SheetForm and Dialog",
"SSH validate button preserved inside shared MachineEditor body"
],
"residualRisks": [
"R4.5 dirty-state outside-click confirm not implemented at SheetForm level (deferred to verify pass)",
"Touch targets on selection-rail rows deferred to Slice 9"
],
"noStagedFiles": true,
"diffSummary": "Adds a mobile (isMobile) branch to the machine editor that renders SheetForm instead of Dialog, using the same machineDialogOpen state. Adds saveDisabled prop to SheetForm for required-field gating. Desktop Dialog is preserved byte-for-byte. 3 new mobile tests (open SheetForm, save payload, cancel closes). 228 changed lines.",
"reviewFindings": [
"no blockers"
],
"manualNotes": "SSHKeyManager was NOT wrapped in SheetForm because it is an inline two-panel layout (not a dialog) that already stacks responsively. The task wording 'SSH-key editor dialog' referred to a dialog that does not exist — the inline grid already handles mobile. Touch-target audit deferred to Slice 9."
}
```