diff --git a/package.json b/package.json index c370011..a21845a 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,11 @@ "tauri": "tauri" }, "dependencies": { + "@fontsource-variable/fraunces": "^5.0.0", + "@fontsource-variable/jetbrains-mono": "^5.0.0", + "@fontsource-variable/newsreader": "^5.0.0", + "@fontsource/dm-sans": "^5.0.0", + "@fontsource/ibm-plex-mono": "^5.0.0", "@tauri-apps/api": "^2.0.0", "@tauri-apps/plugin-autostart": "^2.0.0", "@tauri-apps/plugin-dialog": "^2.0.0", diff --git a/src-tauri/src/settings.rs b/src-tauri/src/settings.rs index 664b744..ed481b5 100644 --- a/src-tauri/src/settings.rs +++ b/src-tauri/src/settings.rs @@ -42,6 +42,8 @@ pub struct Settings { pub claude_command: Option, /// How often (seconds) to refetch `/usage`. Defaults to 300 (5 min). pub cli_refresh_secs: u64, + /// One of: "anthropic" (default), "instrument", "editorial", "retro". + pub theme: String, } impl Default for Settings { @@ -54,6 +56,7 @@ impl Default for Settings { autostart: false, claude_command: None, cli_refresh_secs: 300, + theme: "anthropic".to_string(), } } } diff --git a/src/components/App.svelte b/src/components/App.svelte index c12f3cd..9725f03 100644 --- a/src/components/App.svelte +++ b/src/components/App.svelte @@ -22,10 +22,18 @@ let showSettings = $state(false); /** True when Claude Code can't be found anywhere on this machine. */ let claudeMissing = $state(false); + /** Active theme; applied as data-theme on the document root. */ + let theme = $state("anthropic"); let unlisten1: (() => void) | null = null; let unlisten2: (() => void) | null = null; + // Whenever theme changes, propagate it to the document root so CSS + // [data-theme="..."] selectors take effect. + $effect(() => { + document.documentElement.setAttribute("data-theme", theme); + }); + onMount(async () => { try { snap = await getSnapshot(); @@ -44,9 +52,10 @@ console.error("listen failed", e); } - // Probe whether claude is reachable at all. + // Probe whether claude is reachable at all + load theme preference. try { const settings = await getSettings(); + theme = settings.theme || "anthropic"; const hasOverride = !!(settings.claude_command && settings.claude_command.trim()); const auto = await autodetectClaudeCommand(); claudeMissing = !hasOverride && !auto; diff --git a/src/components/BlockRing.svelte b/src/components/BlockRing.svelte index 26e02f2..7e278fa 100644 --- a/src/components/BlockRing.svelte +++ b/src/components/BlockRing.svelte @@ -52,6 +52,23 @@ transform={`rotate(-90 ${SIZE / 2} ${SIZE / 2})`} class:pulse /> + + + {bar ? `${bar.percent}%` : fallbackText} @@ -83,13 +100,53 @@ max-width: 220px; /* don't get absurdly large in a wide window */ max-height: 220px; } - text { fill: var(--fg); font-family: inherit; } - text.big { font-size: 26px; font-weight: 600; } - text.small { font-size: 11px; fill: var(--fg-dim); } - text.resets { font-size: 10px; fill: var(--fg-dim); } + text { fill: var(--fg); } + text.big { + font-size: 38px; + font-weight: 600; + font-family: var(--font-display); + font-variant-numeric: tabular-nums; + } + text.small { + font-size: 11px; + fill: var(--fg-dim); + font-family: var(--font-body); + text-transform: lowercase; + letter-spacing: 0.5px; + } + text.resets { + font-size: 10px; + fill: var(--fg-dim); + font-family: var(--font-body); + } .pulse { animation: pulse 1.6s ease-in-out infinite; } @keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.55; } } + + /* Per-theme touches on the ring text */ + :global([data-theme="editorial"]) text.big { + font-variation-settings: "opsz" 144, "SOFT" 50; + font-weight: 400; + } + :global([data-theme="editorial"]) text.small { + font-style: italic; + } + :global([data-theme="retro"]) text.big { + font-weight: 700; + letter-spacing: -1px; + } + :global([data-theme="instrument"]) text.big { + font-weight: 700; + letter-spacing: -1px; + } + :global([data-theme="anthropic"]) text.big { + font-variation-settings: "opsz" 36; + font-weight: 500; + } + + /* Tick marks: hidden everywhere except instrument theme */ + .ticks { color: var(--fg-dim); display: none; } + :global([data-theme="instrument"]) .ticks { display: block; } diff --git a/src/components/Settings.svelte b/src/components/Settings.svelte index 9a540b6..b27be17 100644 --- a/src/components/Settings.svelte +++ b/src/components/Settings.svelte @@ -24,6 +24,19 @@ let busy = $state(false); let testingCli = $state(false); let err = $state(null); + /** Theme captured on mount so Cancel can revert a live preview. */ + let originalTheme = "anthropic"; + + const THEMES = [ + { id: "anthropic", label: "Anthropic", blurb: "Warm cream-on-charcoal · serif" }, + { id: "instrument", label: "Instrument", blurb: "Synth panel · mono · chartreuse" }, + { id: "editorial", label: "Editorial", blurb: "Magazine artifact · saffron" }, + { id: "retro", label: "Retro CRT", blurb: "Phosphor green · scanlines" }, + ] as const; + + function applyThemeLive(id: string) { + document.documentElement.setAttribute("data-theme", id); + } onMount(async () => { try { @@ -31,6 +44,7 @@ distros = await listDistros(); roots = await getRoots(); cli = await getCliUsage(); + originalTheme = settings.theme || "anthropic"; const enabled = await isAutostartEnabled(); if (settings && settings.autostart !== enabled) { settings = { ...settings, autostart: enabled }; @@ -56,6 +70,8 @@ try { await setSettings(settings); + // Successful save: lock in the live-previewed theme. + originalTheme = settings.theme; onClose(); } catch (e) { err = `${e}`; @@ -64,6 +80,18 @@ } } + function cancel() { + // Revert any live theme preview the user was sampling. + applyThemeLive(originalTheme); + onClose(); + } + + function pickTheme(id: string) { + if (!settings) return; + settings = { ...settings, theme: id as any }; + applyThemeLive(id); + } + async function testCli() { if (!settings) return; testingCli = true; @@ -84,12 +112,32 @@
Settings - +
{#if err}
{err}
{/if} {#if settings} +
+
theme
+
+ {#each THEMES as t (t.id)} + + {/each} +
+
+
claude command
- +
{:else} @@ -199,6 +247,116 @@ .roots { margin: 0; padding-left: 14px; max-height: 60px; overflow: auto; } .roots code { font-size: 10px; word-break: break-all; } .hint { font-size: 10px; line-height: 1.3; } + + /* ---- Theme picker ---- */ + .theme-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 6px; + } + .theme-card { + display: grid; + grid-template-rows: auto 1fr auto; + gap: 2px; + padding: 8px 10px; + border-radius: 8px; + border: 1px solid var(--border); + cursor: pointer; + text-align: left; + overflow: hidden; + position: relative; + min-height: 64px; + } + .theme-card .tp-name { + font-size: 10px; + text-transform: uppercase; + letter-spacing: 1px; + opacity: 0.65; + } + .theme-card .tp-sample { + font-size: 22px; + line-height: 1; + font-weight: 600; + margin-top: 2px; + } + .theme-card .tp-blurb { + font-size: 9px; + opacity: 0.55; + line-height: 1.2; + } + .theme-card.selected { + outline: 1.5px solid var(--accent); + outline-offset: -1.5px; + } + .theme-card.selected::after { + content: "✓"; + position: absolute; + top: 4px; + right: 6px; + font-size: 11px; + color: var(--accent); + } + + /* Per-theme card visuals (preview in their own typography & palette) */ + .theme-card[data-theme-preview="anthropic"] { + background: linear-gradient(180deg, #1a1815 0%, #1a1815 100%); + color: #faf9f5; + font-family: "DM Sans", sans-serif; + } + .theme-card[data-theme-preview="anthropic"] .tp-sample { + font-family: "Newsreader Variable", "Newsreader", Georgia, serif; + font-weight: 500; + color: #cc785c; + } + + .theme-card[data-theme-preview="instrument"] { + background: + repeating-linear-gradient(180deg, transparent 0 2px, rgba(212,255,61,0.04) 2px 3px), + #0e1014; + color: #d6e2d8; + font-family: "JetBrains Mono Variable", monospace; + border-color: rgba(212, 255, 61, 0.25); + } + .theme-card[data-theme-preview="instrument"] .tp-sample { + color: #d4ff3d; + font-family: "JetBrains Mono Variable", monospace; + font-weight: 700; + } + + .theme-card[data-theme-preview="editorial"] { + background: #1a1813; + color: #f5f1e8; + font-family: "Fraunces Variable", Georgia, serif; + border-radius: 4px; + } + .theme-card[data-theme-preview="editorial"] .tp-name { + font-style: italic; + text-transform: none; + letter-spacing: 0; + font-size: 11px; + } + .theme-card[data-theme-preview="editorial"] .tp-sample { + color: #e8a02f; + font-family: "Fraunces Variable", serif; + font-weight: 500; + font-variation-settings: "opsz" 144; + } + + .theme-card[data-theme-preview="retro"] { + background: + repeating-linear-gradient(180deg, transparent 0 1px, rgba(0,0,0,0.4) 1px 2px), + #0a0e0a; + color: #b8f0b8; + font-family: "IBM Plex Mono", monospace; + border-color: rgba(122, 240, 122, 0.3); + border-radius: 3px; + text-shadow: 0 0 4px rgba(122,240,122,0.4); + } + .theme-card[data-theme-preview="retro"] .tp-sample { + color: #7af07a; + font-family: "IBM Plex Mono", monospace; + font-weight: 700; + } pre.raw { margin: 4px 0 0; padding: 6px; diff --git a/src/components/TitleBar.svelte b/src/components/TitleBar.svelte index 625367c..dee3876 100644 --- a/src/components/TitleBar.svelte +++ b/src/components/TitleBar.svelte @@ -60,6 +60,7 @@ letter-spacing: 0.8px; color: var(--fg-dim); cursor: grab; + font-family: var(--font-body); } .actions { display: inline-flex; gap: 2px; } .icon.spin { animation: spin 1.2s linear infinite; } @@ -67,4 +68,48 @@ from { transform: rotate(0deg); } to { transform: rotate(360deg); } } + + /* Per-theme title voice ------------------------------------------------ */ + + /* Anthropic: serif italic, soft and warm */ + :global([data-theme="anthropic"]) .title { + font-family: var(--font-display); + font-style: italic; + font-weight: 400; + text-transform: none; + letter-spacing: 0; + font-size: 13px; + color: var(--fg); + opacity: 0.85; + } + + /* Instrument: tracked monospace caps, accent colored */ + :global([data-theme="instrument"]) .title { + color: var(--accent); + font-weight: 700; + letter-spacing: 1.6px; + font-size: 10px; + } + + /* Editorial: italic display serif */ + :global([data-theme="editorial"]) .title { + font-family: var(--font-display); + font-style: italic; + text-transform: none; + letter-spacing: 0.2px; + font-size: 14px; + font-variation-settings: "opsz" 18, "SOFT" 50; + color: var(--fg); + opacity: 0.85; + } + + /* Retro: bracketed CLI-style label */ + :global([data-theme="retro"]) .title { + font-weight: 700; + color: var(--accent); + letter-spacing: 1px; + font-size: 11px; + } + :global([data-theme="retro"]) .title::before { content: "[ "; opacity: 0.6; } + :global([data-theme="retro"]) .title::after { content: " ]"; opacity: 0.6; } diff --git a/src/main.ts b/src/main.ts index 953b0de..94e14c5 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,5 +1,19 @@ import { mount } from "svelte"; import App from "./components/App.svelte"; + +// Theme fonts — bundled offline so the widget renders identically without a +// network connection. Variable-axis fonts where available so we can tune +// weight/optical size from CSS. +import "@fontsource-variable/jetbrains-mono"; +import "@fontsource-variable/fraunces"; +import "@fontsource-variable/newsreader"; +import "@fontsource/dm-sans/400.css"; +import "@fontsource/dm-sans/500.css"; +import "@fontsource/dm-sans/600.css"; +import "@fontsource/ibm-plex-mono/400.css"; +import "@fontsource/ibm-plex-mono/500.css"; +import "@fontsource/ibm-plex-mono/700.css"; + import "./styles.css"; const app = mount(App, { target: document.getElementById("app")! }); diff --git a/src/styles.css b/src/styles.css index 5a6f61a..79401f3 100644 --- a/src/styles.css +++ b/src/styles.css @@ -1,25 +1,11 @@ -/* Glass / always-on-top widget look. */ +/* ============================================================ + Base structure (theme-agnostic). + Theme tokens live in the [data-theme="..."] blocks below. + ============================================================ */ :root { - --bg: rgba(18, 20, 26, 0.93); - --bg-card: rgba(255, 255, 255, 0.04); - --border: rgba(255, 255, 255, 0.08); - --fg: #e8eaf0; - --fg-dim: #9aa0aa; - --accent: #b08bff; /* Anthropic-ish purple */ - --opus: #b08bff; - --sonnet: #6ec1ff; - --haiku: #7ee0a3; - --other: #d8d8d8; - --warn: #ffb454; - --danger: #ff6b6b; - --radius: 10px; --pad: 10px; - - font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; - font-size: 13px; - color: var(--fg); } *, *::before, *::after { box-sizing: border-box; } @@ -29,10 +15,16 @@ html, body { padding: 0; width: 100%; height: 100%; - background: transparent; /* honor Tauri transparent:true */ - overflow: hidden; /* viewport never scrolls; sections handle their own */ + background: transparent; + overflow: hidden; user-select: none; -webkit-user-select: none; + font-family: var(--font-body); + font-size: 13px; + color: var(--fg); + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + text-rendering: optimizeLegibility; } #app { @@ -41,18 +33,27 @@ html, body { background: var(--bg); border: 1px solid var(--border); border-radius: 12px; - overflow: hidden; /* the window itself never scrolls */ + overflow: hidden; display: flex; flex-direction: column; backdrop-filter: blur(14px); + position: relative; } -/* Each child of #app gets a sensible flex behavior so resizing reflows - instead of overflowing. TitleBar and section panels are flex:0 (size - to content); the BlockRing wrap is flex:1 (claims remaining space). */ +/* Optional decorative atmosphere layer per theme — set --atmosphere + to a `background` value (e.g. a gradient + noise) to enable. */ +#app::before { + content: ""; + position: absolute; + inset: 0; + pointer-events: none; + background: var(--atmosphere, none); + opacity: var(--atmosphere-opacity, 0); + mix-blend-mode: var(--atmosphere-blend, normal); + z-index: 0; +} +#app > * { position: relative; z-index: 1; } -/* Hide scrollbars unless content really overflows; when they do appear, - make them subtle. */ ::-webkit-scrollbar { width: 6px; height: 6px; } ::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, 0.15); border-radius: 3px; } ::-webkit-scrollbar-track { background: transparent; } @@ -65,8 +66,9 @@ button { border-radius: 6px; padding: 4px 8px; cursor: pointer; + font-family: var(--font-body); } -button:hover { background: rgba(255, 255, 255, 0.06); } +button:hover { background: var(--hover); } button.icon { border: none; padding: 4px; @@ -82,12 +84,12 @@ button.icon:hover { color: var(--fg); } input[type="number"], input[type="text"], select { font: inherit; color: inherit; - background: rgba(0, 0, 0, 0.25); + background: var(--input-bg); border: 1px solid var(--border); border-radius: 6px; padding: 4px 6px; width: 100%; - box-sizing: border-box; + font-family: var(--font-body); } .section { @@ -98,9 +100,9 @@ input[type="number"], input[type="text"], select { .section:first-child { border-top: none; } .label { - font-size: 10px; - text-transform: uppercase; - letter-spacing: 0.6px; + font: var(--label-font); + text-transform: var(--label-transform, uppercase); + letter-spacing: var(--label-tracking, 0.6px); color: var(--fg-dim); margin-bottom: 4px; } @@ -108,3 +110,241 @@ input[type="number"], input[type="text"], select { .row { display: flex; align-items: center; gap: 8px; } .row.spread { justify-content: space-between; } .muted { color: var(--fg-dim); font-size: 11px; } + +/* Tabular numerals everywhere a number can change */ +.num, .big, text.big, .muted, .label, .bar-row .num { + font-variant-numeric: tabular-nums; +} + +/* ============================================================ + Theme: ANTHROPIC (default) + Warm charcoal + cream + sunset orange + claude purple. + Newsreader serif for display, DM Sans for body. + ============================================================ */ + +[data-theme="anthropic"] { + --font-display: "Newsreader Variable", "Newsreader", Georgia, serif; + --font-body: "DM Sans", -apple-system, "Segoe UI", sans-serif; + --font-mono: "DM Sans", monospace; + + --bg: #1a1815; + --bg-card: rgba(250, 249, 245, 0.04); + --border: rgba(250, 249, 245, 0.10); + --hover: rgba(250, 249, 245, 0.06); + --input-bg: rgba(0, 0, 0, 0.20); + + --fg: #faf9f5; + --fg-dim: #b8ad9e; + --accent: #cc785c; /* sunset orange */ + --accent-2: #c084fc; /* claude purple */ + + --opus: #c084fc; + --sonnet: #6ec1ff; + --haiku: #a3d9a5; + --other: #d8d8d8; + --warn: #e8a02f; + --danger: #d97559; + + --label-font: 500 10px/1 var(--font-body); + --atmosphere: radial-gradient(60% 50% at 50% 0%, rgba(204, 120, 92, 0.18), transparent 60%); + --atmosphere-opacity: 1; +} + +/* ============================================================ + Theme: INSTRUMENT + Modular synth panel. JetBrains Mono. Chartreuse on slate. + Faint scanlines, tick marks on the ring. + ============================================================ */ + +[data-theme="instrument"] { + --font-display: "JetBrains Mono Variable", "JetBrains Mono", ui-monospace, monospace; + --font-body: "JetBrains Mono Variable", "JetBrains Mono", ui-monospace, monospace; + --font-mono: "JetBrains Mono Variable", monospace; + + --bg: #0e1014; + --bg-card: rgba(255, 255, 255, 0.03); + --border: rgba(212, 255, 61, 0.18); + --hover: rgba(212, 255, 61, 0.06); + --input-bg: rgba(0, 0, 0, 0.45); + + --fg: #d6e2d8; + --fg-dim: #6f8478; + --accent: #d4ff3d; /* chartreuse */ + --accent-2: #5dd8ff; /* cyan secondary */ + + --opus: #d4ff3d; + --sonnet: #5dd8ff; + --haiku: #ff6fb5; + --other: #9aa0aa; + --warn: #ffb454; + --danger: #ff3b88; + + --label-font: 600 9px/1 var(--font-mono); + --label-tracking: 1.4px; + + /* Faint horizontal scanlines + a subtle grid */ + --atmosphere: + repeating-linear-gradient( + 180deg, + transparent 0px, + transparent 2px, + rgba(212, 255, 61, 0.025) 2px, + rgba(212, 255, 61, 0.025) 3px + ), + radial-gradient(80% 60% at 50% 0%, rgba(212, 255, 61, 0.05), transparent 70%); + --atmosphere-opacity: 1; +} + +[data-theme="instrument"] #app { + border-color: rgba(212, 255, 61, 0.22); +} + +/* Bracket-corners on the title bar in instrument mode */ +[data-theme="instrument"] header::before, +[data-theme="instrument"] header::after { + content: ""; + position: absolute; + width: 6px; height: 6px; + border: 1px solid var(--accent); + opacity: 0.5; +} +[data-theme="instrument"] header { position: relative; } +[data-theme="instrument"] header::before { + top: 4px; left: 4px; + border-right: 0; border-bottom: 0; +} +[data-theme="instrument"] header::after { + top: 4px; right: 4px; + border-left: 0; border-bottom: 0; +} + +/* ============================================================ + Theme: EDITORIAL + Magazine artifact. Fraunces serif. Saffron on warm charcoal. + Hairline rules, generous spacing. + ============================================================ */ + +[data-theme="editorial"] { + --font-display: "Fraunces Variable", "Fraunces", Georgia, serif; + --font-body: "Fraunces Variable", "Fraunces", Georgia, serif; + --font-mono: "Fraunces Variable", monospace; + + --bg: #1a1813; + --bg-card: rgba(245, 241, 232, 0.03); + --border: rgba(245, 241, 232, 0.12); + --hover: rgba(245, 241, 232, 0.05); + --input-bg: rgba(0, 0, 0, 0.25); + + --fg: #f5f1e8; + --fg-dim: #9b9183; + --accent: #e8a02f; /* saffron */ + --accent-2: #d97559; + + --opus: #e8a02f; + --sonnet: #c8987a; + --haiku: #a3a87a; + --other: #9b9183; + --warn: #d97559; + --danger: #b54a3c; + + --label-font: italic 400 11px/1 var(--font-display); + --label-transform: none; + --label-tracking: 0.2px; +} + +/* Editorial uses small-caps italic for labels — let the variable axis breathe */ +[data-theme="editorial"] .label { + font-variation-settings: "opsz" 14, "SOFT" 50; +} + +[data-theme="editorial"] #app { + border-radius: 6px; +} + +[data-theme="editorial"] .section { + border-top-style: solid; + border-top-width: 0.5px; +} + +/* ============================================================ + Theme: RETRO CRT + 1980s home computer. IBM Plex Mono. Phosphor green. + Scanlines, bracket header, blink cursor. + ============================================================ */ + +[data-theme="retro"] { + --font-display: "IBM Plex Mono", ui-monospace, monospace; + --font-body: "IBM Plex Mono", ui-monospace, monospace; + --font-mono: "IBM Plex Mono", monospace; + + --bg: #0a0e0a; + --bg-card: rgba(122, 240, 122, 0.03); + --border: rgba(122, 240, 122, 0.20); + --hover: rgba(122, 240, 122, 0.08); + --input-bg: rgba(0, 0, 0, 0.55); + + --fg: #b8f0b8; + --fg-dim: #5a8a5a; + --accent: #7af07a; /* phosphor green */ + --accent-2: #ffb454; /* amber for warnings */ + + --opus: #7af07a; + --sonnet: #5dd8ff; + --haiku: #ffe066; + --other: #b8f0b8; + --warn: #ffb454; + --danger: #ff5a5a; + + --label-font: 700 9px/1 var(--font-mono); + --label-tracking: 1.5px; + + /* Strong scanlines + vignette + faint phosphor glow */ + --atmosphere: + repeating-linear-gradient( + 180deg, + transparent 0px, + transparent 1px, + rgba(0, 0, 0, 0.35) 1px, + rgba(0, 0, 0, 0.35) 2px + ), + radial-gradient(120% 90% at 50% 50%, transparent 50%, rgba(0, 0, 0, 0.55) 100%), + radial-gradient(60% 40% at 50% 30%, rgba(122, 240, 122, 0.10), transparent 70%); + --atmosphere-opacity: 1; +} + +[data-theme="retro"] #app { + border-radius: 4px; + border-color: rgba(122, 240, 122, 0.30); + text-shadow: 0 0 6px rgba(122, 240, 122, 0.35); +} + +[data-theme="retro"] header { border-bottom-style: dashed; } + +/* Blink cursor in the bottom-right of the widget — pure decoration */ +[data-theme="retro"] #app::after { + content: "█"; + position: absolute; + bottom: 6px; + right: 10px; + color: var(--accent); + opacity: 0.8; + animation: retro-blink 1.1s steps(2, end) infinite; + font-family: var(--font-mono); + font-size: 12px; + pointer-events: none; + z-index: 2; +} +@keyframes retro-blink { + 50% { opacity: 0; } +} + +/* ============================================================ + Reduced-motion accommodation: kill all the breathing effects. + ============================================================ */ +@media (prefers-reduced-motion: reduce) { + *, *::before, *::after { + animation-duration: 0.001ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.001ms !important; + } +} diff --git a/src/types.ts b/src/types.ts index 28e0f47..cd1991b 100644 --- a/src/types.ts +++ b/src/types.ts @@ -45,12 +45,17 @@ export interface UsageSnapshot { generated_at: string; } +export type Theme = "anthropic" | "instrument" | "editorial" | "retro"; + export interface Settings { caps: Caps; wsl_distro_override: string | null; include_native: boolean; window_pos: [number, number] | null; autostart: boolean; + claude_command: string | null; + cli_refresh_secs: number; + theme: Theme; } export interface ResolvedRoots {