aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--renderer/src/App.tsx30
-rw-r--r--renderer/src/components/DocumentPane.tsx15
-rw-r--r--renderer/src/components/NewSessionModal.tsx60
-rw-r--r--renderer/src/components/settings/ModelSettings.tsx156
-rw-r--r--renderer/src/styles/globals.css160
-rw-r--r--src/main/claude/index.ts5
-rw-r--r--src/main/claude/phases.ts10
-rw-r--r--src/main/db/sessions.ts8
-rw-r--r--src/main/ipc/handlers.ts6
-rw-r--r--src/main/preload.ts6
10 files changed, 421 insertions, 35 deletions
diff --git a/renderer/src/App.tsx b/renderer/src/App.tsx
index 719faac..7c5c969 100644
--- a/renderer/src/App.tsx
+++ b/renderer/src/App.tsx
@@ -5,6 +5,7 @@ import { DocumentPane } from "./components/DocumentPane";
5import { ChatPane } from "./components/ChatPane"; 5import { ChatPane } from "./components/ChatPane";
6import { ActionBar } from "./components/ActionBar"; 6import { ActionBar } from "./components/ActionBar";
7import { SettingsPage } from "./components/SettingsPage"; 7import { SettingsPage } from "./components/SettingsPage";
8import { NewSessionModal } from "./components/NewSessionModal";
8import type { Project, Session, Message, Phase, TokenUsage } from "./types"; 9import type { Project, Session, Message, Phase, TokenUsage } from "./types";
9import "./styles/globals.css"; 10import "./styles/globals.css";
10 11
@@ -66,6 +67,8 @@ export function App() {
66 }); 67 });
67 const [error, setError] = useState<string | null>(null); 68 const [error, setError] = useState<string | null>(null);
68 const [showSettings, setShowSettings] = useState(false); 69 const [showSettings, setShowSettings] = useState(false);
70 const [showNewSessionModal, setShowNewSessionModal] = useState(false);
71 const [newSessionProjectId, setNewSessionProjectId] = useState<string | null>(null);
69 const [activeModel, setActiveModel] = useState<string | null>(null); 72 const [activeModel, setActiveModel] = useState<string | null>(null);
70 73
71 const [theme, setTheme] = useState<Theme>( 74 const [theme, setTheme] = useState<Theme>(
@@ -451,12 +454,25 @@ export function App() {
451 setSelectedProject(project); 454 setSelectedProject(project);
452 }; 455 };
453 456
454 const handleCreateSession = async (projectId: string) => { 457 // Called by Sidebar when user clicks "+" — opens the phase-selection modal
458 const handleCreateSession = (projectId: string) => {
459 setNewSessionProjectId(projectId);
460 setShowNewSessionModal(true);
461 };
462
463 // Called by NewSessionModal when user clicks "Create"
464 const handleConfirmNewSession = async (phase: Phase) => {
465 setShowNewSessionModal(false);
466 const projectId = newSessionProjectId;
467 setNewSessionProjectId(null);
468 if (!projectId) return;
469
455 const project = projects.find((p) => p.id === projectId); 470 const project = projects.find((p) => p.id === projectId);
456 if (!project) return; 471 if (!project) return;
472
457 const projectSessions = sessions.filter((s) => s.project_id === projectId); 473 const projectSessions = sessions.filter((s) => s.project_id === projectId);
458 const name = `Session ${projectSessions.length + 1}`; 474 const name = `Session ${projectSessions.length + 1}`;
459 const session = await api.createSession(projectId, name); 475 const session = await api.createSession(projectId, name, phase);
460 setSessions((prev) => [session, ...prev]); 476 setSessions((prev) => [session, ...prev]);
461 setSelectedProject(project); 477 setSelectedProject(project);
462 setSelectedSession(session); 478 setSelectedSession(session);
@@ -623,6 +639,16 @@ export function App() {
623 {showSettings && ( 639 {showSettings && (
624 <SettingsPage onClose={() => setShowSettings(false)} /> 640 <SettingsPage onClose={() => setShowSettings(false)} />
625 )} 641 )}
642
643 {showNewSessionModal && (
644 <NewSessionModal
645 onConfirm={handleConfirmNewSession}
646 onCancel={() => {
647 setShowNewSessionModal(false);
648 setNewSessionProjectId(null);
649 }}
650 />
651 )}
626 </div> 652 </div>
627 ); 653 );
628} 654}
diff --git a/renderer/src/components/DocumentPane.tsx b/renderer/src/components/DocumentPane.tsx
index f5368b3..2ec66f6 100644
--- a/renderer/src/components/DocumentPane.tsx
+++ b/renderer/src/components/DocumentPane.tsx
@@ -117,6 +117,11 @@ export function DocumentPane({
117}: DocumentPaneProps) { 117}: DocumentPaneProps) {
118 const [isEditing, setIsEditing] = useState(false); 118 const [isEditing, setIsEditing] = useState(false);
119 119
120 // Always exit edit mode when the pane becomes read-only.
121 useEffect(() => {
122 if (disabled) setIsEditing(false);
123 }, [disabled]);
124
120 if (showOnboarding) { 125 if (showOnboarding) {
121 return ( 126 return (
122 <div className="document-pane"> 127 <div className="document-pane">
@@ -206,9 +211,13 @@ export function DocumentPane({
206 <div className="document-pane"> 211 <div className="document-pane">
207 <div className="document-header"> 212 <div className="document-header">
208 <span>{filename}</span> 213 <span>{filename}</span>
209 <button onClick={() => setIsEditing(!isEditing)}> 214 {disabled ? (
210 {isEditing ? "Preview" : "Edit"} 215 <span className="badge badge-readonly">Read-only</span>
211 </button> 216 ) : (
217 <button onClick={() => setIsEditing(!isEditing)}>
218 {isEditing ? "Preview" : "Edit"}
219 </button>
220 )}
212 </div> 221 </div>
213 222
214 {isEditing ? ( 223 {isEditing ? (
diff --git a/renderer/src/components/NewSessionModal.tsx b/renderer/src/components/NewSessionModal.tsx
new file mode 100644
index 0000000..ad5dae9
--- /dev/null
+++ b/renderer/src/components/NewSessionModal.tsx
@@ -0,0 +1,60 @@
1import React, { useState, useEffect } from "react";
2import type { Phase } from "../types";
3
4interface NewSessionModalProps {
5 onConfirm: (phase: Phase) => void;
6 onCancel: () => void;
7}
8
9const phaseOptions: { phase: Phase; label: string; description: string }[] = [
10 { phase: "research", label: "Research", description: "Understand the codebase and requirements" },
11 { phase: "plan", label: "Plan", description: "Create a detailed implementation strategy" },
12 { phase: "implement", label: "Implement", description: "Execute the implementation plan" },
13];
14
15export function NewSessionModal({ onConfirm, onCancel }: NewSessionModalProps) {
16 const [selected, setSelected] = useState<Phase>("research");
17
18 // Close on Escape
19 useEffect(() => {
20 const onKey = (e: KeyboardEvent) => { if (e.key === "Escape") onCancel(); };
21 window.addEventListener("keydown", onKey);
22 return () => window.removeEventListener("keydown", onKey);
23 }, [onCancel]);
24
25 return (
26 <div className="modal-backdrop" onClick={onCancel}>
27 <div className="modal" onClick={(e) => e.stopPropagation()}>
28
29 <div className="modal-header">
30 <span className="modal-title">New Session</span>
31 <button className="modal-close" onClick={onCancel}>×</button>
32 </div>
33
34 <div className="modal-body">
35 <p className="modal-prompt">Choose where to start</p>
36 <div className="phase-cards">
37 {phaseOptions.map(({ phase, label, description }) => (
38 <button
39 key={phase}
40 className={`phase-card${selected === phase ? " selected" : ""}`}
41 onClick={() => setSelected(phase)}
42 >
43 <span className="phase-card-label">{label}</span>
44 <span className="phase-card-desc">{description}</span>
45 </button>
46 ))}
47 </div>
48 </div>
49
50 <div className="modal-footer">
51 <button className="modal-btn-secondary" onClick={onCancel}>Cancel</button>
52 <button className="modal-btn-primary" onClick={() => onConfirm(selected)}>
53 Create
54 </button>
55 </div>
56
57 </div>
58 </div>
59 );
60}
diff --git a/renderer/src/components/settings/ModelSettings.tsx b/renderer/src/components/settings/ModelSettings.tsx
index ecfc12c..f0cbda5 100644
--- a/renderer/src/components/settings/ModelSettings.tsx
+++ b/renderer/src/components/settings/ModelSettings.tsx
@@ -1,21 +1,61 @@
1import React, { useState, useEffect } from "react"; 1import React, { useState, useEffect } from "react";
2import type { Phase } from "../../types";
2 3
3const api = window.api; 4const api = window.api;
4 5
6const PHASES: Phase[] = ["research", "plan", "implement"];
7const PHASE_LABELS: Record<Phase, string> = {
8 research: "Research",
9 plan: "Plan",
10 implement: "Implement",
11};
12
5export function ModelSettings() { 13export function ModelSettings() {
6 // null = not yet loaded from DB 14 // ── Global default ───────────────────────────────────────────
7 const [model, setModel] = useState<string | null>(null); 15 const [model, setModel] = useState<string | null>(null);
8 const [draft, setDraft] = useState(""); 16 const [draft, setDraft] = useState("");
9 const [saveStatus, setSaveStatus] = useState<"idle" | "saved">("idle"); 17 const [saveStatus, setSaveStatus] = useState<"idle" | "saved">("idle");
10 18
19 // ── Per-phase overrides ──────────────────────────────────────
20 const [phaseModels, setPhaseModels] = useState<Record<Phase, string | null>>({
21 research: null,
22 plan: null,
23 implement: null,
24 });
25 const [phaseDrafts, setPhaseDrafts] = useState<Record<Phase, string>>({
26 research: "",
27 plan: "",
28 implement: "",
29 });
30 const [phaseStatus, setPhaseStatus] = useState<Record<Phase, "idle" | "saved">>({
31 research: "idle",
32 plan: "idle",
33 implement: "idle",
34 });
35
11 useEffect(() => { 36 useEffect(() => {
12 api.getSettings(["model"]).then((settings) => { 37 api
13 const saved = settings["model"] ?? ""; 38 .getSettings(["model", "model.research", "model.plan", "model.implement"])
14 setModel(saved); 39 .then((settings) => {
15 setDraft(saved); 40 const saved = settings["model"] ?? "";
16 }); 41 setModel(saved);
42 setDraft(saved);
43
44 const pm: Record<Phase, string | null> = {
45 research: settings["model.research"],
46 plan: settings["model.plan"],
47 implement: settings["model.implement"],
48 };
49 setPhaseModels(pm);
50 setPhaseDrafts({
51 research: pm.research ?? "",
52 plan: pm.plan ?? "",
53 implement: pm.implement ?? "",
54 });
55 });
17 }, []); 56 }, []);
18 57
58 // ── Global handlers ──────────────────────────────────────────
19 const handleSave = async () => { 59 const handleSave = async () => {
20 const trimmed = draft.trim(); 60 const trimmed = draft.trim();
21 if (trimmed) { 61 if (trimmed) {
@@ -37,6 +77,34 @@ export function ModelSettings() {
37 setTimeout(() => setSaveStatus("idle"), 1500); 77 setTimeout(() => setSaveStatus("idle"), 1500);
38 }; 78 };
39 79
80 // ── Per-phase handlers ───────────────────────────────────────
81 const handlePhaseSave = async (phase: Phase) => {
82 const trimmed = phaseDrafts[phase].trim();
83 if (trimmed) {
84 await api.setSetting(`model.${phase}`, trimmed);
85 setPhaseModels((prev) => ({ ...prev, [phase]: trimmed }));
86 } else {
87 await api.deleteSetting(`model.${phase}`);
88 setPhaseModels((prev) => ({ ...prev, [phase]: null }));
89 }
90 setPhaseStatus((prev) => ({ ...prev, [phase]: "saved" }));
91 setTimeout(
92 () => setPhaseStatus((prev) => ({ ...prev, [phase]: "idle" })),
93 1500
94 );
95 };
96
97 const handlePhaseReset = async (phase: Phase) => {
98 await api.deleteSetting(`model.${phase}`);
99 setPhaseModels((prev) => ({ ...prev, [phase]: null }));
100 setPhaseDrafts((prev) => ({ ...prev, [phase]: "" }));
101 setPhaseStatus((prev) => ({ ...prev, [phase]: "saved" }));
102 setTimeout(
103 () => setPhaseStatus((prev) => ({ ...prev, [phase]: "idle" })),
104 1500
105 );
106 };
107
40 if (model === null) { 108 if (model === null) {
41 return ( 109 return (
42 <div style={{ color: "var(--text-secondary)", fontSize: 12 }}> 110 <div style={{ color: "var(--text-secondary)", fontSize: 12 }}>
@@ -49,10 +117,11 @@ export function ModelSettings() {
49 117
50 return ( 118 return (
51 <div> 119 <div>
52 <div className="settings-section-title">Model</div> 120 {/* ── Global default ──────────────────────────────────── */}
121 <div className="settings-section-title">Default Model</div>
53 <div className="settings-section-desc"> 122 <div className="settings-section-desc">
54 Claude model to use for all phases. Leave blank to use the SDK&apos;s 123 Fallback model used for all phases unless a per-phase override is set.
55 default model. Takes effect on the next message sent in any session. 124 Leave blank to use the SDK&apos;s built-in default.
56 </div> 125 </div>
57 126
58 <div className="settings-toggle-row"> 127 <div className="settings-toggle-row">
@@ -60,7 +129,7 @@ export function ModelSettings() {
60 className="settings-model-input" 129 className="settings-model-input"
61 type="text" 130 type="text"
62 value={draft} 131 value={draft}
63 placeholder="Default (e.g. claude-sonnet-4-5)" 132 placeholder="e.g. claude-sonnet-4-5"
64 onChange={(e) => { 133 onChange={(e) => {
65 setDraft(e.target.value); 134 setDraft(e.target.value);
66 setSaveStatus("idle"); 135 setSaveStatus("idle");
@@ -75,7 +144,7 @@ export function ModelSettings() {
75 <div className="settings-actions"> 144 <div className="settings-actions">
76 {model && ( 145 {model && (
77 <button className="btn-secondary" onClick={handleReset}> 146 <button className="btn-secondary" onClick={handleReset}>
78 Reset to Default 147 Reset
79 </button> 148 </button>
80 )} 149 )}
81 <button 150 <button
@@ -85,9 +154,68 @@ export function ModelSettings() {
85 > 154 >
86 {saveStatus === "saved" ? "Saved \u2713" : "Save"} 155 {saveStatus === "saved" ? "Saved \u2713" : "Save"}
87 </button> 156 </button>
88 {model && ( 157 {model && <span className="settings-custom-badge">custom</span>}
89 <span className="settings-custom-badge">custom</span> 158 </div>
90 )} 159
160 {/* ── Per-phase overrides ─────────────────────────────── */}
161 <div className="settings-section-title" style={{ marginTop: 24 }}>
162 Phase Overrides
163 </div>
164 <div className="settings-section-desc">
165 Use a different model for a specific phase. Leave blank to inherit the
166 default above. Takes effect on the next message in any session.
167 </div>
168
169 <div className="settings-phase-model-list">
170 {PHASES.map((phase) => {
171 const saved = phaseModels[phase];
172 const phaseDirty = phaseDrafts[phase].trim() !== (saved ?? "");
173
174 return (
175 <div key={phase} className="settings-phase-model-row">
176 <span className="settings-phase-model-label">
177 {PHASE_LABELS[phase]}
178 </span>
179 <input
180 className="settings-model-input"
181 type="text"
182 value={phaseDrafts[phase]}
183 placeholder={model ? `Inherits: ${model}` : "Inherits default"}
184 onChange={(e) => {
185 setPhaseDrafts((prev) => ({
186 ...prev,
187 [phase]: e.target.value,
188 }));
189 setPhaseStatus((prev) => ({ ...prev, [phase]: "idle" }));
190 }}
191 onKeyDown={(e) => {
192 if (e.key === "Enter") handlePhaseSave(phase);
193 }}
194 spellCheck={false}
195 />
196 <div className="settings-phase-model-actions">
197 {saved && (
198 <button
199 className="btn-secondary"
200 onClick={() => handlePhaseReset(phase)}
201 >
202 Reset
203 </button>
204 )}
205 <button
206 className="btn-primary"
207 onClick={() => handlePhaseSave(phase)}
208 disabled={!phaseDirty}
209 >
210 {phaseStatus[phase] === "saved" ? "Saved \u2713" : "Save"}
211 </button>
212 {saved && (
213 <span className="settings-custom-badge">custom</span>
214 )}
215 </div>
216 </div>
217 );
218 })}
91 </div> 219 </div>
92 </div> 220 </div>
93 ); 221 );
diff --git a/renderer/src/styles/globals.css b/renderer/src/styles/globals.css
index c463432..dc7d4a5 100644
--- a/renderer/src/styles/globals.css
+++ b/renderer/src/styles/globals.css
@@ -1711,6 +1711,166 @@ html[data-theme="light"] .settings-textarea:focus {
1711 margin-left: 4px; 1711 margin-left: 4px;
1712} 1712}
1713 1713
1714/* ── New Session Modal ───────────────────────────────────────── */
1715.modal-backdrop {
1716 position: fixed;
1717 inset: 0;
1718 background: rgba(0, 0, 0, 0.55);
1719 display: flex;
1720 align-items: center;
1721 justify-content: center;
1722 z-index: 200;
1723}
1724
1725.modal {
1726 background: var(--bg-secondary);
1727 border: 1px solid var(--border);
1728 border-radius: 4px;
1729 width: 400px;
1730 display: flex;
1731 flex-direction: column;
1732 box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
1733}
1734
1735.modal-header {
1736 display: flex;
1737 justify-content: space-between;
1738 align-items: center;
1739 padding: 12px 16px;
1740 border-bottom: 1px solid var(--border);
1741}
1742
1743.modal-title {
1744 font-size: 13px;
1745 font-weight: 600;
1746 color: var(--text-primary);
1747 text-transform: uppercase;
1748 letter-spacing: 0.08em;
1749}
1750
1751.modal-close {
1752 background: transparent;
1753 border: 1px solid var(--border);
1754 border-radius: 2px;
1755 color: var(--text-secondary);
1756 font-size: 14px;
1757 width: 22px;
1758 height: 22px;
1759 cursor: pointer;
1760 display: flex;
1761 align-items: center;
1762 justify-content: center;
1763 transition: background 0.15s;
1764}
1765
1766.modal-close:hover {
1767 background: var(--bg-tertiary);
1768 color: var(--text-primary);
1769}
1770
1771.modal-body {
1772 padding: 20px 16px 16px;
1773 display: flex;
1774 flex-direction: column;
1775 gap: 12px;
1776}
1777
1778.modal-prompt {
1779 font-size: 12px;
1780 color: var(--text-secondary);
1781 text-transform: uppercase;
1782 letter-spacing: 0.08em;
1783}
1784
1785.phase-cards {
1786 display: flex;
1787 flex-direction: column;
1788 gap: 6px;
1789}
1790
1791.phase-card {
1792 display: flex;
1793 flex-direction: column;
1794 align-items: flex-start;
1795 gap: 2px;
1796 padding: 10px 12px;
1797 background: var(--bg-primary);
1798 border: 1px solid var(--border);
1799 border-radius: 3px;
1800 cursor: pointer;
1801 text-align: left;
1802 transition: border-color 0.15s, background 0.15s;
1803 width: 100%;
1804}
1805
1806.phase-card:hover {
1807 border-color: var(--accent);
1808 background: var(--bg-tertiary);
1809}
1810
1811.phase-card.selected {
1812 border-color: var(--accent);
1813 background: var(--bg-tertiary);
1814}
1815
1816.phase-card-label {
1817 font-size: 12px;
1818 font-weight: 600;
1819 color: var(--text-primary);
1820}
1821
1822.phase-card.selected .phase-card-label {
1823 color: var(--accent);
1824}
1825
1826.phase-card-desc {
1827 font-size: 11px;
1828 color: var(--text-secondary);
1829}
1830
1831.modal-footer {
1832 display: flex;
1833 justify-content: flex-end;
1834 gap: 8px;
1835 padding: 12px 16px;
1836 border-top: 1px solid var(--border);
1837}
1838
1839.modal-btn-secondary {
1840 background: transparent;
1841 border: 1px solid var(--border);
1842 border-radius: 2px;
1843 color: var(--text-secondary);
1844 font-size: 12px;
1845 padding: 5px 12px;
1846 cursor: pointer;
1847 font-family: inherit;
1848 transition: background 0.15s;
1849}
1850
1851.modal-btn-secondary:hover {
1852 background: var(--bg-tertiary);
1853 color: var(--text-primary);
1854}
1855
1856.modal-btn-primary {
1857 background: var(--accent);
1858 border: 1px solid var(--accent);
1859 border-radius: 2px;
1860 color: #fff;
1861 font-size: 12px;
1862 padding: 5px 12px;
1863 cursor: pointer;
1864 font-family: inherit;
1865 font-weight: 600;
1866 transition: background 0.15s;
1867}
1868
1869.modal-btn-primary:hover {
1870 background: var(--accent-hover);
1871 border-color: var(--accent-hover);
1872}
1873
1714.mcp-tools-empty { 1874.mcp-tools-empty {
1715 font-size: 11px; 1875 font-size: 11px;
1716 color: var(--text-secondary); 1876 color: var(--text-secondary);
diff --git a/src/main/claude/index.ts b/src/main/claude/index.ts
index 8cf512c..9139f17 100644
--- a/src/main/claude/index.ts
+++ b/src/main/claude/index.ts
@@ -48,8 +48,9 @@ export async function sendMessage({
48 // Load any custom system prompt for this phase (null → use default) 48 // Load any custom system prompt for this phase (null → use default)
49 const customSystemPrompt = getSetting(`systemPrompt.${session.phase}`) ?? undefined; 49 const customSystemPrompt = getSetting(`systemPrompt.${session.phase}`) ?? undefined;
50 50
51 // Load global model override (empty string or null → let SDK use its default) 51 // Phase-specific model override takes precedence; falls back to global default.
52 const configuredModel = getSetting("model") || undefined; 52 const configuredModel =
53 getSetting(`model.${session.phase}`) || getSetting("model") || undefined;
53 54
54 // Load MCP servers config (JSON string → object, or undefined if not set) 55 // Load MCP servers config (JSON string → object, or undefined if not set)
55 const mcpServersJson = getSetting("mcpServers"); 56 const mcpServersJson = getSetting("mcpServers");
diff --git a/src/main/claude/phases.ts b/src/main/claude/phases.ts
index a1cbba1..e8c16df 100644
--- a/src/main/claude/phases.ts
+++ b/src/main/claude/phases.ts
@@ -87,11 +87,12 @@ CRITICAL RULES:
87 87
88CONTEXT: 88CONTEXT:
89- Read CLAUDE.md at project root for codebase overview 89- Read CLAUDE.md at project root for codebase overview
90- Read ${artifactDir}/research.md to understand the specific task 90- Read ${artifactDir}/research.md to understand the specific task.
91 If research.md does not exist, this session began at the planning phase — base your plan on the task description provided in the chat instead.
91 92
92WORKFLOW: 93WORKFLOW:
931. Read CLAUDE.md for codebase overview 941. Read CLAUDE.md for codebase overview
942. Read ${artifactDir}/research.md to understand the specific task 952. Read ${artifactDir}/research.md if it exists; otherwise use the task description from chat
953. Write a detailed plan to ${artifactDir}/plan.md 963. Write a detailed plan to ${artifactDir}/plan.md
964. Include specific code snippets showing proposed changes 974. Include specific code snippets showing proposed changes
975. Make the plan detailed enough that implementation is mechanical 985. Make the plan detailed enough that implementation is mechanical
@@ -141,14 +142,15 @@ Remember: Your output goes in ${artifactDir}/plan.md, not chat. Chat is for clar
141 systemPrompt: (artifactDir) => `You are in IMPLEMENTATION mode. Execute the approved plan. 142 systemPrompt: (artifactDir) => `You are in IMPLEMENTATION mode. Execute the approved plan.
142 143
143CRITICAL RULES: 144CRITICAL RULES:
1441. Read ${artifactDir}/plan.md and follow it exactly 1451. Read ${artifactDir}/plan.md if it exists and follow it exactly.
146 If plan.md does not exist, this session began at the implementation phase — implement based on the task description provided in the chat messages.
1452. Mark tasks complete in ${artifactDir}/plan.md as you finish them: - [ ] → - [x] 1472. Mark tasks complete in ${artifactDir}/plan.md as you finish them: - [ ] → - [x]
1463. DO NOT deviate from the plan without asking 1483. DO NOT deviate from the plan without asking
1474. Run tests/typecheck if available 1494. Run tests/typecheck if available
1485. Stop and ask if you encounter issues not covered by the plan 1505. Stop and ask if you encounter issues not covered by the plan
149 151
150WORKFLOW: 152WORKFLOW:
1511. Read ${artifactDir}/plan.md 1531. Read ${artifactDir}/plan.md (if it exists)
1522. Execute each task in order 1542. Execute each task in order
1533. Update ${artifactDir}/plan.md to mark tasks complete 1553. Update ${artifactDir}/plan.md to mark tasks complete
1544. Continue until all tasks are done 1564. Continue until all tasks are done
diff --git a/src/main/db/sessions.ts b/src/main/db/sessions.ts
index 3e6352c..bc22d15 100644
--- a/src/main/db/sessions.ts
+++ b/src/main/db/sessions.ts
@@ -36,21 +36,21 @@ export function getSession(id: string): Session | undefined {
36 .get(id) as Session | undefined; 36 .get(id) as Session | undefined;
37} 37}
38 38
39export function createSession(projectId: string, name: string): Session { 39export function createSession(projectId: string, name: string, phase: Phase = "research"): Session {
40 const db = getDb(); 40 const db = getDb();
41 const id = uuid(); 41 const id = uuid();
42 const now = Math.floor(Date.now() / 1000); 42 const now = Math.floor(Date.now() / 1000);
43 43
44 db.prepare( 44 db.prepare(
45 `INSERT INTO sessions (id, project_id, name, phase, permission_mode, created_at, updated_at) 45 `INSERT INTO sessions (id, project_id, name, phase, permission_mode, created_at, updated_at)
46 VALUES (?, ?, ?, 'research', 'acceptEdits', ?, ?)` 46 VALUES (?, ?, ?, ?, 'acceptEdits', ?, ?)`
47 ).run(id, projectId, name, now, now); 47 ).run(id, projectId, name, phase, now, now);
48 48
49 return { 49 return {
50 id, 50 id,
51 project_id: projectId, 51 project_id: projectId,
52 name, 52 name,
53 phase: "research", 53 phase,
54 claude_session_id: null, 54 claude_session_id: null,
55 permission_mode: "acceptEdits", 55 permission_mode: "acceptEdits",
56 git_branch: null, 56 git_branch: null,
diff --git a/src/main/ipc/handlers.ts b/src/main/ipc/handlers.ts
index 4894e1d..975ad01 100644
--- a/src/main/ipc/handlers.ts
+++ b/src/main/ipc/handlers.ts
@@ -5,7 +5,7 @@ import * as claude from "../claude";
5import * as settingsDb from "../db/settings"; 5import * as settingsDb from "../db/settings";
6import { createSessionBranch, ensureGitIgnore, ensureGitRepo, getCurrentBranch } from "../git"; 6import { createSessionBranch, ensureGitIgnore, ensureGitRepo, getCurrentBranch } from "../git";
7import { discoverMcpTools } from "../mcp"; 7import { discoverMcpTools } from "../mcp";
8import type { UserPermissionMode } from "../claude/phases"; 8import type { UserPermissionMode, Phase } from "../claude/phases";
9import { getDefaultSystemPromptTemplate } from "../claude/phases"; 9import { getDefaultSystemPromptTemplate } from "../claude/phases";
10 10
11export function registerIpcHandlers(mainWindow: BrowserWindow): void { 11export function registerIpcHandlers(mainWindow: BrowserWindow): void {
@@ -23,11 +23,11 @@ export function registerIpcHandlers(mainWindow: BrowserWindow): void {
23 sessions.listSessions(projectId) 23 sessions.listSessions(projectId)
24 ); 24 );
25 25
26 ipcMain.handle("sessions:create", (_, projectId: string, name: string) => { 26 ipcMain.handle("sessions:create", (_, projectId: string, name: string, phase?: Phase) => {
27 const project = projects.getProject(projectId); 27 const project = projects.getProject(projectId);
28 if (!project) throw new Error("Project not found"); 28 if (!project) throw new Error("Project not found");
29 29
30 const session = sessions.createSession(projectId, name); 30 const session = sessions.createSession(projectId, name, phase);
31 31
32 // Ensure .claude-flow/ is gitignored from day one. 32 // Ensure .claude-flow/ is gitignored from day one.
33 // Branch creation is deferred until the session advances to implement. 33 // Branch creation is deferred until the session advances to implement.
diff --git a/src/main/preload.ts b/src/main/preload.ts
index e7ee0aa..fbdf871 100644
--- a/src/main/preload.ts
+++ b/src/main/preload.ts
@@ -12,7 +12,7 @@ export interface ClaudeFlowAPI {
12 12
13 // Sessions 13 // Sessions
14 listSessions: (projectId: string) => Promise<Session[]>; 14 listSessions: (projectId: string) => Promise<Session[]>;
15 createSession: (projectId: string, name: string) => Promise<Session>; 15 createSession: (projectId: string, name: string, phase?: Phase) => Promise<Session>;
16 deleteSession: (id: string) => Promise<void>; 16 deleteSession: (id: string) => Promise<void>;
17 getSession: (id: string) => Promise<Session | undefined>; 17 getSession: (id: string) => Promise<Session | undefined>;
18 renameSession: (id: string, name: string) => Promise<void>; 18 renameSession: (id: string, name: string) => Promise<void>;
@@ -86,8 +86,8 @@ const api: ClaudeFlowAPI = {
86 86
87 // Sessions 87 // Sessions
88 listSessions: (projectId) => ipcRenderer.invoke("sessions:list", projectId), 88 listSessions: (projectId) => ipcRenderer.invoke("sessions:list", projectId),
89 createSession: (projectId, name) => 89 createSession: (projectId, name, phase) =>
90 ipcRenderer.invoke("sessions:create", projectId, name), 90 ipcRenderer.invoke("sessions:create", projectId, name, phase),
91 deleteSession: (id) => ipcRenderer.invoke("sessions:delete", id), 91 deleteSession: (id) => ipcRenderer.invoke("sessions:delete", id),
92 getSession: (id) => ipcRenderer.invoke("sessions:get", id), 92 getSession: (id) => ipcRenderer.invoke("sessions:get", id),
93 renameSession: (id, name) => ipcRenderer.invoke("sessions:rename", id, name), 93 renameSession: (id, name) => ipcRenderer.invoke("sessions:rename", id, name),