aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorClawd <ai@clawd.bot>2026-02-28 18:46:11 -0800
committerClawd <ai@clawd.bot>2026-02-28 18:46:11 -0800
commit3ac34530578b9a6f59bcea6b5aeefd97eb03d588 (patch)
tree099fe4a4788c5dd5997d1f16f5d2db917eda86d0
parentde242df9cbe7dfe483f59f9b25e980727baa4c11 (diff)
Move artifacts to ~/.claude-flow/ (outside repo)
- Store session artifacts in ~/.claude-flow/projects/{projectId}/sessions/{sessionId}/ - Artifacts no longer live in project directory - can't be accidentally committed - Remove .claude-flow/ from .gitignore (not needed anymore) - Update all IPC handlers and renderer to use projectId instead of projectPath - Update prompts to remove worktree references - Update README with new storage location
-rw-r--r--.claude-flow/plan.md1090
-rw-r--r--.claude-flow/research.md214
-rw-r--r--.gitignore1
-rw-r--r--README.md11
-rw-r--r--renderer/src/App.tsx14
-rw-r--r--src/main/claude/index.ts65
-rw-r--r--src/main/claude/phases.ts55
-rw-r--r--src/main/ipc/handlers.ts34
-rw-r--r--src/main/preload.ts37
9 files changed, 1376 insertions, 145 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/.gitignore b/.gitignore
index 737dd85..4569d3b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,4 @@
1node_modules 1node_modules
2release 2release
3dist 3dist
4.claude-flow/
5*.sync-conflict-* 4*.sync-conflict-*
diff --git a/README.md b/README.md
index ceac0f3..07d3839 100644
--- a/README.md
+++ b/README.md
@@ -14,15 +14,12 @@ At each phase, edit the document to add notes (`// REVIEW:`, `// NOTE:`), click
14 14
15## Sessions 15## Sessions
16 16
17Each session has isolated artifacts: 17Each session has isolated artifacts stored in `~/.claude-flow/projects/{projectId}/sessions/{sessionId}/`:
18 18
19``` 19- `research.md` — Session research
20.claude-flow/sessions/{sessionId}/ 20- `plan.md` — Session plan
21├── research.md # Session research
22└── plan.md # Session plan
23```
24 21
25Concurrent sessions supported — switch between them freely. 22Concurrent sessions supported — switch between them freely. Artifacts live outside your repo so they never get accidentally committed.
26 23
27## Setup 24## Setup
28 25
diff --git a/renderer/src/App.tsx b/renderer/src/App.tsx
index c56f292..f7ba41d 100644
--- a/renderer/src/App.tsx
+++ b/renderer/src/App.tsx
@@ -92,10 +92,10 @@ export function App() {
92 // Load messages 92 // Load messages
93 api.listMessages(selectedSession.id).then(setMessages); 93 api.listMessages(selectedSession.id).then(setMessages);
94 94
95 // Load session-specific artifact 95 // Load session-specific artifact (from ~/.claude-flow/)
96 const filename = 96 const filename =
97 selectedSession.phase === "research" ? "research.md" : "plan.md"; 97 selectedSession.phase === "research" ? "research.md" : "plan.md";
98 api.readSessionArtifact(selectedProject.path, selectedSession.id, filename).then((content) => { 98 api.readSessionArtifact(selectedProject.id, selectedSession.id, filename).then((content) => {
99 const text = content || ""; 99 const text = content || "";
100 setDocumentContent(text); 100 setDocumentContent(text);
101 setOriginalContent(text); 101 setOriginalContent(text);
@@ -125,7 +125,7 @@ export function App() {
125 if (selectedProject && selectedSession) { 125 if (selectedProject && selectedSession) {
126 const filename = 126 const filename =
127 selectedSession.phase === "research" ? "research.md" : "plan.md"; 127 selectedSession.phase === "research" ? "research.md" : "plan.md";
128 api.readSessionArtifact(selectedProject.path, selectedSession.id, filename).then((content) => { 128 api.readSessionArtifact(selectedProject.id, selectedSession.id, filename).then((content) => {
129 const text = content || ""; 129 const text = content || "";
130 setDocumentContent(text); 130 setDocumentContent(text);
131 setOriginalContent(text); 131 setOriginalContent(text);
@@ -193,10 +193,10 @@ export function App() {
193 if (!selectedSession || !selectedProject) return; 193 if (!selectedSession || !selectedProject) return;
194 setError(null); 194 setError(null);
195 try { 195 try {
196 // Save user edits first (session-specific) 196 // Save user edits first (session-specific, stored in ~/.claude-flow/)
197 const filename = 197 const filename =
198 selectedSession.phase === "research" ? "research.md" : "plan.md"; 198 selectedSession.phase === "research" ? "research.md" : "plan.md";
199 await api.writeSessionArtifact(selectedProject.path, selectedSession.id, filename, documentContent); 199 await api.writeSessionArtifact(selectedProject.id, selectedSession.id, filename, documentContent);
200 setOriginalContent(documentContent); 200 setOriginalContent(documentContent);
201 setIsLoading(true); 201 setIsLoading(true);
202 await api.triggerReview(selectedSession.id); 202 await api.triggerReview(selectedSession.id);
@@ -210,10 +210,10 @@ export function App() {
210 if (!selectedSession || !selectedProject) return; 210 if (!selectedSession || !selectedProject) return;
211 setError(null); 211 setError(null);
212 try { 212 try {
213 // Save any pending edits (session-specific) 213 // Save any pending edits (session-specific, stored in ~/.claude-flow/)
214 const filename = 214 const filename =
215 selectedSession.phase === "research" ? "research.md" : "plan.md"; 215 selectedSession.phase === "research" ? "research.md" : "plan.md";
216 await api.writeSessionArtifact(selectedProject.path, selectedSession.id, filename, documentContent); 216 await api.writeSessionArtifact(selectedProject.id, selectedSession.id, filename, documentContent);
217 217
218 const nextPhase = await api.advancePhase(selectedSession.id); 218 const nextPhase = await api.advancePhase(selectedSession.id);
219 if (nextPhase) { 219 if (nextPhase) {
diff --git a/src/main/claude/index.ts b/src/main/claude/index.ts
index 4d8909b..b8c9c07 100644
--- a/src/main/claude/index.ts
+++ b/src/main/claude/index.ts
@@ -6,16 +6,28 @@ import { getProject } from "../db/projects";
6import { updateSession } from "../db/sessions"; 6import { updateSession } from "../db/sessions";
7import fs from "node:fs"; 7import fs from "node:fs";
8import path from "node:path"; 8import path from "node:path";
9import os from "node:os";
9 10
10// Track active queries by session ID 11// Track active queries by session ID
11const activeQueries = new Map<string, Query>(); 12const activeQueries = new Map<string, Query>();
12 13
14// Global storage in home directory
15const GLOBAL_CLAUDE_FLOW_DIR = path.join(os.homedir(), ".claude-flow");
16
13function ensureDir(dirPath: string): void { 17function ensureDir(dirPath: string): void {
14 if (!fs.existsSync(dirPath)) { 18 if (!fs.existsSync(dirPath)) {
15 fs.mkdirSync(dirPath, { recursive: true }); 19 fs.mkdirSync(dirPath, { recursive: true });
16 } 20 }
17} 21}
18 22
23function getProjectDir(projectId: string): string {
24 return path.join(GLOBAL_CLAUDE_FLOW_DIR, "projects", projectId);
25}
26
27function getSessionDir(projectId: string, sessionId: string): string {
28 return path.join(getProjectDir(projectId), "sessions", sessionId);
29}
30
19export interface SendMessageOptions { 31export interface SendMessageOptions {
20 session: Session; 32 session: Session;
21 message: string; 33 message: string;
@@ -30,8 +42,8 @@ export async function sendMessage({
30 const project = getProject(session.project_id); 42 const project = getProject(session.project_id);
31 if (!project) throw new Error("Project not found"); 43 if (!project) throw new Error("Project not found");
32 44
33 // Ensure session artifact directory exists 45 // Ensure session artifact directory exists in global storage
34 const sessionDir = path.join(project.path, getSessionArtifactDir(session.id)); 46 const sessionDir = getSessionDir(session.project_id, session.id);
35 ensureDir(sessionDir); 47 ensureDir(sessionDir);
36 48
37 const phaseConfig = getPhaseConfig( 49 const phaseConfig = getPhaseConfig(
@@ -100,22 +112,22 @@ export function advancePhase(session: Session): Phase | null {
100} 112}
101 113
102/** 114/**
103 * Get the artifact path for a session and phase 115 * Get the artifact path for a session and phase (in global storage)
104 */ 116 */
105export function getArtifactPath(session: Session): string { 117export function getArtifactPath(session: Session): string {
106 const filename = getArtifactFilename(session.phase as Phase); 118 const filename = getArtifactFilename(session.phase as Phase);
107 return path.join(getSessionArtifactDir(session.id), filename); 119 return path.join(getSessionDir(session.project_id, session.id), filename);
108} 120}
109 121
110/** 122/**
111 * Read an artifact file for a session 123 * Read an artifact file for a session (from global storage)
112 */ 124 */
113export function readSessionArtifact( 125export function readSessionArtifact(
114 projectPath: string, 126 projectId: string,
115 sessionId: string, 127 sessionId: string,
116 filename: string 128 filename: string
117): string | null { 129): string | null {
118 const filePath = path.join(projectPath, getSessionArtifactDir(sessionId), filename); 130 const filePath = path.join(getSessionDir(projectId, sessionId), filename);
119 if (fs.existsSync(filePath)) { 131 if (fs.existsSync(filePath)) {
120 return fs.readFileSync(filePath, "utf-8"); 132 return fs.readFileSync(filePath, "utf-8");
121 } 133 }
@@ -123,15 +135,15 @@ export function readSessionArtifact(
123} 135}
124 136
125/** 137/**
126 * Write an artifact file for a session 138 * Write an artifact file for a session (to global storage)
127 */ 139 */
128export function writeSessionArtifact( 140export function writeSessionArtifact(
129 projectPath: string, 141 projectId: string,
130 sessionId: string, 142 sessionId: string,
131 filename: string, 143 filename: string,
132 content: string 144 content: string
133): void { 145): void {
134 const dir = path.join(projectPath, getSessionArtifactDir(sessionId)); 146 const dir = getSessionDir(projectId, sessionId);
135 ensureDir(dir); 147 ensureDir(dir);
136 fs.writeFileSync(path.join(dir, filename), content, "utf-8"); 148 fs.writeFileSync(path.join(dir, filename), content, "utf-8");
137} 149}
@@ -156,37 +168,10 @@ export function writeClaudeMd(projectPath: string, content: string): void {
156} 168}
157 169
158/** 170/**
159 * Read an artifact file from the project's .claude-flow directory (legacy path) 171 * Clear session artifacts from global storage
160 */
161export function readArtifact(
162 projectPath: string,
163 filename: string
164): string | null {
165 const filePath = path.join(projectPath, ".claude-flow", filename);
166 if (fs.existsSync(filePath)) {
167 return fs.readFileSync(filePath, "utf-8");
168 }
169 return null;
170}
171
172/**
173 * Write an artifact file to the project's .claude-flow directory (legacy path)
174 */
175export function writeArtifact(
176 projectPath: string,
177 filename: string,
178 content: string
179): void {
180 const dir = path.join(projectPath, ".claude-flow");
181 ensureDir(dir);
182 fs.writeFileSync(path.join(dir, filename), content, "utf-8");
183}
184
185/**
186 * Clear session artifacts
187 */ 172 */
188export function clearSessionArtifacts(projectPath: string, sessionId: string): void { 173export function clearSessionArtifacts(projectId: string, sessionId: string): void {
189 const dir = path.join(projectPath, getSessionArtifactDir(sessionId)); 174 const dir = getSessionDir(projectId, sessionId);
190 if (fs.existsSync(dir)) { 175 if (fs.existsSync(dir)) {
191 fs.rmSync(dir, { recursive: true, force: true }); 176 fs.rmSync(dir, { recursive: true, force: true });
192 } 177 }
diff --git a/src/main/claude/phases.ts b/src/main/claude/phases.ts
index 6678c08..f1df719 100644
--- a/src/main/claude/phases.ts
+++ b/src/main/claude/phases.ts
@@ -10,9 +10,9 @@ export interface PhaseConfig {
10 initialMessage: string; 10 initialMessage: string;
11} 11}
12 12
13// Get session-specific artifact path 13// Get session-specific artifact path (relative to ~/.claude-flow/)
14export function getSessionArtifactDir(sessionId: string): string { 14export function getSessionArtifactDir(sessionId: string): string {
15 return `.claude-flow/sessions/${sessionId}`; 15 return `sessions/${sessionId}`;
16} 16}
17 17
18export function getArtifactPath(phase: Phase, sessionId: string): string { 18export function getArtifactPath(phase: Phase, sessionId: string): string {
@@ -30,7 +30,7 @@ export const phaseConfigs: Record<Phase, PhaseConfig> = {
30 systemPrompt: `You are in RESEARCH mode. Your ONLY job is to understand the codebase. 30 systemPrompt: `You are in RESEARCH mode. Your ONLY job is to understand the codebase.
31 31
32CRITICAL RULES: 32CRITICAL RULES:
331. You MUST write ALL findings to \`.claude-flow/sessions/{sessionId}/research.md\` — this is your PRIMARY output 331. You MUST write ALL findings to the session research.md — this is your PRIMARY output
342. DO NOT just respond in chat. The document viewer shows research.md, so write there. 342. DO NOT just respond in chat. The document viewer shows research.md, so write there.
353. DO NOT suggest moving to planning or implementation 353. DO NOT suggest moving to planning or implementation
364. DO NOT ask "are you ready to implement?" or similar 364. DO NOT ask "are you ready to implement?" or similar
@@ -38,16 +38,15 @@ CRITICAL RULES:
386. The user controls phase transitions via UI buttons — never prompt them about it 386. The user controls phase transitions via UI buttons — never prompt them about it
39 39
40CONTEXT: 40CONTEXT:
41- You are in a git worktree at \`.claude-flow/worktrees/{sessionId}/\` 41- Read CLAUDE.md in the project root (if it exists) for codebase overview
42- Read CLAUDE.md in the project root for shared codebase overview
43- If CLAUDE.md doesn't exist, create it with your initial findings
44- This file contains general architecture info shared across all sessions 42- This file contains general architecture info shared across all sessions
43- If CLAUDE.md doesn't exist, create it with your initial findings
45 44
46WORKFLOW: 45WORKFLOW:
471. Read CLAUDE.md (create at project root if missing) 461. Read CLAUDE.md (create at project root if missing)
482. Ask what to research (if unclear) 472. Ask what to research (if unclear)
493. Read files thoroughly using Read, Glob, Grep 483. Read files thoroughly using Read, Glob, Grep
504. Write findings to \`.claude-flow/sessions/{sessionId}/research.md\` 494. Write findings to session research.md
515. Update CLAUDE.md with any new general insights worth sharing 505. Update CLAUDE.md with any new general insights worth sharing
52 51
53FORMAT for research.md: 52FORMAT for research.md:
@@ -70,7 +69,7 @@ FORMAT for research.md:
70[Things that need clarification] 69[Things that need clarification]
71\`\`\` 70\`\`\`
72 71
73Remember: Your output goes in \`.claude-flow/sessions/{sessionId}/research.md\`, not chat. Chat is for clarifying questions only.`, 72Remember: Your output goes in research.md, not chat. Chat is for clarifying questions only.`,
74 }, 73 },
75 74
76 plan: { 75 plan: {
@@ -81,7 +80,7 @@ Remember: Your output goes in \`.claude-flow/sessions/{sessionId}/research.md\`,
81 systemPrompt: `You are in PLANNING mode. Your ONLY job is to create an implementation plan. 80 systemPrompt: `You are in PLANNING mode. Your ONLY job is to create an implementation plan.
82 81
83CRITICAL RULES: 82CRITICAL RULES:
841. You MUST write the plan to \`.claude-flow/sessions/{sessionId}/plan.md\` — this is your PRIMARY output 831. You MUST write the plan to session plan.md — this is your PRIMARY output
852. DO NOT just respond in chat. The document viewer shows plan.md, so write there. 842. DO NOT just respond in chat. The document viewer shows plan.md, so write there.
863. DO NOT implement anything — no code changes to source files 853. DO NOT implement anything — no code changes to source files
874. DO NOT ask "should I start implementing?" or similar 864. DO NOT ask "should I start implementing?" or similar
@@ -89,14 +88,13 @@ CRITICAL RULES:
896. Base your plan on the session research.md and CLAUDE.md 886. Base your plan on the session research.md and CLAUDE.md
90 89
91CONTEXT: 90CONTEXT:
92- You are in a git worktree at \`.claude-flow/worktrees/{sessionId}/\`
93- Read CLAUDE.md at project root for codebase overview 91- Read CLAUDE.md at project root for codebase overview
94- Read \`.claude-flow/sessions/{sessionId}/research.md\` for this specific task 92- Read the session research.md to understand the specific task
95 93
96WORKFLOW: 94WORKFLOW:
971. Read CLAUDE.md for codebase overview 951. Read CLAUDE.md for codebase overview
982. Read the session research.md to understand the specific task 962. Read the session research.md to understand the specific task
993. Write a detailed plan to \`.claude-flow/sessions/{sessionId}/plan.md\` 973. Write a detailed plan to session plan.md
1004. Include specific code snippets showing proposed changes 984. Include specific code snippets showing proposed changes
1015. Make the plan detailed enough that implementation is mechanical 995. Make the plan detailed enough that implementation is mechanical
102 100
@@ -134,47 +132,30 @@ FORMAT for plan.md:
134 132
135When the user adds annotations to plan.md and clicks Review, address each annotation and update the document. 133When the user adds annotations to plan.md and clicks Review, address each annotation and update the document.
136 134
137Remember: Your output goes in \`.claude-flow/sessions/{sessionId}/plan.md\`, not chat. Chat is for clarifying questions only.`, 135Remember: Your output goes in plan.md, not chat. Chat is for clarifying questions only.`,
138 }, 136 },
139 137
140 implement: { 138 implement: {
141 permissionMode: "acceptEdits", 139 permissionMode: "acceptEdits",
142 tools: ["Read", "Write", "Edit", "Bash", "Glob", "Grep"], 140 tools: ["Read", "Write", "Edit", "Bash", "Glob", "Grep"],
143 initialMessage: 141 initialMessage:
144 "Starting implementation. I'll follow the plan exactly, commit as I go, and mark tasks complete.", 142 "Starting implementation. I'll follow the plan exactly and mark tasks complete as I go.",
145 systemPrompt: `You are in IMPLEMENTATION mode. Execute the approved plan. 143 systemPrompt: `You are in IMPLEMENTATION mode. Execute the approved plan.
146 144
147CRITICAL RULES: 145CRITICAL RULES:
1481. Read \`.claude-flow/sessions/{sessionId}/plan.md\` and follow it exactly 1461. Read session plan.md and follow it exactly
1492. Mark tasks complete in plan.md as you finish them: - [ ] → - [x] 1472. Mark tasks complete in plan.md as you finish them: - [ ] → - [x]
1503. DO NOT deviate from the plan without asking 1483. DO NOT deviate from the plan without asking
1514. Run tests/typecheck if available 1494. Run tests/typecheck if available
1525. Make git commits as you complete logical chunks of work 1505. Stop and ask if you encounter issues not covered by the plan
1536. Stop and ask if you encounter issues not covered by the plan
154
155CONTEXT:
156- You are in a git worktree at \`.claude-flow/worktrees/{sessionId}/\`
157- This is an isolated branch: \`claude-flow/{sessionId}\`
158- Your commits will not affect the main branch until merged
159- The user can review your work in this worktree before accepting
160 151
161WORKFLOW: 152WORKFLOW:
1621. Read \`.claude-flow/sessions/{sessionId}/plan.md\` 1531. Read session plan.md
1632. Execute each task in order 1542. Execute each task in order
1643. Update plan.md to mark tasks complete 1553. Update plan.md to mark tasks complete
1654. Make git commits with clear messages as you finish chunks 1564. Continue until all tasks are done
1665. Continue until all tasks are done 157
167 158When complete, summarize what was done and any follow-up tasks.`,
168COMMIT GUIDELINES:
169- Commit after completing logical units of work
170- Use clear commit messages (e.g., "Add user authentication middleware")
171- Don't commit broken or incomplete code
172- Update CLAUDE.md at project root if you discover important architecture info
173
174When complete, summarize what was done and tell the user how to review:
175- The work is in worktree: \`.claude-flow/worktrees/{sessionId}/\`
176- Branch: \`claude-flow/{sessionId}\`
177- They can review, then merge or discard as needed`,
178 }, 159 },
179}; 160};
180 161
diff --git a/src/main/ipc/handlers.ts b/src/main/ipc/handlers.ts
index 2d5e3d3..d9beaf0 100644
--- a/src/main/ipc/handlers.ts
+++ b/src/main/ipc/handlers.ts
@@ -26,11 +26,8 @@ export function registerIpcHandlers(mainWindow: BrowserWindow): void {
26 ipcMain.handle("sessions:delete", (_, id: string) => { 26 ipcMain.handle("sessions:delete", (_, id: string) => {
27 const session = sessions.getSession(id); 27 const session = sessions.getSession(id);
28 if (session) { 28 if (session) {
29 const project = projects.getProject(session.project_id); 29 // Clean up session artifacts from global storage
30 if (project) { 30 claude.clearSessionArtifacts(session.project_id, id);
31 // Clean up session artifacts
32 claude.clearSessionArtifacts(project.path, id);
33 }
34 } 31 }
35 sessions.deleteSession(id); 32 sessions.deleteSession(id);
36 }); 33 });
@@ -103,22 +100,22 @@ export function registerIpcHandlers(mainWindow: BrowserWindow): void {
103 } 100 }
104 ); 101 );
105 102
106 // Session Artifacts (new session-specific API) 103 // Session Artifacts (stored in ~/.claude-flow/)
107 ipcMain.handle( 104 ipcMain.handle(
108 "artifact:readSession", 105 "artifact:readSession",
109 (_, projectPath: string, sessionId: string, filename: string) => { 106 (_, projectId: string, sessionId: string, filename: string) => {
110 return claude.readSessionArtifact(projectPath, sessionId, filename); 107 return claude.readSessionArtifact(projectId, sessionId, filename);
111 } 108 }
112 ); 109 );
113 110
114 ipcMain.handle( 111 ipcMain.handle(
115 "artifact:writeSession", 112 "artifact:writeSession",
116 (_, projectPath: string, sessionId: string, filename: string, content: string) => { 113 (_, projectId: string, sessionId: string, filename: string, content: string) => {
117 claude.writeSessionArtifact(projectPath, sessionId, filename, content); 114 claude.writeSessionArtifact(projectId, sessionId, filename, content);
118 } 115 }
119 ); 116 );
120 117
121 // CLAUDE.md 118 // CLAUDE.md (stored in project)
122 ipcMain.handle("claudemd:read", (_, projectPath: string) => { 119 ipcMain.handle("claudemd:read", (_, projectPath: string) => {
123 return claude.readClaudeMd(projectPath); 120 return claude.readClaudeMd(projectPath);
124 }); 121 });
@@ -127,21 +124,6 @@ export function registerIpcHandlers(mainWindow: BrowserWindow): void {
127 claude.writeClaudeMd(projectPath, content); 124 claude.writeClaudeMd(projectPath, content);
128 }); 125 });
129 126
130 // Legacy artifact API (for backward compatibility)
131 ipcMain.handle(
132 "artifact:read",
133 (_, projectPath: string, filename: string) => {
134 return claude.readArtifact(projectPath, filename);
135 }
136 );
137
138 ipcMain.handle(
139 "artifact:write",
140 (_, projectPath: string, filename: string, content: string) => {
141 claude.writeArtifact(projectPath, filename, content);
142 }
143 );
144
145 // Dialogs 127 // Dialogs
146 ipcMain.handle("dialog:selectDirectory", async () => { 128 ipcMain.handle("dialog:selectDirectory", async () => {
147 const result = await dialog.showOpenDialog(mainWindow, { 129 const result = await dialog.showOpenDialog(mainWindow, {
diff --git a/src/main/preload.ts b/src/main/preload.ts
index 7c1d634..2c228dd 100644
--- a/src/main/preload.ts
+++ b/src/main/preload.ts
@@ -31,34 +31,23 @@ export interface ClaudeFlowAPI {
31 mode: UserPermissionMode 31 mode: UserPermissionMode
32 ) => Promise<void>; 32 ) => Promise<void>;
33 33
34 // Session Artifacts (session-specific) 34 // Session Artifacts (stored in ~/.claude-flow/)
35 readSessionArtifact: ( 35 readSessionArtifact: (
36 projectPath: string, 36 projectId: string,
37 sessionId: string, 37 sessionId: string,
38 filename: string 38 filename: string
39 ) => Promise<string | null>; 39 ) => Promise<string | null>;
40 writeSessionArtifact: ( 40 writeSessionArtifact: (
41 projectPath: string, 41 projectId: string,
42 sessionId: string, 42 sessionId: string,
43 filename: string, 43 filename: string,
44 content: string 44 content: string
45 ) => Promise<void>; 45 ) => Promise<void>;
46 46
47 // CLAUDE.md 47 // CLAUDE.md (stored in project)
48 readClaudeMd: (projectPath: string) => Promise<string | null>; 48 readClaudeMd: (projectPath: string) => Promise<string | null>;
49 writeClaudeMd: (projectPath: string, content: string) => Promise<void>; 49 writeClaudeMd: (projectPath: string, content: string) => Promise<void>;
50 50
51 // Legacy Artifacts (backward compat)
52 readArtifact: (
53 projectPath: string,
54 filename: string
55 ) => Promise<string | null>;
56 writeArtifact: (
57 projectPath: string,
58 filename: string,
59 content: string
60 ) => Promise<void>;
61
62 // Events 51 // Events
63 onClaudeMessage: ( 52 onClaudeMessage: (
64 callback: (sessionId: string, message: SDKMessage) => void 53 callback: (sessionId: string, message: SDKMessage) => void
@@ -97,23 +86,17 @@ const api: ClaudeFlowAPI = {
97 setPermissionMode: (sessionId, mode) => 86 setPermissionMode: (sessionId, mode) =>
98 ipcRenderer.invoke("workflow:setPermissionMode", sessionId, mode), 87 ipcRenderer.invoke("workflow:setPermissionMode", sessionId, mode),
99 88
100 // Session Artifacts 89 // Session Artifacts (stored in ~/.claude-flow/)
101 readSessionArtifact: (projectPath, sessionId, filename) => 90 readSessionArtifact: (projectId, sessionId, filename) =>
102 ipcRenderer.invoke("artifact:readSession", projectPath, sessionId, filename), 91 ipcRenderer.invoke("artifact:readSession", projectId, sessionId, filename),
103 writeSessionArtifact: (projectPath, sessionId, filename, content) => 92 writeSessionArtifact: (projectId, sessionId, filename, content) =>
104 ipcRenderer.invoke("artifact:writeSession", projectPath, sessionId, filename, content), 93 ipcRenderer.invoke("artifact:writeSession", projectId, sessionId, filename, content),
105 94
106 // CLAUDE.md 95 // CLAUDE.md (stored in project)
107 readClaudeMd: (projectPath) => ipcRenderer.invoke("claudemd:read", projectPath), 96 readClaudeMd: (projectPath) => ipcRenderer.invoke("claudemd:read", projectPath),
108 writeClaudeMd: (projectPath, content) => 97 writeClaudeMd: (projectPath, content) =>
109 ipcRenderer.invoke("claudemd:write", projectPath, content), 98 ipcRenderer.invoke("claudemd:write", projectPath, content),
110 99
111 // Legacy Artifacts
112 readArtifact: (projectPath, filename) =>
113 ipcRenderer.invoke("artifact:read", projectPath, filename),
114 writeArtifact: (projectPath, filename, content) =>
115 ipcRenderer.invoke("artifact:write", projectPath, filename, content),
116
117 // Events 100 // Events
118 onClaudeMessage: (callback) => { 101 onClaudeMessage: (callback) => {
119 const handler = ( 102 const handler = (