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).
5.5 KiB
Slice 3 — Media table mobile layout (worker output)
Files changed
| File | Status | Lines |
|---|---|---|
frontend/src/pages/Media.tsx |
modified | +190 / -27 |
frontend/src/pages/__tests__/Media.test.tsx |
modified | +93 / -0 |
Total: ~256 changed lines (283 insertions, 27 deletions). Under the 400-line budget.
What was implemented
3.1 — Mobile fields + card render
Below md (isMobile === true), the page renders <MobileCardRow> inside the same rounded-lg border bg-card wrapper, followed by a standalone MediaMobilePagination component. Desktop renders the existing <DataTable> unchanged.
Mobile card field list (module-level mediaCardFields constant):
| Field | Key | Rationale |
|---|---|---|
| Title (primary) | title |
Primary identifier — bold card title |
| Size | size |
Already human-readable ("12.4 GB"); helps identify large files at a glance |
| HDR | hdr |
Shows HDR format string ("HDR10", "Dolby Vision") or "-" for SDR — key tech quality indicator |
| Library | library |
Which Jellyfin library the item belongs to — context for multi-library setups |
| Year | year |
Quick identification; number rendered as string, "-" if null |
Runtime, bitrate, resolution, video codec, series/season/episode, date_added, and path are omitted from the mobile card — they're available on desktop and would make the card too tall for phone scanning.
Preserved behaviors:
- Row click →
navigate("/files?path=...")— wired viaMobileCardRowonRowClick. - Pagination — a new
MediaMobilePaginationcomponent mirrors the DataTable's internalDataTablePagination(rows count, page-size select, page indicator, prev/next buttons) but works off the rawPaginationStateinstead of a TanStack table instance. - Build index / status controls above the table — unchanged.
- Column-visibility toggle — automatically hidden (DataTable is not rendered below
md). - Desktop (
md+) — byte-for-byte identical: theisMobile === falsebranch renders the exact same<DataTable>with the same props.
3.2 — Tests
Added a setMatchMedia(matches) helper to stub window.matchMedia for jsdom (same pattern as Dashboard.test.tsx). Called setMatchMedia(false) in beforeEach so existing desktop tests are unaffected. 5 new tests in a describe("Media (mobile card layout — slice 3)") block:
- Cards render with title as primary below md — asserts card titles and field labels render, desktop column headers do NOT.
- Column-visibility toggle is hidden below md — asserts no "Columns" button.
- Pagination controls render below cards on mobile — asserts "2 rows", page indicator, and prev/next buttons.
- Card tap navigates to file browser — clicks "Inception" card, asserts
navigatecalled with the encoded path. - DataTable renders at desktop width — asserts column headers present + "Columns" button present.
Validation
npm run lint → 0 errors, 2 pre-existing warnings (UsersPage.impl.tsx, unrelated)
npm run build → ✓ built (tsc -b + vite)
npm run test → 25 files / 94 tests passed (was 89; +5 new)
Deviations from design
-
Standalone
MediaMobilePaginationcomponent instead of reusing DataTable's pagination. The DataTable renders pagination internally (not as a separate export). Extracting a shared pagination component would touchdata-table.tsx(out of scope for this slice). The inlineMediaMobilePaginationmirrorsDataTablePaginationexactly (same labels, same controls, same aria-labels) so the mobile UX is consistent. A future refactor can extract both into a shared<TablePagination>. -
setMatchMediamock added to existing test file. The existing Media tests didn't mockwindow.matchMediabecause the oldusePrefersSmallScreenhook guarded against it (typeof window.matchMedia === "function"). The newuseIsMobilehook callswindow.matchMediaunconditionally (it's the standardized hook from Slice 1). The mock defaults to desktop (matches: false) so all 6 existing tests pass unchanged. -
mediaCardFieldsis a module-level constant. This mirrors the existingmediaColumnspattern (module-level for TanStack stability). MobileCardRow doesn't require it, but keeping it stable avoids per-render allocation and is consistent with the codebase's existing style.
skill_resolution
none — no project/user SKILL.md paths were injected by the parent, and no .atl/skill-registry.md was found.
Residual risks
MediaMobilePaginationduplicatesDataTablePagination. If the desktop pagination UI changes (labels, aria attributes), the mobile version won't auto-update. A shared component extraction in a later refactor would fix this. Low priority since the pagination UI is stable.- jsdom
matchMediais not reactive. ThesetMatchMediamock sets the initialmatchesvalue butaddEventListeneris a no-op (no resize events fire). This is adequate for breakpoint-branch tests but cannot test responsive transitions. Same limitation as Dashboard.test.tsx. - Row selection (
enableRowSelection) is desktop-only on mobile. The mobile card layout has no selection checkboxes (the card itself is the tap target for navigation). This is consistent with the spec (R3.5 says the card picks the fields; R3.3 preserves "row click / selection semantics" but on mobile the primary interaction is navigation, not batch selection). If batch selection is needed on mobile later, theactionsslot can hold a checkbox.