Files
manage/openspec/changes/web-ui-rework/proposal.md
T
Developer dd778d8850 feat(frontend): slice 1 — foundation for MUI->shadcn migration
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.
2026-06-17 12:11:05 +00:00

8.7 KiB
Raw Blame History

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 — Card over ad-hoc Paper, Tailwind flex/grid over MUI Grid/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 Table primitive, 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 the theme.ts shim are gone, and docs/REQUIREMENTS.md reflects 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-grid DataGrid usages to TanStack Table + shadcn Table.
  • 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, the theme.ts shim, 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 @theme tokens 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) and react-router-dom structure unchanged; only nav items/IA may shift.
  • No form-library introduction — keep current uncontrolled/useState form 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 shared Table primitive and tokens are already settled.
  • UsersPage size (high). pages/UsersPage.impl.tsx is 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 (lowmedium). ^1.14.0 is 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:

  1. Foundation — shadcn primitives + @tanstack/react-table, remove recharts/d3, delete theme.ts.
  2. Shared componentsSectionCard, SelectionRailCard, TabbedCard, MetricCard, DiskSpaceCard, HoverEditButton, DialogFooter, ConfirmDialog, LibraryOverview, NowPlaying/SessionActivityPanel.
  3. Backups cluster (+ surface /backups in nav).
  4. Dashboard + Applications.
  5. Settings + Actions.
  6. Users (largest; may sub-split).
  7. DataGrid → TanStack Table — Media + FileBrowser (highest risk; last).
  8. Cleanup + docs — remove @mui/* + @emotion/*, final typecheck/lint, update docs/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) and npm run lint green; existing node --test suites pass.
  • Zero @mui/* or @emotion/* imports remain in frontend/src (verified by grep); recharts, d3, and theme.ts removed.
  • 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.md updated 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.