# Apply Progress — web-ui-rework > Cumulative apply log. Updated by `sdd-apply` after each slice/sub-slice. > Strict TDD is **not** active for this change (no strict-tdd directive in > `openspec/config.yaml`); this run used standard mode against `design.md` / `tasks.md`. ## Status context consumed - `schemaName: gentle-pi.sdd-status`, `changeName: web-ui-rework`. - `applyState` reported by the status engine: **blocked** (`blockedReasons`: domain specs missing/partial; legacy flat `spec.md` present without domain specs). This is a **planning-completeness** gap, not a safety/`actionContext` blocker. - `actionContext`: `mode: repo-local`, `workspaceRoot: /home/user/Manage_01`, `allowedEditRoots: ["/home/user/Manage_01"]`, `warnings: []` — safe. - This run executed the explicitly delegated **Slice 1 (Foundation)** scope per the parent acceptance contract. The foundation work is fully specified in `tasks.md` (Slice 1) and does not depend on the missing domain specs, so it proceeds under the parent's explicit delegation. The `instructions.apply` line itself says "Implement only unchecked tasks from the tasks artifact." - `artifactStore: openspec`; persisted task checkboxes updated in `tasks.md` (13 → `[x]`). ## Slice 1 — Foundation (primitives, TanStack Table, Vitest, token/Badge cleanup) — COMPLETE All 13 Slice-1 tasks in `tasks.md` are marked `- [x]`. Summary below. ### Completed tasks (persisted checkboxes updated) - [x] Added the missing shadcn primitives (13): `tabs table dialog input label checkbox switch progress separator avatar textarea dropdown-menu scroll-area` via `npx shadcn@latest add …`. The 9 already-present primitives (`button tooltip sheet card badge alert select skeleton collapsible`) were **not** re-added. - [x] Installed `@tanstack/react-table` (`^8.21.3`, dependency). - [x] Removed orphaned charting deps: `recharts`, `d3`, and the companion `@types/d3` from `package.json` (zero `from "recharts"` / `from "d3"` / `from "d3-*"` imports existed in `frontend/src`). - [x] Deleted `frontend/src/theme.ts` (no-op `getAppTheme` shim). Grep confirmed the only reference was the file itself; no source file imported it. - [x] Added the `success` Badge variant (`chart-2`) and the `warning` variant (`chart-3`) to `frontend/src/components/ui/badge.tsx`, mirroring the `destructive` soft-cue pattern exactly per design §2.3. - [x] Documented the `chart-1..5` role mapping as an inline comment block above the tokens in **both** the `@theme` block and `.dark` block of `frontend/src/index.css` (chart-1=info/brand, chart-2=success, chart-3=warning, chart-4=destructive, chart-5=neutral-accent). **No token value changed; no token dropped. Primary `#4f8cff`.** - [x] Created `frontend/vitest.config.ts` (separate from `vite.config.ts`): `vitest/config` `defineConfig`, `@vitejs/plugin-react`, `environment: "jsdom"`, `globals: true`, `setupFiles: ["./src/test/setup.ts"]`, the `@` path alias from `tsconfig.app.json`, and `include: ["src/**/*.{test,spec}.{ts,tsx}"]` (does NOT claim the `frontend/tests/*.mjs` node suites). - [x] Created `frontend/src/test/setup.ts` registering `@testing-library/jest-dom` matchers (via the `/vitest` entry for runtime + TS augmentation). - [x] Installed dev deps: `vitest@^4.1.9`, `@testing-library/react@^16.3.2`, `@testing-library/jest-dom@^6.9.1`, `@testing-library/user-event@^14.6.1`, `jsdom@^29.1.1`. - [x] Added npm scripts: `"test": "vitest run"`, `"test:watch": "vitest"`, `"test:node": "node --test tests/*.test.mjs"` (see Deviations). - [x] Added one trivial passing component test: `src/components/ui/__tests__/badge.test.tsx` (asserts a `success` Badge renders with the `chart-2` cue). - [x] Verified lucide-react `1.14.0` exports every name used by later slices: `Pencil, X, Paperclip, Bold, Italic, Link, List, Mail, Send, Trash2, DatabaseBackup, ExternalLink`. **`DatabaseBackup` IS exported — no fallback needed** (slice-3 nav will use `DatabaseBackup`). - [x] **Exit gate green:** `npm run build` + `npm run lint` + `npm test` + `npm run test:node` all pass; no `theme`/`getAppTheme` imports remain. ### Files changed (tracked) Modified: - `frontend/package.json` (added `@tanstack/react-table`, vitest/testing-library/jsdom dev deps, test scripts; removed `recharts`, `d3`, `@types/d3`) - `frontend/package-lock.json` (regenerated by npm) - `frontend/src/components/ui/badge.tsx` (added `success` + `warning` variants) - `frontend/src/components/ui/tabs.tsx` (vendored primitive; un-exported `tabsListVariants` to satisfy `react-refresh/only-export-components` — see Deviations) - `frontend/src/index.css` (chart-1..5 role-mapping comment blocks in `@theme` + `.dark`) Deleted: - `frontend/src/theme.ts` Added (new): - `frontend/vitest.config.ts` - `frontend/src/test/setup.ts` - `frontend/src/components/ui/__tests__/badge.test.tsx` - 13 new shadcn primitives under `frontend/src/components/ui/`: `tabs.tsx, table.tsx, dialog.tsx, input.tsx, label.tsx, checkbox.tsx, switch.tsx, progress.tsx, separator.tsx, avatar.tsx, textarea.tsx, dropdown-menu.tsx, scroll-area.tsx` Untouched (no-unintended-edits respected): the 22 MUI page/component files in Slice 1's scope (all `frontend/src/pages/*` and `frontend/src/components/*.tsx` outside `components/ui/`) — `git status --porcelain frontend/src/pages 'frontend/src/components/*.tsx' | grep -v 'components/ui/'` returns nothing. Also touched (gitignored generated docs, not tracked changes): `frontend/src/.pi-map.md` and `frontend/src/.pi-map.index.md` were refreshed to remove the now-deleted `theme.ts` / `getAppTheme` references (the `project_map_patch` tool is unavailable in this session, so the stale generated maps were minimally reconciled to match source). ### Commands run (validation) - `npx shadcn@latest add tabs table dialog input label checkbox switch progress separator avatar textarea dropdown-menu scroll-area --yes` → created 14 files (13 primitives + a duplicate button written to a stray `@/` path; stray dir removed, dup button discarded). - `npm i @tanstack/react-table` → ok. - `npm i -D vitest @testing-library/react @testing-library/jest-dom @testing-library/user-event jsdom` → ok. - `npm uninstall recharts d3 @types/d3` → ok. - `cd frontend && npm run build` → **PASS** (`tsc -b` + `vite build`). - `cd frontend && npm run lint` → **PASS** (0 errors, 0 errors; 2 pre-existing `react-hooks/exhaustive-deps` warnings in `UsersPage.impl.tsx` that are out of Slice-1 scope). - `cd frontend && npm test` → **PASS** (Vitest: 1 file, 1 test passed). - `cd frontend && npm run test:node` → **PASS** (legacy node:test suites green). ### Deviations from design / task text (documented) 1. **`tabs.tsx` variant export.** The vendored shadcn `tabs.tsx` exported `tabsListVariants` (a `cva` function) alongside components, which violates `react-refresh/only-export-components` and broke `npm run lint`. The project's own convention (`button.tsx`, `select.tsx`) is to **not** export the `cva` variant helper, so `tabsListVariants` was removed from the `export { … }` statement (the `const` is retained for internal use by `TabsList`). This keeps the component API intact for all later slices (TabbedCard uses ``, not the helper). 2. **`test:node` script form.** The task text specifies `"test:node": "node --test tests"`, but on **Node v22.22.2** a bare directory argument is resolved as a CommonJS module entry point (`Error: Cannot find module '…/tests'`). The functional equivalent `"test:node": "node --test tests/*.test.mjs"` is used instead, which targets exactly the legacy node:test suites (`users.test.mjs`, `userState.test.mjs`) and passes. `node --test` auto-discovery also passes; the glob form is the explicit, minimal choice. 3. **shadcn CLI path artifact.** The CLI resolved the `@/` alias to a literal `frontend/@/` directory. All 13 primitives were moved to `frontend/src/components/ui/` and the stray `frontend/@/` tree was deleted; the duplicate generated `button.tsx` was discarded to keep the existing tracked `button.tsx` unchanged. 4. **Stale generated `.pi-map.md`** files referenced the deleted `theme.ts`. Since the `project_map_patch` tool is unavailable this session, they were minimally reconciled to match source (these are gitignored generated docs, not tracked changes). ### Slice boundary / PR - Slice 1 is the foundation slice (~vendored primitives dominate the line count). Per the Review Workload Forecast this slice is "at/over" 400 lines and may be taken as a vendored-primitives **size exception** or sub-split 1a/1b. The majority of the diff is generated shadcn primitives (mechanical, vendored). **No delivery decision was provided in this run's delegation** (the parent delegated a single foundation slice); this run implemented the whole foundation in one slice. The parent should confirm the PR shape (1a/1b split vs. generated-code size exception) when opening the PR. ## Remaining tasks (Slices 2–8, 58 unchecked) Slice 1 is the only completed slice. The remaining unchecked work (Slices 2–8) is listed verbatim in `tasks.md`. The first unchecked items are (Slice 2 — Shared components): - [ ] Migrate `frontend/src/components/SectionCard.tsx` … - [ ] Migrate `frontend/src/components/SelectionRailCard.tsx` … - [ ] … (see `tasks.md` Slice 2 → Slice 8) Dependency order is fixed (DAG in `tasks.md`): `1 → 2 → {3,4,5,6}`; `4 ← 3`; `{3,4,5,6} → 7`; `{1..7} → 8`. **Next recommended:** Slice 2 (Shared components) — it is unblocked now that all primitives, TanStack Table, the Vitest harness, the `success`/ `warning` Badge variants, and the cleared `theme.ts` are in place. ## Risks / notes for the parent - The overall change `applyState` is **blocked** on **missing domain specs** (legacy flat `spec.md` present without domain specs). This does not block Slice 1 (done) but should be resolved before `sdd-verify`/archive. Recommended: convert/supplement `spec.md` into domain specs, or have the orchestrator confirm the flat spec is acceptable. - The two `react-hooks/exhaustive-deps` **warnings** in `UsersPage.impl.tsx` are pre-existing and out of Slice-1 scope (lint still passes — warnings, not errors). - `npm audit` reports 8 vulnerabilities in the dependency tree (pre-existing across the MUI/emotion/react stack); not introduced by this slice and out of scope. - `DatabaseBackup` from lucide-react is available — Slice 3 nav should use it directly. ## Slice 2 — Shared components (lock the building-block language) — COMPLETE All 12 Slice-2 task lines in `tasks.md` are now `- [x]`. The 11 shared components are MUI-free and the exported APIs are preserved so every consuming page still compiles unchanged (no `frontend/src/pages/*` file was edited this slice). ### Status context consumed - `applyState` reported by the status engine: **blocked** (`blockedReasons`: domain specs missing/partial; legacy flat `spec.md` present without domain specs). This is a **planning-completeness** gap, not a safety/`actionContext` blocker. `actionContext`: `mode: repo-local`, `allowedEditRoots: ["/home/user/Manage_01"]`, `warnings: []` — safe. - This run executed the explicitly delegated **Slice 2 (Shared components)** scope per the parent acceptance contract. Slice-2 work is fully specified in `tasks.md` + `design.md` (§1 mapping table, §2 typography ramp, §2.3 success Badge variant) and does not depend on the missing domain specs. The `instructions.apply` line says "Implement only unchecked tasks from the tasks artifact." → proceeded under the parent's explicit slice delegation. - `artifactStore: openspec`; persisted task checkboxes updated in `tasks.md` (Slice 2: 0 → 12 `[x]`). Cumulative change task progress: 13 → **25/71** complete. ### What was migrated (10 files rewritten; 1 already-clean) - `SectionCard.tsx` → shadcn `Card`/`CardContent`; `gap-4` comfortable density; section title `text-base font-semibold`, description `text-sm text-muted-foreground`. - `SelectionRailCard.tsx` → `Card` with titled header / scrollable body / footer. Preserved `minHeight` (inline style), `footer`, scrollable body. Legacy `contentSx`/`bodySx` MUI-sx props **retained in the interface as no-ops** so Actions/Settings compile unchanged. - `TabbedCard.tsx` → shadcn `Tabs` (`TabsList variant="line"`) on a `Card`. `value`/`onChange` stay string-typed (controlled); `tabs` stays `ReactElement[]`; legacy `contentSx`/`tabsSx` retained as no-op props. - `MetricCard.tsx` → `Card`/`CardContent` on the design ramp: label `text-sm`, value `text-lg font-semibold`, subtext `text-xs text-muted-foreground`. - `DiskSpaceCard.tsx` → `Card` + CSS grid (`grid-cols-1 sm:grid-cols-3 gap-4`) - shadcn `Progress`; used/free/total/percent breakdown preserved; the progress color cue (chart-2 success / chart-3 warning / destructive) is emitted via full static `[&_[data-slot=progress-indicator]]:bg-*` class strings (verified present in the built CSS). - `HoverEditButton.tsx` → `Button variant="ghost" size="icon-sm"` + lucide `Pencil`. Kept the `rail-edit` class + `opacity-0`/`transition-opacity` base so the existing hover-reveal rules in Actions/Settings (`&:hover .rail-edit { opacity: 1 }`) still target it until those pages migrate (slices 5). MUI `IconButton`+ `EditOutlined` removed. - `DialogFooter.tsx` → horizontal `Button` row (`flex flex-row items-center gap-2`). **All legacy props preserved**: `confirmColor` (`error`→`destructive` variant) and `confirmVariant` (`outlined`→`outline`, `text`→`ghost`, else `default`) are mapped internally onto shadcn variants; `confirmBusyLabel`, `confirmDisabled`, `confirmStartIcon`, `secondaryAction`, `cancelLabel` all honored. - `ConfirmDialog.tsx` → shadcn `Dialog` family (`DialogContent`/`DialogHeader`/ `DialogTitle`/`DialogDescription`) reusing the migrated `DialogFooter`. Esc / overlay click routes to `onCancel` via `onOpenChange`. Same exported props. - `LibraryOverview.tsx` → `Card`/`CardContent` on a responsive CSS grid (`grid grid-cols-1 md:grid-cols-2 gap-4`); movie/TV counts render verbatim. - `SessionActivityPanel.tsx` → shadcn `Table` family on a bordered rounded scrollable surface + `Badge` (status variant map per design §2.3: `playing`→ `success`/chart-2, `paused`→`warning`/chart-3, idle/other→`secondary`) + `Button` for the action. Row-click + action-button callbacks, status summary row, and transcoding formatting preserved. - `NowPlaying.tsx` → **already MUI-free** (it only imports `../types` + `./SessionActivityPanel`); left unchanged. Its empty-state message contract ("No recent user activity sessions right now.") is exercised by a new test. ### Component tests added (11 files, co-located under `src/components/__tests__/`) - `MetricCard` (label/value/subtext + subtext-omitted), `DiskSpaceCard` (used/ free/total + percent headline), `HoverEditButton` (onClick fires, custom label), `DialogFooter` (cancel/confirm callbacks, busy label, error→destructive, secondary action), `ConfirmDialog` (title/message render + confirm/cancel, closed renders nothing), `SectionCard` (title/description/action/children), `SelectionRailCard` (title/body/footer + `minHeight` applied), `TabbedCard` (renders triggers + reports selection change), `LibraryOverview` (movie/TV cards + counts), `SessionActivityPanel` (status→Badge variant mapping playing/paused/idle, empty-state, row-click + action-button callbacks), `NowPlaying` (dashboard empty-state message contract). ### Files changed (tracked) Modified (10 components): - `frontend/src/components/{SectionCard,SelectionRailCard,TabbedCard,MetricCard, DiskSpaceCard,HoverEditButton,DialogFooter,ConfirmDialog,LibraryOverview, SessionActivityPanel}.tsx` - `openspec/changes/web-ui-rework/tasks.md` (Slice 2 checkboxes 0 → 12 `[x]`) Added (new, 11 test files): - `frontend/src/components/__tests__/{MetricCard,DiskSpaceCard,HoverEditButton, DialogFooter,ConfirmDialog,SectionCard,SelectionRailCard,TabbedCard, LibraryOverview,SessionActivityPanel,NowPlaying}.test.tsx` Untouched (no-unintended-edits respected): **no `frontend/src/pages/*` file edited this slice** — `git status --porcelain frontend/src/pages` is empty. `NowPlaying.tsx` is unchanged (already MUI-free). No `components/ui/*` primitive was modified. ### Commands run (validation) — all green - `grep -rlE '@mui/(material|icons-material)' <11 files>` → **ALL 11 MUI-FREE**. - `cd frontend && npm run build` → **PASS** (`tsc -b` + `vite build`). - `cd frontend && npm run lint` → **PASS** (0 errors; the only 2 items are the pre-existing `react-hooks/exhaustive-deps` **warnings** in `UsersPage.impl.tsx`, out of Slice-2 scope). - `cd frontend && npm test` → **PASS** (Vitest: **12 files, 22 tests** passed; 11 new component tests + the slice-1 Badge smoke test). - `cd frontend && npm run test:node` → **PASS** (legacy node:test: 4 tests, 0 fail). - Verified Tailwind emitted the DiskSpaceCard `[data-slot=progress-indicator]` chart-2/3/destructive utilities into the built CSS (arbitrary-variant classes written as full static strings). ### API-preserving compromises (design constraints accepted to keep pages compiling) 1. **`SelectionRailCard` / `TabbedCard` legacy `*Sx` props.** Kept `contentSx`/`bodySx`/`tabsSx` in the prop interfaces as documented no-ops (MUI `sx` objects have no Tailwind equivalent). They are intentionally **not** destructured into locals (avoids `no-unused-vars`) and are ignored at render. Consumers (Actions, Settings) pass them today and compile unchanged. 2. **`DialogFooter` color/variant mapping.** `confirmColor`/`confirmVariant` are MUI-only concepts; they are retained on the API and mapped to shadcn Button variants (`error`→`destructive`, `outlined`→`outline`, `text`→`ghost`). All current consumers pass at most `confirmColor="error"` and `secondaryAction`, which map cleanly. No consumer passes `confirmVariant` explicitly today. 3. **`HoverEditButton` hover reveal.** Implemented with a Tailwind `opacity-0`/ `transition-opacity` base (not inline style) so the not-yet-migrated pages' MUI-sx `&:hover .rail-edit { opacity: 1 }` rules still override it on hover (higher specificity) during the interim. The `rail-edit` class is preserved. 4. **`SessionActivityPanel` status→Badge mapping.** `playing` (active/healthy) → `success` per the task's "healthy=`success`" + design §2.3; `paused`→`warning`; idle/unknown→`secondary`. This is the documented cue map (no app semantics changed — the prior MUI Chip used primary/warning/default coloring). ### Carry-over risk / top risk for slice 3 (and interim) - **`TabbedCard` interim tab-bar rendering.** Until Applications (slice 4) and Settings/Actions (slice 5) migrate, those pages still pass MUI `` elements as the `tabs` prop, which now render inside a shadcn ``. MUI `` outside an MUI `` does **not** throw (renders with a console warning) and the page content still swaps via the page's external `value` state, but the tab "active" highlight is cosmetic-only until those pages migrate. **Build / lint / test are unaffected.** This is an expected interim state of the chained model and resolves fully once slices 4–5 land. (Pages are intentionally not edited in slice 2.) - **Slice-2 PR budget.** Review churn ≈ 775 lines on the 10 rewritten components (303 ins / 472 del; mostly MUI teardown) + 335 lines of new tests ≈ ~1,110 changed lines, **over the ≤400 budget**. The forecast authorizes sub-split 2a (cards/buttons/dialogs) → 2b (tables/panels) on overrun. The parent delegated the whole slice as one unit and owns the commit/PR, so this run delivered it in one piece; the parent may split 2a/2b at PR time or take a size exception (mirroring the slice-1 precedent). Functionally the slice is complete and all gates are green. - Overall change `applyState` remains **blocked** on missing domain specs (legacy flat `spec.md`); does not block Slice 2 (done) but should be resolved before `sdd-verify`/archive. ### Remaining tasks (Slices 3–8, 46 unchecked) Slice 2 is complete (25/71 tasks). Next in dependency order: **Slice 3 — Backups cluster + nav/IA** (uses slice-2 `Table`/`Badge`/`Tabs`/cards and lands the `/backups` nav item + `/applications`→`/media` redirect; `DatabaseBackup` icon confirmed available). See `tasks.md` Slices 3–8 for the verbatim unchecked list. ## Slice 3 — Backups cluster + navigation/IA — COMPLETE All 8 Slice-3 task lines in `tasks.md` are now `- [x]`. The 5 Backups components are MUI-free and the reconciled IA (`/backups` top-level nav item, Media at `/media`, `/applications` → redirect) is live. Cumulative change task progress: 25 → **33/71** complete. ### Status context consumed - `applyState` reported by the status engine: **blocked** (`blockedReasons`: domain specs missing/partial; legacy flat `spec.md` present without domain specs). Same planning-completeness gap as slices 1–2 — **not** a safety or `actionContext` blocker. - `actionContext`: `mode: repo-local`, `workspaceRoot: /home/user/Manage_01`, `allowedEditRoots: ["/home/user/Manage_01"]`, `warnings: []` — safe. - This run executed the explicitly delegated **Slice 3 (Backups cluster + nav/IA)** scope per the parent acceptance contract. Slice-3 work is fully specified in `tasks.md` (Slice 3) + `design.md` (§1 mapping table, §2.3 status→Badge variant map, §4 IA) and does not depend on the missing domain specs. `instructions.apply`: "Implement only unchecked tasks from the tasks artifact." → proceeded under the parent's explicit slice delegation. - `artifactStore: openspec`; persisted task checkboxes updated in `tasks.md` (Slice 3: 0 → 8 `[x]`). ### Completed tasks (persisted checkboxes updated) - [x] Migrated `BackupAlertsTable.tsx` → shadcn `Table` family on a bordered rounded surface + `Badge` (`severityVariant`: `critical`→`destructive`, `warning`→`warning`) + `Button variant="outline" size="sm"` acknowledge (hidden when `acknowledged`). Preserve `formatTimestamp`, props, callback. - [x] Migrated `BackupJobsTable.tsx` → shadcn `Table` + `Badge` (`statusVariant`: `success`→`success`, `failure`→`destructive`, `in_progress`→`warning`, unknown→`secondary`). Latest-run status, `formatInterval`, last-run + next-expected timing preserved. - [x] Migrated `BackupRunsTable.tsx` → shadcn `Select` (status filter, `onValueChange`) + `Badge` (`success`/`destructive`/`warning`) + `Table`. `formatDuration`/`formatBytes`/`formatTimestamp` + filter logic preserved. - [x] Migrated `BackupDashboardWidget.tsx` → shadcn `Card`/`CardHeader`/ `CardTitle`/`CardContent` + `Badge` (active_alerts>0 → `destructive`) + `text-destructive` last-failed line. total_jobs / 24h success rate / active alerts / last-failed-time + loading state preserved. - [x] Migrated `BackupsPage.tsx` → shadcn `Tabs` (controlled `value`/ `onValueChange`, string values `jobs|runs|alerts`) with `TabsContent` per tab; jobs/runs/alerts behavior, latest-runs map, and the acknowledge mutation preserved. In-page `

