From 66e25aa778b00b6b1866e79965a01879c8bd6198 Mon Sep 17 00:00:00 2001 From: Jake Date: Fri, 15 May 2026 08:35:54 +0100 Subject: [PATCH] v0.2 Phase 3: additive semantic tokens + KI-05 resolution MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Additive token grammar (no renames, no replacements): - --color-caution (dark #e8be4a, light #a08a1f) becomes the canonical name for tuned-amber notice surfaces in the new wrapper grammar - --color-warning is kept as a CSS var() alias of --color-caution so every existing text-warning / bg-warning call site stays valid - --color-info (dark #7a9ec0, light #3d6a8a) is the soft blue-grey signal for the new LumotiaNotice info variant - --color-accent-environment (dark #8fae9a, light #4a7058) is an optional sage/moss support token for empty-state illustrations and environment-neutral status dots. NOT a brand swap — amber/ copper --color-accent stays primary Mirrored in src/design-system/colors_and_type.css (the buildless preview pages bypass Tailwind so the duplication is intentional). KI-05 resolved in the same commit, per the plan: - src/lib/types/app.ts: drop `theme` from SettingsState - src/lib/stores/page.svelte.ts: drop `theme: "Dark"` from defaults - src/routes/+layout.svelte: drop the migration $effect, add a one-shot migrateLegacyTheme() on mount that copies any historical lumotia_settings.theme into preferences.theme and strips the legacy field. Idempotent — subsequent loads short-circuit - src/routes/{float,viewer,preview}/+layout@.svelte: drop the same $effect; secondary windows inherit theme via PREFERENCES_CHANGED_EVENT - src/lib/pages/SettingsPage.svelte: both SegmentedButton bindings (quick-settings row at :1272, Appearance group at :2606) now use Svelte 5 function bindings ({ get, set }) backed by prefs.theme and updatePreferences. No SegmentedButton API change Phase 3 per-page gate green: npm run check (0/0/4129 files), npm test (13/13), npm run test:e2e (16/16). No regressions in the Phase 1 smoke baseline. Co-Authored-By: Claude Opus 4.7 (1M context) --- docs/release/v0.2-frontend-overhaul.md | 4 +-- src/app.css | 20 ++++++++++++-- src/design-system/colors_and_type.css | 8 +++++- src/lib/pages/SettingsPage.svelte | 17 ++++++++++-- src/lib/stores/page.svelte.ts | 1 - src/lib/types/app.ts | 4 ++- src/routes/+layout.svelte | 38 +++++++++++++++++++------- src/routes/float/+layout@.svelte | 10 ++----- src/routes/preview/+layout@.svelte | 10 ++----- src/routes/viewer/+layout@.svelte | 10 ++----- 10 files changed, 79 insertions(+), 43 deletions(-) diff --git a/docs/release/v0.2-frontend-overhaul.md b/docs/release/v0.2-frontend-overhaul.md index 45c25f0..0a2d211 100644 --- a/docs/release/v0.2-frontend-overhaul.md +++ b/docs/release/v0.2-frontend-overhaul.md @@ -199,8 +199,8 @@ Filled during execution. Each entry: phase #, date, what landed, what's next. |---|---|---| | 0 | ✅ complete | Doc written; tooling pins recorded | | 1 | ✅ complete | Tooling baseline green: `npm run check` clean, `npm test` clean, `npm run test:e2e` 16/16, `npm run guard:no-skeleton` clean, 10 screenshot artefacts. Browser-preview OS detection fixed (see Regression diary). | -| 2 | pending | UI/form deps | -| 3 | pending | Additive tokens + KI-05 | +| 2 | ✅ complete | bits-ui 2.18.1, formsnap 2.0.1, sveltekit-superforms 2.30.1, zod 4.4.3, @internationalized/date 3.12.1. `npm audit signatures` 273 verified + 93 attestations. | +| 3 | ✅ complete | New tokens `--color-caution`, `--color-info`, `--color-accent-environment` (dark + light), `--color-warning` aliased to `var(--color-caution)`. KI-05 resolved: `theme` dropped from SettingsState type + defaults, all four route-layout migration `$effect`s deleted, two SettingsPage SegmentedButton bindings repointed to `preferences.theme` via Svelte 5 function bindings, one-shot legacy-theme migration on first mount strips the field after copying. Gate green (check 0/0, vitest 13/13, e2e 16/16). | | 4 | pending | Wrapper alias layer | | 5 | pending | New primitives + design-system-v2 preview | | 6 | pending | Shell split | diff --git a/src/app.css b/src/app.css index 9d0e98c..582fe89 100644 --- a/src/app.css +++ b/src/app.css @@ -100,7 +100,18 @@ (#1b1a17) verified mentally for each. */ --color-success: #5fc28a; --color-danger: #e85f5f; - --color-warning: #e8be4a; + /* v0.2 coherence-pass aliases. `caution` is the canonical name in the + new wrapper grammar; `warning` is kept as a CSS var() alias so + existing `text-warning` / `bg-warning` call sites resolve to the + same value without a touch. */ + --color-caution: #e8be4a; + --color-warning: var(--color-caution); + --color-info: #7a9ec0; + + /* v0.2: optional support token for sage/moss surfaces — empty-state + illustrations, environmental neutral status dots. NOT a brand + swap; --color-accent (amber/copper) stays primary. */ + --color-accent-environment: #8fae9a; /* Overlays — used by modal scrims. Derived from --color-bg #0f0e0c at 0.7 alpha so the dim sits on the brand neutral, not pure black. */ @@ -190,7 +201,12 @@ AA on cream backgrounds. */ --color-success: #1f7344; --color-danger: #b32626; - --color-warning: #a08a1f; + /* v0.2 coherence-pass: --color-warning inherits @theme's + `var(--color-caution)`, so light theme only needs to redefine the + source token. */ + --color-caution: #a08a1f; + --color-info: #3d6a8a; + --color-accent-environment: #4a7058; --color-sidebar: #f5f2ed; --color-nav-active: #eae6e0; diff --git a/src/design-system/colors_and_type.css b/src/design-system/colors_and_type.css index 98dfa93..e5acd54 100644 --- a/src/design-system/colors_and_type.css +++ b/src/design-system/colors_and_type.css @@ -95,7 +95,13 @@ /* — Semantic — Phase 10b chroma bump for clearer signal. — */ --success: #5fc28a; --danger: #e85f5f; - --warning: #e8be4a; + /* v0.2 coherence-pass aliases (mirrors src/app.css @theme). */ + --caution: #e8be4a; + --warning: var(--caution); + --info: #7a9ec0; + + /* v0.2 optional sage/moss support token (mirrors src/app.css). */ + --accent-environment: #8fae9a; /* — Overlays — modal scrim. Derived from --bg #0f0e0c at 0.7 alpha. — */ --overlay-dim: rgba(15, 14, 12, 0.7); diff --git a/src/lib/pages/SettingsPage.svelte b/src/lib/pages/SettingsPage.svelte index d3d0310..c00c739 100644 --- a/src/lib/pages/SettingsPage.svelte +++ b/src/lib/pages/SettingsPage.svelte @@ -1269,7 +1269,14 @@

Theme

- + prefs.theme === "light" ? "Light" : "Dark", + (v) => updatePreferences({ theme: v === "Light" ? "light" : "dark" }) + } + />

