chore(openspec): verify + sync reports, canonical web-ui spec (rework PASS)

Web UI rework lifecycle: verify + sync phases.
- verify-report.md: PASS verdict (23/64 vitest, 5/5 node, zero @mui in src,
  71/71 tasks, all spec scenarios green). Non-blocking findings recorded:
  slice-5 review-budget overage (5a/5b not split); node --test tests cmd typo.
- sync-report.md + openspec/specs/web-ui/spec.md: canonical domain spec
  distilled from the verified change (design system, thin-dashboard model,
  TanStack visibility-only tables, reconciled IA, removed deps, Vitest harness).
- Change-side delta spec under openspec/changes/web-ui-rework/specs/.
This commit is contained in:
Developer
2026-06-17 19:15:15 +00:00
parent 9de2d5b8d2
commit 7646f3236f
4 changed files with 1020 additions and 0 deletions
@@ -0,0 +1,282 @@
# Web UI — Delta (`web-ui-rework`)
> Change: `web-ui-rework` · Domain: `web-ui` · Phase: **spec** (reconciled during `sdd-sync`).
> Distilled from the verified flat `spec.md` + `design.md` of change `web-ui-rework`. Captures the
> **durable, post-change end-state contracts** of the Manage web frontend, not the migration steps
> (the per-slice delivery strategy and per-slice build/lint gate are intentionally excluded as
> migration-process contracts; they remain documented in the change `spec.md` / `tasks.md`).
## ADDED Requirements
> The canonical `openspec/specs/web-ui/spec.md` did not exist before this change. All requirements
> below are therefore **ADDED** to a new `web-ui` domain; `sdd-sync` copies them verbatim into the
> canonical spec (native helper rule: when the canonical spec does not exist, the change spec
> becomes the new canonical spec).
### Requirement: Single design system
The frontend MUST render every surface using only **shadcn/ui** primitives, **Tailwind v4** `@theme`
tokens (configured in `frontend/src/index.css`), and **lucide-react** icons. The application MUST NOT
introduce any additional component or styling library, and MUST NOT retain any `@mui/*` or
`@emotion/*` import or dependency.
#### Scenario: No MUI or Emotion remains in source or dependencies
- GIVEN the `web-ui-rework` change is fully applied
- WHEN a recursive search of `frontend/src` is performed for imports from `@mui/material`, `@mui/icons-material`, `@mui/x-data-grid`, `@emotion/react`, or `@emotion/styled`
- THEN the search returns zero matches
- AND none of `@mui/material`, `@mui/icons-material`, `@mui/x-data-grid`, `@emotion/react`, or `@emotion/styled` is listed in `frontend/package.json`
#### Scenario: No additional design system adopted
- GIVEN the rework is applied
- WHEN `frontend/package.json` dependencies are inspected
- THEN no component or styling library other than the existing shadcn/ui + Tailwind v4 + lucide-react + Radix primitives stack is present
### Requirement: No in-app charting and orphaned charting dependencies removed
The frontend MUST NOT render any in-app chart, sparkline, time-series canvas, or log-stream
visualization. The unused `recharts` and `d3` dependencies MUST be absent from
`frontend/package.json`, and the no-op `frontend/src/theme.ts` shim MUST be deleted. Observability
metric surfaces MUST be expressed only as numeric values, status Badges, or outbound Grafana
deep-link affordances.
#### Scenario: Charting dependencies are gone
- GIVEN the rework is applied
- WHEN `frontend/package.json` is inspected
- THEN neither `recharts` nor `d3` is listed as a dependency
- AND a recursive search of `frontend/src` for imports from `recharts`, `d3`, or `d3-*` returns zero matches
#### Scenario: No chart component exists
- GIVEN the rework is applied
- WHEN the migrated pages and components are inspected
- THEN no component renders an in-app chart, sparkline, or graph canvas
- AND metric surfaces render only as numeric values, status Badges, or outbound Grafana deep-links
#### Scenario: Theme shim removed
- GIVEN the foundation slice is applied
- WHEN `frontend/src/theme.ts` is checked for existence
- THEN it does not exist
- AND no import of `theme` or `getAppTheme` remains in `frontend/src`
### Requirement: Design tokens — primary color and repurposed status cues
The primary color MUST resolve to `#4f8cff` in both the light and dark themes. The existing
`chart-1` through `chart-5` CSS tokens in `frontend/src/index.css` MUST be retained and documented
as the single source of truth for **status / Grafana-link semantic color cues** (info / success /
warning / destructive / neutral-accent), and MUST NOT be dropped. The Badge component MUST expose
`success` and `warning` variants that draw color from these cues.
#### Scenario: Primary color unchanged
- GIVEN the rework is applied
- WHEN `frontend/src/index.css` is inspected
- THEN the primary color token resolves to `#4f8cff` in both the `@theme` (light) and `.dark` blocks
#### Scenario: Chart tokens retained and semantically repurposed
- GIVEN the rework is applied
- WHEN `frontend/src/index.css` is inspected
- THEN the `chart-1` through `chart-5` tokens are still defined in both blocks
- AND they are documented/applied as status / Grafana-link semantic cues (info, success, warning, destructive, neutral-accent)
- AND status Badges draw their variant colors from these cues
### Requirement: Comfortable visual density with no compact mode
Every surface MUST use comfortable density: page padding `p-4 md:p-6` (matching the migrated shell
`<main>`) and card spacing `gap-4`. The application MUST NOT introduce a compact or dense mode for
any table, list, or panel.
#### Scenario: Comfortable density on every page
- GIVEN each migrated page is rendered
- WHEN the page content container is inspected
- THEN it uses comfortable padding consistent with the shell (`p-4 md:p-6`) and card gaps (`gap-4`)
#### Scenario: No compact mode exists
- GIVEN the rework is applied
- WHEN the frontend is inspected for a density/compact toggle or compact spacing classes on tables
- THEN no compact or dense mode exists for Media, Users, Backups runs, or any other surface
### Requirement: Status Badge semantic variants
The Badge component MUST provide variants covering success (healthy/OK), info (default), neutral
(secondary), warning, and destructive (error / firing alert). Status displays across Backups,
Observability, and Users MUST map status values to these variants consistently via the repurposed
`chart-*` cues.
#### Scenario: Healthy status uses a success cue
- GIVEN a monitoring target or backup run is in a healthy/OK state
- WHEN its status is rendered
- THEN it displays a Badge using the success color cue (from `chart-2`)
#### Scenario: Firing alert uses a destructive cue
- GIVEN an Alertmanager alert is firing
- WHEN its status is rendered
- THEN it displays a destructive Badge (from `chart-4`)
### Requirement: Information architecture — Backups top-level navigation
The **Backups** surface MUST be exposed as a top-level navigation item in the sidebar, linking to the
existing `/backups` route.
#### Scenario: Backups is reachable from the sidebar
- GIVEN the application shell is rendered
- WHEN the sidebar navigation items are inspected
- THEN a top-level "Backups" item is present
- AND selecting it navigates to `/backups` and renders the Backups page
### Requirement: Information architecture — Media route and legacy redirects
The Media/Applications surface MUST be named **"Media"** going forward, and its canonical route MUST
be `/media`. The legacy `/applications` route MUST redirect (replace) to `/media`, mirroring the
existing `/monitoring``/observability` redirect. The OIDC authentication model and the
`react-router-dom` routing structure MUST be otherwise unchanged.
#### Scenario: Media route is the canonical entry
- GIVEN the rework is applied
- WHEN the sidebar "Media" item is selected
- THEN the browser navigates to `/media`
- AND the Media surface is rendered under the name "Media"
#### Scenario: Legacy /applications redirects to /media
- GIVEN the application is running
- WHEN a user navigates directly to `/applications`
- THEN the client router issues a replace redirect to `/media`
- AND the Media surface is rendered (same pattern as `/monitoring``/observability`)
#### Scenario: Auth and routing model unchanged
- GIVEN the rework is applied
- WHEN the authentication and routing setup is inspected
- THEN the OIDC flow (`auth.ts`, `react-oidc-context`) and the `react-router-dom` structure are unchanged
- AND only nav items, the `/media` route, and the `/applications` redirect differ from the prior state
### Requirement: Data tables use TanStack Table with visibility-only features
The Media and FileBrowser data grids MUST be built on `@tanstack/react-table` behind a shared
`DataTable` wrapper styled with the shadcn `Table` primitive. The rebuilt tables MUST reproduce only
the features in use: **column visibility**, **pagination** (where present), **row selection**, and
**row click**. The tables MUST NOT add column sorting, and MUST NOT add column resizing.
#### Scenario: No sorting added
- GIVEN the rebuilt Media and FileBrowser tables are rendered
- WHEN the table headers and column definitions are inspected
- THEN no `getSortedRowModel`, sortable-column behavior, or sort affordance is present
#### Scenario: No column resizing added
- GIVEN the rebuilt Media and FileBrowser tables are rendered
- WHEN the column edges are inspected
- THEN no column-resize handles or resizing behavior are present
### Requirement: Data table interaction parity preserved
The rebuilt tables MUST preserve their pre-rework interaction behavior: column visibility (Media's
15 columns and FileBrowser's 5 columns), pagination (Media, server-driven via the query layer), row
selection, and row click.
#### Scenario: Column visibility preserved
- GIVEN the rebuilt tables are rendered
- WHEN a user toggles the visibility of a column
- THEN on Media the toggleable columns match `title, series, season, episode, type, year, runtime, size, bitrate, hdr, video, resolution, date_added, library, path`
- AND on FileBrowser the toggleable columns match `type, name, ext, size, modified`
#### Scenario: Media pagination parity
- GIVEN the rebuilt Media table is rendered with more rows than one page
- WHEN a user changes the page size and navigates between pages
- THEN the visible rows, total count, and page index update consistently with the pre-rework behavior (page index/size drive the query layer's limit/offset)
#### Scenario: Row selection preserved
- GIVEN the rebuilt tables are rendered
- WHEN a user selects and deselects rows via the selection control
- THEN the selection state is maintained across pagination and matches the pre-rework model
#### Scenario: Row click behavior preserved
- GIVEN the rebuilt tables are rendered
- WHEN a user clicks a Media row
- THEN navigation occurs to the file browser at the clicked item's path
- AND WHEN a user clicks a FileBrowser row
- THEN that file is selected for ffprobe preview
### Requirement: Thin-dashboard observability parity
The Observability surface MUST continue to render Alertmanager alerts, Prometheus target health,
monitoring-machine status, and Grafana deep-link cards (kiosk iframe URLs and Explore links). The
rework MUST NOT introduce any in-app chart, and MUST preserve all existing Grafana deep-link
affordances.
#### Scenario: Alerts and target health still shown
- GIVEN the Observability page is rendered with Alertmanager and Prometheus data available
- THEN Alertmanager alerts and Prometheus target health are displayed as before the rework
#### Scenario: Grafana deep-links preserved
- GIVEN the Observability page is rendered
- WHEN a Grafana deep-link card is inspected
- THEN the outbound kiosk/explore URL with instance variables is preserved
- AND a consistent external-link affordance is present across the surface
#### Scenario: No in-app chart on observability
- GIVEN the Observability page is rendered
- WHEN metric surfaces are inspected
- THEN no in-app chart is rendered; metrics are numbers, status Badges, or Grafana deep-links only
### Requirement: Frontend component test harness
A **Vitest** + **@testing-library/react** component test harness MUST be present and configured.
Migrated components MUST be covered by behavioral component tests. The legacy
`frontend/tests/*.mjs` `node --test` suites MUST continue to run and pass.
#### Scenario: Vitest harness is present
- GIVEN the rework is applied
- WHEN `frontend/package.json` and config are inspected
- THEN Vitest and `@testing-library/react` are installed
- AND a Vitest test script is configured
#### Scenario: Legacy node:test suites keep passing
- GIVEN the rework is applied
- WHEN the `frontend/tests/*.mjs` suites are executed with `node --test`
- THEN all pre-existing assertions still pass
### Requirement: Documentation reflects the post-rework architecture
`docs/REQUIREMENTS.md` MUST reflect the post-rework UX and architecture: the single design system
(shadcn/ui + Tailwind + lucide-react), the thin-dashboard observability model (no in-app charts),
TanStack tables, the `/media` route with `/applications` redirect, the Backups top-level nav item,
and the removal of `@mui/*`, `@emotion/*`, `recharts`, and `d3`.
#### Scenario: Requirements doc reflects the rework
- GIVEN the final slice is applied
- WHEN `docs/REQUIREMENTS.md` is inspected
- THEN it documents the shadcn/ui + Tailwind + lucide-react stack, the thin-dashboard observability model (no in-app charts), the `/media` route with `/applications` redirect, the Backups top-level nav item, and the removed dependencies
### Requirement: No backend API or frontend data-contract changes
The rework MUST NOT change any backend API contract or any type in `frontend/src/types/*`. If a UI
simplification forces a contract change, that change MUST be flagged and approved separately.
#### Scenario: Frontend data contracts unchanged
- GIVEN the rework is applied
- WHEN `frontend/src/types/*` and the backend API surface are compared to the pre-rework state
- THEN no backend endpoint, response shape, or shared frontend type is altered by this change
@@ -0,0 +1,177 @@
# Sync Report — `web-ui-rework`
> Phase: **sync** · Change: `web-ui-rework` · Repo: `/home/user/Manage_01`
> Mode: file-backed (`artifactStore: openspec`). No source-code edits; only OpenSpec artifacts were written. Not committed (parent owns the commit).
**Status: SYNCED.** Canonical `openspec/specs/web-ui/spec.md` created from the verified change;
the change-side domain delta spec that unblocks the native status engine is also in place. Archive
gate is now satisfiable (see §5).
---
## 1. Executive summary
The `web-ui-rework` change shipped a **complete but flat** `openspec/changes/web-ui-rework/spec.md`
(15 requirements, declared `Domain: web-ui`) with **no** per-domain delta specs under
`openspec/changes/web-ui-rework/specs/<domain>/`. The native `gentle-pi.sdd-status` engine
consequently reported `artifacts.specs: partial`, `legacyFlatSpec.hasDomainSpecs: false`, and
`applyState/sync/archive: blocked`, with blocked reasons *"domain specs are missing or partial"* and
*"Legacy flat spec is present without domain specs."*
Verify already returned **PASS** (verdict in `verify-report.md`; all gates green, 71/71 tasks,
zero blockers). The flat-spec-vs-domain-spec gap was an **artifact-format** condition gating
sync/archive, not a verification blocker. This sync **reconciles** it:
1. Authored the missing **change-side domain delta spec**
`openspec/changes/web-ui-rework/specs/web-ui/spec.md` — using a clean `## ADDED Requirements`
structure with stable requirement IDs. This is what flips the native status engine's `specs`
artifact from `partial``done` and clears the legacy-flat-without-domain-specs block.
2. **Synced** the end-state into the **canonical store**
`openspec/specs/web-ui/spec.md` — the actual sync target. Because the canonical `web-ui`
domain did not previously exist, the native helper rule applies: *when the canonical spec does
not exist, the change spec becomes the new canonical spec.* The two files therefore carry the
same requirement bodies (delta under `## ADDED Requirements`; canonical under `## Requirements`).
Domain name **`web-ui`** was chosen to match the change's own declared domain (`spec.md` header) and
the proposal context; it keeps `changes/web-ui-rework/specs/web-ui/` and `openspec/specs/web-ui/`
aligned.
## 2. Structured status & actionContext findings
Consumed from the authoritative `gentle-pi.sdd-status` passed by the parent (treated as
authoritative over prompt inference):
- `changeName: web-ui-rework`, `artifactStore: openspec`, change root correct.
- **Pre-sync:** `artifacts.specs: partial`; `artifactPaths.specs: []`; `legacyFlatSpec.path =
openspec/changes/web-ui-rework/spec.md`, `hasDomainSpecs: false`; `sync: blocked`, `archive:
blocked`.
- `taskProgress`: total 71 / complete 71 / remaining 0 / unchecked [] — independently re-confirmed
in the verify report (zero `- [ ]` lines).
- `verify: ready` (verify-report verdict **PASS**).
- `actionContext`: `mode: repo-local`, `workspaceRoot: /home/user/Manage_01`,
`allowedEditRoots: ["/home/user/Manage_01"]`, `warnings: []`. All artifacts written are inside
the authoritative workspace / allowed edit roots. ✓
- `relationships.sameDomainActiveChanges: []`, `collisions: []` — **no active same-domain
collisions**, so no archive/sync ordering decision was required.
**Post-sync structural change:** `openspec/changes/web-ui-rework/specs/web-ui/spec.md` now exists
(`hasDomainSpecs` → true; `artifactPaths.specs` populated), which resolves both pre-sync blocked
reasons. The flat `spec.md` is intentionally **left in place** as the authoritative contract the
work was built against; it no longer triggers the "flat spec *without* domain specs" condition now
that a domain spec sits alongside it.
## 3. Domains synced & canonical files updated
| Domain | Change-side delta (source) | Canonical (sync target) | Action |
|---|---|---|---|
| `web-ui` | `openspec/changes/web-ui-rework/specs/web-ui/spec.md` | `openspec/specs/web-ui/spec.md` | **NEW domain** — `## ADDED Requirements` copied into canonical as a new spec |
- **Canonical file created:** `openspec/specs/web-ui/spec.md` (13 requirements).
- **Change-side delta created:** `openspec/changes/web-ui-rework/specs/web-ui/spec.md` (13
requirements, all `## ADDED Requirements`).
## 4. Requirement delta (ADDED / MODIFIED / REMOVED)
- **ADDED (13)** — all to the new `web-ui` domain (canonical did not exist pre-change):
1. Single design system
2. No in-app charting and orphaned charting dependencies removed
3. Design tokens — primary color and repurposed status cues
4. Comfortable visual density with no compact mode
5. Status Badge semantic variants
6. Information architecture — Backups top-level navigation
7. Information architecture — Media route and legacy redirects
8. Data tables use TanStack Table with visibility-only features
9. Data table interaction parity preserved
10. Thin-dashboard observability parity
11. Frontend component test harness
12. Documentation reflects the post-rework architecture
13. No backend API or frontend data-contract changes
- **MODIFIED (0)** — none (new domain; no pre-existing canonical requirements to replace).
- **REMOVED (0)** — none.
- **RENAMED (0)** — none (RENAMED is intentionally unsupported by the native delta helper; not used).
The 13 end-state requirements were **distilled** from the verified flat `spec.md` (15 requirements)
- `design.md`. Two of the flat spec's requirements were **intentionally not carried into the
canonical/delta** because they are **migration-process contracts, not durable end-state**:
- *"Per-slice build and lint green gate"* and *"Eight-slice delivery strategy"* — these describe
*how the rework was delivered*, not what the system *is* afterwards. They remain on record in the
change's flat `spec.md` and `tasks.md`. (Build/lint/test greenness is, of course, reflected
indirectly by the design-system, test-harness, and observability requirements.)
## 5. Guardrails, approvals & destructive-sync assessment
- **Same-domain collisions:** none (`sameDomainActiveChanges: []`). No ordering decision needed.
- **Destructive sync:** **not applicable.** There are zero REMOVED requirements and zero large
MODIFIED blocks (new domain; everything is ADDED). No parent approval was required for this sync
beyond the explicit reconciliation instruction in the task.
- **Legacy flat spec:** detected pre-sync; resolved by adding the domain delta spec alongside it
(the block condition was specifically "flat spec *without* domain specs").
- **No backend / data-contract impact:** the rework's own non-goal ("no backend API / frontend
types changes") is preserved as canonical requirement #13; this sync touches only OpenSpec docs.
## 6. Validation / checks performed (file-backed, read-only)
Run from `/home/user/Manage_01` (no source edits, no test re-runs — those are owned by verify and
were already green at `baf412b`):
| Check | Command | Result |
|---|---|---|
| Canonical store populated | `find openspec/specs -type f` | `openspec/specs/web-ui/spec.md` ✓ |
| Change-side domain spec present | `find openspec/changes/web-ui-rework/specs -type f` | `openspec/changes/web-ui-rework/specs/web-ui/spec.md` ✓ |
| Requirement-ID parity (delta ↔ canonical) | `grep -E '^### Requirement:'` both files | **13 == 13**, identical IDs in identical order ✓ |
| Delta is pure ADDED | count `## ADDED/MODIFIED/REMOVED/RENAMED Requirements` | ADDED=1, MODIFIED=0, REMOVED=0, RENAMED=0 ✓ (no destructive sync) |
| No edits outside openspec | `git status --porcelain \| grep -vE 'openspec/'` | "no edits outside openspec" ✓ |
| Markdown validity | write-time lint | both files "Markdown clean" ✓ |
## 7. Archive-gate readiness
After this sync the native status archive gate is **satisfiable**:
- ✅ Verify clean — `verify-report.md` verdict **PASS**; 23/64 vitest, 5/5 `node --test`, build +
lint green at `baf412b`.
- ✅ Completed sync — canonical `openspec/specs/web-ui/spec.md` written; change-side domain delta
present (resolves the `specs: partial` block).
- ✅ Zero unchecked implementation tasks — 71/71.
> **Residual confirmation for `sdd-archive`:** the structural conditions that produced the pre-sync
> `specs: partial` / `sync: blocked` block (missing `changes/web-ui-rework/specs/<domain>/`) are now
> resolved. A re-scan by the native status engine should report `specs: done` and move `sync`→ready
> and `archive`→ready; the archive executor should treat that re-scan as authoritative before
> moving the change to `archive/YYYY-MM-DD-web-ui-rework`.
## 8. Carry-over items for the archive summary (recorded per task)
These two verify-phase findings are non-blocking and should land in the archive summary:
1. **Slice-5 review-budget deviation (process note, not a defect).** The review-workload forecast
(`tasks.md` Review Workload Forecast, slice 5 = "Medium-High") prescribed a **5a (Actions) → 5b
(Settings)** sub-split if over 400 lines. Slice 5 shipped as a **single commit**
(`cd95f25`, ~1,097 hand-written source insertions: `Settings.tsx` +758, `Actions.tsx` +338, plus
240 lines of component tests) — exceeding the 400-line budget without the prescribed sub-split.
The code is correct, fully migrated, MUI-free, and test-covered; all gates green. Mandatory
sub-splits for the other over-budget slices (6 → 6a/6b, 7 → 7a/7b) **were** honored. This is a
forecast-vs-actual process deviation, recorded for the archive, **not** a correctness regression.
2. **`node --test tests` cross-check command is a pre-existing typo (recommend follow-up).** The
acceptance-crosscheck line in `tasks.md` (and the `"test:node": "node --test tests"` npm script)
reference `node --test tests`. This command is **pre-existing broken** (verified identical at
baseline `ef5311b`): `node` treats the bare `tests` argument as a module path and fails with
`Cannot find module '.../frontend/tests'` (real exit code 1 at both HEAD and baseline). The
**correct** command is `node --test` (auto-discover), which passes **5/5**. Recommend a
follow-up docs/script cleanup to correct the `npm run test:node` script and the `tasks.md` /
`apply-progress.md` cross-check lines. **Not introduced by this change.**
## 9. Next recommended phase
→ **`sdd-archive`** (clean). Confirm the native status re-scan reports `specs: done` / `archive:
ready`, then move the change to `openspec/changes/archive/YYYY-MM-DD-web-ui-rework`, carrying over
the two items in §8 into the archive summary.
---
### Appendix — Files written by this sync (OpenSpec only; no source code)
- `openspec/specs/web-ui/spec.md` — **canonical spec (new domain), 13 requirements.**
- `openspec/changes/web-ui-rework/specs/web-ui/spec.md` — **change-side domain delta (`## ADDED Requirements`).**
- `openspec/changes/web-ui-rework/sync-report.md` — this report.
@@ -0,0 +1,273 @@
# Verify Report — web-ui-rework
> Phase: **verify** · Change: `web-ui-rework` · Repo: `/home/user/Manage_01` (frontend at `frontend/`)
> FRESH-CONTEXT adversarial verification of the full change against `proposal.md`,
> `spec.md`, `design.md`, `tasks.md`, and `apply-progress.md`. All 8 apply slices
> are committed (`c767fc6` slice 1 … `baf412b` slice 8, with sub-splits 6a/6b/7a/7b).
> Read-only verification; no source edits. The verify report is the only file written.
**Head commit verified:** `baf412b` (`feat(frontend): slice 8 — remove MUI/@emotion deps + update REQUIREMENTS`).
---
## 0. Executive summary / verdict
**VERDICT: PASS** (proceed to `sdd-sync`).
The MUI v9 → shadcn/ui + Tailwind v4 + lucide-react migration is **complete and
verified green** at `baf412b`. Every spec scenario was checked against source and
passes: zero `@mui/*`/`@emotion/*` imports in `frontend/src` (grep-verified);
`@mui/*`, `@emotion/*`, `recharts`, `d3` absent from `package.json`; `theme.ts`
deleted; `/media` is canonical with `/applications``<Navigate to="/media" replace />`
mirroring `/monitoring``/observability`; top-level Backups nav item present;
both TanStack tables are **visibility-only** (no `getSortedRowModel`, no resizing —
the only `enableSorting` occurrence is `enableSorting: false` on the selection
display column, which *enforces* the no-sort rule); ObservabilityPage parity
preserved (no in-app charts); `docs/REQUIREMENTS.md` updated comprehensively.
All four gates are green: `npm run build` (exit 0), `npm run lint` (exit 0, 2
*pre-existing* warnings verified identical at baseline `ef5311b`), `npm test`
(Vitest **23 files / 64 tests**), `node --test` (auto-discover, **5/5**). TDD
evidence is genuine and behavioral (selection toggle, select-all, column-visibility,
row-click + stopPropagation edge cases, the exact 15-column Media set, status→Badge
variant mapping). **71/71 tasks checked**; zero unchecked implementation tasks.
Two findings, neither blocking:
- **[WARNING] Review-workload boundary deviation on slice 5.** Slice 5 (Settings +
Actions) shipped as a single commit with ~1,097 hand-written source insertions
(`Settings.tsx` +758, `Actions.tsx` +338), exceeding the 400-line budget **without**
the prescribed `5a`/`5b` sub-split. Code is correct and tested; this is a
process/budget deviation, not a defect.
- **[INFO] Structured-status artifact-format note.** The change uses a flat
`spec.md` (15 requirements, complete) with no `openspec/specs/<domain>/` delta
specs. The native status engine reports `specs: partial` / `applyState: blocked`
on this basis. The flat spec is the authoritative contract the work was built
against and is complete; the domain-spec format gap is a **sync/archive**
concern, not a verify blocker (the verify phase itself is `ready`).
---
## 1. Structured status & actionContext findings
Consumed from the native `gentle-pi.sdd-status` (authoritative):
- `changeName: web-ui-rework`, `artifactStore: openspec`, change root correct.
- Artifacts present and populated: `proposal`/`design`/`tasks`/`apply-progress` =
done; `spec.md` present (legacy-flat, no domain delta specs).
- `taskProgress`: **total 71, complete 71, remaining 0, unchecked []**
independently re-confirmed: `grep -nE '^\s*- \[ \]' tasks.md`**NONE**;
`grep -cE '^\s*- \[x\]'`**71**.
- `verify: ready`; `apply: blocked`, `sync: blocked`, `archive: blocked`.
- `actionContext`: `mode: repo-local`, `workspaceRoot: /home/user/Manage_01`,
`allowedEditRoots: ["/home/user/Manage_01"]`, `warnings: []`. Implementation
ownership and target files are provably inside the authoritative workspace. ✓
- `blockedReasons`: "domain specs are missing or partial" + "Legacy flat spec
present without domain specs." → This is an artifact-**format** condition
(no `openspec/specs/web-ui/` delta). It does **not** block verify; the flat
`spec.md` is complete and was the contract for the work. It gates **sync/archive**.
## 2. Gate results (actual output, run from `frontend/` at `baf412b`)
| Gate | Command | Result | Evidence |
|------|---------|--------|----------|
| Build | `npm run build` (`tsc -b` + `vite build`) | **PASS** exit 0 | `✓ built in 789ms`; 1959 modules transformed. Non-fatal `>500 kB` chunk-size warning (pre-existing, present at baseline). |
| Lint | `npm run lint` (`eslint .`) | **PASS** exit 0 | `✖ 2 problems (0 errors, 2 warnings)`. Both warnings `react-hooks/exhaustive-deps` in `UsersPage.impl.tsx` (lines 120, 159). **Adversarially verified pre-existing** at baseline `ef5311b` (same `baseRows` + `rows`-missing-dep warnings). |
| Vitest | `npm test` (`vitest run`) | **PASS** | `Test Files 23 passed (23)` · `Tests 64 passed (64)`. Matches expected 23/64. |
| node:test | `node --test` (auto-discover) | **PASS** 5/5 | `# tests 5 / # pass 5 / # fail 0`. Legacy `users.test.mjs` + `userState.test.mjs`. |
### ⚠ Acceptance-crosscheck command correction (recorded, NOT a regression)
`tasks.md` and `apply-progress.md` reference `node --test tests` (or `npm run
test:node`). **This command is pre-existing broken** (verified identically at
baseline `ef5311b`): `node` treats the bare `tests` argument as a *module path*,
failing with `Cannot find module '/home/user/Manage_01/frontend/tests'` (real
exit code `1` at both HEAD and `ef5311b`). The **correct** command is
`node --test` (auto-discover), which passes **5/5**. This is a documentation
typo in the cross-check line, not a defect introduced by the rework.
### Runtime gates configured by parent
- `zero-mui`: `grep -rlE '@mui/(material|icons-material|x-data-grid)|@emotion/(react|styled)' src`**ZERO**
- `build`: `npm run build`**passed** (exit 0) ✓
- `test`: `npm test`**passed** (23/64) ✓
- `exists`: `test -s verify-report.md` → this file ✓
## 3. Spec scenario coverage (spec.md, 15 requirements)
| # | Requirement / scenario | Check | Result |
|---|------------------------|-------|--------|
| R1 | No MUI/`@emotion` imports anywhere in `frontend/src` | `grep -rlE '@mui/(material\|icons-material\|x-data-grid)\|@emotion/(react\|styled)' src` | **ZERO** ✓; deps absent from `package.json` ✓ |
| R1 | No new design system adopted | `package.json` deps | Only shadcn/Tailwind/lucide/radix; no `@mui`/`@emotion`/`recharts`/`d3` ✓ |
| R2 | `recharts`/`d3` gone from `package.json` + no imports | grep src + package.json | **ZERO** imports; absent from deps ✓ |
| R2 | No chart component introduced; metrics = numbers/Badges/Grafana links | ObservabilityPage inspection | No recharts/d3; no chart canvas ✓ |
| R2 | `theme.ts` deleted; no `theme`/`getAppTheme` imports | `test ! -e src/theme.ts` + grep | **GONE**; **NO-IMPORTS** ✓ |
| R3 | Primary stays `#4f8cff` (light + dark) | `index.css` | `--color-primary: #4f8cff` in `@theme` (L13) **and** `.dark` (L52) ✓ |
| R3 | `chart-1..5` retained + repurposed (documented) | `index.css` | All 5 present in **both** blocks with role comment (chart-1=info, 2=success, 3=warning, 4=destructive, 5=neutral) ✓ |
| R4 | Comfortable density (`p-4 md:p-6`, `gap-4`); no compact mode | `App.tsx` shell + grep | `<main className="p-4 md:p-6">`; no compact/dense toggle found ✓ |
| R5 | Status Badge semantics (success/warning/destructive cues) | `badge.tsx` + tests | `success` (chart-2) + `warning` (chart-3) variants present; mapping asserted in tests ✓ |
| R6 | Backups top-level nav item → `/backups` | `App.tsx` `navItems` | `{ path: "/backups", label: "Backups", icon: DatabaseBackup }` (after Files, before Users) ✓ |
| R7 | Media canonical `/media`; `/applications` → redirect; `/monitoring``/observability` intact | `App.tsx` routes | `<Route path="/media" element={<Applications />}/>` + `<Route path="/applications" element={<Navigate to="/media" replace />}/>` in **both** route blocks; `/monitoring``/observability` intact ✓ |
| R7 | Auth/routing model unchanged | `App.tsx` | `AuthProvider`/`react-oidc-context`/`react-router-dom` structure unchanged ✓ |
| R8/R9/R10/R11 | TanStack visibility-only (no sort, no resize) | `data-table.tsx`/`Media.tsx`/`FileBrowser.impl.tsx` | **No** `getSortedRowModel`, **no** `enableColumnResizing`/`columnResizing`/sortable header. Only `enableSorting: false` (selection column). `getCoreRowModel` + conditional `getPaginationRowModel` (manual for Media) ✓ |
| R8 | Column visibility parity (Media 15 / FileBrowser 5) | column defs + tests | Media: `title,series,season,episode,type,year,runtime_min,size,bitrate,hdr,video,resolution,date_added,library,path` = **15** ✓; FileBrowser: `type,name,ext,size,modified` = **5** ✓ (both asserted in tests) |
| R12 | Media pagination parity (server-driven) | `Media.tsx` + `data-table.tsx` | `enablePagination`+`manualPagination`+`rowCount` (from `queryResult.total`); `limit`/`offset` lifted to `usePersistentState` ✓ |
| R13 | Observability parity (alerts + target health + Grafana deep-links; no in-app chart) | `ObservabilityPage.tsx` | On shadcn (card/badge/alert/select/skeleton/collapsible); no recharts/d3; Grafana/ExternalLink refs present ✓ |
| R14 | Vitest harness introduced; migrated components have tests; legacy node suites pass | `package.json` + `src/**` | `vitest` + `@testing-library/react` present; 23 test files; `node --test` 5/5 ✓ |
| R15 | Per-slice build + lint green gate | gate runs | All green at HEAD ✓ |
| R16 | Eight-slice delivery strategy | git log | 10 commits: slices 18 + 6a/6b/7a/7b sub-splits ✓ |
| R17 | `docs/REQUIREMENTS.md` updated | grep | Documents shadcn/Tailwind/lucide, thin-dashboard model, TanStack tables, `/media` + `/applications` redirect, Backups nav, removed deps, decision-log entry 2026-06-17 ✓ |
| R18 | No backend API / `frontend/src/types/*` changes | spec non-goal | No router/type-contract changes introduced (scope honored) ✓ |
**Spec coverage: 100% of verifiable scenarios PASS.**
## 4. Task completion status
- **71/71 tasks checked.** Zero unchecked implementation task lines
(`grep -nE '^\s*- \[ \]' tasks.md` → none).
- No archive blockers from incomplete tasks.
## 5. TDD compliance & assertion-quality assessment
Strict-TDD was active for this change (design §6, spec R14; apply-progress carries
`TDD Cycle Evidence (standard mode; RED → GREEN)` tables at the slice-7a and
slice-7b sections, e.g. `apply-progress.md:54` and `:1066`).
**Assertion quality — assessed as GENUINELY BEHAVIORAL, not trivial.** Spot-checked
the highest-risk tests:
- `data-table.test.tsx` (9 tests): row selection toggle + state reflection + un-select;
header select-all (indeterminate → all-checked → all-unchecked); **column-visibility
dropdown removes a column and leaves others intact**; **`onRowClick` fires with
`row.original` on row-body click**; **does NOT fire when a selection checkbox is
toggled** (stopPropagation edge case); empty message; client pagination controls;
manual-pagination total (`rowCount=42` → "42 rows", "Page 1 of 21").
- `Media.test.tsx`: **"exposes exactly the 15 locked toggleable columns"**
(`toEqual([...sorted 15...])`, `toHaveLength(15)`, `not.toContain("__select__")`);
**"navigates to the file browser at the item path on row click"**
(`navigate` called once with the encoded path); **"does NOT navigate when toggling
a selection checkbox"**; server-driven pagination total.
- `FileBrowser.test.tsx`: 5 locked columns via `arrayContaining` + uniqueness;
**clicking a file row selects for ffprobe preview**; **clicking a directory row
navigates in** (clean dir-vs-file distinction).
- `BackupAlertsTable.test.tsx` + `SessionActivityPanel.test.tsx`: status→Badge
variant mapping asserted via rendered `data-variant` attribute —
critical→`destructive`, warning→`warning`, playing/healthy→`success`, paused→`warning`,
idle→`secondary`. **This directly covers spec R5 "Healthy status uses a success cue"
and "Firing alert uses a destructive cue."** No type-only, CSS-detail, or
tautological assertions found.
No tautologies, no ghost loops, no smoke-only, no implementation-detail CSS
assertions identified. Tests assert the spec-parity behaviors (visibility-only
features, row-click, selection/stopPropagation, exact column sets, status semantics).
## 6. Review-workload / PR-boundary findings
Per-slice changed-line counts (numstat, excluding `package-lock.json` churn):
| Commit | Slice | Source Δ (no-lock) | Lockfile | Sub-split? | Verdict |
|--------|-------|--------------------|----------|------------|---------|
| `c767fc6` | 1 Foundation | 2,616 (≈1,527 = planning docs; ≈984 = 15 vendored shadcn primitives; ≈60 = hand-written) | 2,135 | — | **OK** — vendored-generated + planning-doc dominated; explicit size exception per forecast |
| `b8be41f` | 2 Shared | 1,299 (10 rewrites) | 0 | not split | Minor over; per-file boundaries clean |
| `befebb6` | 3 Backups + nav | 1,043 | 0 | — | OK (forecast "likely OK"; close to budget) |
| `b6c3b76` | 4 Dashboard + Applications | 988 | 0 | not split | Minor over |
| **`cd95f25`** | **5 Settings + Actions** | **2,837 (Settings +758 / Actions +338 + 240 test)** | 0 | **NOT split** | **⚠ WARNING** — exceeded 400 by ~2.7× without prescribed 5a/5b |
| `3f7b249` | 6a Users dir | 1,620 | 0 | **6a done** ✓ | OK (mandatory split honored) |
| `5601575` | 6b Users compose | 251 | 0 | **6b done** ✓ | OK |
| `df2a4de` | 7a DataTable + FileBrowser | 1,819 | 0 | **7a done** ✓ | OK (mandatory split honored) |
| `58f41c6` | 7b Media | 1,177 | 0 | **7b done** ✓ | OK |
| `baf412b` | 8 Cleanup + docs | 318 | 626 | — | OK |
**Mandatory sub-splits honored:** slices 6 and 7 (the forecast-confirmed over-400
slices) were both sub-split as prescribed (6a/6b, 7a/7b). ✓
**Deviation — slice 5 [WARNING]:** The forecast (`tasks.md` Review Workload
Forecast) rates slice 5 "Medium-High" with policy *"If over, split 5a (Actions)
→ 5b (Settings)."* Slice 5 was delivered as a **single commit** with **~1,097
hand-written source insertions** (`Settings.tsx` +758, `Actions.tsx` +338), far
over the 400-line budget, **without** the `5a`/`5b` sub-split. `apply-progress.md:739`
characterizes this as "under the 400-line added budget," which is **incorrect**
under the standard insertions+deletions review metric (it appears the author used
net delta). This is a **review-workload process deviation**, not a correctness
defect: both pages are fully migrated, MUI-free, covered by 240 lines of
component tests, and the gates are green. The per-file boundary (Settings vs
Actions) is clean. Recommend noting this in the archive summary so the 5a/5b
forecast-vs-actual delta is on record; **does not block sync/archive**.
## 7. Residual risks
1. **No visual / browser smoke was performed** (none in scope; component tests
assert DOM structure and behavior, not pixel fidelity). Layout regressions
(spacing, table density, Sheet/Drawer transitions, responsive grids) are only
covered structurally. A manual browser smoke of Media paging + row-click,
FileBrowser row-click preview, the Users compose dialog, and Backups tabs is
advisable before release — not a verify gate.
2. **`lucide-react@^1.14.0` pin re-confirmed.** Residual risk (unusual major)
verified resolved: all 14 names the rework depends on — `Pencil, X, Paperclip,
Bold, Italic, Link, List, Mail, Send, Trash2, DatabaseBackup, ExternalLink,
HardDrive, Archive`**export at the installed version** (`node -e` import
probe → `ALL_EXPORT_OK`). The `DatabaseBackup` fallback (`HardDrive`/`Archive`)
was not needed.
3. **`ResizeObserver` polyfill is test-only.** Defined in `src/test/setup.ts`
(jsdom no-op stub for Radix primitives). Not a runtime concern; flagged for
completeness.
4. **Two `react-hooks/exhaustive-deps` lint warnings** in `UsersPage.impl.tsx`
(`baseRows` logical expr; `rows` missing memo dep) — **adversarially verified
pre-existing** at baseline `ef5311b` (identical substance, pre-dating slice 1).
They are warnings (lint passes at exit 0), but if the project ever tightens
eslint to `--max-warnings 0`, these would surface. Worth a follow-up cleanup
task outside this change.
5. **`node --test tests` cross-check command is broken** (pre-existing typo;
correct form is `node --test`). Recorded; recommend correcting the
`npm run test:node` script + tasks.md/apply-progress cross-check lines in a
follow-up docs commit. Not introduced by this change.
6. **Chunk-size build warning** (`index-*.js` ~701 kB > 500 kB) is non-fatal and
pre-existing; orthogonal to this migration but worth future code-splitting.
7. **Slice-5 review-budget overage** (see §6) — single-commit delivery above the
400-line forecast without the prescribed sub-split. Process note for the
archive record.
## 8. Exact blockers
**None (CRITICAL).** No blockers to `sdd-sync`. The two findings (slice-5
review-budget deviation; flat-spec vs domain-spec format) are WARNING/INFO and
do not gate sync. All spec scenarios pass, all gates green, all tasks complete,
TDD evidence genuine.
## 9. Recommended next phase
**`sdd-sync`** (PASS). Before archive: reconcile the flat `spec.md` into
`openspec/specs/web-ui/` delta specs (the native status blocks archive on the
missing domain specs), and optionally correct the `node --test tests` typo +
record the slice-5 forecast-vs-actual in the archive summary.
---
### Appendix A — Verification commands run (authoritative, at `baf412b`)
```
cd /home/user/Manage_01/frontend
npm run build → exit 0 (✓ built in 789ms; >500kB chunk warning pre-existing)
npm run lint → exit 0 (0 errors, 2 pre-existing warnings)
npm test → 23 files / 64 tests passed
node --test → 5/5 pass (auto-discover) [correct command]
node --test tests → FAIL exit 1 "Cannot find module .../tests" [PRE-EXISTING broken; identical at ef5311b]
grep -rlE '@mui/(@mui/(material|icons-material|x-data-grid)|@emotion/(react|styled)' src → ZERO
test ! -e src/theme.ts → GONE
lucide import probe (14 names) → ALL_EXPORT_OK
```
### Appendix B — Files substantively changed by the change (representative)
- `frontend/src/App.tsx``/media` canonical, `/applications` redirect, Backups nav, `/monitoring``/observability` intact.
- `frontend/src/components/ui/data-table.tsx` — new TanStack wrapper (visibility-only).
- `frontend/src/pages/Media.tsx`, `frontend/src/pages/FileBrowser.impl.tsx` — off `@mui/x-data-grid`.
- `frontend/src/components/ui/{tabs,table,dialog,input,label,checkbox,switch,progress,separator,avatar,textarea,dropdown-menu,scroll-area}.tsx` — added primitives.
- `frontend/src/components/ui/badge.tsx``success`/`warning` variants (chart-2/chart-3).
- `frontend/src/index.css``chart-1..5` role comments (both blocks); primary `#4f8cff` unchanged.
- `frontend/vitest.config.ts`, `frontend/src/test/setup.ts` — harness.
- `frontend/package.json``@tanstack/react-table` + vitest/testing-library added; `@mui/*`,`@emotion/*`,`recharts`,`d3` removed; `theme.ts` deleted.
- `frontend/src/**/__tests__/*.test.tsx` (23 files) — component tests.
- `docs/REQUIREMENTS.md` — rework documented.
- `frontend/src/components/{SectionCard,SelectionRailCard,TabbedCard,MetricCard,DiskSpaceCard,HoverEditButton,DialogFooter,ConfirmDialog,LibraryOverview,NowPlaying,SessionActivityPanel,Backup*,ObservabilityPage}.tsx`,
`frontend/src/pages/{Dashboard,Applications,Settings,Actions,UsersPage.impl}.tsx` — migrated to shadcn/Tailwind/lucide.