diff --git a/openspec/changes/web-ui-rework/specs/web-ui/spec.md b/openspec/changes/web-ui-rework/specs/web-ui/spec.md new file mode 100644 index 0000000..aee29fa --- /dev/null +++ b/openspec/changes/web-ui-rework/specs/web-ui/spec.md @@ -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 +`
`) 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 diff --git a/openspec/changes/web-ui-rework/sync-report.md b/openspec/changes/web-ui-rework/sync-report.md new file mode 100644 index 0000000..8c58535 --- /dev/null +++ b/openspec/changes/web-ui-rework/sync-report.md @@ -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//`. 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//`) 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. diff --git a/openspec/changes/web-ui-rework/verify-report.md b/openspec/changes/web-ui-rework/verify-report.md new file mode 100644 index 0000000..fbba83a --- /dev/null +++ b/openspec/changes/web-ui-rework/verify-report.md @@ -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` → `` +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//` 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 | `
`; 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 | `}/>` + `}/>` 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 1–8 + 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. diff --git a/openspec/specs/web-ui/spec.md b/openspec/specs/web-ui/spec.md new file mode 100644 index 0000000..b537f13 --- /dev/null +++ b/openspec/specs/web-ui/spec.md @@ -0,0 +1,288 @@ +# Web UI + +> Domain: `web-ui` · **Canonical specification.** Synced from change `web-ui-rework`. +> +> This is the merged end-state of the Manage web-frontend rework. It captures the durable, +> post-change contracts (the rework's end-state), not the migration steps. The per-slice delivery +> strategy and per-slice build/lint gate were part of the change's migration process and are +> intentionally not carried into this canonical spec; they remain in the change record +> (`spec.md` / `tasks.md`) under `openspec/changes/web-ui-rework/`. + +## Purpose + +Define WHAT must be true of the Manage web frontend after the rework: a single coherent UI on +**shadcn/ui + Tailwind v4 + lucide-react**, a **thin-dashboard** observability model (no in-app +charts; Grafana deep-links preserved), **TanStack Table** data grids with a **visibility-only** +feature set (no sorting, no resizing), a **reconciled information architecture**, and **zero** +`@mui/*` / `@emotion/*` / `recharts` / `d3` / `theme.ts` residue. This spec is acceptance-focused and +verifiable; it deliberately does not prescribe implementation. + +## Requirements + +### 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 +`
`) 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