Chart theming: selectable chart theme + spec↔config merge/extract

This commit is contained in:
2026-06-12 16:48:48 +03:00
parent fe9d588103
commit 44a601affd
27 changed files with 1103 additions and 121 deletions
+8 -3
View File
@@ -243,7 +243,7 @@ Small, frequently-read structured records live in `localStorage`, not IndexedDB:
The pattern is **load-with-fallback, per-slice write-through merge.**
- **Load-with-fallback:** merge the parsed stored object over a complete `DEFAULTS` constant. Missing keys (a setting added in a later build) and malformed JSON silently fall back to defaults — the app always gets a fully-populated object and never `undefined`-crashes on a new field. For `UserSettings`, the normalization is **pure and lives in `@core/settings` (`loadSettings(raw)` / `defaultSettings()`)** — it clamps ranges and validates enums; the infra adapter is just the thin localStorage reader (`loadUserSettings()` = `loadSettings(readRaw())`).
- **Per-slice write-through merge:** every update reads current, merges in **just its slice**, and writes back. **The one `astrolabe:settings` record has multiple independent writers**, because settings are distributed and live-applied (spec §07 — no central save, no Apply step): the header theme toggle writes `ui.theme`, the preview Fit control writes `ui.previewFitMode`, and the per-pane settings clusters write `editor`/`performance`/`formatting`. **A writer that replaced the whole record would clobber the slices it doesn't own** — so each must field-merge. (There is deliberately no whole-record `saveSettings`.)
- **Per-slice write-through merge:** every update reads current, merges in **just its slice**, and writes back. **The one `astrolabe:settings` record has multiple independent writers**, because settings are distributed and live-applied (spec §07 — no central save, no Apply step): the header theme toggle writes `ui.theme`, the preview Fit control writes `ui.previewFitMode`, the preview Chart-theme picker writes `ui.chartTheme`, and the per-pane settings clusters write `editor`/`performance`/`formatting`. **A writer that replaced the whole record would clobber the slices it doesn't own** — so each must field-merge. (There is deliberately no whole-record `saveSettings`.)
- **Environment-guarded:** `localStorage` is absent or throws in some test/SSR contexts; guard access and degrade to defaults rather than throwing.
```ts
@@ -263,7 +263,11 @@ export interface UserSettings {
tabSize: number;
};
performance: { renderDebounce: number };
ui: { theme: 'light' | 'dark'; previewFitMode: 'default' | 'width' | 'height' | 'full' };
ui: {
theme: 'light' | 'dark';
previewFitMode: 'default' | 'width' | 'height' | 'full';
chartTheme: ChartThemeId; // 'astrolabe' | 'stock' | vega-themes preset id
};
formatting: { dateFormat: 'smart' | 'iso' | 'custom'; customDateFormat: string };
}
@@ -280,7 +284,7 @@ const DEFAULTS: UserSettings = {
tabSize: 2,
},
performance: { renderDebounce: 1500 },
ui: { theme: 'light', previewFitMode: 'default' },
ui: { theme: 'light', previewFitMode: 'default', chartTheme: 'astrolabe' },
formatting: { dateFormat: 'smart', customDateFormat: '' },
};
@@ -324,6 +328,7 @@ export function loadSettings(): UserSettings {
// record, so the others survive (see the per-slice rule above):
// saveUiTheme(theme) -> { ...current, ui: { ...current.ui, theme } }
// savePreviewFitMode(mode) -> { ...current, ui: { ...current.ui, previewFitMode } }
// saveChartTheme(chartTheme) -> { ...current, ui: { ...current.ui, chartTheme } }
// saveManagedSettings(managed) -> { ...current, editor, performance, formatting }
```