77c6b62ee2
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.
501 lines
30 KiB
Markdown
501 lines
30 KiB
Markdown
# 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 `<TabsList variant=…>`, 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 `<Tab>` elements
|
||
as the `tabs` prop, 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 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 `<h1>` 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
|
||
`<Applications />` element and `/applications` is `<Navigate to="/media"
|
||
replace />` (mirrors the existing `/monitoring` → `/observability` redirect).
|
||
`/backups` already rendered `<BackupsPage />`.
|
||
- [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
|
||
`<Applications />` 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 `<h1>`** ("Backups") to match the
|
||
already-migrated `ObservabilityPage` (`<h1 className="text-2xl font-bold
|
||
tracking-tight">`). The shell's `<main>` 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.
|