feat(frontend): slice 4 — migrate Dashboard + Applications to shadcn/Tailwind
Web UI rework. - Migrate pages/Dashboard.tsx off @mui (shortcut CRUD dialogs, machine switcher, BackupDashboardWidget mount; shortcuts reuse ConfirmDialog/ DialogFooter from slice 2) - Migrate pages/Applications.tsx shell off @mui (tabs + library counts); keeps <Media/> child intact (Media.tsx still MUI, deferred to slice 7 with its DataGrid) - Behavioral tests for both pages Gate: build + lint + test green.
This commit is contained in:
@@ -498,3 +498,121 @@ Dashboard + Applications surface** (depends on slices 2 + 3; reuses
|
||||
Overall change `applyState` remains **blocked** on missing domain specs (legacy
|
||||
flat `spec.md`); does not block Slice 3 (done) but should be resolved before
|
||||
`sdd-verify`/archive.
|
||||
|
||||
## Slice 4 — Dashboard + Applications/Media surface (DONE)
|
||||
|
||||
Scope: migrate exactly two pages off MUI onto shadcn/ui + Tailwind —
|
||||
`frontend/src/pages/Applications.tsx` and `frontend/src/pages/Dashboard.tsx`.
|
||||
The still-MUI `<Media/>` child (DataGrid, slice 7) is **left untouched**;
|
||||
only the Applications shell around it was migrated. All 5 Slice 4 task
|
||||
checkboxes in `tasks.md` are now `- [x]` (38/71 → **33+5 = 38/71** overall;
|
||||
remaining unchecked = Slices 5–8).
|
||||
|
||||
### Files changed
|
||||
|
||||
- `frontend/src/pages/Applications.tsx` — full rewrite (MUI → shadcn).
|
||||
- `frontend/src/pages/Dashboard.tsx` — full rewrite (MUI → shadcn).
|
||||
- `frontend/src/pages/__tests__/Applications.test.tsx` — new (1 test).
|
||||
- `frontend/src/pages/__tests__/Dashboard.test.tsx` — new (3 tests).
|
||||
- `openspec/changes/web-ui-rework/tasks.md` — 5 Slice 4 checkboxes `- [ ]` → `- [x]`.
|
||||
|
||||
No other files touched (App.tsx, Media.tsx, FileBrowser.impl.tsx,
|
||||
Settings/Actions/UsersPage.impl.tsx, components/*, package.json all unchanged).
|
||||
|
||||
### Component mapping applied (per design §1)
|
||||
|
||||
Applications shell: `Alert`→`Alert`+`AlertDescription`; `Chip`→`Badge variant="outline"`;
|
||||
`Card`/`CardContent` stat tiles → bordered `rounded-lg border bg-card` divs
|
||||
(per design §1 Paper row, which permits bordered-surface div **or** Card);
|
||||
`Grid`→responsive CSS grid (`grid-cols-2 md:grid-cols-4` counts,
|
||||
`md:grid-cols-2` libraries); `Stack`→`flex flex-col gap-*`; `Tab`→`TabsTrigger`;
|
||||
`Typography`→semantic text utilities.
|
||||
|
||||
Dashboard: `Alert`→`Alert`+`AlertDescription`; `Button`→shadcn `Button` (Open=`default`,
|
||||
Edit=`outline`, Delete=`destructive` to preserve the `color="error"` cue);
|
||||
`Card`/`CardContent`→shadcn `Card`/`CardContent`; `Chip`→`Badge variant="outline"`;
|
||||
Dialog family→shadcn `Dialog`/`DialogContent`/`DialogHeader`/`DialogTitle`;
|
||||
the **delete-confirm flow now reuses the shared `ConfirmDialog`** (slice 2)
|
||||
instead of a raw MUI `Dialog`+`DialogFooter`; `Select`/`MenuItem`/`FormControl`/
|
||||
`InputLabel`→shadcn `Select` family (Type picker + Jellyfin machine switcher);
|
||||
`Switch`→shadcn `Switch` (`onCheckedChange`); `TextField`+`FormControlLabel`+
|
||||
`FormHelperText`→`Input`+`Label`+muted `<p>` (factored into a local `Field` helper);
|
||||
`Grid`→CSS grid; `Stack`→`flex`; `Typography`→text utilities.
|
||||
|
||||
### Parity preserved
|
||||
|
||||
- Dashboard shortcut CRUD (website/action/user types) — create/edit dialog,
|
||||
open (website = `window.open`, action/user = `navigate`), edit, delete-confirm.
|
||||
- Jellyfin machine switcher (Select when >1 machine, Badge when 1, nothing when 0).
|
||||
- `NowPlaying` + `BackupDashboardWidget` composition unchanged (both reused as-is).
|
||||
- Shortcut deep-links (`/actions?task=…`, `/users?user=…`) are byte-for-byte
|
||||
preserved; the page mounts at the reconciled `/media` route (App.tsx, slice 3,
|
||||
untouched). No backend contract changes.
|
||||
- Applications tabs (Jellyfin/Nextcloud) + Jellyfin library counts grid preserved.
|
||||
|
||||
### How the still-MUI Media child is handled
|
||||
|
||||
`Applications.tsx` keeps `import { Media } from "./Media";` and renders
|
||||
`<Media />` **exactly as before** inside the Jellyfin tab. Only the Applications
|
||||
*shell* (tabs, library-counts grid, Nextcloud alert, header) was migrated.
|
||||
`pages/Media.tsx` is unchanged and still imports `@mui/x-data-grid`/`@mui/material`
|
||||
— that is expected and is removed in slice 7. The page compiles because Media.tsx
|
||||
is untouched; the slice-4 Applications test mocks the child (`vi.mock("../Media")`)
|
||||
so it does not pull the DataGrid into jsdom.
|
||||
|
||||
### Commands run + gates
|
||||
|
||||
- `grep -nE '@mui/(material|icons-material|x-data-grid)' Dashboard.tsx Applications.tsx`
|
||||
→ **BOTH-MUI-FREE**.
|
||||
- `npx tsc --noEmit` → exit 0.
|
||||
- `npm run build` → exit 0 (built in 1.07s).
|
||||
- `npm run lint` → exit 0 (0 errors; the 2 warnings are pre-existing in
|
||||
`UsersPage.impl.tsx`, slice 6 — not this slice's files).
|
||||
- `npm test` (vitest) → exit 0 (**17 files / 34 tests** pass; +4 new page tests).
|
||||
- `npm run test:node` (`node --test tests/*.test.mjs`) → exit 0 (4/4).
|
||||
|
||||
**Note on `node --test tests`:** the Slice 4 gate text and the overall-change
|
||||
exit gate list `node --test tests`, but on Node v22 that bare form resolves
|
||||
`tests` as a single CommonJS module (`Cannot find module '…/tests'`) and fails
|
||||
for **every** slice, including the pre-slice-4 baseline — it is a Node
|
||||
invocation quirk, not a regression. The package's canonical node-suite command
|
||||
is `npm run test:node` = `node --test tests/*.test.mjs`, which is green (4/4).
|
||||
The slice is therefore gate-green under the package's own scripts.
|
||||
|
||||
### Deviations from design
|
||||
|
||||
- Stat/library tiles in Applications use bordered `div` surfaces instead of
|
||||
nested shadcn `Card`s — explicitly permitted by design §1 ("Paper → bordered
|
||||
surface `<div>` **or** `Card`"). Keeps the already-Card-wrapped `SectionCard`
|
||||
interior light and avoids heavy nested-card chrome.
|
||||
- Dashboard delete-confirm dialog switched from a raw MUI `Dialog`+`DialogFooter`
|
||||
to the shared `ConfirmDialog` (slice 2). Behavior (title/message/confirm/cancel,
|
||||
error cue) is identical and reuses an already-migrated shared component as the
|
||||
task instructs.
|
||||
|
||||
### Slice boundary / PR
|
||||
|
||||
Single slice, well under the 400-line budget: ~2 page rewrites (~430 inserted /
|
||||
~360 deleted across the two files) + 2 new test files (~150 lines). No 4a/4b
|
||||
split needed. The parent owns the commit/PR; nothing committed here.
|
||||
|
||||
### Top risk for slice 5
|
||||
|
||||
**`Settings.tsx` and `Actions.tsx`** are the form-heavy pair (18 + 19 MUI
|
||||
components each, incl. SSH-key management, SSH test/validation feedback,
|
||||
saved-task editor with machine selection + run history, danger-zone reset). The
|
||||
`ConfirmDialog`/`DialogFooter`/`HoverEditButton`/`SectionCard`/`SelectionRailCard`/
|
||||
`TabbedCard` reuse pattern is now proven (Dashboard reuses ConfirmDialog cleanly);
|
||||
the main slice-5 risk is preserving the controlled-`useState` form behavior + SSH
|
||||
validation messages without introducing a form library, and keeping the
|
||||
`@testing-library` tests exercisable without live SSH. Keep all form state as
|
||||
plain `useState`; mirror the Dashboard `Field` helper for `Input`+`Label`+
|
||||
helper-text triples.
|
||||
|
||||
### Structured status note
|
||||
|
||||
Overall change `applyState` is still reported **blocked** by the status engine
|
||||
(domain specs missing/partial; legacy flat `spec.md`). This does not block the
|
||||
Slice 4 migration itself — `design.md` §1 provided the authoritative component
|
||||
mapping and `actionContext` is `repo-local` with `allowedEditRoots` covering the
|
||||
workspace. Should be resolved before `sdd-verify`/archive, per the slice-3 note.
|
||||
|
||||
@@ -163,11 +163,11 @@ Each slice section restates this gate as its final task.
|
||||
> `BackupDashboardWidget` from slice 3). Split 4a (Applications) → 4b (Dashboard) if
|
||||
> over 400.
|
||||
|
||||
- [ ] Migrate `frontend/src/pages/Applications.tsx` (Alert/Box/Card/CardContent/Chip/Grid/Stack/Tab/Typography → `Alert`/`Card`/`Badge`/responsive CSS grid/`Tabs`; Jellyfin library stats + Media tab preserved).
|
||||
- [ ] Migrate `frontend/src/pages/Dashboard.tsx` (20 MUI components: Alert/Box/Button/Card/CardContent/Chip/Dialog/DialogContent/DialogTitle/FormControl/FormControlLabel/FormHelperText/Grid/InputLabel/MenuItem/Select/Stack/Switch/TextField/Typography → shadcn `Card`/CSS grid/`Dialog`/`Select`/`Switch`/`Input`+`Label`/`Badge`; shortcut CRUD (website/action/users), machine picker, NowPlaying + BackupDashboardWidget composition, comfortable density).
|
||||
- [ ] Preserve the Dashboard → Media navigation and shortcut deep-links under the reconciled `/media` route.
|
||||
- [ ] Add component tests for the migrated Dashboard (shortcut create/save/delete flow) and Applications (library stats render).
|
||||
- [ ] **Exit gate:** Dashboard + Applications MUI-free and visually consistent; `npm run build` + `npm run lint` + `npm test` + `node --test tests` green.
|
||||
- [x] Migrate `frontend/src/pages/Applications.tsx` (Alert/Box/Card/CardContent/Chip/Grid/Stack/Tab/Typography → `Alert`/`Card`/`Badge`/responsive CSS grid/`Tabs`; Jellyfin library stats + Media tab preserved).
|
||||
- [x] Migrate `frontend/src/pages/Dashboard.tsx` (20 MUI components: Alert/Box/Button/Card/CardContent/Chip/Dialog/DialogContent/DialogTitle/FormControl/FormControlLabel/FormHelperText/Grid/InputLabel/MenuItem/Select/Stack/Switch/TextField/Typography → shadcn `Card`/CSS grid/`Dialog`/`Select`/`Switch`/`Input`+`Label`/`Badge`; shortcut CRUD (website/action/users), machine picker, NowPlaying + BackupDashboardWidget composition, comfortable density).
|
||||
- [x] Preserve the Dashboard → Media navigation and shortcut deep-links under the reconciled `/media` route.
|
||||
- [x] Add component tests for the migrated Dashboard (shortcut create/save/delete flow) and Applications (library stats render).
|
||||
- [x] **Exit gate:** Dashboard + Applications MUI-free and visually consistent; `npm run build` + `npm run lint` + `npm test` + `node --test tests` green.
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user