docs(plan): MUI migration Phase 0 (theming foundation) implementation plan
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,680 @@
|
||||
# MUI Migration — Phase 0: Theming Foundation — Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** Stand up the MUI theming foundation — install MUI, build `theme.ts` (light+dark from existing tokens), wire the provider + `data-theme` color-scheme bridge + `ThemeContext` sync, apply the token-level palette refresh app-wide, and ship a dev-only `/ui-kit` route proving the theme on the first wrappers (`Button`/`Card`/`TextField`).
|
||||
|
||||
**Architecture:** MUI's CSS-variable theme is configured with `colorSchemeSelector: "[data-theme='%s']"` so it reads the same `data-theme` attribute `ThemeContext` already writes — one toggle drives both MUI and legacy CSS. A tiny sync component mirrors the attribute into MUI's JS color-scheme state. `ScopedCssBaseline` scopes MUI's reset to migrated subtrees (here, only `/ui-kit`); legacy pages keep `base.css`.
|
||||
|
||||
**Tech Stack:** React 18.3, Vite 8, TypeScript (strict), MUI v7 (`@mui/material`) + Emotion + MUI X v8 pickers, Vitest (node).
|
||||
|
||||
**Spec:** `docs/superpowers/specs/2026-06-06-css-mui-migration-design.md` (§3, §4, §7 token-level layer, §8 Phase 0).
|
||||
|
||||
**Scope note:** This plan is Phase 0 ONLY. The rest of the `ui/` kit (Modal, Toast, Select, Switch, Tabs, DataTable, DatePicker), the AppShell (Phase 1), and the Vozidla pilot (Phase 2) are separate plans.
|
||||
|
||||
---
|
||||
|
||||
## File Structure
|
||||
|
||||
| File | Responsibility | Action |
|
||||
| ------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------- | ------ |
|
||||
| `package.json` | MUI/Emotion/X-pickers deps; `@types/react` reconciled to 18 | Modify |
|
||||
| `src/admin/theme.ts` | The MUI theme: cssVariables + colorSchemeSelector, light/dark palettes, typography, shape, component overrides | Create |
|
||||
| `src/admin/theme.test.ts` | Unit test asserting theme palette/typography/shape values | Create |
|
||||
| `src/admin/ui/MuiProvider.tsx` | Wraps children in MUI `ThemeProvider` + renders the sync component | Create |
|
||||
| `src/admin/ui/MuiColorSchemeSync.tsx` | Mirrors `useTheme()` (custom) → MUI `useColorScheme().setMode` | Create |
|
||||
| `src/admin/ui/Button.tsx`, `Card.tsx`, `TextField.tsx` | Thin brand wrappers over MUI (stable app-facing API) | Create |
|
||||
| `src/admin/ui/index.ts` | Barrel export for the kit | Create |
|
||||
| `src/admin/pages/UiKit.tsx` | Dev-only showcase, wrapped in `ScopedCssBaseline` | Create |
|
||||
| `src/admin/AdminApp.tsx` | Mount `MuiProvider`; add DEV-only `/ui-kit` route | Modify |
|
||||
| `src/admin/variables.css` | Token-level palette refresh (canvas, chip tints) — app-wide | Modify |
|
||||
|
||||
---
|
||||
|
||||
## Task 1: Install dependencies and reconcile React types
|
||||
|
||||
**Files:**
|
||||
|
||||
- Modify: `package.json` (via npm)
|
||||
|
||||
- [ ] **Step 1: Install MUI + Emotion + X pickers**
|
||||
|
||||
Run (date-fns ^4 is already a dependency):
|
||||
|
||||
```bash
|
||||
npm install @mui/material@^7 @emotion/react@^11 @emotion/styled@^11 @mui/x-date-pickers@^8
|
||||
```
|
||||
|
||||
Expected: packages added to `dependencies`; no peer-dependency errors that block install.
|
||||
|
||||
- [ ] **Step 2: Reconcile `@types/react` to the React 18 runtime**
|
||||
|
||||
The repo runs `react@^18.3.1` but pins `@types/react@^19`. MUI's component types are sensitive to the `@types/react` major. Align the types to the runtime:
|
||||
|
||||
```bash
|
||||
npm install -D @types/react@^18.3 @types/react-dom@^18.3
|
||||
```
|
||||
|
||||
Expected: devDependencies now show `@types/react@^18.3.x`.
|
||||
|
||||
- [ ] **Step 3: Verify the project still type-checks and builds**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
npx tsc --noEmit -p tsconfig.json
|
||||
npm run build:client
|
||||
```
|
||||
|
||||
Expected: both succeed with no errors (this is the pre-change baseline; if `tsc` was already failing, capture that before proceeding).
|
||||
|
||||
- [ ] **Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git add package.json package-lock.json
|
||||
git commit -m "build(mui): add MUI v7 + Emotion + X pickers; align @types/react to 18"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 2: Create `theme.ts` with light/dark palettes (TDD)
|
||||
|
||||
**Files:**
|
||||
|
||||
- Create: `src/admin/theme.ts`
|
||||
- Test: `src/admin/theme.test.ts`
|
||||
|
||||
Token values are taken from `src/admin/variables.css` (`[data-theme="light"]` / `[data-theme="dark"]`).
|
||||
|
||||
- [ ] **Step 1: Write the failing test**
|
||||
|
||||
Create `src/admin/theme.test.ts`:
|
||||
|
||||
```ts
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { theme } from "./theme";
|
||||
|
||||
describe("MUI theme", () => {
|
||||
it("uses the brand fonts", () => {
|
||||
expect(theme.typography.fontFamily).toContain("Plus Jakarta Sans");
|
||||
expect(String(theme.typography.h1.fontFamily)).toContain("Urbanist");
|
||||
});
|
||||
|
||||
it("maps the brand red per color scheme", () => {
|
||||
expect(theme.colorSchemes.light.palette.primary.main).toBe("#c73030");
|
||||
expect(theme.colorSchemes.dark.palette.primary.main).toBe("#d63031");
|
||||
});
|
||||
|
||||
it("maps the refreshed light canvas + paper", () => {
|
||||
expect(theme.colorSchemes.light.palette.background.default).toBe("#f4f3f1");
|
||||
expect(theme.colorSchemes.light.palette.background.paper).toBe("#ffffff");
|
||||
});
|
||||
|
||||
it("maps semantic colors per scheme", () => {
|
||||
expect(theme.colorSchemes.light.palette.error.main).toBe("#b91c1c");
|
||||
expect(theme.colorSchemes.dark.palette.success.main).toBe("#22c55e");
|
||||
});
|
||||
|
||||
it("uses the base radius of 10", () => {
|
||||
expect(theme.shape.borderRadius).toBe(10);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run the test to verify it fails**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
npx vitest run src/admin/theme.test.ts
|
||||
```
|
||||
|
||||
Expected: FAIL — `Cannot find module './theme'` (or `theme` is undefined).
|
||||
|
||||
- [ ] **Step 3: Implement `theme.ts` (palettes/typography/shape only — overrides come in Task 3)**
|
||||
|
||||
Create `src/admin/theme.ts`:
|
||||
|
||||
```ts
|
||||
import { createTheme } from "@mui/material/styles";
|
||||
|
||||
const FONT_BODY = "'Plus Jakarta Sans', system-ui, sans-serif";
|
||||
const FONT_HEADING = "'Urbanist', sans-serif";
|
||||
const FONT_MONO = "'DM Mono', Menlo, monospace";
|
||||
|
||||
export const theme = createTheme({
|
||||
cssVariables: {
|
||||
// Read the SAME attribute ThemeContext already writes (`data-theme`).
|
||||
// The "%s" placeholder is required; value must start with "." or "[".
|
||||
colorSchemeSelector: "[data-theme='%s']",
|
||||
},
|
||||
colorSchemes: {
|
||||
light: {
|
||||
palette: {
|
||||
mode: "light",
|
||||
primary: { main: "#c73030" },
|
||||
success: { main: "#15803d" },
|
||||
warning: { main: "#b45309" },
|
||||
error: { main: "#b91c1c" },
|
||||
info: { main: "#1d4ed8" },
|
||||
background: { default: "#f4f3f1", paper: "#ffffff" },
|
||||
text: { primary: "#1a1a1a", secondary: "#555555" },
|
||||
divider: "rgba(0,0,0,0.1)",
|
||||
},
|
||||
},
|
||||
dark: {
|
||||
palette: {
|
||||
mode: "dark",
|
||||
primary: { main: "#d63031" },
|
||||
success: { main: "#22c55e" },
|
||||
warning: { main: "#f59e0b" },
|
||||
error: { main: "#ef4444" },
|
||||
info: { main: "#3b82f6" },
|
||||
background: { default: "#0f0f0f", paper: "#1a1a1a" },
|
||||
text: { primary: "#ffffff", secondary: "#a0a0a0" },
|
||||
divider: "rgba(255,255,255,0.08)",
|
||||
},
|
||||
},
|
||||
},
|
||||
shape: { borderRadius: 10 },
|
||||
typography: {
|
||||
fontFamily: FONT_BODY,
|
||||
h1: { fontFamily: FONT_HEADING, fontWeight: 800 },
|
||||
h2: { fontFamily: FONT_HEADING, fontWeight: 800 },
|
||||
h3: { fontFamily: FONT_HEADING, fontWeight: 800 },
|
||||
h4: { fontFamily: FONT_HEADING, fontWeight: 700 },
|
||||
h5: { fontFamily: FONT_HEADING, fontWeight: 700 },
|
||||
h6: { fontFamily: FONT_HEADING, fontWeight: 700 },
|
||||
button: { textTransform: "none", fontWeight: 600 },
|
||||
},
|
||||
});
|
||||
|
||||
// Re-exported for tasks/components that need the mono stack.
|
||||
export const fonts = {
|
||||
body: FONT_BODY,
|
||||
heading: FONT_HEADING,
|
||||
mono: FONT_MONO,
|
||||
};
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Run the test to verify it passes**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
npx vitest run src/admin/theme.test.ts
|
||||
```
|
||||
|
||||
Expected: PASS (5 tests).
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add src/admin/theme.ts src/admin/theme.test.ts
|
||||
git commit -m "feat(mui): theme.ts with light/dark palettes, brand fonts, data-theme selector"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 3: Add component overrides (the "Soft-SaaS" defaults)
|
||||
|
||||
**Files:**
|
||||
|
||||
- Modify: `src/admin/theme.ts`
|
||||
- Test: `src/admin/theme.test.ts`
|
||||
|
||||
- [ ] **Step 1: Add a failing assertion for the overrides**
|
||||
|
||||
Append to `src/admin/theme.test.ts` (inside the `describe`):
|
||||
|
||||
```ts
|
||||
it("makes buttons pill-shaped and cards 16px by default", () => {
|
||||
const btn = theme.components?.MuiButton?.styleOverrides?.root as any;
|
||||
expect(btn?.borderRadius).toBe(999);
|
||||
const card = theme.components?.MuiCard?.styleOverrides?.root as any;
|
||||
expect(card?.borderRadius).toBe(16);
|
||||
});
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run to verify it fails**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
npx vitest run src/admin/theme.test.ts
|
||||
```
|
||||
|
||||
Expected: FAIL — `theme.components` is undefined / `borderRadius` is undefined.
|
||||
|
||||
- [ ] **Step 3: Add `components` to the `createTheme` call**
|
||||
|
||||
In `src/admin/theme.ts`, add a `components` key to the `createTheme({...})` object (after `typography`):
|
||||
|
||||
```ts
|
||||
components: {
|
||||
MuiButton: {
|
||||
defaultProps: { disableElevation: true },
|
||||
styleOverrides: {
|
||||
root: { borderRadius: 999, paddingInline: "0.95rem" },
|
||||
containedPrimary: {
|
||||
backgroundImage: "linear-gradient(135deg, #e23a3a, #c01f1f)",
|
||||
boxShadow: "0 5px 14px rgba(214,48,49,0.32)",
|
||||
},
|
||||
},
|
||||
},
|
||||
MuiCard: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
borderRadius: 16,
|
||||
boxShadow:
|
||||
"0 6px 20px rgba(20,20,40,0.06), 0 1px 2px rgba(0,0,0,0.03)",
|
||||
},
|
||||
},
|
||||
},
|
||||
MuiChip: {
|
||||
styleOverrides: { root: { borderRadius: 999, fontWeight: 700 } },
|
||||
},
|
||||
},
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Run to verify it passes**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
npx vitest run src/admin/theme.test.ts
|
||||
```
|
||||
|
||||
Expected: PASS (6 tests).
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add src/admin/theme.ts src/admin/theme.test.ts
|
||||
git commit -m "feat(mui): soft-SaaS component defaults (pill buttons, 16px cards, pill chips)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 4: MUI provider + color-scheme sync
|
||||
|
||||
**Files:**
|
||||
|
||||
- Create: `src/admin/ui/MuiColorSchemeSync.tsx`
|
||||
- Create: `src/admin/ui/MuiProvider.tsx`
|
||||
- Modify: `src/admin/AdminApp.tsx`
|
||||
|
||||
- [ ] **Step 1: Create the sync component**
|
||||
|
||||
Create `src/admin/ui/MuiColorSchemeSync.tsx`:
|
||||
|
||||
```tsx
|
||||
import { useEffect } from "react";
|
||||
import { useColorScheme } from "@mui/material/styles";
|
||||
import { useTheme as useAppTheme } from "../../context/ThemeContext";
|
||||
|
||||
/**
|
||||
* ThemeContext is the single owner of the `data-theme` attribute. This mirrors
|
||||
* its value into MUI's JS color-scheme state so theme.applyStyles('dark') and
|
||||
* useColorScheme().mode stay in sync with the attribute the CSS selector reads.
|
||||
*/
|
||||
export default function MuiColorSchemeSync() {
|
||||
const { theme } = useAppTheme();
|
||||
const { mode, setMode } = useColorScheme();
|
||||
|
||||
useEffect(() => {
|
||||
if (mode !== theme) setMode(theme as "light" | "dark");
|
||||
}, [theme, mode, setMode]);
|
||||
|
||||
return null;
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Create the provider wrapper**
|
||||
|
||||
Create `src/admin/ui/MuiProvider.tsx`:
|
||||
|
||||
```tsx
|
||||
import type { ReactNode } from "react";
|
||||
import { ThemeProvider } from "@mui/material/styles";
|
||||
import { theme } from "../theme";
|
||||
import MuiColorSchemeSync from "./MuiColorSchemeSync";
|
||||
|
||||
export default function MuiProvider({ children }: { children: ReactNode }) {
|
||||
return (
|
||||
<ThemeProvider theme={theme} defaultMode="dark">
|
||||
<MuiColorSchemeSync />
|
||||
{children}
|
||||
</ThemeProvider>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Mount the provider at the top of `AdminApp`**
|
||||
|
||||
In `src/admin/AdminApp.tsx`, add the import after the other component imports (e.g. after line 10):
|
||||
|
||||
```tsx
|
||||
import MuiProvider from "./ui/MuiProvider";
|
||||
```
|
||||
|
||||
Then wrap the existing returned tree. Change:
|
||||
|
||||
```tsx
|
||||
return (
|
||||
<AuthProvider>
|
||||
```
|
||||
|
||||
to:
|
||||
|
||||
```tsx
|
||||
return (
|
||||
<MuiProvider>
|
||||
<AuthProvider>
|
||||
```
|
||||
|
||||
and change the matching closing tag at the end of the JSX:
|
||||
|
||||
```tsx
|
||||
</AuthProvider>
|
||||
);
|
||||
```
|
||||
|
||||
to:
|
||||
|
||||
```tsx
|
||||
</AuthProvider>
|
||||
</MuiProvider>
|
||||
);
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Verify the app builds and renders with no console errors**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
npx tsc --noEmit -p tsconfig.json
|
||||
npm run build:client
|
||||
```
|
||||
|
||||
Expected: both succeed. (The user runs the dev server separately — do not start it. Verification of live render happens via the `/ui-kit` route in Task 6.)
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add src/admin/ui/MuiProvider.tsx src/admin/ui/MuiColorSchemeSync.tsx src/admin/AdminApp.tsx
|
||||
git commit -m "feat(mui): mount MUI provider + sync color scheme to data-theme"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 5: First UI-kit wrappers + barrel
|
||||
|
||||
**Files:**
|
||||
|
||||
- Create: `src/admin/ui/Button.tsx`, `src/admin/ui/Card.tsx`, `src/admin/ui/TextField.tsx`, `src/admin/ui/index.ts`
|
||||
|
||||
- [ ] **Step 1: Create the wrappers**
|
||||
|
||||
Create `src/admin/ui/Button.tsx`:
|
||||
|
||||
```tsx
|
||||
import MuiButton, { type ButtonProps } from "@mui/material/Button";
|
||||
|
||||
/** App Button: defaults to the brand primary contained style. */
|
||||
export default function Button(props: ButtonProps) {
|
||||
return <MuiButton variant="contained" color="primary" {...props} />;
|
||||
}
|
||||
```
|
||||
|
||||
Create `src/admin/ui/Card.tsx`:
|
||||
|
||||
```tsx
|
||||
import MuiCard, { type CardProps } from "@mui/material/Card";
|
||||
import CardContent from "@mui/material/CardContent";
|
||||
|
||||
export default function Card({ children, ...props }: CardProps) {
|
||||
return (
|
||||
<MuiCard {...props}>
|
||||
<CardContent>{children}</CardContent>
|
||||
</MuiCard>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
Create `src/admin/ui/TextField.tsx`:
|
||||
|
||||
```tsx
|
||||
import MuiTextField, { type TextFieldProps } from "@mui/material/TextField";
|
||||
|
||||
/** App TextField: small + outlined + full width by default. */
|
||||
export default function TextField(props: TextFieldProps) {
|
||||
return <MuiTextField size="small" variant="outlined" fullWidth {...props} />;
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Create the barrel**
|
||||
|
||||
Create `src/admin/ui/index.ts`:
|
||||
|
||||
```ts
|
||||
export { default as MuiProvider } from "./MuiProvider";
|
||||
export { default as Button } from "./Button";
|
||||
export { default as Card } from "./Card";
|
||||
export { default as TextField } from "./TextField";
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Verify type-check**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
npx tsc --noEmit -p tsconfig.json
|
||||
```
|
||||
|
||||
Expected: PASS.
|
||||
|
||||
- [ ] **Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git add src/admin/ui/Button.tsx src/admin/ui/Card.tsx src/admin/ui/TextField.tsx src/admin/ui/index.ts
|
||||
git commit -m "feat(mui): first ui-kit wrappers (Button, Card, TextField) + barrel"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 6: Dev-only `/ui-kit` showcase route
|
||||
|
||||
**Files:**
|
||||
|
||||
- Create: `src/admin/pages/UiKit.tsx`
|
||||
- Modify: `src/admin/AdminApp.tsx`
|
||||
|
||||
- [ ] **Step 1: Create the showcase page**
|
||||
|
||||
Create `src/admin/pages/UiKit.tsx`:
|
||||
|
||||
```tsx
|
||||
import ScopedCssBaseline from "@mui/material/ScopedCssBaseline";
|
||||
import Box from "@mui/material/Box";
|
||||
import Stack from "@mui/material/Stack";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import Chip from "@mui/material/Chip";
|
||||
import { Button, Card, TextField } from "../ui";
|
||||
import { useTheme } from "../../context/ThemeContext";
|
||||
|
||||
export default function UiKit() {
|
||||
const { theme, toggleTheme } = useTheme();
|
||||
return (
|
||||
<ScopedCssBaseline>
|
||||
<Box sx={{ p: 4, minHeight: "100vh", bgcolor: "background.default" }}>
|
||||
<Stack direction="row" alignItems="center" spacing={2} mb={3}>
|
||||
<Typography variant="h4">UI Kit</Typography>
|
||||
<Button color="inherit" variant="outlined" onClick={toggleTheme}>
|
||||
Theme: {theme}
|
||||
</Button>
|
||||
</Stack>
|
||||
|
||||
<Stack spacing={3} sx={{ maxWidth: 520 }}>
|
||||
<Card>
|
||||
<Typography variant="h6" gutterBottom>
|
||||
Faktury
|
||||
</Typography>
|
||||
<Typography color="text.secondary" gutterBottom>
|
||||
12 vystavených tento měsíc
|
||||
</Typography>
|
||||
<Stack direction="row" spacing={1} mb={2}>
|
||||
<Chip label="Zaplaceno" color="success" size="small" />
|
||||
<Chip label="Po splatnosti" color="error" size="small" />
|
||||
</Stack>
|
||||
<TextField
|
||||
label="Hledat fakturu"
|
||||
placeholder="Číslo nebo klient…"
|
||||
/>
|
||||
<Box mt={2}>
|
||||
<Button>Nová faktura</Button>
|
||||
</Box>
|
||||
</Card>
|
||||
</Stack>
|
||||
</Box>
|
||||
</ScopedCssBaseline>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Register the route, DEV-only**
|
||||
|
||||
In `src/admin/AdminApp.tsx`, add the lazy import alongside the other lazy pages (after line 11's eager imports / with the `lazy(...)` block):
|
||||
|
||||
```tsx
|
||||
const UiKit = lazy(() => import("./pages/UiKit"));
|
||||
```
|
||||
|
||||
Then, inside `<Routes>`, add a sibling route to `login` (so the showcase renders without the shell), guarded so it is dropped from production builds:
|
||||
|
||||
```tsx
|
||||
{
|
||||
import.meta.env.DEV && <Route path="ui-kit" element={<UiKit />} />;
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Verify build (prod) excludes it and type-check passes**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
npx tsc --noEmit -p tsconfig.json
|
||||
npm run build:client
|
||||
```
|
||||
|
||||
Expected: both succeed. `import.meta.env.DEV` is `false` in the production build, so the route is tree-shaken.
|
||||
|
||||
- [ ] **Step 4: Manual + Playwright verification**
|
||||
|
||||
The user runs the dev server. Once it's up, verify at `/ui-kit`:
|
||||
|
||||
- Card, button (red gradient, pill), chips (tinted), and text field render in the Soft-SaaS style.
|
||||
- Clicking **Theme: dark/light** flips `data-theme` and **both MUI components AND the page background** swap — proving the `colorSchemeSelector` bridge works.
|
||||
- No new console errors.
|
||||
|
||||
Capture baseline screenshots with the available Playwright tooling (light + dark) and save under `docs/superpowers/visual-baselines/phase-0-ui-kit-{light,dark}.png`.
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add src/admin/pages/UiKit.tsx src/admin/AdminApp.tsx
|
||||
git commit -m "feat(mui): dev-only /ui-kit showcase proving the theme + data-theme bridge"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 7: Token-level palette refresh (app-wide)
|
||||
|
||||
**Files:**
|
||||
|
||||
- Modify: `src/admin/variables.css`
|
||||
|
||||
Per spec §7, the token-level palette refresh is applied app-wide so migrated and legacy pages share the refreshed palette. The only token change for Phase 0 is the light canvas (the new look's `#f4f3f1` vs current `#f5f4f2`); chip/structure changes are component-level (MUI) and need no token edit.
|
||||
|
||||
- [ ] **Step 1: Update the light canvas token**
|
||||
|
||||
In `src/admin/variables.css`, inside the `[data-theme="light"]` block, change:
|
||||
|
||||
```css
|
||||
--bg-primary: #f5f4f2;
|
||||
```
|
||||
|
||||
to:
|
||||
|
||||
```css
|
||||
--bg-primary: #f4f3f1;
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Verify build**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
npm run build:client
|
||||
```
|
||||
|
||||
Expected: succeeds.
|
||||
|
||||
- [ ] **Step 3: Manual check**
|
||||
|
||||
With the dev server (user-run), confirm in light mode the app canvas is the refreshed `#f4f3f1` and nothing else shifted structurally. (This is the deliberate, minor app-wide color change noted in the spec — not a no-op.)
|
||||
|
||||
- [ ] **Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git add src/admin/variables.css
|
||||
git commit -m "style(tokens): refresh light canvas to #f4f3f1 (two-layer refresh, token level)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 8: Phase-0 release verification
|
||||
|
||||
**Files:** none (verification + tag)
|
||||
|
||||
- [ ] **Step 1: Full type-check, unit tests, and build**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
npx tsc --noEmit -p tsconfig.json
|
||||
npx vitest run src/admin/theme.test.ts
|
||||
npm run build
|
||||
```
|
||||
|
||||
Expected: `tsc` clean; 6 theme tests pass; server + client build succeed.
|
||||
|
||||
- [ ] **Step 2: Backend test suite still green (server untouched)**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
npm test
|
||||
```
|
||||
|
||||
Expected: existing `auth` + `numbering` suites pass (Phase 0 changed no server code).
|
||||
|
||||
- [ ] **Step 3: Definition-of-done checklist (manual, dev server)**
|
||||
|
||||
- `/ui-kit` renders Button/Card/TextField/Chip in the Soft-SaaS style in **both** themes.
|
||||
- Theme toggle flips `data-theme` and swaps MUI + legacy colors together; MUI's `useColorScheme().mode` matches (no console warnings).
|
||||
- No new console errors on any existing page (the provider mount + token refresh are non-breaking).
|
||||
- Light canvas is `#f4f3f1`.
|
||||
- Production build excludes the `/ui-kit` route.
|
||||
|
||||
- [ ] **Step 4: Tag/release per the project release process**
|
||||
|
||||
Follow `CLAUDE.md` "Release Process" (bump `package.json`, build, tag, deploy) when shipping Phase 0. No Prisma migration is involved (frontend-only).
|
||||
|
||||
---
|
||||
|
||||
## Self-review notes (for the controller)
|
||||
|
||||
- **Spec coverage:** Phase 0 items from spec §8 are all covered — deps + `@types/react` (Task 1), `theme.ts` light/dark (Tasks 2–3), provider + `ScopedCssBaseline` + `ThemeContext`↔`setMode` sync (Tasks 4, 6), dev-only `/ui-kit` (Task 6), token-level refresh (Task 7). The full `ui/` kit, AppShell, and Vozidla are explicitly deferred to later plans (scope note).
|
||||
- **Type consistency:** `theme` (named export) is used consistently in `theme.test.ts`, `MuiProvider.tsx`. `useTheme` refers to the app's `ThemeContext` (aliased `useAppTheme` in the sync component to avoid colliding with MUI's `useTheme`). Wrappers are default exports re-exported via the barrel.
|
||||
- **Open risk to verify in Task 1/6:** exact MUI v7 API for `cssVariables.colorSchemeSelector` + `ThemeProvider` `defaultMode` — confirm against the installed version's docs; if `defaultMode` belongs on a different provider in the installed version, adjust in Task 4.
|
||||
Reference in New Issue
Block a user