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,142 @@
|
||||
# Slice 3 Review — Media table mobile layout (`mobile-responsive-parity`)
|
||||
|
||||
Reviewer: fresh adversarial pass. Scope: unstaged diff on
|
||||
`frontend/src/pages/Media.tsx` and `frontend/src/pages/__tests__/Media.test.tsx`.
|
||||
|
||||
## Verification commands run
|
||||
|
||||
| Command | Result |
|
||||
|---|---|
|
||||
| `npm run lint` | pass (0 errors; 2 pre-existing warnings in `UsersPage.impl.tsx`, unrelated to Slice 3) |
|
||||
| `npm run build` | pass (`tsc -b` + vite, built in 854ms) |
|
||||
| `npm run test` | pass (25 files, 94 tests) |
|
||||
|
||||
## Point-by-point
|
||||
|
||||
### 1. Desktop non-regression — CONFIRMED CORRECT
|
||||
|
||||
The diff is a clean branch-add, not a rewrite. The `DataTable` block was moved
|
||||
into the `else` of `isMobile ? <mobile> : <DataTable>` with every prop byte-for-
|
||||
byte identical to the pre-change version (`Media.tsx:671-697`):
|
||||
`columns`, `data`, `getRowId`, `enableRowSelection`, `rowSelection`,
|
||||
`onRowSelectionChange`, `onRowClick`, `enableColumnVisibilityToggle`,
|
||||
`columnVisibility`, `onColumnVisibilityChange`, `enablePagination`,
|
||||
`manualPagination`, `pagination`, `onPaginationChange`, `pageSizeOptions`,
|
||||
`rowCount`, `emptyMessage`. The wrapping `<div className="rounded-lg border
|
||||
bg-card">` and the `status?.exists` gate are preserved on both branches. No
|
||||
desktop prop was dropped, renamed, or reordered. R3.6 / R10.1 satisfied.
|
||||
|
||||
### 2. Mobile card fields — CONFIRMED CORRECT
|
||||
|
||||
`mediaCardFields` (`Media.tsx:83-96`) matches the real `MediaItem` type
|
||||
(`types/index.ts:274`), not the design doc's illustrative field names:
|
||||
|
||||
- `title` (string) — primary ✓
|
||||
- `size` → `r.size || "-"` (string, null-safe) ✓
|
||||
- `hdr` → `r.hdr || "-"` (string, null-safe) ✓
|
||||
- `library` → `r.library || "-"` (string, null-safe) ✓
|
||||
- `year` (`number | null`) → `r.year != null ? String(r.year) : "-"` ✓ explicitly null-safe
|
||||
|
||||
5 fields total (1 primary + 4), inside the spec's 3–5 range (R3.2). No
|
||||
undefined access possible — every field guards against empty/null. The design
|
||||
example used `size_display`/`is_hdr`/`library_name` (illustrative); the worker
|
||||
correctly used the real keys. Good.
|
||||
|
||||
### 3. Pagination duplication — NOT A BUG; acceptable tech debt
|
||||
|
||||
`MediaMobilePagination` (`Media.tsx:107-188`) duplicates `DataTablePagination`
|
||||
(`data-table.tsx`). I verified the semantics match exactly:
|
||||
|
||||
| Concern | DataTable | MediaMobilePagination | Match |
|
||||
|---|---|---|---|
|
||||
| Rows count | `rowCount ?? 0` (manual) | `totalRows` = `total` (`queryResult?.total ?? 0`) | ✓ |
|
||||
| pageCount | `Math.max(1, Math.ceil(rowCount/pageSize))` | `totalPages` = `Math.max(1, Math.ceil(total/pageSize))` (`Media.tsx:403`) | ✓ |
|
||||
| Page-size change | `table.setPageSize()` → resets `pageIndex:0` | `onPaginationChange(() => ({pageIndex:0, pageSize:Number(value)}))` | ✓ |
|
||||
| Prev disabled | `!getCanPreviousPage()` = `pageIndex>0` inverted | `pageIndex <= 0` | ✓ |
|
||||
| Next disabled | `!getCanNextPage()` = `pageIndex>=pageCount-1` inverted | `pageIndex >= pageCount - 1` | ✓ |
|
||||
| Page indicator | `Page {pageIndex+1} of {pageCount}` | same | ✓ |
|
||||
|
||||
No off-by-one, no missing clamp, no stale state. The mobile component reads
|
||||
`pageIndex`/`pageSize` derived the same way as the controlled `pagination`
|
||||
state fed to DataTable (`Media.tsx:355-356`), so the two paths can't drift on
|
||||
values.
|
||||
|
||||
Could they reuse DataTable's pagination by extracting it? That would require
|
||||
editing the shared `data-table.tsx` (export `DataTablePagination` or split a
|
||||
`TablePagination`), which is explicitly out of scope for Slice 3 and would risk
|
||||
R3.6/R10.1 (the shared component powers the desktop path). Acceptable to defer
|
||||
to a follow-up refactor slice. **Non-blocking smell, not a must-fix.**
|
||||
|
||||
### 4. Row click navigation — CONFIRMED CORRECT
|
||||
|
||||
`handleRowClick` (`Media.tsx:398-400`) is passed unchanged to
|
||||
`MobileCardRow.onRowClick` (`Media.tsx:659`). `MobileCardRow` makes the whole
|
||||
card a `<button type="button">` with `onClick={() => onRowClick(row)}`
|
||||
(`mobile-card.tsx`), so a tap navigates to `/files?path=<encoded>`. The test
|
||||
"navigates to the file browser when a card is tapped on mobile" asserts
|
||||
`navigate` is called once with the encoded path. ✓
|
||||
|
||||
### 5. Column-visibility toggle hidden below md (R3.5) — CONFIRMED CORRECT
|
||||
|
||||
On the mobile branch only `MobileCardRow` renders; no `DataTable`, so the
|
||||
`Columns` `DropdownMenu` never mounts. Tested explicitly:
|
||||
`hides the column-visibility toggle below md` asserts
|
||||
`queryByRole("button", { name: /Columns/ })` is null. The desktop test asserts
|
||||
the same button is present at desktop width. ✓ R3.5 satisfied both ways.
|
||||
|
||||
### 6. Test quality — GOOD
|
||||
|
||||
- **matchMedia mock** (`Media.test.tsx:159-183`): correct. It discriminates on
|
||||
`query.includes("768")` so `useIsMobile` (768px) toggles with the flag while
|
||||
`usePrefersSmallScreen` (900px) stays `false` — which is the right default for
|
||||
the desktop path (no `MOBILE_HIDDEN_COLUMNS` forcing). Adds/removes listeners
|
||||
are no-ops; sufficient for jsdom. Applied in `beforeEach` defaulting to
|
||||
desktop, overridden per-test via `setMatchMedia(true)`.
|
||||
- **Desktop test** asserts BOTH a DataTable column header (`Title`) AND the
|
||||
Columns toggle button. ✓
|
||||
- The 5 new tests assert real behavior: card titles + field labels render, no
|
||||
column headers leak, pagination renders (2 rows, Page 1 of 1, Previous
|
||||
disabled), card tap navigates, desktop renders DataTable. None are tautological.
|
||||
|
||||
### 7. `enableRowSelection` on mobile — NOT A REGRESSION (minor spec note)
|
||||
|
||||
The mobile card does not render a selection checkbox; `MobileCardRow` has no
|
||||
selection affordance. However, `rowSelection`/`setRowSelection` in `Media.tsx`
|
||||
is **vestigial**: grepping the file, the state is declared (`Media.tsx:326`) and
|
||||
passed to DataTable, but nothing in `Media.tsx` consumes it — there is no batch
|
||||
action, bulk-delete, or "selected count" UI wired to it. So dropping selection
|
||||
on mobile breaks no actual workflow, because no batch workflow exists on desktop
|
||||
either. R3.3's literal "selection semantics preserved on the card" is loosely
|
||||
violated, but the spec's "(tap target = the whole card where applicable)"
|
||||
clause and the absence of any selection consumer make navigation the correct
|
||||
primary mobile interaction. **Non-blocking note.** If a batch action is ever
|
||||
added to Media later, mobile selection will need an explicit follow-up.
|
||||
|
||||
## Other observations (non-blocking)
|
||||
|
||||
- The mobile card is wrapped in `<div className="p-4">` inside the bordered
|
||||
card, then `MediaMobilePagination` sits below it inside the same
|
||||
`rounded-lg border bg-card`. Consistent with the desktop wrapping. Fine.
|
||||
- `isSmall` (`usePrefersSmallScreen`, 900px) is still used for
|
||||
`effectiveColumnVisibility` desktop sub-breakpoint hiding; `isMobile`
|
||||
(`useIsMobile`, 768px) drives the card/table branch. Two hooks, two distinct
|
||||
purposes, correctly not conflated. The design notes `useIsMobile` should
|
||||
replace ad-hoc matchMedia; `usePrefersSmallScreen` is a separate 900px concern
|
||||
left intact — acceptable, not in Slice 3's scope to consolidate.
|
||||
- Lint warnings are in `UsersPage.impl.tsx`, pre-existing, unrelated.
|
||||
|
||||
## Verdict: **commit**
|
||||
|
||||
No blockers. Desktop path is byte-identical (non-regression confirmed), mobile
|
||||
fields are null-safe and type-correct, pagination duplication is semantically
|
||||
equivalent (tech debt, not a bug), navigation preserved, column-visibility
|
||||
correctly hidden, tests assert real behavior on both breakpoints, and
|
||||
lint/build/test are green. The two non-blocking notes (pagination duplication;
|
||||
vestigial selection not surfaced on mobile) are appropriate follow-up items,
|
||||
not commit gates.
|
||||
|
||||
## Acceptance
|
||||
|
||||
- criterion-1 (implement the change without widening scope): satisfied. Only
|
||||
the two Slice 3 files changed; DataTable and other slices untouched; no scope
|
||||
creep into shared-component refactors.
|
||||
Reference in New Issue
Block a user