Implement M1.5 visual design foundation: tokens, IBM Plex, theme toggle, chart themes

This commit is contained in:
2026-06-05 01:29:10 +03:00
parent 547edb85e4
commit 22f0556ef8
31 changed files with 713 additions and 136 deletions
+2 -2
View File
@@ -373,8 +373,8 @@ export function ThemeToggle() {
const theme = useAppStore((s) => s.uiTheme);
const setTheme = useAppStore((s) => s.setTheme);
return (
<button onClick={() => setTheme(theme === 'experimental' ? 'light' : 'experimental')}>
{theme === 'experimental' ? '🌙' : '☀️'}
<button onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}>
{theme === 'dark' ? '🌙' : '☀️'}
</button>
);
}
+4 -2
View File
@@ -240,7 +240,7 @@ export interface UserSettings {
editor: { fontSize: number; theme: string; minimap: boolean; wordWrap: 'on' | 'off';
lineNumbers: 'on' | 'off'; tabSize: number };
performance: { renderDebounce: number };
ui: { theme: 'light' | 'experimental'; previewFitMode: 'default' | 'width' | 'height' | 'full' };
ui: { theme: 'light' | 'dark'; previewFitMode: 'default' | 'width' | 'height' | 'full' };
formatting: { dateFormat: 'smart' | 'iso' | 'custom'; customDateFormat: string };
}
@@ -256,7 +256,7 @@ const DEFAULTS: UserSettings = {
};
// NOTE — editor.theme default is 'auto': the editor theme follows the app UI
// theme (light -> light editor theme, experimental -> dark) via custom Monaco
// theme (light -> light editor theme, dark -> dark) via custom Monaco
// themes that match the app chrome, unless the user picks an explicit override.
// The explicit-override option set (custom themes; whether to include High
// Contrast or the stock Monaco themes) is still TBD — see spec §07's provisional
@@ -315,6 +315,8 @@ const SORT_DEFAULTS = { sortBy: 'modified' as const, sortOrder: 'desc' as const
> **Do:** keep a single complete `DEFAULTS` object as the source of truth and merge over it.
> **Don't:** read individual keys with bespoke `?? fallback` at each call site; one stale default and the shapes drift.
> **Testing:** exercise localStorage adapters against an **injected stub** (`vi.stubGlobal('localStorage', …)`), not the ambient global. Under Node + happy-dom a non-functional Node `localStorage` global shadows happy-dom's, so relying on the ambient one fails with `localStorage.clear is not a function`. Applies to every prefs/settings adapter test (settings-store today; dataset-payload/prefs stores later).
---
## 6. Storage Tiers, Budgets & Quota Monitoring
@@ -139,7 +139,7 @@ export const lightChartConfig: Config = {
view: { stroke: 'transparent' },
};
export const experimentalChartConfig: Config = {
export const darkChartConfig: Config = {
background: 'transparent',
font: '"Inter", sans-serif',
title: { fontSize: 15, fontWeight: 600, color: '#f4f4f5' },
@@ -167,7 +167,7 @@ import type { UiTheme } from './theme'; // core-local — never import from src/
const CHART_CONFIG: Record<UiTheme, Config> = {
light: lightChartConfig,
experimental: experimentalChartConfig,
dark: darkChartConfig,
};
export function chartConfigFor(theme: UiTheme): Config {
@@ -190,6 +190,26 @@ re-rendering picks up the new config and the chart restyles automatically.
- **Don't** let the user's stored spec carry a `config`; the theme config is
applied at embed time via the embed options, leaving the spec theme-agnostic.
### Theme flow (end to end)
Theme spans several layers; the path is:
`AppStore.uiTheme` (+ `toggleTheme`) → `orchestration/theme.ts` mirrors it onto
`<html data-theme>` and writes through to `infrastructure/settings-store.ts`
(localStorage `ui.theme`). On load, `initTheme()` — called from `main.tsx`
**before** `createRoot().render` — hydrates the saved theme. Chart and editor
follow by subscribing to `uiTheme`: `LivePreview` re-embeds with
`chartConfigFor(theme)`, `SpecEditor` sets the Monaco theme. UI chrome repaints
purely from the `[data-theme]` token swap in `styles/tokens.css`. The header
`ThemeToggle` is the user control.
- **Do** hydrate the theme **synchronously before first paint** — an async
hydrate (e.g. inside `initApp`) flashes the default theme on load.
- **Do** keep the store browser-free: the `data-theme` write and the localStorage
write-through live in `orchestration/theme.ts`, never in the store or a component.
- The control currently lives in the header; spec §07 houses it in the Settings
modal (M5), which will share the same `ui.theme` key.
---
## 4. Field-Name Escaping
+5 -4
View File
@@ -60,8 +60,9 @@ were choices, not drift:
## 3. Tokens
All tokens are CSS custom properties on `:root`, themed by overriding them on
`[data-theme]` (and, for accent, `[data-accent]`). The specimen is the live source
of truth for values until they're ported to `styles/tokens.css`.
`[data-theme]` (and, for accent, `[data-accent]`). As of M1.5 the settled values
live in `styles/tokens.css`; the specimen remains the sandbox for trying new
tokens/themes before porting them across.
### 3.1 Typography — IBM Plex
@@ -172,7 +173,7 @@ The chart `Config` is themed to match the app, per theme:
| Artifact | Role |
|---|---|
| [`visual-specimen.html`](./visual-specimen.html) | Living preview + token sandbox. Iterate here first |
| `styles/tokens.css` | The settled tokens (currently placeholders) — port from the specimen |
| `styles/tokens.css` | The settled tokens — ported from the specimen in M1.5 |
| `styles/base.css` | Font wiring (`@fontsource`), reset, reduced-motion |
| component `*.module.css` | Consume tokens only; no raw hexes, no hardcoded hue |
| `src/core/vega-themes.ts` | Chart `Config` per theme; categorical palettes |
@@ -196,7 +197,7 @@ Convention: clone under `/Users/oleh/code/reference/` with
| **Principles / the "why"** (philosophy, 2x grid, color rationale, type, motion, icon geometry) | `design-language-website` | `src/pages/`: `philosophy/principles.mdx`, `2x-grid.mdx`, `color.mdx`, `typography/*.mdx`, `animation/overview.mdx`, `iconography/ui-icons/design.mdx` (~1.4 GB clone — image-heavy; the MDX is what we want) |
| **Token values** (gray/blue ramps, type scale, font families, motion durations/easings, theme role→value maps) | `carbon` | `packages/colors/src/colors.ts`, `packages/type/src/{scale,fontFamily,fontWeight}.ts`, `packages/motion/src/index.ts`, `packages/themes/src/{white,g100}.ts` |
| **Component-level usage guidance** | `carbon-website` | `src/pages/**/*.mdx` |
| **Data-viz categorical chart palette** (for `vega-themes.ts` `range.category`) | `carbon-charts` | *not yet cloned* — clone when we do the chart-theming pass |
| **Data-viz categorical chart palette** (for `vega-themes.ts` `range.category`) | `carbon-charts` | cloned in M1.5 → `packages/core/scss/_color-palette.scss` (the `'14'` pairing, white + g100); token→hex resolved against `carbon` `packages/colors/src/colors.ts` |
> The decisions we made *from* these sources are captured above (§16) and in the
> specimen, so we don't need to re-derive them — only return to the repos to extend