aboutsummaryrefslogtreecommitdiffstats
path: root/renderer/src/App.tsx
diff options
context:
space:
mode:
authorClawd <ai@clawd.bot>2026-02-28 07:32:23 -0800
committerClawd <ai@clawd.bot>2026-02-28 07:32:23 -0800
commit35a38d909f1777e3b384ba26d68b88f641ec6a1a (patch)
tree811e5763a5207433896c986e7654cd5bff9ad93e /renderer/src/App.tsx
parent6d70c5f8a3ed90564b08616a3fb041409916059c (diff)
Phase 5: Integration & polish
- Add error handling with auto-dismissing error bar - Add keyboard shortcuts: - Escape to interrupt Claude - Cmd/Ctrl+Enter to submit - Fix unused import warning - Update README.md with project documentation - Concept and workflow explanation - UI layout diagram - Installation and development instructions - Project structure overview - Keyboard shortcuts reference
Diffstat (limited to 'renderer/src/App.tsx')
-rw-r--r--renderer/src/App.tsx106
1 files changed, 82 insertions, 24 deletions
diff --git a/renderer/src/App.tsx b/renderer/src/App.tsx
index 7a8c378..22082a2 100644
--- a/renderer/src/App.tsx
+++ b/renderer/src/App.tsx
@@ -1,4 +1,4 @@
1import React, { useState, useEffect, useCallback } from "react"; 1import React, { useState, useEffect } from "react";
2import { Header } from "./components/Header"; 2import { Header } from "./components/Header";
3import { DocumentPane } from "./components/DocumentPane"; 3import { DocumentPane } from "./components/DocumentPane";
4import { ChatPane } from "./components/ChatPane"; 4import { ChatPane } from "./components/ChatPane";
@@ -21,9 +21,42 @@ export function App() {
21 inputTokens: 0, 21 inputTokens: 0,
22 outputTokens: 0, 22 outputTokens: 0,
23 }); 23 });
24 const [error, setError] = useState<string | null>(null);
24 25
25 const hasChanges = documentContent !== originalContent; 26 const hasChanges = documentContent !== originalContent;
26 27
28 // Clear error after 5 seconds
29 useEffect(() => {
30 if (error) {
31 const timer = setTimeout(() => setError(null), 5000);
32 return () => clearTimeout(timer);
33 }
34 }, [error]);
35
36 // Keyboard shortcuts
37 useEffect(() => {
38 const handleKeyDown = (e: KeyboardEvent) => {
39 // Escape to interrupt
40 if (e.key === "Escape" && isLoading && selectedSession) {
41 api.interruptSession(selectedSession.id);
42 setIsLoading(false);
43 }
44 // Cmd/Ctrl + Enter to submit
45 if (
46 e.key === "Enter" &&
47 (e.metaKey || e.ctrlKey) &&
48 selectedSession &&
49 selectedSession.phase !== "implement" &&
50 !isLoading
51 ) {
52 e.preventDefault();
53 handleSubmit();
54 }
55 };
56 window.addEventListener("keydown", handleKeyDown);
57 return () => window.removeEventListener("keydown", handleKeyDown);
58 }, [selectedSession, isLoading]);
59
27 // Load projects on mount 60 // Load projects on mount
28 useEffect(() => { 61 useEffect(() => {
29 api.listProjects().then(setProjects); 62 api.listProjects().then(setProjects);
@@ -122,6 +155,7 @@ export function App() {
122 const handleSendMessage = async (message: string) => { 155 const handleSendMessage = async (message: string) => {
123 if (!selectedSession) return; 156 if (!selectedSession) return;
124 setIsLoading(true); 157 setIsLoading(true);
158 setError(null);
125 setMessages((prev) => [ 159 setMessages((prev) => [
126 ...prev, 160 ...prev,
127 { 161 {
@@ -132,37 +166,54 @@ export function App() {
132 created_at: Date.now() / 1000, 166 created_at: Date.now() / 1000,
133 }, 167 },
134 ]); 168 ]);
135 await api.sendMessage(selectedSession.id, message); 169 try {
170 await api.sendMessage(selectedSession.id, message);
171 } catch (err) {
172 setError(err instanceof Error ? err.message : "Failed to send message");
173 setIsLoading(false);
174 }
136 }; 175 };
137 176
138 const handleReview = async () => { 177 const handleReview = async () => {
139 if (!selectedSession || !selectedProject) return; 178 if (!selectedSession || !selectedProject) return;
140 // Save user edits first 179 setError(null);
141 const filename = 180 try {
142 selectedSession.phase === "research" ? "research.md" : "plan.md"; 181 // Save user edits first
143 await api.writeArtifact(selectedProject.path, filename, documentContent); 182 const filename =
144 setOriginalContent(documentContent); 183 selectedSession.phase === "research" ? "research.md" : "plan.md";
145 setIsLoading(true); 184 await api.writeArtifact(selectedProject.path, filename, documentContent);
146 await api.triggerReview(selectedSession.id); 185 setOriginalContent(documentContent);
186 setIsLoading(true);
187 await api.triggerReview(selectedSession.id);
188 } catch (err) {
189 setError(err instanceof Error ? err.message : "Review failed");
190 setIsLoading(false);
191 }
147 }; 192 };
148 193
149 const handleSubmit = async () => { 194 const handleSubmit = async () => {
150 if (!selectedSession || !selectedProject) return; 195 if (!selectedSession || !selectedProject) return;
151 // Save any pending edits 196 setError(null);
152 const filename = 197 try {
153 selectedSession.phase === "research" ? "research.md" : "plan.md"; 198 // Save any pending edits
154 await api.writeArtifact(selectedProject.path, filename, documentContent); 199 const filename =
155 200 selectedSession.phase === "research" ? "research.md" : "plan.md";
156 const nextPhase = await api.advancePhase(selectedSession.id); 201 await api.writeArtifact(selectedProject.path, filename, documentContent);
157 if (nextPhase) { 202
158 setSelectedSession({ ...selectedSession, phase: nextPhase }); 203 const nextPhase = await api.advancePhase(selectedSession.id);
159 // Trigger initial message for next phase 204 if (nextPhase) {
160 setIsLoading(true); 205 setSelectedSession({ ...selectedSession, phase: nextPhase });
161 const initialMsg = 206 // Trigger initial message for next phase
162 nextPhase === "plan" 207 setIsLoading(true);
163 ? "Create a detailed implementation plan based on the research." 208 const initialMsg =
164 : "Begin implementing the plan."; 209 nextPhase === "plan"
165 await api.sendMessage(selectedSession.id, initialMsg); 210 ? "Create a detailed implementation plan based on the research."
211 : "Begin implementing the plan.";
212 await api.sendMessage(selectedSession.id, initialMsg);
213 }
214 } catch (err) {
215 setError(err instanceof Error ? err.message : "Submit failed");
216 setIsLoading(false);
166 } 217 }
167 }; 218 };
168 219
@@ -220,6 +271,13 @@ export function App() {
220 /> 271 />
221 </div> 272 </div>
222 273
274 {error && (
275 <div className="error-bar">
276 <span>⚠️ {error}</span>
277 <button onClick={() => setError(null)}>×</button>
278 </div>
279 )}
280
223 <ActionBar 281 <ActionBar
224 phase={selectedSession?.phase || "research"} 282 phase={selectedSession?.phase || "research"}
225 hasChanges={hasChanges} 283 hasChanges={hasChanges}