Files
manage/openspec/specs/web-ui/spec.md
T
Developer 7646f3236f 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/.
2026-06-17 19:15:15 +00:00

13 KiB

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
  • 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