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 --- README.md | 136 +++++++++++++++++++--------------------- renderer/src/App.tsx | 106 ++++++++++++++++++++++++------- renderer/src/styles/globals.css | 24 +++++++ 3 files changed, 171 insertions(+), 95 deletions(-) diff --git a/README.md b/README.md index f74d4fc..1e23bc9 100644 --- a/README.md +++ b/README.md @@ -1,104 +1,98 @@ -# minimal-electron-bsql +# Claude Flow -A bare-bones Electron + TypeScript + [better-sqlite3](https://github.com/WiseLibs/better-sqlite3) app. -No forge, no boilerplate, no scaffolding. Just the absolute minimum setup that: +A document-centric coding assistant that enforces a structured workflow: **Research → Plan → Implement**. -- Runs in dev mode on macOS/Linux/Windows -- Uses `better-sqlite3` from the **main process** -- Creates a SQLite DB at startup, inserts + selects a row -- Displays a trivial HTML UI -- Can be packaged for distribution (SQLite included) +Built with Electron, React, TypeScript, better-sqlite3, and the Claude Agent SDK. ---- +## Concept -## Project structure +The primary UI is a **markdown document viewer/editor** with a **chat sidebar**. The workflow is driven by the document, not the chat. + +### Workflow Phases + +1. **Research** — Claude researches your codebase based on your direction, writing findings to `.claude-flow/research.md` +2. **Plan** — Claude creates a detailed implementation plan in `.claude-flow/plan.md` with code snippets and a TODO checklist +3. **Implement** — Claude executes the plan, marking tasks complete as it goes + +At each phase, you can edit the document to add notes (marked with `// REVIEW:` or `// NOTE:`), click **Review** to have Claude address your feedback, then **Submit** to advance to the next phase. + +## UI Layout ``` -minimal-electron-bsql/ -├── src/ -│ └── main/ -│ └── index.ts # Electron main process -├── renderer/ -│ └── index.html # Minimal UI -├── package.json -├── tsconfig.json +┌──────────────────────────────────────────────────────────────┐ +│ [Project ▾] [Session ▾] [Research ● ─ Plan ─] │ +├────────────────────────────────────────┬─────────────────────┤ +│ │ │ +│ # Research Findings │ Chat Dialogue │ +│ │ │ +│ ## Authentication System │ Claude: What │ +│ │ should I research?│ +│ The auth module uses JWT... │ │ +│ │ You: Research the │ +│ // REVIEW: check OAuth too │ auth system │ +│ │ │ +├────────────────────────────────────────┼─────────────────────┤ +│ 42k / 200k tokens ████░░░░ │ [Send] │ +│ [Review] [Submit] │ │ +└────────────────────────────────────────┴─────────────────────┘ ``` ## Requirements - Node.js ≥ 18 -- macOS (Apple Silicon or Intel). Windows & Linux should work with native build tools installed. -- Xcode Command Line Tools (macOS) or MSVC Build Tools (Windows) for native rebuilds - ---- +- macOS (Apple Silicon or Intel), Windows, or Linux +- Claude API key or Claude Code subscription ## Install ```bash git clone -cd minimal-electron-bsql +cd claude-flow npm install ``` -ℹ️ On install, native modules are rebuilt for your Electron version via `@electron/rebuild`. - -⸻ - -Scripts - -- npm run dev → compile TS and start Electron in dev mode -- npm run build → compile TypeScript only -- npm run start → start Electron with compiled code -- npm run rebuild → force-rebuild native modules (better-sqlite3) -- npm run dist → create distributable builds via electron-builder -- npm run pack → package into unpacked app directory - -## What happens on startup - -In src/main/index.ts: - -1. Database file created at app.getPath('userData')/app.db -2. A table messages is created (if not exists) -3. One row "hello from better-sqlite3" is inserted -4. A SELECT runs and the row is logged to console -5. Window shows renderer/index.html → just
hi
- -⸻ - -Packaging notes -• Native modules: better-sqlite3.node must live outside app.asar. This is handled via: - -``` -"asarUnpack": ["node_modules/better-sqlite3/**/*"] -``` - -- Cross-arch builds (macOS): -- Build separately for arm64 and x64: +## Development ```bash -npm run dist -- --mac arm64 -npm run dist -- --mac x64 +npm run dev ``` -- Optionally merge into a universal binary: +This starts Vite dev server and Electron in watch mode. + +## Build ```bash -npx electron-builder --universal +npm run dist ``` -- Database location: Always use app.getPath('userData'). Do not write next to your code files, since those are inside app.asar when packaged. +Creates distributable builds via electron-builder. + +## Project Structure -⸻ +``` +claude-flow/ +├── src/main/ # Electron main process +│ ├── index.ts # App lifecycle, window management +│ ├── preload.ts # IPC bridge with typed API +│ ├── db/ # SQLite database (projects, sessions, messages) +│ ├── claude/ # Claude SDK wrapper with phase configs +│ └── ipc/ # IPC handlers +├── renderer/ # React frontend +│ ├── src/ +│ │ ├── App.tsx # Main app with state management +│ │ ├── components/ # Header, DocumentPane, ChatPane, ActionBar +│ │ ├── styles/ # CSS +│ │ └── types.ts # TypeScript types +│ └── index.html +├── package.json +├── tsconfig.json +└── vite.config.ts +``` -Common pitfalls & fixes +## Keyboard Shortcuts -- was compiled against a different Node.js version error -- Run npm run rebuild after npm install or upgrading Electron. -- .node module not found after packaging -- Check asarUnpack includes better-sqlite3. -- Windows/Linux builds -- Windows: install Build Tools for Visual Studio + Python -- Linux: sudo apt install build-essential python3 +- **Cmd/Ctrl + Enter** — Submit (advance to next phase) +- **Escape** — Interrupt Claude ## License 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} + +
+ )} +