Font size

@@ -2603,7 +2610,13 @@

Theme

- + prefs.theme === "light" ? "Light" : prefs.theme === "system" ? "System" : "Dark", + (v) => updatePreferences({ theme: v === "Light" ? "light" : v === "System" ? "system" : "dark" }) + } + />
diff --git a/src/lib/stores/page.svelte.ts b/src/lib/stores/page.svelte.ts index 8c56a65..c31add7 100644 --- a/src/lib/stores/page.svelte.ts +++ b/src/lib/stores/page.svelte.ts @@ -69,7 +69,6 @@ const defaults: SettingsState = { soundCues: false, soundCueVolume: 0.15, includeTimestamps: true, - theme: "Dark", fontSize: 14, aiTier: "cleanup", llmModelId: null, diff --git a/src/lib/types/app.ts b/src/lib/types/app.ts index 34409d5..a560657 100644 --- a/src/lib/types/app.ts +++ b/src/lib/types/app.ts @@ -54,7 +54,9 @@ export interface SettingsState { soundCues: boolean; soundCueVolume: number; includeTimestamps: boolean; - theme: "Dark" | "Light" | "System"; + // theme removed in v0.2 Phase 3 — KI-05 resolution. The canonical + // theme field is `preferences.theme` (preferences.svelte.ts). The + // legacy field migrates on first load (see page.svelte.ts). fontSize: number; aiTier: AiTier; llmModelId: LlmModelIdStr | null; diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index 1f4afc9..15a0af0 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -56,16 +56,28 @@ $sveltePage.url.pathname.startsWith("/viewer") ); - // Theme — migrate from old class-based to new data-attribute system - // The preferences store handles DOM application via data-theme attribute - $effect(() => { - // Sync legacy settings.theme → preferences store - const legacyTheme = settings.theme; - const mapped = legacyTheme === "Light" ? "light" : legacyTheme === "Dark" ? "dark" : "system"; - if (prefs.theme !== mapped) { - updatePreferences({ theme: mapped }); - } - }); + // KI-05 resolution (v0.2 Phase 3) — preferences.theme is canonical. + // The legacy settings.theme field is gone from SettingsState; a + // one-shot localStorage migration (migrateLegacyTheme below) runs + // once on mount to copy any historical "Dark"/"Light"/"System" + // value into preferences, then strips the field so subsequent + // loads short-circuit. + function migrateLegacyTheme() { + if (typeof localStorage === "undefined") return; + const raw = localStorage.getItem("lumotia_settings"); + if (!raw) return; + let parsed; + try { parsed = JSON.parse(raw); } catch { return; } + const data = parsed?.data ?? parsed; + if (!data || typeof data !== "object" || !("theme" in data)) return; + const legacy = data.theme; + const mapped = legacy === "Light" ? "light" : legacy === "System" ? "system" : "dark"; + if (mapped !== prefs.theme) updatePreferences({ theme: mapped }); + delete data.theme; + if (parsed && typeof parsed === "object" && "data" in parsed) parsed.data = data; + else parsed = data; + try { localStorage.setItem("lumotia_settings", JSON.stringify(parsed)); } catch {} + } // Global hotkey registration — dual backend // Wayland: evdev via lumotia-hotkey crate (works without display server) @@ -340,6 +352,12 @@ } onMount(async () => { + // KI-05 v0.2 one-shot: copy any legacy settings.theme into + // preferences.theme, then strip it from localStorage. Runs first + // so the DOM picks up the right data-theme attribute before any + // theme-sensitive rendering. + migrateLegacyTheme(); + // Auto-collapse if window is already narrow on first load handleResize(); window.addEventListener("resize", handleResize); diff --git a/src/routes/float/+layout@.svelte b/src/routes/float/+layout@.svelte index a6ad755..61f334f 100644 --- a/src/routes/float/+layout@.svelte +++ b/src/routes/float/+layout@.svelte @@ -23,14 +23,8 @@ const prefs = getPreferences(); - // Theme — sync legacy settings to preferences store (same as main layout) - $effect(() => { - const legacyTheme = settings.theme; - const mapped = legacyTheme === "Light" ? "light" : legacyTheme === "Dark" ? "dark" : "system"; - if (prefs.theme !== mapped) { - updatePreferences({ theme: mapped }); - } - }); + // v0.2 Phase 3 KI-05: theme is now preferences.theme only. The + // float window inherits via PREFERENCES_CHANGED_EVENT below. // Listen for settings changes from main window if (typeof window !== "undefined") { diff --git a/src/routes/preview/+layout@.svelte b/src/routes/preview/+layout@.svelte index d023117..73c5c33 100644 --- a/src/routes/preview/+layout@.svelte +++ b/src/routes/preview/+layout@.svelte @@ -17,14 +17,8 @@ const prefs = getPreferences(); - // Keep transcript-editor theme sync trick: legacy settings → preferences - $effect(() => { - const legacyTheme = settings.theme; - const mapped = legacyTheme === "Light" ? "light" : legacyTheme === "Dark" ? "dark" : "system"; - if (prefs.theme !== mapped) { - updatePreferences({ theme: mapped }); - } - }); + // v0.2 Phase 3 KI-05: theme is now preferences.theme only. The + // preview window inherits via PREFERENCES_CHANGED_EVENT below. if (typeof window !== "undefined") { window.addEventListener("storage", (event) => { diff --git a/src/routes/viewer/+layout@.svelte b/src/routes/viewer/+layout@.svelte index 2efddd4..eb6415f 100644 --- a/src/routes/viewer/+layout@.svelte +++ b/src/routes/viewer/+layout@.svelte @@ -20,14 +20,8 @@ const prefs = getPreferences(); - // Theme — sync legacy settings to preferences store - $effect(() => { - const legacyTheme = settings.theme; - const mapped = legacyTheme === "Light" ? "light" : legacyTheme === "Dark" ? "dark" : "system"; - if (prefs.theme !== mapped) { - updatePreferences({ theme: mapped }); - } - }); + // v0.2 Phase 3 KI-05: theme is now preferences.theme only. The + // viewer window inherits via PREFERENCES_CHANGED_EVENT below. // Sync settings from main window if (typeof window !== "undefined") {