` kept to match the already-migrated `ObservabilityPage`. - [x] Applied the §4 IA nav + route edits in `App.tsx`: imported `DatabaseBackup` from lucide-react; nav `Media` retargeted `/applications` → `/media`; added top-level `{ path: "/backups", label: "Backups", icon: DatabaseBackup }` (after Files, before Users); in **both** route blocks `/media` is the canonical `` element and `/applications` is `` (mirrors the existing `/monitoring` → `/observability` redirect). `/backups` already rendered ``. - [x] Added component tests: `BackupRunsTable` (status→Badge variant mapping + formatted duration/size), `BackupAlertsTable` (severity→variant, acknowledge callback fires with the id, acknowledged hides the button), `BackupDashboardWidget` (loading state, stats render, active_alerts>0 → destructive Badge + last-failed line). 8 new tests across 3 files. - [x] **Exit gate green:** `/backups` reachable from the sidebar; `/applications` redirects to `/media`; 5 Backups components MUI-free; `npm run build` + `npm run lint` + `npm test` + `npm run test:node` all pass. ### Files changed (tracked) Modified (6): - `frontend/src/components/BackupAlertsTable.tsx` - `frontend/src/components/BackupJobsTable.tsx` - `frontend/src/components/BackupRunsTable.tsx` - `frontend/src/components/BackupDashboardWidget.tsx` - `frontend/src/components/BackupsPage.tsx` - `frontend/src/App.tsx` (icon import, `navItems`, both route blocks) Added (new, 3 test files): - `frontend/src/components/__tests__/BackupRunsTable.test.tsx` - `frontend/src/components/__tests__/BackupAlertsTable.test.tsx` - `frontend/src/components/__tests__/BackupDashboardWidget.test.tsx` Persisted artifact updated: - `openspec/changes/web-ui-rework/tasks.md` (Slice 3 checkboxes 0 → 8 `[x]`) Untouched (no-unintended-edits respected): **no `frontend/src/pages/*` file was edited this slice** — `git status --porcelain frontend/src/pages` is empty. Applications.tsx itself was **not** edited (slice 4 owns it); only its route + nav entry changed. No `components/ui/*` primitive was modified. The pre-existing `/monitoring` → `/observability` redirect is intact. ### Commands run (validation) — all green - `grep -rlE '@mui/(material|icons-material)' <5 backups files>` → **ALL 5 BACKUPS MUI-FREE**. - `cd frontend && npm run build` → **PASS** (`tsc -b` + `vite build`; the >500 kB chunk-size warning is pre-existing and unrelated). - `cd frontend && npm run lint` → **PASS** (0 errors; the only 2 items are the pre-existing `react-hooks/exhaustive-deps` **warnings** in `UsersPage.impl.tsx`, out of Slice-3 scope). - `cd frontend && npm test` → **PASS** (Vitest: **15 files, 30 tests**; +3 files and +8 tests vs slice-2 baseline of 12 files / 22 tests + the badge smoke). - `cd frontend && npm run test:node` → **PASS** (legacy node:test: 4 tests, 0 fail). - IA verification (`grep` of `App.tsx`): nav has `/media` Media + `/backups` Backups; both route blocks have `/media` canonical + `/applications` Navigate redirect + `/backups` BackupsPage; `/monitoring`→`/observability` intact. ### Design decisions / deviations 1. **Backups nav icon: `DatabaseBackup`.** Verified at the pinned `lucide-react@^1.14.0` (slice-1 confirmation re-checked this run via `node -e "…require('lucide-react').DatabaseBackup"` → object). Semantic fit, no fallback needed. Placed after Files, before Users per design §4.1. 2. **`/applications` is a replace-redirect to `/media`** in both route blocks (OIDC-configured branch + unauthenticated branch), mirroring the existing `/monitoring` → `/observability` redirect. `/media` is the canonical `` route. React Router v6 ranks routes by specificity, so order is cosmetic; rendered `/media` before the `/applications` redirect to match design §4.2's example snippet. The `Applications.tsx` component file is **not** renamed (out of scope — design §4 non-goal). 3. **`BackupsPage` keeps an in-page `

`** ("Backups") to match the already-migrated `ObservabilityPage` (`

`). The shell's `
` already provides `p-4 md:p-6`, so the old `Box sx={{ p: 3 }}` double-padding was dropped per design §2.2. 4. **Status / severity → Badge variant** uses the `chart-N` cue map per design §2.3 (success=chart-2/success, failure/critical=chart-4/destructive, in_progress=chart-3/warning, unknown=secondary). Verified via the `data-variant` attribute assertions in the new tests (mirrors the slice-2 `SessionActivityPanel` test pattern). 5. **`BackupRunsTable` status filter** uses the shadcn `Select` (`onValueChange` with string values) instead of MUI `Select`/`FormControl`/`InputLabel`/ `MenuItem`; an `aria-label="Status filter"` is on the trigger for a11y. The `all|success|failure|in_progress` filter set is unchanged. 6. **`latestRuns` map left un-memoized** in `BackupsPage` (verbatim from the pre-migration source) to preserve behavior exactly and avoid an extra `useMemo`/exhaustive-deps surface. ### Slice boundary / PR - Slice 3 is forecast "likely OK" at ≤400 lines as a single PR (tasks §per-slice table). Actual review churn: ~6 modified components/App (~250 ins / ~270 del) - 3 new test files (~150 lines) ≈ **~400 changed lines**, right at the budget. The parent delegated the whole slice as one unit and owns the commit/PR; if the parent prefers, the nav/route edit in `App.tsx` is a clean split point. All gates are green. ### Remaining tasks (Slices 4–8, 38 unchecked) Slice 3 is complete (33/71 tasks). Next in dependency order: **Slice 4 — Dashboard + Applications surface** (depends on slices 2 + 3; reuses `BackupDashboardWidget` from this slice and the reconciled `/media` route; the `Applications.tsx` component itself is migrated here). The first unchecked items: - [ ] Migrate `frontend/src/pages/Applications.tsx` … - [ ] Migrate `frontend/src/pages/Dashboard.tsx` … - … (see `tasks.md` Slices 4–8 for the verbatim unchecked list) ### Top risk for slice 4 - **`Dashboard.tsx` is the heaviest single page** (20 distinct MUI components: Dialog/FormControl/FormControlLabel/Grid/Select/Switch/TextField/Stack/Grid…) and composes the slice-3 `BackupDashboardWidget` plus `NowPlaying`. It is forecast "medium" (~300–450 lines) and may need a 4a (Applications, smaller) → 4b (Dashboard) sub-split on overrun. The reconciled `/media` route + the shortcut deep-links must be re-pointed to `/media` (any Dashboard shortcut still linking `/applications` will rely on the new redirect until re-pointed). 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.