From 35a38d909f1777e3b384ba26d68b88f641ec6a1a Mon Sep 17 00:00:00 2001
From: Clawd <ai@clawd.bot>
Date: Sat, 28 Feb 2026 07:32:23 -0800
Subject: 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
---
 renderer/src/App.tsx | 106 +++++++++++++++++++++++++++++++++++++++------------
 1 file changed, 82 insertions(+), 24 deletions(-)

(limited to 'renderer/src/App.tsx')

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 @@
-import React, { useState, useEffect, useCallback } from "react";
+import React, { useState, useEffect } from "react";
 import { Header } from "./components/Header";
 import { DocumentPane } from "./components/DocumentPane";
 import { ChatPane } from "./components/ChatPane";
@@ -21,9 +21,42 @@ export function App() {
     inputTokens: 0,
     outputTokens: 0,
   });
+  const [error, setError] = useState<string | null>(null);
 
   const hasChanges = documentContent !== originalContent;
 
+  // Clear error after 5 seconds
+  useEffect(() => {
+    if (error) {
+      const timer = setTimeout(() => setError(null), 5000);
+      return () => clearTimeout(timer);
+    }
+  }, [error]);
+
+  // Keyboard shortcuts
+  useEffect(() => {
+    const handleKeyDown = (e: KeyboardEvent) => {
+      // Escape to interrupt
+      if (e.key === "Escape" && isLoading && selectedSession) {
+        api.interruptSession(selectedSession.id);
+        setIsLoading(false);
+      }
+      // Cmd/Ctrl + Enter to submit
+      if (
+        e.key === "Enter" &&
+        (e.metaKey || e.ctrlKey) &&
+        selectedSession &&
+        selectedSession.phase !== "implement" &&
+        !isLoading
+      ) {
+        e.preventDefault();
+        handleSubmit();
+      }
+    };
+    window.addEventListener("keydown", handleKeyDown);
+    return () => window.removeEventListener("keydown", handleKeyDown);
+  }, [selectedSession, isLoading]);
+
   // Load projects on mount
   useEffect(() => {
     api.listProjects().then(setProjects);
@@ -122,6 +155,7 @@ export function App() {
   const handleSendMessage = async (message: string) => {
     if (!selectedSession) return;
     setIsLoading(true);
+    setError(null);
     setMessages((prev) => [
       ...prev,
       {
@@ -132,37 +166,54 @@ export function App() {
         created_at: Date.now() / 1000,
       },
     ]);
-    await api.sendMessage(selectedSession.id, message);
+    try {
+      await api.sendMessage(selectedSession.id, message);
+    } catch (err) {
+      setError(err instanceof Error ? err.message : "Failed to send message");
+      setIsLoading(false);
+    }
   };
 
   const handleReview = async () => {
     if (!selectedSession || !selectedProject) return;
-    // Save user edits first
-    const filename =
-      selectedSession.phase === "research" ? "research.md" : "plan.md";
-    await api.writeArtifact(selectedProject.path, filename, documentContent);
-    setOriginalContent(documentContent);
-    setIsLoading(true);
-    await api.triggerReview(selectedSession.id);
+    setError(null);
+    try {
+      // Save user edits first
+      const filename =
+        selectedSession.phase === "research" ? "research.md" : "plan.md";
+      await api.writeArtifact(selectedProject.path, filename, documentContent);
+      setOriginalContent(documentContent);
+      setIsLoading(true);
+      await api.triggerReview(selectedSession.id);
+    } catch (err) {
+      setError(err instanceof Error ? err.message : "Review failed");
+      setIsLoading(false);
+    }
   };
 
   const handleSubmit = async () => {
     if (!selectedSession || !selectedProject) return;
-    // Save any pending edits
-    const filename =
-      selectedSession.phase === "research" ? "research.md" : "plan.md";
-    await api.writeArtifact(selectedProject.path, filename, documentContent);
-
-    const nextPhase = await api.advancePhase(selectedSession.id);
-    if (nextPhase) {
-      setSelectedSession({ ...selectedSession, phase: nextPhase });
-      // Trigger initial message for next phase
-      setIsLoading(true);
-      const initialMsg =
-        nextPhase === "plan"
-          ? "Create a detailed implementation plan based on the research."
-          : "Begin implementing the plan.";
-      await api.sendMessage(selectedSession.id, initialMsg);
+    setError(null);
+    try {
+      // Save any pending edits
+      const filename =
+        selectedSession.phase === "research" ? "research.md" : "plan.md";
+      await api.writeArtifact(selectedProject.path, filename, documentContent);
+
+      const nextPhase = await api.advancePhase(selectedSession.id);
+      if (nextPhase) {
+        setSelectedSession({ ...selectedSession, phase: nextPhase });
+        // Trigger initial message for next phase
+        setIsLoading(true);
+        const initialMsg =
+          nextPhase === "plan"
+            ? "Create a detailed implementation plan based on the research."
+            : "Begin implementing the plan.";
+        await api.sendMessage(selectedSession.id, initialMsg);
+      }
+    } catch (err) {
+      setError(err instanceof Error ? err.message : "Submit failed");
+      setIsLoading(false);
     }
   };
 
@@ -220,6 +271,13 @@ export function App() {
         />
       </div>
 
+      {error && (
+        <div className="error-bar">
+          <span>⚠️ {error}</span>
+          <button onClick={() => setError(null)}>×</button>
+        </div>
+      )}
+
       <ActionBar
         phase={selectedSession?.phase || "research"}
         hasChanges={hasChanges}
-- 
cgit v1.2.3

