From a9733544c0a3e059b59939a1c6719fd407d2311b Mon Sep 17 00:00:00 2001 From: Jake Date: Fri, 15 May 2026 08:51:55 +0100 Subject: [PATCH] =?UTF-8?q?v0.2=20Phase=206:=20shell=20split=20=E2=80=94?= =?UTF-8?q?=20AppRuntime=20/=20AppChrome=20/=20AppOverlays?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit src/routes/+layout.svelte was 537 LOC of mixed runtime, chrome and overlay concerns. Split into three single-purpose shells under src/lib/shell/, with +layout.svelte reduced to ~28 LOC of pure composition. AppRuntime (no DOM beyond ): - Global hotkey dual backend (evdev / tauri-plugin-global-shortcut) - 120ms hotkey debounce (sacred behaviour §5 #2) - PREFERENCES_CHANGED_EVENT listener (sacred §5 #4) - KI-05 one-shot legacy-theme migration - Sidebar hotkeys: [ toggle, Ctrl+K, Ctrl+, (sacred §5 #10) - Wind-down tray listener - Meeting auto-capture poller - Global frontend error capture - Nudge bus + implementation intentions lifecycle - Font-size CSS var $effect - Window resize → sidebar auto-collapse - Onboarding/first-run check + update check + LLM status warm-up AppChrome (the visual shell): - Titlebar (OS-aware via customChrome helper) - Sidebar (recording-state-aware — sacred §5 #1 stays in Sidebar.svelte verbatim) - Main slot - TaskSidebar conditional rail AppOverlays (mounted-once globals): - ToastViewport - FocusTimer - MorningTriageModal - ResizeHandles (OS-gated) src/lib/utils/customChrome.svelte.ts holds the single source of truth for useCustomChrome, a module-level $state both AppChrome and AppOverlays subscribe to. Each only ever sees one loadOsInfo() call between them. Secondary windows still escape via their own +layout@.svelte; the defensive isSecondaryWindow check in +layout.svelte stays so a direct /float, /viewer, /preview navigation through the root layout also drops the chrome. Phase 6 per-page gate green: npm run check (0/0/5704 files), npm test, npm run test:browser (3/3), 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 | 2 +- src/lib/shell/AppChrome.svelte | 43 ++ src/lib/shell/AppOverlays.svelte | 43 ++ src/lib/shell/AppRuntime.svelte | 399 ++++++++++++++++++ src/lib/utils/customChrome.svelte.ts | 26 ++ src/routes/+layout.svelte | 555 +------------------------ 6 files changed, 530 insertions(+), 538 deletions(-) create mode 100644 src/lib/shell/AppChrome.svelte create mode 100644 src/lib/shell/AppOverlays.svelte create mode 100644 src/lib/shell/AppRuntime.svelte create mode 100644 src/lib/utils/customChrome.svelte.ts diff --git a/docs/release/v0.2-frontend-overhaul.md b/docs/release/v0.2-frontend-overhaul.md index 601ed34..4cb799b 100644 --- a/docs/release/v0.2-frontend-overhaul.md +++ b/docs/release/v0.2-frontend-overhaul.md @@ -203,7 +203,7 @@ Filled during execution. Each entry: phase #, date, what landed, what's next. | 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 | ✅ complete | Six wrapper aliases under `src/lib/ui/`: Lumotia{Card,StatusPill,Toggle,SettingsGroup,EmptyState,PostCaptureCard}. Same prop APIs, $bindable forwarded for Toggle. Underlying `src/lib/components/*.svelte` untouched. | | 5 | ✅ complete | 11 primitives shipped under `src/lib/ui/`. `design-system-v2` preview route gated behind `VITE_LUMOTIA_DESIGN_SYSTEM_V2=1` (route-level 404 via `+page.ts` load, not nav-hidden). Browser-mode component test (LumotiaButton): 3/3 passing in Chromium. Gate green (check 0/0/5700 files, vitest 0/0, test:browser 3/3, e2e 16/16). | -| 6 | pending | Shell split | +| 6 | ✅ complete | `src/routes/+layout.svelte` (537 LOC) split into `$lib/shell/AppRuntime.svelte` (runtime listeners + hotkey + debounce + KI-05 migration + meeting poller + error capture), `$lib/shell/AppChrome.svelte` (titlebar + sidebar + task rail), `$lib/shell/AppOverlays.svelte` (toasts + focus timer + triage modal + resize handles). Shared `useCustomChrome` flag moved into `src/lib/utils/customChrome.svelte.ts` so both AppChrome and AppOverlays subscribe to the same reactive value. +layout.svelte is now ~28 LOC of pure composition. Gate green. | | 7.1 ShutdownRitualPage | pending | | | 7.2 FilesPage | pending | | | 7.3 FirstRunPage | pending | | diff --git a/src/lib/shell/AppChrome.svelte b/src/lib/shell/AppChrome.svelte new file mode 100644 index 0000000..6af38f4 --- /dev/null +++ b/src/lib/shell/AppChrome.svelte @@ -0,0 +1,43 @@ + + +
+ {#if customChrome.current} + + {/if} +
+ {#if page.current !== "first-run"} + + {/if} +
+ {@render children()} +
+ {#if page.taskSidebarOpen && page.current !== "first-run"} +
+ +
+ {/if} +
+
diff --git a/src/lib/shell/AppOverlays.svelte b/src/lib/shell/AppOverlays.svelte new file mode 100644 index 0000000..9cf2a30 --- /dev/null +++ b/src/lib/shell/AppOverlays.svelte @@ -0,0 +1,43 @@ + + + + + + + + + + + + +{#if customChrome.current} + +{/if} diff --git a/src/lib/shell/AppRuntime.svelte b/src/lib/shell/AppRuntime.svelte new file mode 100644 index 0000000..6d320ed --- /dev/null +++ b/src/lib/shell/AppRuntime.svelte @@ -0,0 +1,399 @@ + + + diff --git a/src/lib/utils/customChrome.svelte.ts b/src/lib/utils/customChrome.svelte.ts new file mode 100644 index 0000000..ddeea4c --- /dev/null +++ b/src/lib/utils/customChrome.svelte.ts @@ -0,0 +1,26 @@ +// v0.2 Phase 6 helper. Single source of truth for the "should this +// window paint its own titlebar + resize handles?" flag. +// +// Linux uses native KWin/Mutter decorations, so useCustomChrome is +// false. macOS and Windows opt in. AppChrome (titlebar) and +// AppOverlays (ResizeHandles) both subscribe to the same reactive +// value rather than each calling loadOsInfo independently. + +import { loadOsInfo, isLinux } from "$lib/utils/osInfo.js"; + +let initialised = false; +let value = $state(false); + +export function ensureCustomChromeLoaded() { + if (initialised) return; + initialised = true; + loadOsInfo() + .then(() => { value = !isLinux(); }) + .catch(() => { /* fallback to false; matches the safer Linux path */ }); +} + +export const customChrome = { + get current() { + return value; + }, +}; diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index 15a0af0..3caeb38 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -1,555 +1,36 @@ - - + {#if isSecondaryWindow} - {@render children()} {:else} -
- {#if useCustomChrome} - - {/if} -
- {#if page.current !== "first-run"} - - {/if} -
- {@render children()} -
- {#if page.taskSidebarOpen && page.current !== "first-run"} -
- -
- {/if} -
-
+ + {@render children()} + {/if} - - - - - - - - - - -{#if useCustomChrome} - -{/if} +