From 519d6b850e07a0387511a8f024dc394250b1a241 Mon Sep 17 00:00:00 2001 From: Clawd Date: Sat, 28 Feb 2026 15:26:02 -0800 Subject: Clear artifacts when creating new session Each new session now starts with empty research.md and plan.md files, preventing stale content from previous sessions appearing. --- .claude-flow/plan.md | 1090 ++++++++++++++++++++++++++++++++++++++++++++++ .claude-flow/research.md | 214 +++++++++ 2 files changed, 1304 insertions(+) create mode 100644 .claude-flow/plan.md create mode 100644 .claude-flow/research.md (limited to '.claude-flow') diff --git a/.claude-flow/plan.md b/.claude-flow/plan.md new file mode 100644 index 0000000..bbcae6a --- /dev/null +++ b/.claude-flow/plan.md @@ -0,0 +1,1090 @@ +# Implementation Plan + +## Goal +Restyle the Claude Flow Electron app with: +1. Fix the window/tab title from "minimal" → "Claude Flow" +2. Add a "CLAUDE FLOW" wordmark to the header +3. Apply a cipherpunk aesthetic (full monospace UI font, sharper geometry, electric-blue accent, uppercase meta labels, focus glow) +4. Add light/dark mode with a text toggle (`[dark]` / `[light]`) persisted to `localStorage` + +## Approach +Five files touched in order of dependency. No new npm packages. The CSS rewrite is the biggest change; the JS/TSX changes are small and surgical. + +**Order of changes:** +1. `renderer/index.html` — title fix (trivial, isolated) +2. `renderer/src/styles/globals.css` — full restyle (all visual work) +3. `renderer/src/App.tsx` — add theme state, wire it down +4. `renderer/src/components/Header.tsx` — wordmark + toggle button +5. `renderer/src/components/DocumentPane.tsx` — dynamic CodeMirror theme + +--- + +## Changes + +### 1. Fix Window Title +**File:** `renderer/index.html` +**What:** Change `minimal` to `Claude Flow` on line 10. + +```html + +minimal + + +Claude Flow +``` + +--- + +### 2. Restyle the CSS +**File:** `renderer/src/styles/globals.css` +**What:** Complete replacement of the file. Key changes from the original: +- `:root` dark-mode accent updated to `#60a5fa` / `#93c5fd` +- `body` font-family changed to full monospace stack +- New `html[data-theme="light"]` block added after `:root` +- `border-radius` reduced to 2px on all interactive controls (4px on chat bubbles) +- `text-transform: uppercase; letter-spacing: 0.07em` added to all meta/chrome labels +- Focus glow (`box-shadow`) added on inputs and focused buttons +- New `.app-wordmark` class added +- New `.theme-toggle` class added +- All existing selectors preserved; only values changed + +```css +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +/* ── Dark theme (default) ────────────────────────────────────── */ +:root { + --bg-primary: #1a1a1a; + --bg-secondary: #252525; + --bg-tertiary: #333; + --border: #444; + --text-primary: #e0e0e0; + --text-secondary: #888; + --accent: #60a5fa; /* electric blue — brighter than original #3b82f6 */ + --accent-hover: #93c5fd; + --success: #10b981; + --warning: #f59e0b; + --danger: #ef4444; +} + +/* ── Light theme overrides ───────────────────────────────────── */ +html[data-theme="light"] { + --bg-primary: #f4f4f2; + --bg-secondary: #e8e8e5; + --bg-tertiary: #d8d8d4; + --border: #b4b4b0; + --text-primary: #1a1a18; + --text-secondary: #5a5a56; + --accent: #2563eb; + --accent-hover: #1d4ed8; + --success: #059669; + --warning: #d97706; + --danger: #dc2626; +} + +/* ── Base ────────────────────────────────────────────────────── */ +body { + font-family: "SF Mono", "Cascadia Code", "JetBrains Mono", "Fira Code", + Monaco, "Courier New", monospace; + background: var(--bg-primary); + color: var(--text-primary); + overflow: hidden; + font-size: 13px; +} + +.app { + display: flex; + flex-direction: column; + height: 100vh; +} + +/* ── Header ──────────────────────────────────────────────────── */ +.header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 10px 16px; + background: var(--bg-secondary); + border-bottom: 1px solid var(--border); + -webkit-app-region: drag; +} + +.header-left, +.header-right { + display: flex; + align-items: center; + gap: 8px; + -webkit-app-region: no-drag; +} + +/* App wordmark */ +.app-wordmark { + font-size: 12px; + font-weight: 700; + letter-spacing: 0.15em; + text-transform: uppercase; + color: var(--text-primary); + padding-right: 12px; + border-right: 1px solid var(--border); + margin-right: 4px; + user-select: none; + white-space: nowrap; +} + +.header select, +.header button { + padding: 5px 10px; + background: var(--bg-tertiary); + border: 1px solid var(--border); + border-radius: 2px; + color: var(--text-primary); + cursor: pointer; + font-size: 12px; + font-family: inherit; +} + +.header button:hover { + background: var(--border); +} + +.header button.btn-delete { + background: transparent; + border: 1px solid var(--border); + padding: 5px 8px; + font-size: 13px; +} + +.header button.btn-delete:hover { + background: var(--danger); + border-color: var(--danger); +} + +/* Theme toggle */ +.theme-toggle { + font-size: 11px; + letter-spacing: 0.08em; + text-transform: lowercase; + opacity: 0.7; + transition: opacity 0.15s; +} + +.theme-toggle:hover { + opacity: 1; + background: var(--bg-tertiary) !important; +} + +/* Phase indicator */ +.phase-indicator { + display: flex; + gap: 4px; +} + +.phase-step { + padding: 3px 10px; + font-size: 11px; + letter-spacing: 0.07em; + text-transform: uppercase; + border-radius: 2px; + background: var(--bg-tertiary); + color: var(--text-secondary); +} + +.phase-step.active { + background: var(--accent); + color: white; +} + +.phase-step.complete { + background: var(--success); + color: white; +} + +/* ── Main Content ─────────────────────────────────────────────── */ +.main-content { + flex: 1; + display: flex; + overflow: hidden; +} + +/* ── Document Pane ───────────────────────────────────────────── */ +.document-pane { + flex: 1; + display: flex; + flex-direction: column; + border-right: 1px solid var(--border); + min-width: 0; + overflow: hidden; +} + +.document-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 7px 16px; + background: var(--bg-secondary); + border-bottom: 1px solid var(--border); + font-size: 11px; + letter-spacing: 0.07em; + text-transform: uppercase; + color: var(--text-secondary); +} + +.document-header button { + padding: 3px 8px; + background: var(--bg-tertiary); + border: 1px solid var(--border); + border-radius: 2px; + color: var(--text-primary); + cursor: pointer; + font-size: 11px; + font-family: inherit; + letter-spacing: 0.05em; +} + +.document-header button:hover { + background: var(--border); +} + +.document-content { + flex: 1; + overflow-y: auto; + padding: 24px; +} + +.document-content.editing { + font-family: inherit; + font-size: 13px; + line-height: 1.6; + background: var(--bg-primary); + border: none; + resize: none; + color: var(--text-primary); +} + +.codemirror-editor { + flex: 1; + overflow: hidden; + min-height: 0; +} + +.codemirror-editor .cm-editor { + height: 100%; + max-width: 100%; +} + +.codemirror-editor .cm-scroller { + overflow: auto !important; +} + +.codemirror-editor .cm-gutters { + background: var(--bg-secondary); + border-right: 1px solid var(--border); +} + +.document-content.rendered { + line-height: 1.7; +} + +.document-content.rendered h1 { + font-size: 22px; + margin: 24px 0 16px; + letter-spacing: -0.01em; +} +.document-content.rendered h2 { + font-size: 17px; + margin: 20px 0 12px; + color: var(--text-secondary); + text-transform: uppercase; + letter-spacing: 0.05em; +} +.document-content.rendered h3 { + font-size: 14px; + margin: 16px 0 8px; +} +.document-content.rendered p { + margin: 8px 0; + line-height: 1.6; +} +.document-content.rendered code { + background: var(--bg-tertiary); + padding: 2px 6px; + border-radius: 2px; + font-size: 12px; + font-family: inherit; +} +.document-content.rendered pre { + background: var(--bg-tertiary); + padding: 16px; + border-radius: 2px; + overflow-x: auto; + margin: 16px 0; +} +.document-content.rendered pre code { + background: none; + padding: 0; +} +.document-content.rendered ul, +.document-content.rendered ol { + margin: 12px 0; + padding-left: 24px; +} +.document-content.rendered li { + margin-bottom: 6px; + line-height: 1.5; +} +.document-content.rendered ul.contains-task-list { + list-style: none; + padding-left: 0; +} +.document-content.rendered li.task-list-item { + display: flex; + align-items: flex-start; + gap: 8px; +} +.document-content.rendered li.task-list-item input[type="checkbox"] { + margin-top: 4px; +} +.document-content.rendered table { + width: 100%; + border-collapse: collapse; + margin: 16px 0; + font-size: 12px; +} +.document-content.rendered th, +.document-content.rendered td { + padding: 8px 12px; + text-align: left; + border: 1px solid var(--border); +} +.document-content.rendered th { + background: var(--bg-tertiary); + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.06em; + font-size: 11px; +} +.document-content.rendered tr:nth-child(even) td { + background: var(--bg-secondary); +} +.document-content.rendered blockquote { + border-left: 3px solid var(--accent); + margin: 16px 0; + padding-left: 16px; + color: var(--text-secondary); +} +.document-content.rendered hr { + border: none; + border-top: 1px solid var(--border); + margin: 24px 0; +} +.document-content.rendered a { + color: var(--accent); + text-decoration: none; +} +.document-content.rendered a:hover { + text-decoration: underline; +} +.document-content.rendered .empty { + color: var(--text-secondary); + font-style: italic; +} + +.badge { + background: var(--accent); + color: white; + padding: 2px 8px; + border-radius: 2px; + font-size: 10px; + letter-spacing: 0.08em; + text-transform: uppercase; +} + +/* ── Chat Pane ───────────────────────────────────────────────── */ +.chat-pane { + width: 380px; + display: flex; + flex-direction: column; + background: var(--bg-secondary); +} + +.chat-messages { + flex: 1; + overflow-y: auto; + padding: 16px; +} + +.message { + margin-bottom: 10px; + padding: 9px 13px; + border-radius: 4px; + max-width: 90%; + font-size: 13px; + line-height: 1.5; + white-space: pre-wrap; +} + +.message.user { + background: var(--accent); + margin-left: auto; + color: white; +} + +.message.assistant { + background: var(--bg-tertiary); +} + +.message.loading { + color: var(--text-secondary); + font-style: italic; +} + +.chat-input { + display: flex; + gap: 8px; + padding: 12px; + border-top: 1px solid var(--border); +} + +.chat-input input { + flex: 1; + padding: 9px 13px; + background: var(--bg-tertiary); + border: 1px solid var(--border); + border-radius: 2px; + color: var(--text-primary); + font-size: 13px; + font-family: inherit; + transition: border-color 0.15s, box-shadow 0.15s; +} + +.chat-input input:focus { + outline: none; + border-color: var(--accent); + box-shadow: 0 0 0 2px rgba(96, 165, 250, 0.2); +} + +html[data-theme="light"] .chat-input input:focus { + box-shadow: 0 0 0 2px rgba(37, 99, 235, 0.2); +} + +.chat-input button { + padding: 9px 15px; + background: var(--accent); + border: none; + border-radius: 2px; + color: white; + cursor: pointer; + font-size: 12px; + font-family: inherit; + letter-spacing: 0.05em; + text-transform: uppercase; + transition: background 0.15s; +} + +.chat-input button:hover:not(:disabled) { + background: var(--accent-hover); +} + +.chat-input button:disabled { + opacity: 0.4; + cursor: not-allowed; +} + +/* ── Action Bar ──────────────────────────────────────────────── */ +.action-bar { + display: flex; + justify-content: space-between; + align-items: center; + padding: 10px 16px; + background: var(--bg-secondary); + border-top: 1px solid var(--border); +} + +.action-bar-left, +.action-bar-right { + display: flex; + align-items: center; + gap: 16px; +} + +.token-indicator { + display: flex; + align-items: center; + gap: 8px; +} + +.token-bar { + width: 100px; + height: 4px; + background: var(--bg-tertiary); + border-radius: 1px; + overflow: hidden; +} + +.token-fill { + height: 100%; + transition: width 0.3s ease; +} + +.token-label { + font-size: 10px; + letter-spacing: 0.08em; + text-transform: uppercase; + color: var(--text-secondary); +} + +.permission-toggle { + display: flex; + align-items: center; + gap: 6px; + font-size: 11px; + letter-spacing: 0.05em; + text-transform: uppercase; + color: var(--text-secondary); + cursor: pointer; +} + +.permission-toggle input { + cursor: pointer; +} + +.btn-secondary { + padding: 6px 14px; + background: var(--bg-tertiary); + border: 1px solid var(--border); + border-radius: 2px; + color: var(--text-primary); + cursor: pointer; + font-size: 12px; + font-family: inherit; + letter-spacing: 0.05em; + transition: background 0.15s; +} + +.btn-secondary:hover:not(:disabled) { + background: var(--border); +} + +.btn-secondary:disabled { + opacity: 0.4; + cursor: not-allowed; +} + +.btn-primary { + padding: 6px 18px; + background: var(--accent); + border: none; + border-radius: 2px; + color: white; + cursor: pointer; + font-weight: 600; + font-size: 12px; + font-family: inherit; + letter-spacing: 0.07em; + text-transform: uppercase; + transition: background 0.15s; +} + +.btn-primary:hover:not(:disabled) { + background: var(--accent-hover); +} + +.btn-primary:disabled { + opacity: 0.4; + cursor: not-allowed; +} + +.implementing-status { + color: var(--success); + font-size: 11px; + letter-spacing: 0.1em; + text-transform: uppercase; +} + +/* ── Error Bar ───────────────────────────────────────────────── */ +.error-bar { + display: flex; + justify-content: space-between; + align-items: center; + padding: 8px 16px; + background: var(--danger); + color: white; + font-size: 12px; + letter-spacing: 0.03em; +} + +.error-bar button { + background: none; + border: none; + color: white; + font-size: 16px; + cursor: pointer; + padding: 0 4px; +} + +.error-bar button:hover { + opacity: 0.8; +} + +/* ── Onboarding ──────────────────────────────────────────────── */ +.onboarding h1 { + font-size: 24px; + margin-bottom: 8px; + letter-spacing: 0.05em; + text-transform: uppercase; +} + +.onboarding h2 { + font-size: 13px; + margin-top: 28px; + margin-bottom: 12px; + color: var(--accent); + text-transform: uppercase; + letter-spacing: 0.1em; +} + +.onboarding p { + margin: 12px 0; + line-height: 1.6; +} + +.onboarding ol { + margin: 12px 0 12px 24px; +} + +.onboarding li { + margin: 8px 0; + line-height: 1.5; +} + +.onboarding pre { + background: var(--bg-tertiary); + padding: 12px 16px; + border-radius: 2px; + margin: 12px 0; +} + +.onboarding code { + font-family: inherit; + font-size: 12px; +} + +.onboarding a { + color: var(--accent); + text-decoration: none; +} + +.onboarding a:hover { + text-decoration: underline; +} + +.onboarding-tip { + margin-top: 28px; + padding: 16px; + background: var(--bg-tertiary); + border-left: 3px solid var(--accent); + border-radius: 0 2px 2px 0; +} +``` + +--- + +### 3. Add Theme State to App +**File:** `renderer/src/App.tsx` +**What:** Three additions to the existing file — no existing logic is touched. + +**3a. Add `Theme` type and `theme` state** — insert after the existing imports, before the `App` function: + +```typescript +// Add this type alias near the top, alongside other local types if any, +// or just before the App function: +type Theme = "dark" | "light"; +``` + +**3b. Add `theme` state and side-effects** — insert inside `App()`, after the existing `useState` declarations (after `const [error, setError] = useState(null)`): + +```typescript +const [theme, setTheme] = useState( + () => (localStorage.getItem("cf-theme") as Theme) ?? "dark" +); + +// Keep document.documentElement in sync, and persist to localStorage +useEffect(() => { + document.documentElement.setAttribute("data-theme", theme); + localStorage.setItem("cf-theme", theme); +}, [theme]); + +const handleToggleTheme = () => + setTheme((t) => (t === "dark" ? "light" : "dark")); +``` + +**3c. Pass new props to `Header` and `DocumentPane`** — update the JSX inside the `return`: + +```tsx +// Header — add two new props: +
+ +// DocumentPane — add one new prop: + +``` + +--- + +### 4. Update Header Component +**File:** `renderer/src/components/Header.tsx` +**What:** Add `theme` and `onToggleTheme` to the props interface, insert wordmark element, add toggle button. + +**Complete updated file:** + +```tsx +import React from "react"; +import type { Project, Session, Phase } from "../types"; + +type Theme = "dark" | "light"; + +interface HeaderProps { + projects: Project[]; + sessions: Session[]; + selectedProject: Project | null; + selectedSession: Session | null; + onSelectProject: (project: Project | null) => void; + onSelectSession: (session: Session | null) => void; + onCreateProject: () => void; + onCreateSession: () => void; + onDeleteProject?: (id: string) => void; + onDeleteSession?: (id: string) => void; + theme: Theme; + onToggleTheme: () => void; +} + +const phaseLabels: Record = { + research: "Research", + plan: "Plan", + implement: "Implement", +}; + +const phases: Phase[] = ["research", "plan", "implement"]; + +export function Header({ + projects, + sessions, + selectedProject, + selectedSession, + onSelectProject, + onSelectSession, + onCreateProject, + onCreateSession, + onDeleteProject, + onDeleteSession, + theme, + onToggleTheme, +}: HeaderProps) { + const handleDeleteProject = () => { + if (!selectedProject || !onDeleteProject) return; + if (confirm(`Delete project "${selectedProject.name}"? This cannot be undone.`)) { + onDeleteProject(selectedProject.id); + } + }; + + const handleDeleteSession = () => { + if (!selectedSession || !onDeleteSession) return; + if (confirm(`Delete session "${selectedSession.name}"? This cannot be undone.`)) { + onDeleteSession(selectedSession.id); + } + }; + + return ( +
+
+ {/* ── Wordmark ── */} + Claude Flow + + + + {selectedProject && onDeleteProject && ( + + )} + + {selectedProject && ( + <> + + + {selectedSession && onDeleteSession && ( + + )} + + )} +
+ +
+ {selectedSession && ( +
+ {phases.map((phase) => { + const phaseIndex = phases.indexOf(phase); + const currentIndex = phases.indexOf(selectedSession.phase); + const isComplete = phaseIndex < currentIndex; + const isActive = phase === selectedSession.phase; + + return ( + + {phaseLabels[phase]} + + ); + })} +
+ )} + + {/* ── Theme toggle ── */} + +
+
+ ); +} +``` + +--- + +### 5. Dynamic CodeMirror Theme in DocumentPane +**File:** `renderer/src/components/DocumentPane.tsx` +**What:** Three surgical changes to `MarkdownEditor` — add `theme` prop, update imports, update `useEffect`. + +**5a. Update imports** — add `syntaxHighlighting` and `defaultHighlightStyle` to the `@codemirror/language` import: + +```typescript +// BEFORE: +import { markdown } from "@codemirror/lang-markdown"; +import { languages } from "@codemirror/language-data"; + +// AFTER: +import { markdown } from "@codemirror/lang-markdown"; +import { languages } from "@codemirror/language-data"; +import { syntaxHighlighting, defaultHighlightStyle } from "@codemirror/language"; +``` + +**5b. Update `MarkdownEditor` props interface** — add `theme`: + +```typescript +// BEFORE: +function MarkdownEditor({ + content, + onChange, + disabled, +}: { + content: string; + onChange: (content: string) => void; + disabled: boolean; +}) + +// AFTER: +function MarkdownEditor({ + content, + onChange, + disabled, + theme, +}: { + content: string; + onChange: (content: string) => void; + disabled: boolean; + theme: "dark" | "light"; +}) +``` + +**5c. Update the `useEffect` inside `MarkdownEditor`** — swap the theme extension and add `theme` to the dependency array: + +```typescript +// BEFORE (inside useEffect): +extensions: [ + lineNumbers(), + highlightActiveLine(), + drawSelection(), + history(), + keymap.of([...defaultKeymap, ...historyKeymap]), + markdown({ codeLanguages: languages }), + oneDark, // ← hardcoded + updateListener, + EditorView.editable.of(!disabled), + EditorView.lineWrapping, + EditorView.theme({ ... }), +], +// dependency array: +}, [disabled]); + +// AFTER: +extensions: [ + lineNumbers(), + highlightActiveLine(), + drawSelection(), + history(), + keymap.of([...defaultKeymap, ...historyKeymap]), + markdown({ codeLanguages: languages }), + theme === "dark" ? oneDark : syntaxHighlighting(defaultHighlightStyle), // ← dynamic + updateListener, + EditorView.editable.of(!disabled), + EditorView.lineWrapping, + EditorView.theme({ ... }), +], +// dependency array: +}, [disabled, theme]); // ← theme added +``` + +**5d. Update `DocumentPaneProps` interface and pass `theme` through** — add to the interface and forward to `MarkdownEditor`: + +```typescript +// BEFORE: +interface DocumentPaneProps { + content: string; + onChange: (content: string) => void; + phase: Phase; + disabled: boolean; + showOnboarding?: boolean; +} + +// AFTER: +interface DocumentPaneProps { + content: string; + onChange: (content: string) => void; + phase: Phase; + disabled: boolean; + showOnboarding?: boolean; + theme: "dark" | "light"; +} +``` + +And in the `DocumentPane` function body, destructure `theme` and pass it to `MarkdownEditor`: + +```tsx +// BEFORE: +export function DocumentPane({ + content, + onChange, + phase, + disabled, + showOnboarding, +}: DocumentPaneProps) + +// AFTER: +export function DocumentPane({ + content, + onChange, + phase, + disabled, + showOnboarding, + theme, +}: DocumentPaneProps) +``` + +```tsx +// BEFORE (in JSX): + + +// AFTER: + +``` + +--- + +## TODO +- [x] **1.** `renderer/index.html` — change `minimal` to `Claude Flow` +- [x] **2.** `renderer/src/styles/globals.css` — full replacement with new CSS (monospace body, electric accent, 2px radii, uppercase labels, light theme block, focus glow, `.app-wordmark`, `.theme-toggle`) +- [x] **3a.** `renderer/src/App.tsx` — add `type Theme` alias +- [x] **3b.** `renderer/src/App.tsx` — add `theme` state + `useEffect` + `handleToggleTheme` +- [x] **3c.** `renderer/src/App.tsx` — pass `theme`/`onToggleTheme` to `Header`, pass `theme` to `DocumentPane` +- [x] **4.** `renderer/src/components/Header.tsx` — full replacement (new props, wordmark, toggle button) +- [x] **5a.** `renderer/src/components/DocumentPane.tsx` — add `syntaxHighlighting, defaultHighlightStyle` import +- [x] **5b.** `renderer/src/components/DocumentPane.tsx` — add `theme` to `MarkdownEditor` props +- [x] **5c.** `renderer/src/components/DocumentPane.tsx` — dynamic theme extension + `theme` in dep array +- [x] **5d.** `renderer/src/components/DocumentPane.tsx` — add `theme` to `DocumentPaneProps`, destructure, forward to `MarkdownEditor` + +--- + +## Risks / Considerations + +**CodeMirror reinitialization on theme switch**: Adding `theme` to the `useEffect` dependency array means the entire editor is torn down and recreated when the theme changes. This is intentional and correct — CodeMirror extensions are baked into the `EditorState` at creation time and can't be hot-swapped. The existing `content` sync `useEffect` will immediately restore the document contents after reinit, so no text loss occurs. There is a brief visual flash on theme toggle; this is acceptable. + +**`data-theme` initial state and flash of wrong theme**: The `useState` initializer reads `localStorage` synchronously, and the `useEffect` sets `data-theme` on first render. Because this happens before paint (Electron loads the renderer in a hidden window and only shows it after load), there should be no flash of the wrong theme in production. In dev the Vite HMR setup may briefly show unstyled content; this is not a concern for shipping. + +**Monospace font rendering differences per OS**: "SF Mono" ships with macOS/Xcode; "Cascadia Code" ships with Windows Terminal; "JetBrains Mono" and "Fira Code" are user-installed. The fallback chain is safe — `Monaco` is widely available on macOS, and `"Courier New", monospace` are universal final fallbacks. No font files are bundled; this is system-font-only. + +**`Theme` type duplication**: The `type Theme = "dark" | "light"` alias is defined in both `App.tsx` and `Header.tsx`. This is a minor smell. If it grows to more places, move it into `renderer/src/types.ts`. For this task (2 files) the duplication is acceptable. + +**Light mode CodeMirror gutter background**: The `.codemirror-editor .cm-gutters` rule uses `var(--bg-secondary)` which will automatically pick up the light-mode value — no extra change needed there. + +**`select` element styling in light mode**: Native `