7646f3236f
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/.
289 lines
13 KiB
Markdown
289 lines
13 KiB
Markdown
# 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
|
|
`<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
|