aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.claude-flow/plan.md1090
-rw-r--r--.claude-flow/research.md214
-rw-r--r--renderer/index.html2
-rw-r--r--renderer/src/App.tsx18
-rw-r--r--renderer/src/components/DocumentPane.tsx10
-rw-r--r--renderer/src/components/Header.tsx14
-rw-r--r--renderer/src/styles/globals.css259
7 files changed, 1523 insertions, 84 deletions
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 @@
1# Implementation Plan
2
3## Goal
4Restyle the Claude Flow Electron app with:
51. Fix the window/tab title from "minimal" → "Claude Flow"
62. Add a "CLAUDE FLOW" wordmark to the header
73. Apply a cipherpunk aesthetic (full monospace UI font, sharper geometry, electric-blue accent, uppercase meta labels, focus glow)
84. Add light/dark mode with a text toggle (`[dark]` / `[light]`) persisted to `localStorage`
9
10## Approach
11Five 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.
12
13**Order of changes:**
141. `renderer/index.html` — title fix (trivial, isolated)
152. `renderer/src/styles/globals.css` — full restyle (all visual work)
163. `renderer/src/App.tsx` — add theme state, wire it down
174. `renderer/src/components/Header.tsx` — wordmark + toggle button
185. `renderer/src/components/DocumentPane.tsx` — dynamic CodeMirror theme
19
20---
21
22## Changes
23
24### 1. Fix Window Title
25**File:** `renderer/index.html`
26**What:** Change `<title>minimal</title>` to `<title>Claude Flow</title>` on line 10.
27
28```html
29<!-- BEFORE -->
30<title>minimal</title>
31
32<!-- AFTER -->
33<title>Claude Flow</title>
34```
35
36---
37
38### 2. Restyle the CSS
39**File:** `renderer/src/styles/globals.css`
40**What:** Complete replacement of the file. Key changes from the original:
41- `:root` dark-mode accent updated to `#60a5fa` / `#93c5fd`
42- `body` font-family changed to full monospace stack
43- New `html[data-theme="light"]` block added after `:root`
44- `border-radius` reduced to 2px on all interactive controls (4px on chat bubbles)
45- `text-transform: uppercase; letter-spacing: 0.07em` added to all meta/chrome labels
46- Focus glow (`box-shadow`) added on inputs and focused buttons
47- New `.app-wordmark` class added
48- New `.theme-toggle` class added
49- All existing selectors preserved; only values changed
50
51```css
52* {
53 box-sizing: border-box;
54 margin: 0;
55 padding: 0;
56}
57
58/* ── Dark theme (default) ────────────────────────────────────── */
59:root {
60 --bg-primary: #1a1a1a;
61 --bg-secondary: #252525;
62 --bg-tertiary: #333;
63 --border: #444;
64 --text-primary: #e0e0e0;
65 --text-secondary: #888;
66 --accent: #60a5fa; /* electric blue — brighter than original #3b82f6 */
67 --accent-hover: #93c5fd;
68 --success: #10b981;
69 --warning: #f59e0b;
70 --danger: #ef4444;
71}
72
73/* ── Light theme overrides ───────────────────────────────────── */
74html[data-theme="light"] {
75 --bg-primary: #f4f4f2;
76 --bg-secondary: #e8e8e5;
77 --bg-tertiary: #d8d8d4;
78 --border: #b4b4b0;
79 --text-primary: #1a1a18;
80 --text-secondary: #5a5a56;
81 --accent: #2563eb;
82 --accent-hover: #1d4ed8;
83 --success: #059669;
84 --warning: #d97706;
85 --danger: #dc2626;
86}
87
88/* ── Base ────────────────────────────────────────────────────── */
89body {
90 font-family: "SF Mono", "Cascadia Code", "JetBrains Mono", "Fira Code",
91 Monaco, "Courier New", monospace;
92 background: var(--bg-primary);
93 color: var(--text-primary);
94 overflow: hidden;
95 font-size: 13px;
96}
97
98.app {
99 display: flex;
100 flex-direction: column;
101 height: 100vh;
102}
103
104/* ── Header ──────────────────────────────────────────────────── */
105.header {
106 display: flex;
107 justify-content: space-between;
108 align-items: center;
109 padding: 10px 16px;
110 background: var(--bg-secondary);
111 border-bottom: 1px solid var(--border);
112 -webkit-app-region: drag;
113}
114
115.header-left,
116.header-right {
117 display: flex;
118 align-items: center;
119 gap: 8px;
120 -webkit-app-region: no-drag;
121}
122
123/* App wordmark */
124.app-wordmark {
125 font-size: 12px;
126 font-weight: 700;
127 letter-spacing: 0.15em;
128 text-transform: uppercase;
129 color: var(--text-primary);
130 padding-right: 12px;
131 border-right: 1px solid var(--border);
132 margin-right: 4px;
133 user-select: none;
134 white-space: nowrap;
135}
136
137.header select,
138.header button {
139 padding: 5px 10px;
140 background: var(--bg-tertiary);
141 border: 1px solid var(--border);
142 border-radius: 2px;
143 color: var(--text-primary);
144 cursor: pointer;
145 font-size: 12px;
146 font-family: inherit;
147}
148
149.header button:hover {
150 background: var(--border);
151}
152
153.header button.btn-delete {
154 background: transparent;
155 border: 1px solid var(--border);
156 padding: 5px 8px;
157 font-size: 13px;
158}
159
160.header button.btn-delete:hover {
161 background: var(--danger);
162 border-color: var(--danger);
163}
164
165/* Theme toggle */
166.theme-toggle {
167 font-size: 11px;
168 letter-spacing: 0.08em;
169 text-transform: lowercase;
170 opacity: 0.7;
171 transition: opacity 0.15s;
172}
173
174.theme-toggle:hover {
175 opacity: 1;
176 background: var(--bg-tertiary) !important;
177}
178
179/* Phase indicator */
180.phase-indicator {
181 display: flex;
182 gap: 4px;
183}
184
185.phase-step {
186 padding: 3px 10px;
187 font-size: 11px;
188 letter-spacing: 0.07em;
189 text-transform: uppercase;
190 border-radius: 2px;
191 background: var(--bg-tertiary);
192 color: var(--text-secondary);
193}
194
195.phase-step.active {
196 background: var(--accent);
197 color: white;
198}
199
200.phase-step.complete {
201 background: var(--success);
202 color: white;
203}
204
205/* ── Main Content ─────────────────────────────────────────────── */
206.main-content {
207 flex: 1;
208 display: flex;
209 overflow: hidden;
210}
211
212/* ── Document Pane ───────────────────────────────────────────── */
213.document-pane {
214 flex: 1;
215 display: flex;
216 flex-direction: column;
217 border-right: 1px solid var(--border);
218 min-width: 0;
219 overflow: hidden;
220}
221
222.document-header {
223 display: flex;
224 justify-content: space-between;
225 align-items: center;
226 padding: 7px 16px;
227 background: var(--bg-secondary);
228 border-bottom: 1px solid var(--border);
229 font-size: 11px;
230 letter-spacing: 0.07em;
231 text-transform: uppercase;
232 color: var(--text-secondary);
233}
234
235.document-header button {
236 padding: 3px 8px;
237 background: var(--bg-tertiary);
238 border: 1px solid var(--border);
239 border-radius: 2px;
240 color: var(--text-primary);
241 cursor: pointer;
242 font-size: 11px;
243 font-family: inherit;
244 letter-spacing: 0.05em;
245}
246
247.document-header button:hover {
248 background: var(--border);
249}
250
251.document-content {
252 flex: 1;
253 overflow-y: auto;
254 padding: 24px;
255}
256
257.document-content.editing {
258 font-family: inherit;
259 font-size: 13px;
260 line-height: 1.6;
261 background: var(--bg-primary);
262 border: none;
263 resize: none;
264 color: var(--text-primary);
265}
266
267.codemirror-editor {
268 flex: 1;
269 overflow: hidden;
270 min-height: 0;
271}
272
273.codemirror-editor .cm-editor {
274 height: 100%;
275 max-width: 100%;
276}
277
278.codemirror-editor .cm-scroller {
279 overflow: auto !important;
280}
281
282.codemirror-editor .cm-gutters {
283 background: var(--bg-secondary);
284 border-right: 1px solid var(--border);
285}
286
287.document-content.rendered {
288 line-height: 1.7;
289}
290
291.document-content.rendered h1 {
292 font-size: 22px;
293 margin: 24px 0 16px;
294 letter-spacing: -0.01em;
295}
296.document-content.rendered h2 {
297 font-size: 17px;
298 margin: 20px 0 12px;
299 color: var(--text-secondary);
300 text-transform: uppercase;
301 letter-spacing: 0.05em;
302}
303.document-content.rendered h3 {
304 font-size: 14px;
305 margin: 16px 0 8px;
306}
307.document-content.rendered p {
308 margin: 8px 0;
309 line-height: 1.6;
310}
311.document-content.rendered code {
312 background: var(--bg-tertiary);
313 padding: 2px 6px;
314 border-radius: 2px;
315 font-size: 12px;
316 font-family: inherit;
317}
318.document-content.rendered pre {
319 background: var(--bg-tertiary);
320 padding: 16px;
321 border-radius: 2px;
322 overflow-x: auto;
323 margin: 16px 0;
324}
325.document-content.rendered pre code {
326 background: none;
327 padding: 0;
328}
329.document-content.rendered ul,
330.document-content.rendered ol {
331 margin: 12px 0;
332 padding-left: 24px;
333}
334.document-content.rendered li {
335 margin-bottom: 6px;
336 line-height: 1.5;
337}
338.document-content.rendered ul.contains-task-list {
339 list-style: none;
340 padding-left: 0;
341}
342.document-content.rendered li.task-list-item {
343 display: flex;
344 align-items: flex-start;
345 gap: 8px;
346}
347.document-content.rendered li.task-list-item input[type="checkbox"] {
348 margin-top: 4px;
349}
350.document-content.rendered table {
351 width: 100%;
352 border-collapse: collapse;
353 margin: 16px 0;
354 font-size: 12px;
355}
356.document-content.rendered th,
357.document-content.rendered td {
358 padding: 8px 12px;
359 text-align: left;
360 border: 1px solid var(--border);
361}
362.document-content.rendered th {
363 background: var(--bg-tertiary);
364 font-weight: 600;
365 text-transform: uppercase;
366 letter-spacing: 0.06em;
367 font-size: 11px;
368}
369.document-content.rendered tr:nth-child(even) td {
370 background: var(--bg-secondary);
371}
372.document-content.rendered blockquote {
373 border-left: 3px solid var(--accent);
374 margin: 16px 0;
375 padding-left: 16px;
376 color: var(--text-secondary);
377}
378.document-content.rendered hr {
379 border: none;
380 border-top: 1px solid var(--border);
381 margin: 24px 0;
382}
383.document-content.rendered a {
384 color: var(--accent);
385 text-decoration: none;
386}
387.document-content.rendered a:hover {
388 text-decoration: underline;
389}
390.document-content.rendered .empty {
391 color: var(--text-secondary);
392 font-style: italic;
393}
394
395.badge {
396 background: var(--accent);
397 color: white;
398 padding: 2px 8px;
399 border-radius: 2px;
400 font-size: 10px;
401 letter-spacing: 0.08em;
402 text-transform: uppercase;
403}
404
405/* ── Chat Pane ───────────────────────────────────────────────── */
406.chat-pane {
407 width: 380px;
408 display: flex;
409 flex-direction: column;
410 background: var(--bg-secondary);
411}
412
413.chat-messages {
414 flex: 1;
415 overflow-y: auto;
416 padding: 16px;
417}
418
419.message {
420 margin-bottom: 10px;
421 padding: 9px 13px;
422 border-radius: 4px;
423 max-width: 90%;
424 font-size: 13px;
425 line-height: 1.5;
426 white-space: pre-wrap;
427}
428
429.message.user {
430 background: var(--accent);
431 margin-left: auto;
432 color: white;
433}
434
435.message.assistant {
436 background: var(--bg-tertiary);
437}
438
439.message.loading {
440 color: var(--text-secondary);
441 font-style: italic;
442}
443
444.chat-input {
445 display: flex;
446 gap: 8px;
447 padding: 12px;
448 border-top: 1px solid var(--border);
449}
450
451.chat-input input {
452 flex: 1;
453 padding: 9px 13px;
454 background: var(--bg-tertiary);
455 border: 1px solid var(--border);
456 border-radius: 2px;
457 color: var(--text-primary);
458 font-size: 13px;
459 font-family: inherit;
460 transition: border-color 0.15s, box-shadow 0.15s;
461}
462
463.chat-input input:focus {
464 outline: none;
465 border-color: var(--accent);
466 box-shadow: 0 0 0 2px rgba(96, 165, 250, 0.2);
467}
468
469html[data-theme="light"] .chat-input input:focus {
470 box-shadow: 0 0 0 2px rgba(37, 99, 235, 0.2);
471}
472
473.chat-input button {
474 padding: 9px 15px;
475 background: var(--accent);
476 border: none;
477 border-radius: 2px;
478 color: white;
479 cursor: pointer;
480 font-size: 12px;
481 font-family: inherit;
482 letter-spacing: 0.05em;
483 text-transform: uppercase;
484 transition: background 0.15s;
485}
486
487.chat-input button:hover:not(:disabled) {
488 background: var(--accent-hover);
489}
490
491.chat-input button:disabled {
492 opacity: 0.4;
493 cursor: not-allowed;
494}
495
496/* ── Action Bar ──────────────────────────────────────────────── */
497.action-bar {
498 display: flex;
499 justify-content: space-between;
500 align-items: center;
501 padding: 10px 16px;
502 background: var(--bg-secondary);
503 border-top: 1px solid var(--border);
504}
505
506.action-bar-left,
507.action-bar-right {
508 display: flex;
509 align-items: center;
510 gap: 16px;
511}
512
513.token-indicator {
514 display: flex;
515 align-items: center;
516 gap: 8px;
517}
518
519.token-bar {
520 width: 100px;
521 height: 4px;
522 background: var(--bg-tertiary);
523 border-radius: 1px;
524 overflow: hidden;
525}
526
527.token-fill {
528 height: 100%;
529 transition: width 0.3s ease;
530}
531
532.token-label {
533 font-size: 10px;
534 letter-spacing: 0.08em;
535 text-transform: uppercase;
536 color: var(--text-secondary);
537}
538
539.permission-toggle {
540 display: flex;
541 align-items: center;
542 gap: 6px;
543 font-size: 11px;
544 letter-spacing: 0.05em;
545 text-transform: uppercase;
546 color: var(--text-secondary);
547 cursor: pointer;
548}
549
550.permission-toggle input {
551 cursor: pointer;
552}
553
554.btn-secondary {
555 padding: 6px 14px;
556 background: var(--bg-tertiary);
557 border: 1px solid var(--border);
558 border-radius: 2px;
559 color: var(--text-primary);
560 cursor: pointer;
561 font-size: 12px;
562 font-family: inherit;
563 letter-spacing: 0.05em;
564 transition: background 0.15s;
565}
566
567.btn-secondary:hover:not(:disabled) {
568 background: var(--border);
569}
570
571.btn-secondary:disabled {
572 opacity: 0.4;
573 cursor: not-allowed;
574}
575
576.btn-primary {
577 padding: 6px 18px;
578 background: var(--accent);
579 border: none;
580 border-radius: 2px;
581 color: white;
582 cursor: pointer;
583 font-weight: 600;
584 font-size: 12px;
585 font-family: inherit;
586 letter-spacing: 0.07em;
587 text-transform: uppercase;
588 transition: background 0.15s;
589}
590
591.btn-primary:hover:not(:disabled) {
592 background: var(--accent-hover);
593}
594
595.btn-primary:disabled {
596 opacity: 0.4;
597 cursor: not-allowed;
598}
599
600.implementing-status {
601 color: var(--success);
602 font-size: 11px;
603 letter-spacing: 0.1em;
604 text-transform: uppercase;
605}
606
607/* ── Error Bar ───────────────────────────────────────────────── */
608.error-bar {
609 display: flex;
610 justify-content: space-between;
611 align-items: center;
612 padding: 8px 16px;
613 background: var(--danger);
614 color: white;
615 font-size: 12px;
616 letter-spacing: 0.03em;
617}
618
619.error-bar button {
620 background: none;
621 border: none;
622 color: white;
623 font-size: 16px;
624 cursor: pointer;
625 padding: 0 4px;
626}
627
628.error-bar button:hover {
629 opacity: 0.8;
630}
631
632/* ── Onboarding ──────────────────────────────────────────────── */
633.onboarding h1 {
634 font-size: 24px;
635 margin-bottom: 8px;
636 letter-spacing: 0.05em;
637 text-transform: uppercase;
638}
639
640.onboarding h2 {
641 font-size: 13px;
642 margin-top: 28px;
643 margin-bottom: 12px;
644 color: var(--accent);
645 text-transform: uppercase;
646 letter-spacing: 0.1em;
647}
648
649.onboarding p {
650 margin: 12px 0;
651 line-height: 1.6;
652}
653
654.onboarding ol {
655 margin: 12px 0 12px 24px;
656}
657
658.onboarding li {
659 margin: 8px 0;
660 line-height: 1.5;
661}
662
663.onboarding pre {
664 background: var(--bg-tertiary);
665 padding: 12px 16px;
666 border-radius: 2px;
667 margin: 12px 0;
668}
669
670.onboarding code {
671 font-family: inherit;
672 font-size: 12px;
673}
674
675.onboarding a {
676 color: var(--accent);
677 text-decoration: none;
678}
679
680.onboarding a:hover {
681 text-decoration: underline;
682}
683
684.onboarding-tip {
685 margin-top: 28px;
686 padding: 16px;
687 background: var(--bg-tertiary);
688 border-left: 3px solid var(--accent);
689 border-radius: 0 2px 2px 0;
690}
691```
692
693---
694
695### 3. Add Theme State to App
696**File:** `renderer/src/App.tsx`
697**What:** Three additions to the existing file — no existing logic is touched.
698
699**3a. Add `Theme` type and `theme` state** — insert after the existing imports, before the `App` function:
700
701```typescript
702// Add this type alias near the top, alongside other local types if any,
703// or just before the App function:
704type Theme = "dark" | "light";
705```
706
707**3b. Add `theme` state and side-effects** — insert inside `App()`, after the existing `useState` declarations (after `const [error, setError] = useState<string | null>(null)`):
708
709```typescript
710const [theme, setTheme] = useState<Theme>(
711 () => (localStorage.getItem("cf-theme") as Theme) ?? "dark"
712);
713
714// Keep document.documentElement in sync, and persist to localStorage
715useEffect(() => {
716 document.documentElement.setAttribute("data-theme", theme);
717 localStorage.setItem("cf-theme", theme);
718}, [theme]);
719
720const handleToggleTheme = () =>
721 setTheme((t) => (t === "dark" ? "light" : "dark"));
722```
723
724**3c. Pass new props to `Header` and `DocumentPane`** — update the JSX inside the `return`:
725
726```tsx
727// Header — add two new props:
728<Header
729 projects={projects}
730 sessions={sessions}
731 selectedProject={selectedProject}
732 selectedSession={selectedSession}
733 onSelectProject={setSelectedProject}
734 onSelectSession={setSelectedSession}
735 onCreateProject={handleCreateProject}
736 onCreateSession={handleCreateSession}
737 onDeleteProject={handleDeleteProject}
738 onDeleteSession={handleDeleteSession}
739 theme={theme}
740 onToggleTheme={handleToggleTheme}
741/>
742
743// DocumentPane — add one new prop:
744<DocumentPane
745 content={documentContent}
746 onChange={setDocumentContent}
747 phase={selectedSession?.phase || "research"}
748 disabled={!selectedSession || selectedSession.phase === "implement"}
749 showOnboarding={!selectedProject}
750 theme={theme}
751/>
752```
753
754---
755
756### 4. Update Header Component
757**File:** `renderer/src/components/Header.tsx`
758**What:** Add `theme` and `onToggleTheme` to the props interface, insert wordmark element, add toggle button.
759
760**Complete updated file:**
761
762```tsx
763import React from "react";
764import type { Project, Session, Phase } from "../types";
765
766type Theme = "dark" | "light";
767
768interface HeaderProps {
769 projects: Project[];
770 sessions: Session[];
771 selectedProject: Project | null;
772 selectedSession: Session | null;
773 onSelectProject: (project: Project | null) => void;
774 onSelectSession: (session: Session | null) => void;
775 onCreateProject: () => void;
776 onCreateSession: () => void;
777 onDeleteProject?: (id: string) => void;
778 onDeleteSession?: (id: string) => void;
779 theme: Theme;
780 onToggleTheme: () => void;
781}
782
783const phaseLabels: Record<Phase, string> = {
784 research: "Research",
785 plan: "Plan",
786 implement: "Implement",
787};
788
789const phases: Phase[] = ["research", "plan", "implement"];
790
791export function Header({
792 projects,
793 sessions,
794 selectedProject,
795 selectedSession,
796 onSelectProject,
797 onSelectSession,
798 onCreateProject,
799 onCreateSession,
800 onDeleteProject,
801 onDeleteSession,
802 theme,
803 onToggleTheme,
804}: HeaderProps) {
805 const handleDeleteProject = () => {
806 if (!selectedProject || !onDeleteProject) return;
807 if (confirm(`Delete project "${selectedProject.name}"? This cannot be undone.`)) {
808 onDeleteProject(selectedProject.id);
809 }
810 };
811
812 const handleDeleteSession = () => {
813 if (!selectedSession || !onDeleteSession) return;
814 if (confirm(`Delete session "${selectedSession.name}"? This cannot be undone.`)) {
815 onDeleteSession(selectedSession.id);
816 }
817 };
818
819 return (
820 <header className="header">
821 <div className="header-left">
822 {/* ── Wordmark ── */}
823 <span className="app-wordmark">Claude Flow</span>
824
825 <select
826 value={selectedProject?.id || ""}
827 onChange={(e) => {
828 const project = projects.find((p) => p.id === e.target.value);
829 onSelectProject(project || null);
830 onSelectSession(null);
831 }}
832 >
833 <option value="">Select Project...</option>
834 {projects.map((p) => (
835 <option key={p.id} value={p.id}>
836 {p.name}
837 </option>
838 ))}
839 </select>
840 <button onClick={onCreateProject}>+ Project</button>
841 {selectedProject && onDeleteProject && (
842 <button
843 onClick={handleDeleteProject}
844 className="btn-delete"
845 title="Delete project"
846 >
847 🗑️
848 </button>
849 )}
850
851 {selectedProject && (
852 <>
853 <select
854 value={selectedSession?.id || ""}
855 onChange={(e) => {
856 const session = sessions.find((s) => s.id === e.target.value);
857 onSelectSession(session || null);
858 }}
859 >
860 <option value="">Select Session...</option>
861 {sessions.map((s) => (
862 <option key={s.id} value={s.id}>
863 {s.name}
864 </option>
865 ))}
866 </select>
867 <button onClick={onCreateSession}>+ Session</button>
868 {selectedSession && onDeleteSession && (
869 <button
870 onClick={handleDeleteSession}
871 className="btn-delete"
872 title="Delete session"
873 >
874 🗑️
875 </button>
876 )}
877 </>
878 )}
879 </div>
880
881 <div className="header-right">
882 {selectedSession && (
883 <div className="phase-indicator">
884 {phases.map((phase) => {
885 const phaseIndex = phases.indexOf(phase);
886 const currentIndex = phases.indexOf(selectedSession.phase);
887 const isComplete = phaseIndex < currentIndex;
888 const isActive = phase === selectedSession.phase;
889
890 return (
891 <span
892 key={phase}
893 className={`phase-step ${isActive ? "active" : ""} ${
894 isComplete ? "complete" : ""
895 }`}
896 >
897 {phaseLabels[phase]}
898 </span>
899 );
900 })}
901 </div>
902 )}
903
904 {/* ── Theme toggle ── */}
905 <button className="theme-toggle" onClick={onToggleTheme}>
906 {theme === "dark" ? "[light]" : "[dark]"}
907 </button>
908 </div>
909 </header>
910 );
911}
912```
913
914---
915
916### 5. Dynamic CodeMirror Theme in DocumentPane
917**File:** `renderer/src/components/DocumentPane.tsx`
918**What:** Three surgical changes to `MarkdownEditor` — add `theme` prop, update imports, update `useEffect`.
919
920**5a. Update imports** — add `syntaxHighlighting` and `defaultHighlightStyle` to the `@codemirror/language` import:
921
922```typescript
923// BEFORE:
924import { markdown } from "@codemirror/lang-markdown";
925import { languages } from "@codemirror/language-data";
926
927// AFTER:
928import { markdown } from "@codemirror/lang-markdown";
929import { languages } from "@codemirror/language-data";
930import { syntaxHighlighting, defaultHighlightStyle } from "@codemirror/language";
931```
932
933**5b. Update `MarkdownEditor` props interface** — add `theme`:
934
935```typescript
936// BEFORE:
937function MarkdownEditor({
938 content,
939 onChange,
940 disabled,
941}: {
942 content: string;
943 onChange: (content: string) => void;
944 disabled: boolean;
945})
946
947// AFTER:
948function MarkdownEditor({
949 content,
950 onChange,
951 disabled,
952 theme,
953}: {
954 content: string;
955 onChange: (content: string) => void;
956 disabled: boolean;
957 theme: "dark" | "light";
958})
959```
960
961**5c. Update the `useEffect` inside `MarkdownEditor`** — swap the theme extension and add `theme` to the dependency array:
962
963```typescript
964// BEFORE (inside useEffect):
965extensions: [
966 lineNumbers(),
967 highlightActiveLine(),
968 drawSelection(),
969 history(),
970 keymap.of([...defaultKeymap, ...historyKeymap]),
971 markdown({ codeLanguages: languages }),
972 oneDark, // ← hardcoded
973 updateListener,
974 EditorView.editable.of(!disabled),
975 EditorView.lineWrapping,
976 EditorView.theme({ ... }),
977],
978// dependency array:
979}, [disabled]);
980
981// AFTER:
982extensions: [
983 lineNumbers(),
984 highlightActiveLine(),
985 drawSelection(),
986 history(),
987 keymap.of([...defaultKeymap, ...historyKeymap]),
988 markdown({ codeLanguages: languages }),
989 theme === "dark" ? oneDark : syntaxHighlighting(defaultHighlightStyle), // ← dynamic
990 updateListener,
991 EditorView.editable.of(!disabled),
992 EditorView.lineWrapping,
993 EditorView.theme({ ... }),
994],
995// dependency array:
996}, [disabled, theme]); // ← theme added
997```
998
999**5d. Update `DocumentPaneProps` interface and pass `theme` through** — add to the interface and forward to `MarkdownEditor`:
1000
1001```typescript
1002// BEFORE:
1003interface DocumentPaneProps {
1004 content: string;
1005 onChange: (content: string) => void;
1006 phase: Phase;
1007 disabled: boolean;
1008 showOnboarding?: boolean;
1009}
1010
1011// AFTER:
1012interface DocumentPaneProps {
1013 content: string;
1014 onChange: (content: string) => void;
1015 phase: Phase;
1016 disabled: boolean;
1017 showOnboarding?: boolean;
1018 theme: "dark" | "light";
1019}
1020```
1021
1022And in the `DocumentPane` function body, destructure `theme` and pass it to `MarkdownEditor`:
1023
1024```tsx
1025// BEFORE:
1026export function DocumentPane({
1027 content,
1028 onChange,
1029 phase,
1030 disabled,
1031 showOnboarding,
1032}: DocumentPaneProps)
1033
1034// AFTER:
1035export function DocumentPane({
1036 content,
1037 onChange,
1038 phase,
1039 disabled,
1040 showOnboarding,
1041 theme,
1042}: DocumentPaneProps)
1043```
1044
1045```tsx
1046// BEFORE (in JSX):
1047<MarkdownEditor
1048 content={content}
1049 onChange={onChange}
1050 disabled={disabled}
1051/>
1052
1053// AFTER:
1054<MarkdownEditor
1055 content={content}
1056 onChange={onChange}
1057 disabled={disabled}
1058 theme={theme}
1059/>
1060```
1061
1062---
1063
1064## TODO
1065- [x] **1.** `renderer/index.html` — change `<title>minimal</title>` to `<title>Claude Flow</title>`
1066- [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`)
1067- [x] **3a.** `renderer/src/App.tsx` — add `type Theme` alias
1068- [x] **3b.** `renderer/src/App.tsx` — add `theme` state + `useEffect` + `handleToggleTheme`
1069- [x] **3c.** `renderer/src/App.tsx` — pass `theme`/`onToggleTheme` to `Header`, pass `theme` to `DocumentPane`
1070- [x] **4.** `renderer/src/components/Header.tsx` — full replacement (new props, wordmark, toggle button)
1071- [x] **5a.** `renderer/src/components/DocumentPane.tsx` — add `syntaxHighlighting, defaultHighlightStyle` import
1072- [x] **5b.** `renderer/src/components/DocumentPane.tsx` — add `theme` to `MarkdownEditor` props
1073- [x] **5c.** `renderer/src/components/DocumentPane.tsx` — dynamic theme extension + `theme` in dep array
1074- [x] **5d.** `renderer/src/components/DocumentPane.tsx` — add `theme` to `DocumentPaneProps`, destructure, forward to `MarkdownEditor`
1075
1076---
1077
1078## Risks / Considerations
1079
1080**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.
1081
1082**`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.
1083
1084**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.
1085
1086**`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.
1087
1088**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.
1089
1090**`select` element styling in light mode**: Native `<select>` elements on macOS render with a system style that may look slightly inconsistent in light mode. This is an existing limitation of the app's native select usage and is out of scope for this task.
diff --git a/.claude-flow/research.md b/.claude-flow/research.md
new file mode 100644
index 0000000..30c4832
--- /dev/null
+++ b/.claude-flow/research.md
@@ -0,0 +1,214 @@
1# Research Findings
2
3## Overview
4
5This is an Electron + React + TypeScript app called "Claude Flow" that provides a structured AI-assisted coding workflow (Research → Plan → Implement). The styling is managed through a single monolithic CSS file using CSS custom properties. The current window title is "minimal" (a leftover from the starter template). There is no light/dark mode — only dark. The goal is to: rename the title, add a header wordmark, modernize the UI with a cipherpunk aesthetic (full monospace font, sharper borders, electric-blue accent), and add a text-based light/dark mode toggle.
6
7---
8
9## Architecture
10
11### Tech Stack
12- **Electron 38** — main process, window chrome, IPC
13- **React 19 + Vite** — renderer process
14- **TypeScript** throughout
15- **CodeMirror 6** — markdown editor in DocumentPane
16- **better-sqlite3** — local DB for projects/sessions/messages
17
18### Renderer Structure
19```
20renderer/
21 index.html ← <title>minimal</title> — MUST CHANGE to "Claude Flow"
22 src/
23 main.tsx ← React root mount
24 App.tsx ← Top-level state, layout
25 types.ts ← Shared TS types
26 styles/globals.css ← ALL styling lives here (CSS custom properties)
27 components/
28 Header.tsx ← Top bar: project/session selectors, phase indicator
29 DocumentPane.tsx ← Left pane: CodeMirror editor + ReactMarkdown preview
30 ChatPane.tsx ← Right pane: message list + text input
31 ActionBar.tsx ← Bottom bar: token usage bar, Review/Submit buttons
32```
33
34### Main Process Structure
35```
36src/main/
37 index.ts ← BrowserWindow creation (titleBarStyle: "hiddenInset"), IPC setup
38 preload.ts ← Exposes window.api bridge
39 ipc/handlers.ts
40 db/ ← SQLite schema/queries
41 claude/ ← Claude SDK integration
42```
43
44---
45
46## Relevant Files
47
48| File | Relevance to Task |
49|------|-------------------|
50| `renderer/index.html` | **Line 10**: `<title>minimal</title>` → change to `Claude Flow` |
51| `renderer/src/styles/globals.css` | **All CSS** — CSS custom properties `:root {}` define the entire theme. Single file, ~543 lines |
52| `renderer/src/components/Header.tsx` | Add app wordmark + text theme toggle button (header-right div) |
53| `renderer/src/App.tsx` | Add theme state, persist to localStorage, set `data-theme` on `document.documentElement`, pass props down |
54| `renderer/src/components/DocumentPane.tsx` | Make CodeMirror theme dynamic — swap `oneDark` ↔ `syntaxHighlighting(defaultHighlightStyle)` |
55| `src/main/index.ts` | `titleBarStyle: "hiddenInset"` — macOS frameless, HTML title used in Dock/app switcher |
56
57---
58
59## Key Insights
60
61### 1. Title Change — Two Locations
62The title "minimal" appears in:
63- `renderer/index.html` line 10: `<title>minimal</title>` — the browser/OS-level window title
64- `package.json` line 1: `"name": "minimal-electron-vite-react-better-sqlite"` — package name (lower-priority)
65- `package.json` line 19: `"appId": "com.example.minimalbsqlite"` — build identifier (lower-priority)
66
67**Primary fix**: change `<title>minimal</title>` in `renderer/index.html`.
68
69### 2. Current Color Palette (Dark Only)
70```css
71:root {
72 --bg-primary: #1a1a1a /* near-black background */
73 --bg-secondary: #252525 /* panels, header */
74 --bg-tertiary: #333 /* inputs, code blocks */
75 --border: #444 /* dividers */
76 --text-primary: #e0e0e0 /* main text */
77 --text-secondary:#888 /* muted text, labels */
78 --accent: #3b82f6 /* blue — buttons, links */
79 --accent-hover: #2563eb
80 --success: #10b981 /* green */
81 --warning: #f59e0b /* amber */
82 --danger: #ef4444 /* red */
83}
84```
85No light mode variables exist. The mechanism for light/dark is straightforward: add `html[data-theme="light"]` overrides.
86
87### 3. Light/Dark Mode — Implementation Path
88**Theme storage**: `localStorage` — persist across sessions
89**Toggle mechanism**: Set `data-theme="light"` or `data-theme="dark"` on `document.documentElement`
90**CSS**: Override variables under `html[data-theme="light"]` selector
91**State**: `useState` + `useEffect` in `App.tsx` — no Context needed, just pass `theme` + `onToggleTheme` as props
92**Toggle style**: Text-only, bracket notation: `[dark]` / `[light]` — fits the cipherpunk chrome feel
93
94The `Header` will receive `theme: "dark" | "light"` and `onToggleTheme: () => void` as new props.
95
96**CodeMirror — RESOLVED (no new packages needed)**:
97`@codemirror/language` is already installed as a transitive dependency and exports both `defaultHighlightStyle` and `syntaxHighlighting`. The `defaultHighlightStyle` provides a full light-mode syntax highlighting scheme (keywords in purple, strings in red, literals in green, etc.) — identical coverage to `oneDark`, just different colors.
98
99Strategy:
100- **Dark** → use `[oneDark]` as today
101- **Light** → use `[syntaxHighlighting(defaultHighlightStyle)]` from `@codemirror/language`
102
103`DocumentPane` will receive a `theme` prop. The CodeMirror `useEffect` dependency array will include `theme` (alongside the existing `disabled`) so the editor reinitializes with the correct extension set when the theme changes.
104
105### 4. Accent Color — RESOLVED
106
107**Keep blue, go more electric. No green/cyan.**
108
109The cipherpunk feel will come from sharpness and monospace typography — not a color gimmick.
110
111Proposed accent colors:
112```
113Dark mode: #60a5fa (bright electric blue — more vivid than current #3b82f6)
114 hover: #93c5fd
115Light mode: #2563eb (deeper blue — maintains contrast on light backgrounds)
116 hover: #1d4ed8
117```
118
119### 5. Font — RESOLVED: Full Monospace
120
121Apply a monospace font stack to `body` — the entire UI goes mono. The codebase already uses this stack for code blocks, so extending it to the whole UI creates a unified terminal aesthetic without importing any font files.
122
123```css
124body {
125 font-family: "SF Mono", "Cascadia Code", "JetBrains Mono", "Fira Code", Monaco, "Courier New", monospace;
126}
127```
128
129### 6. Header Wordmark — RESOLVED: Yes, Add It
130
131Add a styled `<span className="app-wordmark">CLAUDE FLOW</span>` as the **first element** inside `.header-left`, before the project dropdown. Separated from the controls by a subtle right border.
132
133```css
134.app-wordmark {
135 font-size: 13px;
136 font-weight: 700;
137 letter-spacing: 0.15em;
138 text-transform: uppercase;
139 color: var(--text-primary);
140 padding-right: 12px;
141 border-right: 1px solid var(--border);
142 margin-right: 4px;
143 user-select: none;
144 white-space: nowrap;
145}
146```
147
148### 7. Other Cipherpunk Touches — All CSS-Only
149
150- **Sharper borders**: reduce `border-radius` from 4–8px → **2px** on all controls (buttons, inputs, messages, badges). Phase steps → 2px. Chat messages → 4px.
151- **Uppercase meta labels**: `text-transform: uppercase; letter-spacing: 0.07em` on `.token-label`, `.phase-step`, `.document-header span`, `.badge`, `.implementing-status`
152- **Subtle focus glow**: `box-shadow: 0 0 0 2px rgba(96, 165, 250, 0.3)` on focused inputs and buttons
153- **Phase brackets**: Phase step text to be updated in `Header.tsx` JSX: `[Research]` → `[RESEARCH]` etc., with CSS uppercase doing the work so the component just renders the labels naturally
154
155### 8. Light Mode Color Palette (Proposed)
156
157```css
158html[data-theme="light"] {
159 --bg-primary: #f4f4f2; /* warm off-white — not stark, feels terminal-adjacent */
160 --bg-secondary: #e8e8e5; /* panels, header */
161 --bg-tertiary: #d8d8d4; /* inputs, code blocks */
162 --border: #b4b4b0; /* dividers */
163 --text-primary: #1a1a18; /* near-black */
164 --text-secondary:#5a5a56; /* muted */
165 --accent: #2563eb; /* deep blue */
166 --accent-hover: #1d4ed8;
167 --success: #059669;
168 --warning: #d97706;
169 --danger: #dc2626;
170}
171```
172
173Warm stone tones rather than pure white — keeps the app from feeling like a generic SaaS product.
174
175### 9. No Existing Theme Infrastructure
176There is currently **no ThemeContext, no localStorage usage, no `data-theme` attribute**. Fully greenfield. Simple prop-passing from `App.tsx` is sufficient.
177
178### 10. Window Styling Note
179- **macOS** (`titleBarStyle: "hiddenInset"`): traffic lights appear, no visible title text in window frame. HTML `<title>` shows in Dock tooltip and app switcher.
180- **Linux/Windows**: default Electron title bar shows `<title>` in window chrome. Critical to fix.
181
182---
183
184## Resolved Decisions
185
186| Question | Decision |
187|----------|----------|
188| Accent color | Electric blue: `#60a5fa` (dark) / `#2563eb` (light). No green/cyan. |
189| CodeMirror light theme | `syntaxHighlighting(defaultHighlightStyle)` from already-installed `@codemirror/language`. Zero new packages. |
190| Font scope | **Full mono** — `body` font-family. Entire UI uses monospace stack. |
191| Header wordmark | **Yes** — `CLAUDE FLOW` in `.header-left`, uppercase + letter-spacing + right-border separator |
192| Theme toggle style | **Text toggle**: `[dark]` / `[light]` bracket notation |
193
194---
195
196## Proposed Change Scope
197
198| Change | File(s) | Effort |
199|--------|---------|--------|
200| Fix `<title>` | `renderer/index.html` | Trivial |
201| Apply monospace body font | `globals.css` | Trivial |
202| Sharpen border-radius across all controls | `globals.css` | Small |
203| Uppercase + letter-spacing on meta labels | `globals.css` | Small |
204| Brighter dark-mode accent (#60a5fa) | `globals.css` | Trivial |
205| Add `html[data-theme="light"]` color block | `globals.css` | Small |
206| Add focus glow styles | `globals.css` | Small |
207| Add `.app-wordmark` styles | `globals.css` | Trivial |
208| Add `[dark]`/`[light]` toggle button styles | `globals.css` | Trivial |
209| Add theme state + `localStorage` init | `App.tsx` | Small |
210| Pass `theme` + `onToggleTheme` props to Header | `App.tsx` → `Header.tsx` | Small |
211| Pass `theme` prop to DocumentPane | `App.tsx` → `DocumentPane.tsx` | Small |
212| Add `CLAUDE FLOW` wordmark element | `Header.tsx` | Trivial |
213| Add `[dark]`/`[light]` toggle button | `Header.tsx` | Small |
214| Make CodeMirror theme dynamic | `DocumentPane.tsx` | Small |
diff --git a/renderer/index.html b/renderer/index.html
index 8bce8a6..027682c 100644
--- a/renderer/index.html
+++ b/renderer/index.html
@@ -7,7 +7,7 @@
7 content="default-src 'self'; img-src 'self' data:; style-src 'self' 'unsafe-inline'; script-src 'self'; connect-src 'self' ws: http://localhost:5173" 7 content="default-src 'self'; img-src 'self' data:; style-src 'self' 'unsafe-inline'; script-src 'self'; connect-src 'self' ws: http://localhost:5173"
8 /> 8 />
9 <meta name="viewport" content="width=device-width, initial-scale=1" /> 9 <meta name="viewport" content="width=device-width, initial-scale=1" />
10 <title>minimal</title> 10 <title>Claude Flow</title>
11 </head> 11 </head>
12 <body> 12 <body>
13 <div id="root"></div> 13 <div id="root"></div>
diff --git a/renderer/src/App.tsx b/renderer/src/App.tsx
index 26ac252..5ca7a9b 100644
--- a/renderer/src/App.tsx
+++ b/renderer/src/App.tsx
@@ -8,6 +8,8 @@ import "./styles/globals.css";
8 8
9const api = window.api; 9const api = window.api;
10 10
11type Theme = "dark" | "light";
12
11export function App() { 13export function App() {
12 const [projects, setProjects] = useState<Project[]>([]); 14 const [projects, setProjects] = useState<Project[]>([]);
13 const [sessions, setSessions] = useState<Session[]>([]); 15 const [sessions, setSessions] = useState<Session[]>([]);
@@ -23,6 +25,19 @@ export function App() {
23 }); 25 });
24 const [error, setError] = useState<string | null>(null); 26 const [error, setError] = useState<string | null>(null);
25 27
28 const [theme, setTheme] = useState<Theme>(
29 () => (localStorage.getItem("cf-theme") as Theme) ?? "dark"
30 );
31
32 // Keep document.documentElement in sync and persist to localStorage
33 useEffect(() => {
34 document.documentElement.setAttribute("data-theme", theme);
35 localStorage.setItem("cf-theme", theme);
36 }, [theme]);
37
38 const handleToggleTheme = () =>
39 setTheme((t) => (t === "dark" ? "light" : "dark"));
40
26 const hasChanges = documentContent !== originalContent; 41 const hasChanges = documentContent !== originalContent;
27 42
28 // Clear error after 5 seconds 43 // Clear error after 5 seconds
@@ -282,6 +297,8 @@ export function App() {
282 onCreateSession={handleCreateSession} 297 onCreateSession={handleCreateSession}
283 onDeleteProject={handleDeleteProject} 298 onDeleteProject={handleDeleteProject}
284 onDeleteSession={handleDeleteSession} 299 onDeleteSession={handleDeleteSession}
300 theme={theme}
301 onToggleTheme={handleToggleTheme}
285 /> 302 />
286 303
287 <div className="main-content"> 304 <div className="main-content">
@@ -291,6 +308,7 @@ export function App() {
291 phase={selectedSession?.phase || "research"} 308 phase={selectedSession?.phase || "research"}
292 disabled={!selectedSession || selectedSession.phase === "implement"} 309 disabled={!selectedSession || selectedSession.phase === "implement"}
293 showOnboarding={!selectedProject} 310 showOnboarding={!selectedProject}
311 theme={theme}
294 /> 312 />
295 313
296 <ChatPane 314 <ChatPane
diff --git a/renderer/src/components/DocumentPane.tsx b/renderer/src/components/DocumentPane.tsx
index c5f456c..f5368b3 100644
--- a/renderer/src/components/DocumentPane.tsx
+++ b/renderer/src/components/DocumentPane.tsx
@@ -5,6 +5,7 @@ import { EditorState } from "@codemirror/state";
5import { EditorView, keymap, lineNumbers, highlightActiveLine, drawSelection } from "@codemirror/view"; 5import { EditorView, keymap, lineNumbers, highlightActiveLine, drawSelection } from "@codemirror/view";
6import { markdown } from "@codemirror/lang-markdown"; 6import { markdown } from "@codemirror/lang-markdown";
7import { languages } from "@codemirror/language-data"; 7import { languages } from "@codemirror/language-data";
8import { syntaxHighlighting, defaultHighlightStyle } from "@codemirror/language";
8import { defaultKeymap, history, historyKeymap } from "@codemirror/commands"; 9import { defaultKeymap, history, historyKeymap } from "@codemirror/commands";
9import { oneDark } from "@codemirror/theme-one-dark"; 10import { oneDark } from "@codemirror/theme-one-dark";
10import type { Phase } from "../types"; 11import type { Phase } from "../types";
@@ -15,16 +16,19 @@ interface DocumentPaneProps {
15 phase: Phase; 16 phase: Phase;
16 disabled: boolean; 17 disabled: boolean;
17 showOnboarding?: boolean; 18 showOnboarding?: boolean;
19 theme: "dark" | "light";
18} 20}
19 21
20function MarkdownEditor({ 22function MarkdownEditor({
21 content, 23 content,
22 onChange, 24 onChange,
23 disabled, 25 disabled,
26 theme,
24}: { 27}: {
25 content: string; 28 content: string;
26 onChange: (content: string) => void; 29 onChange: (content: string) => void;
27 disabled: boolean; 30 disabled: boolean;
31 theme: "dark" | "light";
28}) { 32}) {
29 const editorRef = useRef<HTMLDivElement>(null); 33 const editorRef = useRef<HTMLDivElement>(null);
30 const viewRef = useRef<EditorView | null>(null); 34 const viewRef = useRef<EditorView | null>(null);
@@ -47,7 +51,7 @@ function MarkdownEditor({
47 history(), 51 history(),
48 keymap.of([...defaultKeymap, ...historyKeymap]), 52 keymap.of([...defaultKeymap, ...historyKeymap]),
49 markdown({ codeLanguages: languages }), 53 markdown({ codeLanguages: languages }),
50 oneDark, 54 theme === "dark" ? oneDark : syntaxHighlighting(defaultHighlightStyle),
51 updateListener, 55 updateListener,
52 EditorView.editable.of(!disabled), 56 EditorView.editable.of(!disabled),
53 EditorView.lineWrapping, 57 EditorView.lineWrapping,
@@ -81,7 +85,7 @@ function MarkdownEditor({
81 view.destroy(); 85 view.destroy();
82 viewRef.current = null; 86 viewRef.current = null;
83 }; 87 };
84 }, [disabled]); 88 }, [disabled, theme]);
85 89
86 // Update content when it changes externally 90 // Update content when it changes externally
87 useEffect(() => { 91 useEffect(() => {
@@ -109,6 +113,7 @@ export function DocumentPane({
109 phase, 113 phase,
110 disabled, 114 disabled,
111 showOnboarding, 115 showOnboarding,
116 theme,
112}: DocumentPaneProps) { 117}: DocumentPaneProps) {
113 const [isEditing, setIsEditing] = useState(false); 118 const [isEditing, setIsEditing] = useState(false);
114 119
@@ -211,6 +216,7 @@ export function DocumentPane({
211 content={content} 216 content={content}
212 onChange={onChange} 217 onChange={onChange}
213 disabled={disabled} 218 disabled={disabled}
219 theme={theme}
214 /> 220 />
215 ) : ( 221 ) : (
216 <div 222 <div
diff --git a/renderer/src/components/Header.tsx b/renderer/src/components/Header.tsx
index b6bed26..b4faa6e 100644
--- a/renderer/src/components/Header.tsx
+++ b/renderer/src/components/Header.tsx
@@ -1,6 +1,8 @@
1import React from "react"; 1import React from "react";
2import type { Project, Session, Phase } from "../types"; 2import type { Project, Session, Phase } from "../types";
3 3
4type Theme = "dark" | "light";
5
4interface HeaderProps { 6interface HeaderProps {
5 projects: Project[]; 7 projects: Project[];
6 sessions: Session[]; 8 sessions: Session[];
@@ -12,6 +14,8 @@ interface HeaderProps {
12 onCreateSession: () => void; 14 onCreateSession: () => void;
13 onDeleteProject?: (id: string) => void; 15 onDeleteProject?: (id: string) => void;
14 onDeleteSession?: (id: string) => void; 16 onDeleteSession?: (id: string) => void;
17 theme: Theme;
18 onToggleTheme: () => void;
15} 19}
16 20
17const phaseLabels: Record<Phase, string> = { 21const phaseLabels: Record<Phase, string> = {
@@ -33,6 +37,8 @@ export function Header({
33 onCreateSession, 37 onCreateSession,
34 onDeleteProject, 38 onDeleteProject,
35 onDeleteSession, 39 onDeleteSession,
40 theme,
41 onToggleTheme,
36}: HeaderProps) { 42}: HeaderProps) {
37 const handleDeleteProject = () => { 43 const handleDeleteProject = () => {
38 if (!selectedProject || !onDeleteProject) return; 44 if (!selectedProject || !onDeleteProject) return;
@@ -51,6 +57,9 @@ export function Header({
51 return ( 57 return (
52 <header className="header"> 58 <header className="header">
53 <div className="header-left"> 59 <div className="header-left">
60 {/* ── Wordmark ── */}
61 <span className="app-wordmark">Claude Flow</span>
62
54 <select 63 <select
55 value={selectedProject?.id || ""} 64 value={selectedProject?.id || ""}
56 onChange={(e) => { 65 onChange={(e) => {
@@ -129,6 +138,11 @@ export function Header({
129 })} 138 })}
130 </div> 139 </div>
131 )} 140 )}
141
142 {/* ── Theme toggle ── */}
143 <button className="theme-toggle" onClick={onToggleTheme}>
144 {theme === "dark" ? "[light]" : "[dark]"}
145 </button>
132 </div> 146 </div>
133 </header> 147 </header>
134 ); 148 );
diff --git a/renderer/src/styles/globals.css b/renderer/src/styles/globals.css
index 7ad199e..97b7bb8 100644
--- a/renderer/src/styles/globals.css
+++ b/renderer/src/styles/globals.css
@@ -4,25 +4,44 @@
4 padding: 0; 4 padding: 0;
5} 5}
6 6
7/* ── Dark theme (default) ────────────────────────────────────── */
7:root { 8:root {
8 --bg-primary: #1a1a1a; 9 --bg-primary: #1a1a1a;
9 --bg-secondary: #252525; 10 --bg-secondary: #252525;
10 --bg-tertiary: #333; 11 --bg-tertiary: #333;
11 --border: #444; 12 --border: #444;
12 --text-primary: #e0e0e0; 13 --text-primary: #e0e0e0;
13 --text-secondary: #888; 14 --text-secondary: #888;
14 --accent: #3b82f6; 15 --accent: #60a5fa;
15 --accent-hover: #2563eb; 16 --accent-hover: #93c5fd;
16 --success: #10b981; 17 --success: #10b981;
17 --warning: #f59e0b; 18 --warning: #f59e0b;
18 --danger: #ef4444; 19 --danger: #ef4444;
19} 20}
20 21
22/* ── Light theme overrides ───────────────────────────────────── */
23html[data-theme="light"] {
24 --bg-primary: #f4f4f2;
25 --bg-secondary: #e8e8e5;
26 --bg-tertiary: #d8d8d4;
27 --border: #b4b4b0;
28 --text-primary: #1a1a18;
29 --text-secondary: #5a5a56;
30 --accent: #2563eb;
31 --accent-hover: #1d4ed8;
32 --success: #059669;
33 --warning: #d97706;
34 --danger: #dc2626;
35}
36
37/* ── Base ────────────────────────────────────────────────────── */
21body { 38body {
22 font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; 39 font-family: "SF Mono", "Cascadia Code", "JetBrains Mono", "Fira Code",
40 Monaco, "Courier New", monospace;
23 background: var(--bg-primary); 41 background: var(--bg-primary);
24 color: var(--text-primary); 42 color: var(--text-primary);
25 overflow: hidden; 43 overflow: hidden;
44 font-size: 13px;
26} 45}
27 46
28.app { 47.app {
@@ -31,12 +50,12 @@ body {
31 height: 100vh; 50 height: 100vh;
32} 51}
33 52
34/* Header */ 53/* ── Header ──────────────────────────────────────────────────── */
35.header { 54.header {
36 display: flex; 55 display: flex;
37 justify-content: space-between; 56 justify-content: space-between;
38 align-items: center; 57 align-items: center;
39 padding: 12px 16px; 58 padding: 10px 16px;
40 background: var(--bg-secondary); 59 background: var(--bg-secondary);
41 border-bottom: 1px solid var(--border); 60 border-bottom: 1px solid var(--border);
42 -webkit-app-region: drag; 61 -webkit-app-region: drag;
@@ -50,15 +69,30 @@ body {
50 -webkit-app-region: no-drag; 69 -webkit-app-region: no-drag;
51} 70}
52 71
72/* App wordmark */
73.app-wordmark {
74 font-size: 12px;
75 font-weight: 700;
76 letter-spacing: 0.15em;
77 text-transform: uppercase;
78 color: var(--text-primary);
79 padding-right: 12px;
80 border-right: 1px solid var(--border);
81 margin-right: 4px;
82 user-select: none;
83 white-space: nowrap;
84}
85
53.header select, 86.header select,
54.header button { 87.header button {
55 padding: 6px 12px; 88 padding: 5px 10px;
56 background: var(--bg-tertiary); 89 background: var(--bg-tertiary);
57 border: 1px solid var(--border); 90 border: 1px solid var(--border);
58 border-radius: 4px; 91 border-radius: 2px;
59 color: var(--text-primary); 92 color: var(--text-primary);
60 cursor: pointer; 93 cursor: pointer;
61 font-size: 13px; 94 font-size: 12px;
95 font-family: inherit;
62} 96}
63 97
64.header button:hover { 98.header button:hover {
@@ -68,8 +102,8 @@ body {
68.header button.btn-delete { 102.header button.btn-delete {
69 background: transparent; 103 background: transparent;
70 border: 1px solid var(--border); 104 border: 1px solid var(--border);
71 padding: 6px 8px; 105 padding: 5px 8px;
72 font-size: 14px; 106 font-size: 13px;
73} 107}
74 108
75.header button.btn-delete:hover { 109.header button.btn-delete:hover {
@@ -77,15 +111,32 @@ body {
77 border-color: var(--danger); 111 border-color: var(--danger);
78} 112}
79 113
114/* Theme toggle */
115.theme-toggle {
116 font-size: 11px;
117 letter-spacing: 0.08em;
118 text-transform: lowercase;
119 opacity: 0.7;
120 transition: opacity 0.15s;
121}
122
123.theme-toggle:hover {
124 opacity: 1;
125 background: var(--bg-tertiary) !important;
126}
127
128/* Phase indicator */
80.phase-indicator { 129.phase-indicator {
81 display: flex; 130 display: flex;
82 gap: 4px; 131 gap: 4px;
83} 132}
84 133
85.phase-step { 134.phase-step {
86 padding: 4px 12px; 135 padding: 3px 10px;
87 font-size: 12px; 136 font-size: 11px;
88 border-radius: 4px; 137 letter-spacing: 0.07em;
138 text-transform: uppercase;
139 border-radius: 2px;
89 background: var(--bg-tertiary); 140 background: var(--bg-tertiary);
90 color: var(--text-secondary); 141 color: var(--text-secondary);
91} 142}
@@ -100,14 +151,14 @@ body {
100 color: white; 151 color: white;
101} 152}
102 153
103/* Main Content */ 154/* ── Main Content ─────────────────────────────────────────────── */
104.main-content { 155.main-content {
105 flex: 1; 156 flex: 1;
106 display: flex; 157 display: flex;
107 overflow: hidden; 158 overflow: hidden;
108} 159}
109 160
110/* Document Pane */ 161/* ── Document Pane ───────────────────────────────────────────── */
111.document-pane { 162.document-pane {
112 flex: 1; 163 flex: 1;
113 display: flex; 164 display: flex;
@@ -121,21 +172,29 @@ body {
121 display: flex; 172 display: flex;
122 justify-content: space-between; 173 justify-content: space-between;
123 align-items: center; 174 align-items: center;
124 padding: 8px 16px; 175 padding: 7px 16px;
125 background: var(--bg-secondary); 176 background: var(--bg-secondary);
126 border-bottom: 1px solid var(--border); 177 border-bottom: 1px solid var(--border);
127 font-size: 14px; 178 font-size: 11px;
179 letter-spacing: 0.07em;
180 text-transform: uppercase;
128 color: var(--text-secondary); 181 color: var(--text-secondary);
129} 182}
130 183
131.document-header button { 184.document-header button {
132 padding: 4px 8px; 185 padding: 3px 8px;
133 background: var(--bg-tertiary); 186 background: var(--bg-tertiary);
134 border: 1px solid var(--border); 187 border: 1px solid var(--border);
135 border-radius: 4px; 188 border-radius: 2px;
136 color: var(--text-primary); 189 color: var(--text-primary);
137 cursor: pointer; 190 cursor: pointer;
138 font-size: 12px; 191 font-size: 11px;
192 font-family: inherit;
193 letter-spacing: 0.05em;
194}
195
196.document-header button:hover {
197 background: var(--border);
139} 198}
140 199
141.document-content { 200.document-content {
@@ -145,8 +204,8 @@ body {
145} 204}
146 205
147.document-content.editing { 206.document-content.editing {
148 font-family: "SF Mono", Monaco, "Cascadia Code", monospace; 207 font-family: inherit;
149 font-size: 14px; 208 font-size: 13px;
150 line-height: 1.6; 209 line-height: 1.6;
151 background: var(--bg-primary); 210 background: var(--bg-primary);
152 border: none; 211 border: none;
@@ -179,16 +238,19 @@ body {
179} 238}
180 239
181.document-content.rendered h1 { 240.document-content.rendered h1 {
182 font-size: 28px; 241 font-size: 22px;
183 margin: 24px 0 16px; 242 margin: 24px 0 16px;
243 letter-spacing: -0.01em;
184} 244}
185.document-content.rendered h2 { 245.document-content.rendered h2 {
186 font-size: 22px; 246 font-size: 17px;
187 margin: 20px 0 12px; 247 margin: 20px 0 12px;
188 color: var(--text-secondary); 248 color: var(--text-secondary);
249 text-transform: uppercase;
250 letter-spacing: 0.05em;
189} 251}
190.document-content.rendered h3 { 252.document-content.rendered h3 {
191 font-size: 18px; 253 font-size: 14px;
192 margin: 16px 0 8px; 254 margin: 16px 0 8px;
193} 255}
194.document-content.rendered p { 256.document-content.rendered p {
@@ -198,14 +260,14 @@ body {
198.document-content.rendered code { 260.document-content.rendered code {
199 background: var(--bg-tertiary); 261 background: var(--bg-tertiary);
200 padding: 2px 6px; 262 padding: 2px 6px;
201 border-radius: 4px; 263 border-radius: 2px;
202 font-size: 13px; 264 font-size: 12px;
203 font-family: "SF Mono", Monaco, "Cascadia Code", monospace; 265 font-family: inherit;
204} 266}
205.document-content.rendered pre { 267.document-content.rendered pre {
206 background: var(--bg-tertiary); 268 background: var(--bg-tertiary);
207 padding: 16px; 269 padding: 16px;
208 border-radius: 8px; 270 border-radius: 2px;
209 overflow-x: auto; 271 overflow-x: auto;
210 margin: 16px 0; 272 margin: 16px 0;
211} 273}
@@ -238,17 +300,20 @@ body {
238 width: 100%; 300 width: 100%;
239 border-collapse: collapse; 301 border-collapse: collapse;
240 margin: 16px 0; 302 margin: 16px 0;
241 font-size: 14px; 303 font-size: 12px;
242} 304}
243.document-content.rendered th, 305.document-content.rendered th,
244.document-content.rendered td { 306.document-content.rendered td {
245 padding: 10px 12px; 307 padding: 8px 12px;
246 text-align: left; 308 text-align: left;
247 border: 1px solid var(--border); 309 border: 1px solid var(--border);
248} 310}
249.document-content.rendered th { 311.document-content.rendered th {
250 background: var(--bg-tertiary); 312 background: var(--bg-tertiary);
251 font-weight: 600; 313 font-weight: 600;
314 text-transform: uppercase;
315 letter-spacing: 0.06em;
316 font-size: 11px;
252} 317}
253.document-content.rendered tr:nth-child(even) td { 318.document-content.rendered tr:nth-child(even) td {
254 background: var(--bg-secondary); 319 background: var(--bg-secondary);
@@ -280,11 +345,13 @@ body {
280 background: var(--accent); 345 background: var(--accent);
281 color: white; 346 color: white;
282 padding: 2px 8px; 347 padding: 2px 8px;
283 border-radius: 4px; 348 border-radius: 2px;
284 font-size: 11px; 349 font-size: 10px;
350 letter-spacing: 0.08em;
351 text-transform: uppercase;
285} 352}
286 353
287/* Chat Pane */ 354/* ── Chat Pane ───────────────────────────────────────────────── */
288.chat-pane { 355.chat-pane {
289 width: 380px; 356 width: 380px;
290 display: flex; 357 display: flex;
@@ -299,11 +366,11 @@ body {
299} 366}
300 367
301.message { 368.message {
302 margin-bottom: 12px; 369 margin-bottom: 10px;
303 padding: 10px 14px; 370 padding: 9px 13px;
304 border-radius: 8px; 371 border-radius: 4px;
305 max-width: 90%; 372 max-width: 90%;
306 font-size: 14px; 373 font-size: 13px;
307 line-height: 1.5; 374 line-height: 1.5;
308 white-space: pre-wrap; 375 white-space: pre-wrap;
309} 376}
@@ -311,6 +378,7 @@ body {
311.message.user { 378.message.user {
312 background: var(--accent); 379 background: var(--accent);
313 margin-left: auto; 380 margin-left: auto;
381 color: white;
314} 382}
315 383
316.message.assistant { 384.message.assistant {
@@ -331,27 +399,38 @@ body {
331 399
332.chat-input input { 400.chat-input input {
333 flex: 1; 401 flex: 1;
334 padding: 10px 14px; 402 padding: 9px 13px;
335 background: var(--bg-tertiary); 403 background: var(--bg-tertiary);
336 border: 1px solid var(--border); 404 border: 1px solid var(--border);
337 border-radius: 8px; 405 border-radius: 2px;
338 color: var(--text-primary); 406 color: var(--text-primary);
339 font-size: 14px; 407 font-size: 13px;
408 font-family: inherit;
409 transition: border-color 0.15s, box-shadow 0.15s;
340} 410}
341 411
342.chat-input input:focus { 412.chat-input input:focus {
343 outline: none; 413 outline: none;
344 border-color: var(--accent); 414 border-color: var(--accent);
415 box-shadow: 0 0 0 2px rgba(96, 165, 250, 0.2);
416}
417
418html[data-theme="light"] .chat-input input:focus {
419 box-shadow: 0 0 0 2px rgba(37, 99, 235, 0.2);
345} 420}
346 421
347.chat-input button { 422.chat-input button {
348 padding: 10px 16px; 423 padding: 9px 15px;
349 background: var(--accent); 424 background: var(--accent);
350 border: none; 425 border: none;
351 border-radius: 8px; 426 border-radius: 2px;
352 color: white; 427 color: white;
353 cursor: pointer; 428 cursor: pointer;
354 font-size: 14px; 429 font-size: 12px;
430 font-family: inherit;
431 letter-spacing: 0.05em;
432 text-transform: uppercase;
433 transition: background 0.15s;
355} 434}
356 435
357.chat-input button:hover:not(:disabled) { 436.chat-input button:hover:not(:disabled) {
@@ -359,16 +438,16 @@ body {
359} 438}
360 439
361.chat-input button:disabled { 440.chat-input button:disabled {
362 opacity: 0.5; 441 opacity: 0.4;
363 cursor: not-allowed; 442 cursor: not-allowed;
364} 443}
365 444
366/* Action Bar */ 445/* ── Action Bar ──────────────────────────────────────────────── */
367.action-bar { 446.action-bar {
368 display: flex; 447 display: flex;
369 justify-content: space-between; 448 justify-content: space-between;
370 align-items: center; 449 align-items: center;
371 padding: 12px 16px; 450 padding: 10px 16px;
372 background: var(--bg-secondary); 451 background: var(--bg-secondary);
373 border-top: 1px solid var(--border); 452 border-top: 1px solid var(--border);
374} 453}
@@ -388,9 +467,9 @@ body {
388 467
389.token-bar { 468.token-bar {
390 width: 100px; 469 width: 100px;
391 height: 6px; 470 height: 4px;
392 background: var(--bg-tertiary); 471 background: var(--bg-tertiary);
393 border-radius: 3px; 472 border-radius: 1px;
394 overflow: hidden; 473 overflow: hidden;
395} 474}
396 475
@@ -400,7 +479,9 @@ body {
400} 479}
401 480
402.token-label { 481.token-label {
403 font-size: 12px; 482 font-size: 10px;
483 letter-spacing: 0.08em;
484 text-transform: uppercase;
404 color: var(--text-secondary); 485 color: var(--text-secondary);
405} 486}
406 487
@@ -408,7 +489,9 @@ body {
408 display: flex; 489 display: flex;
409 align-items: center; 490 align-items: center;
410 gap: 6px; 491 gap: 6px;
411 font-size: 13px; 492 font-size: 11px;
493 letter-spacing: 0.05em;
494 text-transform: uppercase;
412 color: var(--text-secondary); 495 color: var(--text-secondary);
413 cursor: pointer; 496 cursor: pointer;
414} 497}
@@ -418,13 +501,16 @@ body {
418} 501}
419 502
420.btn-secondary { 503.btn-secondary {
421 padding: 8px 16px; 504 padding: 6px 14px;
422 background: var(--bg-tertiary); 505 background: var(--bg-tertiary);
423 border: 1px solid var(--border); 506 border: 1px solid var(--border);
424 border-radius: 6px; 507 border-radius: 2px;
425 color: var(--text-primary); 508 color: var(--text-primary);
426 cursor: pointer; 509 cursor: pointer;
427 font-size: 14px; 510 font-size: 12px;
511 font-family: inherit;
512 letter-spacing: 0.05em;
513 transition: background 0.15s;
428} 514}
429 515
430.btn-secondary:hover:not(:disabled) { 516.btn-secondary:hover:not(:disabled) {
@@ -432,19 +518,23 @@ body {
432} 518}
433 519
434.btn-secondary:disabled { 520.btn-secondary:disabled {
435 opacity: 0.5; 521 opacity: 0.4;
436 cursor: not-allowed; 522 cursor: not-allowed;
437} 523}
438 524
439.btn-primary { 525.btn-primary {
440 padding: 8px 20px; 526 padding: 6px 18px;
441 background: var(--accent); 527 background: var(--accent);
442 border: none; 528 border: none;
443 border-radius: 6px; 529 border-radius: 2px;
444 color: white; 530 color: white;
445 cursor: pointer; 531 cursor: pointer;
446 font-weight: 500; 532 font-weight: 600;
447 font-size: 14px; 533 font-size: 12px;
534 font-family: inherit;
535 letter-spacing: 0.07em;
536 text-transform: uppercase;
537 transition: background 0.15s;
448} 538}
449 539
450.btn-primary:hover:not(:disabled) { 540.btn-primary:hover:not(:disabled) {
@@ -452,16 +542,18 @@ body {
452} 542}
453 543
454.btn-primary:disabled { 544.btn-primary:disabled {
455 opacity: 0.5; 545 opacity: 0.4;
456 cursor: not-allowed; 546 cursor: not-allowed;
457} 547}
458 548
459.implementing-status { 549.implementing-status {
460 color: var(--success); 550 color: var(--success);
461 font-size: 14px; 551 font-size: 11px;
552 letter-spacing: 0.1em;
553 text-transform: uppercase;
462} 554}
463 555
464/* Error Bar */ 556/* ── Error Bar ───────────────────────────────────────────────── */
465.error-bar { 557.error-bar {
466 display: flex; 558 display: flex;
467 justify-content: space-between; 559 justify-content: space-between;
@@ -469,14 +561,15 @@ body {
469 padding: 8px 16px; 561 padding: 8px 16px;
470 background: var(--danger); 562 background: var(--danger);
471 color: white; 563 color: white;
472 font-size: 14px; 564 font-size: 12px;
565 letter-spacing: 0.03em;
473} 566}
474 567
475.error-bar button { 568.error-bar button {
476 background: none; 569 background: none;
477 border: none; 570 border: none;
478 color: white; 571 color: white;
479 font-size: 18px; 572 font-size: 16px;
480 cursor: pointer; 573 cursor: pointer;
481 padding: 0 4px; 574 padding: 0 4px;
482} 575}
@@ -485,17 +578,21 @@ body {
485 opacity: 0.8; 578 opacity: 0.8;
486} 579}
487 580
488/* Onboarding */ 581/* ── Onboarding ──────────────────────────────────────────────── */
489.onboarding h1 { 582.onboarding h1 {
490 font-size: 32px; 583 font-size: 24px;
491 margin-bottom: 8px; 584 margin-bottom: 8px;
585 letter-spacing: 0.05em;
586 text-transform: uppercase;
492} 587}
493 588
494.onboarding h2 { 589.onboarding h2 {
495 font-size: 20px; 590 font-size: 13px;
496 margin-top: 28px; 591 margin-top: 28px;
497 margin-bottom: 12px; 592 margin-bottom: 12px;
498 color: var(--accent); 593 color: var(--accent);
594 text-transform: uppercase;
595 letter-spacing: 0.1em;
499} 596}
500 597
501.onboarding p { 598.onboarding p {
@@ -515,13 +612,13 @@ body {
515.onboarding pre { 612.onboarding pre {
516 background: var(--bg-tertiary); 613 background: var(--bg-tertiary);
517 padding: 12px 16px; 614 padding: 12px 16px;
518 border-radius: 6px; 615 border-radius: 2px;
519 margin: 12px 0; 616 margin: 12px 0;
520} 617}
521 618
522.onboarding code { 619.onboarding code {
523 font-family: "SF Mono", Monaco, monospace; 620 font-family: inherit;
524 font-size: 13px; 621 font-size: 12px;
525} 622}
526 623
527.onboarding a { 624.onboarding a {
@@ -538,5 +635,5 @@ body {
538 padding: 16px; 635 padding: 16px;
539 background: var(--bg-tertiary); 636 background: var(--bg-tertiary);
540 border-left: 3px solid var(--accent); 637 border-left: 3px solid var(--accent);
541 border-radius: 0 6px 6px 0; 638 border-radius: 0 2px 2px 0;
542} 639}