# 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 `