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:
@@ -0,0 +1,88 @@
|
||||
# Follow-up 1 — SheetForm isDirty wiring (worker output)
|
||||
|
||||
## Task
|
||||
|
||||
Wire the new `isDirty` prop of `SheetForm` into three remaining form consumers (Settings machine editor, message compose, WidgetConfigDialog) so unsaved edits trigger a "Discard changes?" confirm before closing.
|
||||
|
||||
## Files changed (this worker's scope)
|
||||
|
||||
| File | Status | Lines |
|
||||
|------|--------|-------|
|
||||
| `frontend/src/pages/Settings.tsx` | modified | +25 (isMachineDraftDirty helper + isDirty prop) |
|
||||
| `frontend/src/pages/UsersPage.impl.tsx` | modified | +5 (isDirty prop on compose SheetForm) |
|
||||
| `frontend/src/components/WidgetConfigDialog.tsx` | modified | +1 (isDirty prop) |
|
||||
| `frontend/src/pages/__tests__/Settings.test.tsx` | modified | +23 (dirty guard test) |
|
||||
| `frontend/src/pages/__tests__/UsersPage.test.tsx` | modified | +31 (compose dirty guard test) |
|
||||
| `frontend/src/components/__tests__/WidgetConfigDialog.test.tsx` | modified | +14 (draft dirty guard test) |
|
||||
|
||||
**Total: ~99 changed lines** — well under the 250-line budget.
|
||||
|
||||
## isDirty expressions per consumer
|
||||
|
||||
### 1. Settings machine editor (`Settings.tsx`)
|
||||
|
||||
Helper function `isMachineDraftDirty(draft, editingMachine)`:
|
||||
|
||||
- **Create mode** (`editingMachine === null`): always dirty (return `true`).
|
||||
- **Edit mode**: field-by-field comparison of user-editable fields:
|
||||
- `name`, `host`, `mode`, `port`, `username`, `ssh_key_id`, `enabled`, `notes`
|
||||
- `services` array (sorted JSON.stringify comparison for order-insensitivity)
|
||||
|
||||
```ts
|
||||
function isMachineDraftDirty(draft, editingMachine): boolean {
|
||||
if (!editingMachine) return true;
|
||||
return (
|
||||
draft.name !== editingMachine.name ||
|
||||
draft.host !== editingMachine.host ||
|
||||
draft.mode !== editingMachine.mode ||
|
||||
draft.port !== editingMachine.port ||
|
||||
draft.username !== editingMachine.username ||
|
||||
draft.ssh_key_id !== editingMachine.ssh_key_id ||
|
||||
draft.enabled !== editingMachine.enabled ||
|
||||
draft.notes !== editingMachine.notes ||
|
||||
JSON.stringify([...draft.services].sort()) !==
|
||||
JSON.stringify([...editingMachine.services].sort())
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
Note: `node_exporter_scrape_host` (mentioned in the task) does not exist in either `MonitoringMachine` or `MonitoringMachineInput` in this codebase. The comparable editable fields were used instead. Secret fields (`ssh_private_key`, `password`) are excluded because they're write-only (the original only has `*_set` booleans, not values).
|
||||
|
||||
### 2. Message compose (`UsersPage.impl.tsx`)
|
||||
|
||||
```ts
|
||||
isDirty={
|
||||
subject.trim() !== "" ||
|
||||
htmlBody.trim() !== DEFAULT_HTML_BODY.trim() ||
|
||||
attachments.length > 0
|
||||
}
|
||||
```
|
||||
|
||||
### 3. WidgetConfigDialog (`WidgetConfigDialog.tsx`)
|
||||
|
||||
```ts
|
||||
isDirty={draft !== null}
|
||||
```
|
||||
|
||||
Dirty only in draft mode (when adding/editing a widget). In list mode, `draft === null` → `isDirty = false` (nothing to discard). In draft mode, `onCancel={reset}` returns to the list (does NOT close the sheet), so `isDirty` prompts before resetting the draft.
|
||||
|
||||
## Validation
|
||||
|
||||
```
|
||||
npm run lint → 0 errors, 2 pre-existing warnings (UsersPage.impl.tsx exhaustive-deps)
|
||||
npm run build → ✓ built (tsc -b + vite)
|
||||
npm run test → 28 files / 122 tests passed (was 119; +3 new dirty-guard tests)
|
||||
```
|
||||
|
||||
## Deviations from task
|
||||
|
||||
1. **`node_exporter_scrape_host` field**: mentioned in the task but does not exist in the type definitions. Used the actual editable fields that exist on both `MonitoringMachine` and `MonitoringMachineInput`.
|
||||
2. **Secret fields excluded from dirty check**: `ssh_private_key`, `password`, `ssh_private_key_passphrase` are write-only on the draft and have no comparable value on `editingMachine` (which only has `*_set` booleans). Including them would make the form always dirty in edit mode.
|
||||
|
||||
## skill_resolution
|
||||
|
||||
`none` — no project/user SKILL.md paths were injected; no `.atl/skill-registry.md` found.
|
||||
|
||||
## Residual risks
|
||||
|
||||
- None for this worker's scope. The SheetForm primitive and ServicePage wiring were done by the parent and are not touched here.
|
||||
Reference in New Issue
Block a user