From a8c0e413f4363d27f091ef4bcf7f44f572eb21fa Mon Sep 17 00:00:00 2001 From: BOHA Date: Sat, 6 Jun 2026 18:44:17 +0200 Subject: [PATCH] docs(spec): CSS->MUI migration design (soft-SaaS + dense tables, incremental) Approved design for migrating the admin frontend off ~7,100 lines of hand-written CSS onto MUI, themed to the existing brand, dark/light preserved, modern 'soft-SaaS shell + dense tables' look, rolled out incrementally (foundation-first, always shippable). Verified via 4-way adversarial review (editorial, MUI-docs accuracy, codebase-fit, completeness) with fixes applied. Also gitignores .superpowers/ brainstorm mockups. Co-Authored-By: Claude Opus 4.8 (1M context) --- .gitignore | 3 + .../2026-06-06-css-mui-migration-design.md | 251 ++++++++++++++++++ 2 files changed, 254 insertions(+) create mode 100644 docs/superpowers/specs/2026-06-06-css-mui-migration-design.md diff --git a/.gitignore b/.gitignore index b9a7657..b95b9a2 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,6 @@ dist-client/ # Claude worktrees .claude/worktrees/ .claude/settings.local.json + +# Superpowers brainstorm mockups +.superpowers/ diff --git a/docs/superpowers/specs/2026-06-06-css-mui-migration-design.md b/docs/superpowers/specs/2026-06-06-css-mui-migration-design.md new file mode 100644 index 0000000..29b30dd --- /dev/null +++ b/docs/superpowers/specs/2026-06-06-css-mui-migration-design.md @@ -0,0 +1,251 @@ +# CSS → MUI Migration — Design Spec + +**Date:** 2026-06-06 +**Status:** Approved design, pending implementation plan +**Topic:** Replace hand-written CSS with MUI (Material UI), themed to the existing brand, with a modern "Soft-SaaS + dense tables" look, rolled out incrementally. + +--- + +## 1. Goal + +Migrate the admin frontend off its ~7,100 lines of hand-written CSS onto **MUI (Material UI)** as the styling system, while simultaneously delivering a **modern visual refresh**. Dark/light theming and the existing design tokens are preserved (they already exist and work). The migration is **incremental and always shippable** — never a frozen big-bang. + +This is explicitly a **styling + component-layer migration**. Application behavior, data flow, API contracts, and business logic remain identical (one deliberate exception: modal accessibility — see §5 / §11). Backend code is not touched. + +--- + +## 2. Current state (audit findings, 2026-06-06) + +The premise "we want dark/light mode" is largely **already met**. The audit found: + +- **19 CSS files, ~7,101 lines** under `src/admin/`. Largest: `components.css` (1,224), `plan.css` (1,152), `offers.css` (641), `layout.css` (582), `dashboard.css` (525), `forms.css` (510), `attendance.css` (455), `base.css` (411). Smallest: `responsive.css` (6 lines, effectively an empty placeholder). The token file `variables.css` (164) and `plan.css` are special-cased in §6. +- **A complete CSS-variable token system** in `variables.css`: spacing scale, colors, radii, motion durations, fonts, safe-area insets. +- **A full dark + light theme** via `[data-theme="dark"]` / `[data-theme="light"]`. The attribute is owned and written by **`src/context/ThemeContext.tsx`** (`useTheme()` / `toggleTheme()`, which calls `document.documentElement.setAttribute("data-theme", …)`). `Sidebar.tsx`, `AdminLayout.tsx`, and `Login.tsx` only **consume** it via `useTheme()`. +- **Distinctive fonts already chosen:** Urbanist (headings), Plus Jakarta Sans (body), DM Mono (mono). +- **Stack:** React 18.3, Vite 8, TypeScript (strict), TanStack Query v5, **framer-motion** (used broadly — see §9: modals, toasts, the AdminLayout logout transition, dashboard cards, Login), react-datepicker, react-quill-new, Leaflet, @dnd-kit. No Tailwind / MUI / Emotion direct deps; no PostCSS config (PostCSS ships only transitively under Vite). +- **Type skew (pre-existing):** `react`/`react-dom` are `^18.3.1` but `@types/react`/`@types/react-dom` are `^19.x`. MUI's component types are sensitive to the `@types/react` major version — see §11 / §14. + +**Implication:** the migration is not about adding theming; it's about replacing the CSS plumbing and modernizing the look while carrying the existing tokens forward. + +--- + +## 3. Locked decisions + +| Decision | Choice | +| ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------- | +| Goal | Both — re-plumb CSS **and** modern visual refresh | +| Approach | Component library (not a utility framework) | +| Library | **MUI (Material UI)** — target **v7** + **MUI X v8** (pickers), themed to the brand | +| Look | **"Soft-SaaS shell (B) + dense data tables (D)"** — airy modern chrome, dense efficient tables | +| Theming | Existing CSS-var tokens become the source; one `data-theme` attribute drives both MUI and legacy CSS (mechanism verified against MUI v7 docs — §4.1) | +| Roll-out | Incremental, foundation-first; each phase is one or more normal releases | +| Pilot page | A focused CRUD page (Vozidla _or_ Uživatelé) | +| Heavy tables | MUI `Table` styled dense, reusing existing `useTableSort` + `usePaginatedQuery` hooks (no DataGrid, no paid license) | + +--- + +## 4. Architecture + +The tokens become the **single shared palette** that feeds both worlds during the migration, so migrated and unmigrated pages stay on the same colors/typography. Structure modernizes per page (see §7 for the token-level vs component-level split). + +``` +Tokens (colors, fonts, radii, motion, dark/light) ← refreshed once, early + │ (identical palette feeds both sides) + ├───────────────► legacy CSS ── unmigrated pages ┐ + │ ├─ coexist, same palette + └──► MUI theme (createTheme + colorSchemes) ──────────────┘ + │ + ▼ + Foundation UI kit (src/admin/ui/*) ──► migrated pages +``` + +### 4.1 Theming bridge (`src/admin/theme.ts`) + +- One `theme.ts` builds the MUI theme from the tokens: + - **Palette:** `primary` = brand red (`#d63031` dark / `#c73030` light), plus `success`/`warning`/`error`/`info` mapped to existing semantic tokens. + - **Typography:** Urbanist for `h1–h6`, Plus Jakarta Sans for body, DM Mono for numeric/monospace contexts. + - **Shape/spacing:** `borderRadius` and spacing from the token scale. + - **`components` overrides:** the Soft-SaaS defaults (rounded cards, pill buttons, soft shadows, tinted chips) baked in so every MUI component is on-brand by default. +- **Dark/light keeps the existing toggle (verified against MUI v7 docs).** MUI's CSS theme variables support a custom color-scheme selector that targets the existing attribute exactly: + ```ts + createTheme({ + cssVariables: { colorSchemeSelector: "[data-theme='%s']" }, + colorSchemes: { light: true, dark: true }, + // …palette/typography/components + }); + ``` + This emits `[data-theme="light"]{…}` and `[data-theme="dark"]{…}`, matching the attribute `ThemeContext` already writes. One attribute drives MUI _and_ legacy CSS → no second toggle, 1:1 dark parity. +- **One attribute owner.** `ThemeContext` remains the sole writer of `data-theme` on ``. Its `toggleTheme()` must **also call MUI's `setMode()`** so MUI's internal color-scheme state stays in sync with the attribute. Do **not** also enable MUI's `InitColorSchemeScript` attribute-writing (avoid two writers). Wired in Phase 0. + +### 4.2 Coexistence (the safety mechanism) + +- Use MUI's **`ScopedCssBaseline`** around migrated page subtrees only, so MUI's CSS reset applies _inside_ migrated pages without disturbing unmigrated ones still on `base.css`. No global reset until teardown. +- The `CssVarsProvider`/`ThemeProvider` must sit **above** every `ScopedCssBaseline`, and `data-theme` must live on a common ancestor (``, as today) so the `[data-theme='%s']` selector resolves inside scoped subtrees. +- Styling engine: MUI default (**Emotion**). SPA only (Vite, no SSR), so no SSR-cache complexity. + +--- + +## 5. Foundation UI kit (`src/admin/ui/`) + +**Principle: pages never import `@mui` directly.** A thin wrapper kit bakes in brand defaults and **keeps the current component APIs and Czech labels**, so migrating a page is mostly swapping imports, not rewriting logic, and tuning a default touches one file, not 57. + +| Today | → Foundation kit (`src/admin/ui/`) | MUI base | +| ------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------- | +| `FormModal` / `ConfirmModal` | `` — preserves **FormModal's** full prop set (`submitDisabled`, `subtitle`, `loading`); ConfirmModal needs only `title`/`message`/`type`/`confirmVariant`/`loading` | `Dialog` | +| `.admin-table*` + `useTableSort` + `usePaginatedQuery` | **``** (the dense "D" look) | `Table` | +| `AdminDatePicker` (react-datepicker) | `` — `cs` locale, `dd.MM.yyyy` | MUI X Pickers (free) | +| `.admin-btn` | `