From 35a38d909f1777e3b384ba26d68b88f641ec6a1a Mon Sep 17 00:00:00 2001 From: Clawd 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 +++++++++++++++++++++++++++++++--------- renderer/src/styles/globals.css | 24 +++++++++ 2 files changed, 106 insertions(+), 24 deletions(-) (limited to 'renderer') 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(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() { /> + {error && ( +
+ ⚠️ {error} + +
+ )} +