Web UI rework (openspec/changes/web-ui-rework). Foundation slice: - Add @tanstack/react-table; remove orphaned recharts, d3 - Vendor shadcn primitives: tabs table dialog input label checkbox switch progress separator avatar textarea dropdown-menu scroll-area - Add Vitest + @testing-library/react + jsdom harness; npm test script - Delete no-op theme.ts shim; remove getAppTheme references - Smoke test proves the harness Gate: build + lint + test green.
8.7 KiB
Proposal — web-ui-rework
Phase: proposal. Grounded in
openspec/changes/web-ui-rework/exploration.md(verified inventory). No code changes. Product/UX questions for the user are collected in §8 — Proposal question round and must be answered before this proposal is treated as locked.
1. Problem / motivation
The Manage frontend is mid-migration: the app shell (App.tsx) and the new ObservabilityPage.tsx already live on shadcn/ui + Tailwind + lucide-react, but 22 files still import @mui/material, 2 use @mui/x-data-grid, and 2 use @mui/icons-material (10 distinct icons). The result is a split-feeling product: two design languages, two theming systems (MUI @emotion vs the Tailwind v4 @theme tokens in index.css), and a no-op theme.ts shim left behind. Orphaned charting deps (recharts, d3) linger in package.json even though no in-app charting exists.
This half-finished state collides with a second driver: Manage has moved to a thin-dashboard observability model where charts/metrics/logs live in external Grafana, and in-app surfaces show only Alertmanager alerts + Prometheus target health + Grafana deep-links. Finishing the migration is therefore not cosmetic — it is how we codify the decoupling (remove the charting deps, retire the last MUI surface, make the external-link affordance consistent) and land a coherent visual redesign on one design system.
2. Target outcome / product vision
A single, coherent Manage UI on shadcn/ui + Tailwind v4 + lucide-react, visually consistent with the already-migrated shell and ObservabilityPage:
- Every page uses the same tokens (primary
#4f8cff, Inter,rounded-lg, one border weight) and the same layout primitives —Cardover ad-hocPaper, Tailwind flex/grid over MUIGrid/Stack. - Observability stays first-class and thin: status as numbers/Badges plus outbound Grafana kiosk deep-links; no in-app charts.
- DataGrid surfaces (Media, FileBrowser) become TanStack Table on the shadcn
Tableprimitive, preserving pagination / row-selection / row-click / column-visibility. - A reconciled information architecture (Backups surfaced in nav; one name for Media/Applications).
@mui/*,@emotion/*,recharts,d3, and thetheme.tsshim are gone, anddocs/REQUIREMENTS.mdreflects the new UX.
3. Scope (in)
- Finish MUI v9 → shadcn/ui + Tailwind + lucide-react across all remaining consumers in
frontend/src(22 material files + 2 DataGrid files + 2 icon files). - Add the missing shadcn primitives (
tabs table dialog input label checkbox switch progress separator avatar textarea dropdown-menu scroll-area) and@tanstack/react-table. - Migrate both
@mui/x-data-gridDataGrid usages to TanStack Table + shadcnTable. - Deliver a visual/UX redesign aligned to the thin-dashboard model (density, typography ramp, status Badge semantics including a success variant, consistent external-link affordance).
- Remove orphaned
recharts,d3, thetheme.tsshim, and all@mui/*+@emotion/*deps. - Reconcile IA: surface Backups in nav; resolve Media vs Applications naming.
- Update
docs/REQUIREMENTS.md.
4. Non-goals
- No in-app charting — no recharts/d3 re-introduction, no custom charts; metrics visualisation stays in Grafana.
- No new design system — shadcn/ui + the existing Tailwind v4
@themetokens only. - No backend API changes — frontend
types/*contracts stay unless a UI simplification forces one, which must be flagged separately. - No MUI retention — all
@mui/*and@emotion/*removed by the final slice. - No auth/routing model change — OIDC (
auth.ts,react-oidc-context) andreact-router-domstructure unchanged; only nav items/IA may shift. - No form-library introduction — keep current uncontrolled/
useStateform parity.
5. Key technical risks
- DataGrid → TanStack Table (high). The two DataGrid usages (
Media.tsx,FileBrowser.impl.tsx) must reproduce pagination, row-selection, row-click, and column-visibility. Whether to also carry over client-side resizing/sorting is an open product question (exploration §4) and is the single highest-risk slice — sequenced last so the sharedTableprimitive and tokens are already settled. - UsersPage size (high).
pages/UsersPage.impl.tsxis the largest consumer (25 MUI components + 9 icons +Drawer+Table+ a rich-text compose UI with formatting actions and attachments). Likely needs a sub-split to stay under the 400-line slice budget. - Cross-slice visual drift (medium). 22 files over ~8 slices; mitigated by migrating shared building blocks first (exploration §9 slice 2) and documenting the Tailwind layout patterns up front.
- lucide-react v1 pin (low–medium).
^1.14.0is an unusual major; the 10 mapped icon names must be verified to exist before the UsersPage slice. - No component test harness today (medium). See exploration §11; whether to add Vitest in slice 1 is an open question.
6. High-level slice strategy
Chained PRs, ≤400 changed lines each, each leaving npm run build + npm run lint green. Full table and sequencing rationale live in exploration.md §9; summary:
- Foundation — shadcn primitives +
@tanstack/react-table, removerecharts/d3, deletetheme.ts. - Shared components —
SectionCard,SelectionRailCard,TabbedCard,MetricCard,DiskSpaceCard,HoverEditButton,DialogFooter,ConfirmDialog,LibraryOverview,NowPlaying/SessionActivityPanel. - Backups cluster (+ surface
/backupsin nav). - Dashboard + Applications.
- Settings + Actions.
- Users (largest; may sub-split).
- DataGrid → TanStack Table — Media + FileBrowser (highest risk; last).
- Cleanup + docs — remove
@mui/*+@emotion/*, final typecheck/lint, updatedocs/REQUIREMENTS.md.
Slices 6 and 7 are the most likely to need sub-splitting to stay ≤400 lines.
7. Success criteria
npm run build(tsc -b + vite build) andnpm run lintgreen; existingnode --testsuites pass.- Zero
@mui/*or@emotion/*imports remain infrontend/src(verified by grep);recharts,d3, andtheme.tsremoved. - Both DataGrid surfaces rebuilt on TanStack Table with pagination / row-selection / row-click / column-visibility parity.
- ObservabilityPage parity preserved (Alertmanager alerts + Prometheus target health + Grafana deep-links); no in-app charts introduced.
- Visual consistency with the migrated shell + ObservabilityPage; Backups surfaced in nav; Media/Applications naming resolved.
docs/REQUIREMENTS.mdupdated to reflect the UX/architecture change.
8. Proposal question round
The following product/UX questions need user answers before this proposal is treated as locked. They are intended to uncover business rules, IA intent, and scope boundaries — not delivery mechanics. (Parent: ask these verbatim.)
Q1 — Information architecture (parent-ask): Today the sidebar lists Dashboard, Observability, Media, Files, Users, Actions, Settings — but "Media" routes to /applications (component Applications), and a working Backups page (/backups) is hidden from the nav. Do you want (a) Backups as its own top-level nav item, (b) Backups grouped under a "Media ops" section, or (c) left as URL-only? And should the Media/Applications surface be named "Media" or "Applications" going forward?
Q2 — Visual density (parent-ask): The migrated shell uses comfortable spacing (p-4 md:p-6, gap-4). Should the redesign keep comfortable density across all 22 pages, or do some dense tables (Media, Users, Backups runs) need a compact mode to fit more rows on screen?
Q3 — TanStack Table feature parity (parent-ask): The current DataGrids use column visibility; resizing and sorting usage is ambiguous. For the rebuilt tables, do you want (a) visibility-only parity (smallest scope), (b) add sorting, or (c) add sorting + column resizing (closest to a full DataGrid)?
Q4 — Test harness (parent-ask): There is no component test harness today (only tsc -b, eslint, and node --test on pure-JS transforms). Should slice 1 introduce a minimal Vitest + Testing Library harness for the migrated components, or proceed on static + manual smoke evidence per slice?
Q5 — Palette (parent-ask): The current primary is #4f8cff with chart-1..5 tokens defined but unused. Do you want to (a) keep #4f8cff and repurpose the chart tokens as Grafana-link color cues, (b) shift the primary to a new accent (please specify), or (c) keep the palette as-is and drop the unused chart tokens entirely?
Recommended next phase: spec — after the §8 questions are answered, refine scope/token/IA decisions and lock the slice contracts.