Web UI rework. - Migrate BackupAlertsTable, BackupJobsTable, BackupRunsTable, BackupsPage, BackupDashboardWidget off @mui (shadcn Table + Badge severity variants: success=chart-2, warning=chart-3, destructive) - App.tsx IA: Backups now top-level nav (DatabaseBackup icon); Media surface primary at /media; /applications -> /media redirect in both route trees (mirrors /monitoring -> /observability) Gate: build + lint + test green.
30 KiB
Apply Progress — web-ui-rework
Cumulative apply log. Updated by
sdd-applyafter each slice/sub-slice. Strict TDD is not active for this change (no strict-tdd directive inopenspec/config.yaml); this run used standard mode againstdesign.md/tasks.md.
Status context consumed
schemaName: gentle-pi.sdd-status,changeName: web-ui-rework.applyStatereported by the status engine: blocked (blockedReasons: domain specs missing/partial; legacy flatspec.mdpresent without domain specs). This is a planning-completeness gap, not a safety/actionContextblocker.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. Theinstructions.applyline itself says "Implement only unchecked tasks from the tasks artifact." artifactStore: openspec; persisted task checkboxes updated intasks.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)
- Added the missing shadcn primitives (13):
tabs table dialog input label checkbox switch progress separator avatar textarea dropdown-menu scroll-areavianpx shadcn@latest add …. The 9 already-present primitives (button tooltip sheet card badge alert select skeleton collapsible) were not re-added. - Installed
@tanstack/react-table(^8.21.3, dependency). - Removed orphaned charting deps:
recharts,d3, and the companion@types/d3frompackage.json(zerofrom "recharts"/from "d3"/from "d3-*"imports existed infrontend/src). - Deleted
frontend/src/theme.ts(no-opgetAppThemeshim). Grep confirmed the only reference was the file itself; no source file imported it. - Added the
successBadge variant (chart-2) and thewarningvariant (chart-3) tofrontend/src/components/ui/badge.tsx, mirroring thedestructivesoft-cue pattern exactly per design §2.3. - Documented the
chart-1..5role mapping as an inline comment block above the tokens in both the@themeblock and.darkblock offrontend/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. - Created
frontend/vitest.config.ts(separate fromvite.config.ts):vitest/configdefineConfig,@vitejs/plugin-react,environment: "jsdom",globals: true,setupFiles: ["./src/test/setup.ts"], the@path alias fromtsconfig.app.json, andinclude: ["src/**/*.{test,spec}.{ts,tsx}"](does NOT claim thefrontend/tests/*.mjsnode suites). - Created
frontend/src/test/setup.tsregistering@testing-library/jest-dommatchers (via the/vitestentry for runtime + TS augmentation). - 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. - Added npm scripts:
"test": "vitest run","test:watch": "vitest","test:node": "node --test tests/*.test.mjs"(see Deviations). - Added one trivial passing component test:
src/components/ui/__tests__/badge.test.tsx(asserts asuccessBadge renders with thechart-2cue). - Verified lucide-react
1.14.0exports every name used by later slices:Pencil, X, Paperclip, Bold, Italic, Link, List, Mail, Send, Trash2, DatabaseBackup, ExternalLink.DatabaseBackupIS exported — no fallback needed (slice-3 nav will useDatabaseBackup). - Exit gate green:
npm run build+npm run lint+npm test+npm run test:nodeall pass; notheme/getAppThemeimports remain.
Files changed (tracked)
Modified:
frontend/package.json(added@tanstack/react-table, vitest/testing-library/jsdom dev deps, test scripts; removedrecharts,d3,@types/d3)frontend/package-lock.json(regenerated by npm)frontend/src/components/ui/badge.tsx(addedsuccess+warningvariants)frontend/src/components/ui/tabs.tsx(vendored primitive; un-exportedtabsListVariantsto satisfyreact-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.tsfrontend/src/test/setup.tsfrontend/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-existingreact-hooks/exhaustive-depswarnings inUsersPage.impl.tsxthat 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)
tabs.tsxvariant export. The vendored shadcntabs.tsxexportedtabsListVariants(acvafunction) alongside components, which violatesreact-refresh/only-export-componentsand brokenpm run lint. The project's own convention (button.tsx,select.tsx) is to not export thecvavariant helper, sotabsListVariantswas removed from theexport { … }statement (theconstis retained for internal use byTabsList). This keeps the component API intact for all later slices (TabbedCard uses<TabsList variant=…>, not the helper).test:nodescript 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 --testauto-discovery also passes; the glob form is the explicit, minimal choice.- shadcn CLI path artifact. The CLI resolved the
@/alias to a literalfrontend/@/directory. All 13 primitives were moved tofrontend/src/components/ui/and the strayfrontend/@/tree was deleted; the duplicate generatedbutton.tsxwas discarded to keep the existing trackedbutton.tsxunchanged. - Stale generated
.pi-map.mdfiles referenced the deletedtheme.ts. Since theproject_map_patchtool 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.mdSlice 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
applyStateis blocked on missing domain specs (legacy flatspec.mdpresent without domain specs). This does not block Slice 1 (done) but should be resolved beforesdd-verify/archive. Recommended: convert/supplementspec.mdinto domain specs, or have the orchestrator confirm the flat spec is acceptable. - The two
react-hooks/exhaustive-depswarnings inUsersPage.impl.tsxare pre-existing and out of Slice-1 scope (lint still passes — warnings, not errors). npm auditreports 8 vulnerabilities in the dependency tree (pre-existing across the MUI/emotion/react stack); not introduced by this slice and out of scope.DatabaseBackupfrom 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
applyStatereported by the status engine: blocked (blockedReasons: domain specs missing/partial; legacy flatspec.mdpresent without domain specs). This is a planning-completeness gap, not a safety/actionContextblocker.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. Theinstructions.applyline says "Implement only unchecked tasks from the tasks artifact." → proceeded under the parent's explicit slice delegation. artifactStore: openspec; persisted task checkboxes updated intasks.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→ shadcnCard/CardContent;gap-4comfortable density; section titletext-base font-semibold, descriptiontext-sm text-muted-foreground.SelectionRailCard.tsx→Cardwith titled header / scrollable body / footer. PreservedminHeight(inline style),footer, scrollable body. LegacycontentSx/bodySxMUI-sx props retained in the interface as no-ops so Actions/Settings compile unchanged.TabbedCard.tsx→ shadcnTabs(TabsList variant="line") on aCard.value/onChangestay string-typed (controlled);tabsstaysReactElement[]; legacycontentSx/tabsSxretained as no-op props.MetricCard.tsx→Card/CardContenton the design ramp: labeltext-sm, valuetext-lg font-semibold, subtexttext-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).
- shadcn
HoverEditButton.tsx→Button variant="ghost" size="icon-sm"+ lucidePencil. Kept therail-editclass +opacity-0/transition-opacitybase so the existing hover-reveal rules in Actions/Settings (&:hover .rail-edit { opacity: 1 }) still target it until those pages migrate (slices 5). MUIIconButton+EditOutlinedremoved.DialogFooter.tsx→ horizontalButtonrow (flex flex-row items-center gap-2). All legacy props preserved:confirmColor(error→destructivevariant) andconfirmVariant(outlined→outline,text→ghost, elsedefault) are mapped internally onto shadcn variants;confirmBusyLabel,confirmDisabled,confirmStartIcon,secondaryAction,cancelLabelall honored.ConfirmDialog.tsx→ shadcnDialogfamily (DialogContent/DialogHeader/DialogTitle/DialogDescription) reusing the migratedDialogFooter. Esc / overlay click routes toonCancelviaonOpenChange. Same exported props.LibraryOverview.tsx→Card/CardContenton a responsive CSS grid (grid grid-cols-1 md:grid-cols-2 gap-4); movie/TV counts render verbatim.SessionActivityPanel.tsx→ shadcnTablefamily 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) +Buttonfor 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 +minHeightapplied),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}.tsxopenspec/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-existingreact-hooks/exhaustive-depswarnings inUsersPage.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)
SelectionRailCard/TabbedCardlegacy*Sxprops. KeptcontentSx/bodySx/tabsSxin the prop interfaces as documented no-ops (MUIsxobjects have no Tailwind equivalent). They are intentionally not destructured into locals (avoidsno-unused-vars) and are ignored at render. Consumers (Actions, Settings) pass them today and compile unchanged.DialogFootercolor/variant mapping.confirmColor/confirmVariantare 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 mostconfirmColor="error"andsecondaryAction, which map cleanly. No consumer passesconfirmVariantexplicitly today.HoverEditButtonhover reveal. Implemented with a Tailwindopacity-0/transition-opacitybase (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. Therail-editclass is preserved.SessionActivityPanelstatus→Badge mapping.playing(active/healthy) →successper 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)
TabbedCardinterim tab-bar rendering. Until Applications (slice 4) and Settings/Actions (slice 5) migrate, those pages still pass MUI<Tab>elements as thetabsprop, which now render inside a shadcn<TabsList>. MUI<Tab>outside an MUI<Tabs>does not throw (renders with a console warning) and the page content still swaps via the page's externalvaluestate, 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
applyStateremains blocked on missing domain specs (legacy flatspec.md); does not block Slice 2 (done) but should be resolved beforesdd-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
applyStatereported by the status engine: blocked (blockedReasons: domain specs missing/partial; legacy flatspec.mdpresent without domain specs). Same planning-completeness gap as slices 1–2 — not a safety oractionContextblocker.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 intasks.md(Slice 3: 0 → 8[x]).
Completed tasks (persisted checkboxes updated)
- Migrated
BackupAlertsTable.tsx→ shadcnTablefamily on a bordered rounded surface +Badge(severityVariant:critical→destructive,warning→warning) +Button variant="outline" size="sm"acknowledge (hidden whenacknowledged). PreserveformatTimestamp, props, callback. - Migrated
BackupJobsTable.tsx→ shadcnTable+Badge(statusVariant:success→success,failure→destructive,in_progress→warning, unknown→secondary). Latest-run status,formatInterval, last-run + next-expected timing preserved. - Migrated
BackupRunsTable.tsx→ shadcnSelect(status filter,onValueChange) +Badge(success/destructive/warning) +Table.formatDuration/formatBytes/formatTimestamp+ filter logic preserved. - Migrated
BackupDashboardWidget.tsx→ shadcnCard/CardHeader/CardTitle/CardContent+Badge(active_alerts>0 →destructive) +text-destructivelast-failed line. total_jobs / 24h success rate / active alerts / last-failed-time + loading state preserved. - Migrated
BackupsPage.tsx→ shadcnTabs(controlledvalue/onValueChange, string valuesjobs|runs|alerts) withTabsContentper tab; jobs/runs/alerts behavior, latest-runs map, and the acknowledge mutation preserved. In-page<h1>kept to match the already-migratedObservabilityPage. - Applied the §4 IA nav + route edits in
App.tsx: importedDatabaseBackupfrom lucide-react; navMediaretargeted/applications→/media; added top-level{ path: "/backups", label: "Backups", icon: DatabaseBackup }(after Files, before Users); in both route blocks/mediais the canonical<Applications />element and/applicationsis<Navigate to="/media" replace />(mirrors the existing/monitoring→/observabilityredirect)./backupsalready rendered<BackupsPage />. - 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. - Exit gate green:
/backupsreachable from the sidebar;/applicationsredirects to/media; 5 Backups components MUI-free;npm run build+npm run lint+npm test+npm run test:nodeall pass.
Files changed (tracked)
Modified (6):
frontend/src/components/BackupAlertsTable.tsxfrontend/src/components/BackupJobsTable.tsxfrontend/src/components/BackupRunsTable.tsxfrontend/src/components/BackupDashboardWidget.tsxfrontend/src/components/BackupsPage.tsxfrontend/src/App.tsx(icon import,navItems, both route blocks)
Added (new, 3 test files):
frontend/src/components/__tests__/BackupRunsTable.test.tsxfrontend/src/components/__tests__/BackupAlertsTable.test.tsxfrontend/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; the500 kB chunk-size warning is pre-existing and unrelated).
cd frontend && npm run lint→ PASS (0 errors; the only 2 items are the pre-existingreact-hooks/exhaustive-depswarnings inUsersPage.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 (
grepofApp.tsx): nav has/mediaMedia +/backupsBackups; both route blocks have/mediacanonical +/applicationsNavigate redirect +/backupsBackupsPage;/monitoring→/observabilityintact.
Design decisions / deviations
- Backups nav icon:
DatabaseBackup. Verified at the pinnedlucide-react@^1.14.0(slice-1 confirmation re-checked this run vianode -e "…require('lucide-react').DatabaseBackup"→ object). Semantic fit, no fallback needed. Placed after Files, before Users per design §4.1. /applicationsis a replace-redirect to/mediain both route blocks (OIDC-configured branch + unauthenticated branch), mirroring the existing/monitoring→/observabilityredirect./mediais the canonical<Applications />route. React Router v6 ranks routes by specificity, so order is cosmetic; rendered/mediabefore the/applicationsredirect to match design §4.2's example snippet. TheApplications.tsxcomponent file is not renamed (out of scope — design §4 non-goal).BackupsPagekeeps an in-page<h1>("Backups") to match the already-migratedObservabilityPage(<h1 className="text-2xl font-bold tracking-tight">). The shell's<main>already providesp-4 md:p-6, so the oldBox sx={{ p: 3 }}double-padding was dropped per design §2.2.- Status / severity → Badge variant uses the
chart-Ncue map per design §2.3 (success=chart-2/success, failure/critical=chart-4/destructive, in_progress=chart-3/warning, unknown=secondary). Verified via thedata-variantattribute assertions in the new tests (mirrors the slice-2SessionActivityPaneltest pattern). BackupRunsTablestatus filter uses the shadcnSelect(onValueChangewith string values) instead of MUISelect/FormControl/InputLabel/MenuItem; anaria-label="Status filter"is on the trigger for a11y. Theall|success|failure|in_progressfilter set is unchanged.latestRunsmap left un-memoized inBackupsPage(verbatim from the pre-migration source) to preserve behavior exactly and avoid an extrauseMemo/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.tsxis a clean split point. All gates are green.
- 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
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.mdSlices 4–8 for the verbatim unchecked list)
Top risk for slice 4
Dashboard.tsxis the heaviest single page (20 distinct MUI components: Dialog/FormControl/FormControlLabel/Grid/Select/Switch/TextField/Stack/Grid…) and composes the slice-3BackupDashboardWidgetplusNowPlaying. It is forecast "medium" (~300–450 lines) and may need a 4a (Applications, smaller) → 4b (Dashboard) sub-split on overrun. The reconciled/mediaroute + the shortcut deep-links must be re-pointed to/media(any Dashboard shortcut still linking/applicationswill rely on the new redirect until re-pointed). Overall changeapplyStateremains blocked on missing domain specs (legacy flatspec.md); does not block Slice 3 (done) but should be resolved beforesdd-verify/archive.