From acab9fd2e0914d34460bccdda20365b7831d10ce Mon Sep 17 00:00:00 2001 From: James Hawkins Date: Tue, 16 Dec 2025 15:40:07 +0400 Subject: [PATCH 1/2] created live plan --- .../plan.md | 10 + .posthog/plan-inline/plan.md | 343 ++++++++++++++++++ .posthog/serene-sand-hamster/plan.md | 8 + .posthog/smart-copper-turtle/plan.md | 5 + .../src/main/services/session-manager.ts | 2 +- .../features/editor/components/PlanEditor.tsx | 115 +++++- .../editor/components/RichTextEditor.tsx | 4 +- .../panels/constants/panelConstants.ts | 1 + .../features/panels/store/panelLayoutStore.ts | 23 +- .../panels/store/panelStoreHelpers.ts | 5 + .../features/panels/store/panelTypes.ts | 3 + .../components/TabContentRenderer.tsx | 4 + .../task-detail/components/TaskDetail.tsx | 6 + .../task-detail/components/TaskPlanPanel.tsx | 35 ++ packages/agent/index.ts | 2 + packages/agent/src/adapters/claude/claude.ts | 39 +- .../agent/src/adapters/claude/mcp-server.ts | 129 +++++++ packages/agent/src/adapters/claude/tools.ts | 21 ++ packages/agent/src/tools/registry.ts | 10 + packages/agent/src/tools/types.ts | 14 +- sounds/danilo.mp4 | Bin 0 -> 22570 bytes sounds/guitar.wav | Bin 0 -> 794202 bytes sounds/revi.mp4 | Bin 0 -> 2048013 bytes 23 files changed, 762 insertions(+), 17 deletions(-) create mode 100644 .posthog/4ff6481c-c37d-4791-bddc-9a8ab007a068/plan.md create mode 100644 .posthog/plan-inline/plan.md create mode 100644 .posthog/serene-sand-hamster/plan.md create mode 100644 .posthog/smart-copper-turtle/plan.md create mode 100644 apps/array/src/renderer/features/task-detail/components/TaskPlanPanel.tsx create mode 100644 sounds/danilo.mp4 create mode 100644 sounds/guitar.wav create mode 100644 sounds/revi.mp4 diff --git a/.posthog/4ff6481c-c37d-4791-bddc-9a8ab007a068/plan.md b/.posthog/4ff6481c-c37d-4791-bddc-9a8ab007a068/plan.md new file mode 100644 index 00000000..07460564 --- /dev/null +++ b/.posthog/4ff6481c-c37d-4791-bddc-9a8ab007a068/plan.md @@ -0,0 +1,10 @@ +# Test Plan + +This was created from the terminal at Wed Dec 10 12:04:40 GMT 2025 + +If you can see this, the Plan tab is working! + + +## LIVE UPDATE TEST Wed Dec 10 12:05:53 GMT 2025 + +This line was just added! diff --git a/.posthog/plan-inline/plan.md b/.posthog/plan-inline/plan.md new file mode 100644 index 00000000..c4257fff --- /dev/null +++ b/.posthog/plan-inline/plan.md @@ -0,0 +1,343 @@ +# Implementation Plan: Inline Editable Plan Tab + +## Overview + +Transform the planning workflow from a separate multi-step process into a single continuous session where: +1. The plan is a visible, editable document in a dedicated Plan tab +2. Users can modify the plan by prompting in the main chat (with full conversation context) +3. Claude writes/updates `plan.md` using tools, keeping everything in one session + +## Current State + +- **Multi-step workflow**: Research → Plan → Build → Finalize (each step can halt) +- **Separate sessions**: Each step runs `query()` which creates fresh context +- **Plan is invisible**: Generated text captured and written to file, not streamed to UI +- **No plan tab**: Plan only visible if you open `.posthog/{taskId}/plan.md` manually + +## Target State + +- **Single continuous session**: One Claude session from start to finish +- **Plan tab**: Dedicated tab showing `plan.md` with real-time updates +- **Bidirectional editing**: User can edit directly OR prompt Claude to update +- **Full context**: "Update the plan to add error handling" works because Claude has full chat history + +--- + +## Implementation Phases + +### Phase 1: Add Plan Tab to UI (No Claude changes) + +**Goal**: Create a Plan tab that displays and allows editing of `plan.md` + +**Changes**: +1. Add `"plan"` to `TabData` union in `panelTypes.ts` +2. Add `PLAN: "plan"` to `DEFAULT_TAB_IDS` in `panelConstants.ts` +3. Create `TaskPlanPanel.tsx` component (wrapper around existing `PlanEditor`) +4. Add `case "plan"` to `TabContentRenderer.tsx` +5. Add `openPlan(taskId)` action to `panelLayoutStore.ts` +6. Auto-open Plan tab when task is selected (alongside Chat tab) + +**Test**: +- Open a task → Plan tab appears +- Edit text in Plan tab → saves to `.posthog/{taskId}/plan.md` +- Reload → edits persist + +**Files to modify**: +- `apps/array/src/renderer/features/panels/store/panelTypes.ts` +- `apps/array/src/renderer/features/panels/constants/panelConstants.ts` +- `apps/array/src/renderer/features/task-detail/components/TaskPlanPanel.tsx` (new) +- `apps/array/src/renderer/features/task-detail/components/TabContentRenderer.tsx` +- `apps/array/src/renderer/features/panels/store/panelLayoutStore.ts` + +--- + +### Phase 2: Real-time Plan File Watching + +**Goal**: Plan tab updates live when `plan.md` changes on disk (from Claude or external edits) + +**Changes**: +1. Add IPC handler `watchPlanFile(repoPath, taskId)` that uses `fs.watch` +2. Add IPC handler `unwatchPlanFile(repoPath, taskId)` for cleanup +3. Add `onPlanFileChange(callback)` to electron API +4. Update `TaskPlanPanel` to subscribe to file changes +5. Invalidate react-query cache when file changes externally + +**Test**: +- Open Plan tab +- Manually edit `.posthog/{taskId}/plan.md` in external editor +- Plan tab updates automatically + +**Files to modify**: +- `apps/array/src/main/services/fs.ts` (add watch handlers) +- `apps/array/src/preload.ts` (expose watch API) +- `apps/array/src/renderer/types/electron.d.ts` (types) +- `apps/array/src/renderer/features/task-detail/components/TaskPlanPanel.tsx` + +--- + +### Phase 3: Add WritePlan Tool to Agent + +**Goal**: Give Claude a dedicated tool to write/update the plan file + +**Changes**: +1. Add `WritePlan` tool definition in `packages/agent/src/tools/registry.ts` +2. Add tool type in `packages/agent/src/tools/types.ts` +3. Implement tool handler in `packages/agent/src/adapters/claude/tools.ts` +4. Tool writes to `.posthog/{taskId}/plan.md` via file manager +5. Include `WritePlan` in allowed tools for the session + +**Tool Schema**: +```typescript +{ + name: "WritePlan", + description: "Write or update the implementation plan. Use this to create initial plans or modify existing ones based on user feedback.", + input_schema: { + type: "object", + properties: { + content: { type: "string", description: "The full plan content in markdown" }, + mode: { + type: "string", + enum: ["replace", "append"], + description: "replace: overwrite entire plan, append: add to end" + } + }, + required: ["content"] + } +} +``` + +**Test**: +- In a session, Claude calls `WritePlan` → file is created/updated +- Plan tab shows the new content (via Phase 2 file watching) + +**Files to modify**: +- `packages/agent/src/tools/registry.ts` +- `packages/agent/src/tools/types.ts` +- `packages/agent/src/adapters/claude/tools.ts` +- `packages/agent/src/adapters/claude/claude.ts` (add to allowed tools) + +--- + +### Phase 4: Add ReadPlan Tool to Agent + +**Goal**: Let Claude read the current plan (including user edits) before modifying + +**Changes**: +1. Add `ReadPlan` tool definition +2. Implement handler that reads `.posthog/{taskId}/plan.md` +3. Returns current content or empty string if no plan exists + +**Tool Schema**: +```typescript +{ + name: "ReadPlan", + description: "Read the current implementation plan. Use this before making modifications to understand what exists.", + input_schema: { + type: "object", + properties: {}, + required: [] + } +} +``` + +**Test**: +- User edits plan in Plan tab +- User prompts "What's in the current plan?" +- Claude calls `ReadPlan` and sees user's edits + +**Files to modify**: +- `packages/agent/src/tools/registry.ts` +- `packages/agent/src/tools/types.ts` +- `packages/agent/src/adapters/claude/tools.ts` + +--- + +### Phase 5: Unified System Prompt for Single-Session Mode + +**Goal**: Create a system prompt that handles planning + execution in one session + +**Changes**: +1. Create `packages/agent/src/agents/unified.ts` with combined prompt +2. Prompt instructs Claude to: + - Use `WritePlan` to create/update implementation plans + - Use `ReadPlan` before modifying to respect user edits + - Transition from planning to implementation when user approves + - Reference the plan during implementation +3. Include guidance on when to update plan vs execute + +**System Prompt Key Points**: +```markdown +You are working on a software task. Your workflow: + +1. PLANNING PHASE + - Analyze the task and codebase + - Use WritePlan to create an implementation plan + - Wait for user approval before implementing + +2. USER INTERACTION + - User may edit the plan directly (use ReadPlan to see changes) + - User may ask you to modify the plan via chat + - When user says "looks good" or "implement", proceed to execution + +3. EXECUTION PHASE + - Follow the plan step by step + - Update the plan if scope changes + - Use TodoWrite to track progress +``` + +**Test**: +- Start new task → Claude creates plan via WritePlan +- Edit plan in UI → prompt "I updated step 2, please review" +- Claude calls ReadPlan, sees changes, acknowledges + +**Files to create/modify**: +- `packages/agent/src/agents/unified.ts` (new) + +--- + +### Phase 6: Single-Session Execution Mode + +**Goal**: Replace multi-step workflow with single continuous session for local mode + +**Changes**: +1. Add new method `runTaskUnified()` in `packages/agent/src/agent.ts` +2. Uses unified system prompt from Phase 5 +3. Single `query()` call with full tool access +4. No workflow steps - Claude manages phases via tools +5. Maintains conversation context throughout + +**Key Implementation**: +```typescript +async runTaskUnified(taskId: string, taskRunId: string, options: TaskExecutionOptions) { + const prompt = buildUnifiedPrompt(task); + + const response = query({ + prompt, + options: { + model: "claude-sonnet-4-5", + cwd: this.workingDirectory, + permissionMode: options.permissionMode ?? "default", + allowedTools: [ + // Read-only tools + "Read", "Glob", "Grep", "WebFetch", "WebSearch", + // Plan tools + "ReadPlan", "WritePlan", "TodoWrite", + // Implementation tools + "Edit", "Write", "Bash", "Task", + // MCP + "ListMcpResources", "ReadMcpResource", + ], + } + }); + + // Stream events to session store + for await (const message of response) { + // ... handle messages, emit to UI + } +} +``` + +**Test**: +- Start task in unified mode +- Claude creates plan → visible in Plan tab +- Say "implement" → Claude begins coding +- Say "wait, add logging to step 2" → Claude updates plan and continues + +**Files to modify**: +- `packages/agent/src/agent.ts` +- `packages/agent/index.ts` (export new method) + +--- + +### Phase 7: Wire Up Unified Mode in Electron App + +**Goal**: Connect the new unified execution mode to the UI + +**Changes**: +1. Update `agentStart` IPC handler to support unified mode +2. Add option to session store for execution mode selection +3. Default to unified mode for new tasks +4. Keep multi-step mode available as fallback + +**Test**: +- Create new task → starts in unified mode +- Full flow: plan created → user edits → user approves → implementation +- All in single session with shared context + +**Files to modify**: +- `apps/array/src/main/services/session-manager.ts` +- `apps/array/src/renderer/features/sessions/stores/sessionStore.ts` +- `apps/array/src/preload.ts` (if new IPC params needed) + +--- + +### Phase 8: Plan Tab Polish + +**Goal**: Enhance Plan tab UX for the new workflow + +**Changes**: +1. Add "Generating..." indicator when Claude is writing plan +2. Add "Approve & Implement" button that sends approval prompt +3. Show plan status: Draft / Approved / Implementing +4. Add diff view option to see what changed +5. Keyboard shortcut to send "implement this plan" from Plan tab + +**Test**: +- Plan generates → "Generating..." shown +- Click "Approve" → sends prompt, implementation starts +- During implementation, plan shows "Implementing" status + +**Files to modify**: +- `apps/array/src/renderer/features/task-detail/components/TaskPlanPanel.tsx` +- `apps/array/src/renderer/features/editor/components/PlanEditor.tsx` + +--- + +## Dependency Graph + +``` +Phase 1 (Plan Tab UI) + ↓ +Phase 2 (File Watching) + ↓ +Phase 3 (WritePlan Tool) ←→ Phase 4 (ReadPlan Tool) + ↓ +Phase 5 (Unified Prompt) + ↓ +Phase 6 (Single-Session Mode) + ↓ +Phase 7 (Electron Wiring) + ↓ +Phase 8 (Polish) +``` + +Phases 1-2 can be tested independently with manual file edits. +Phases 3-4 can be tested in isolation with direct agent calls. +Phases 5-7 require previous phases. +Phase 8 is pure UI enhancement. + +--- + +## Risks and Mitigations + +| Risk | Mitigation | +|------|------------| +| Long sessions may hit context limits | Rely on Claude SDK's automatic summarization | +| User edits conflict with Claude's writes | ReadPlan before WritePlan pattern; file watching for UI | +| Permission mode complexity (plan vs edit) | Unified mode uses "default" - Claude asks for confirmation | +| Breaking existing workflow | Keep multi-step mode as fallback; feature flag | + +--- + +## Success Criteria + +1. User can see plan in dedicated tab as it's being generated +2. User can edit plan directly and Claude respects those edits +3. User can prompt "add error handling to step 3" and Claude updates plan +4. Single continuous conversation from planning through implementation +5. No loss of context when asking Claude to modify the plan + + + +## External Edit Test +This line was added from the terminal at Wed Dec 10 11:49:19 GMT 2025 diff --git a/.posthog/serene-sand-hamster/plan.md b/.posthog/serene-sand-hamster/plan.md new file mode 100644 index 00000000..6b0e59a3 --- /dev/null +++ b/.posthog/serene-sand-hamster/plan.md @@ -0,0 +1,8 @@ +# Test Plan + +This was created from the terminal at Wed Dec 10 11:50:10 GMT 2025 + + +## Live Update Test + +This line was added at Wed Dec 10 11:57:30 GMT 2025 to test real-time file watching! diff --git a/.posthog/smart-copper-turtle/plan.md b/.posthog/smart-copper-turtle/plan.md new file mode 100644 index 00000000..e8e2274d --- /dev/null +++ b/.posthog/smart-copper-turtle/plan.md @@ -0,0 +1,5 @@ +# Test Plan + +This was created from the terminal at Wed Dec 10 11:56:50 GMT 2025 + +If you can see this, file watching is working! diff --git a/apps/array/src/main/services/session-manager.ts b/apps/array/src/main/services/session-manager.ts index 9756c46f..3ade69fe 100644 --- a/apps/array/src/main/services/session-manager.ts +++ b/apps/array/src/main/services/session-manager.ts @@ -276,7 +276,7 @@ export class SessionManager { await connection.newSession({ cwd: repoPath, mcpServers, - _meta: { sessionId: taskRunId }, + _meta: { sessionId: taskRunId, taskId }, }); } diff --git a/apps/array/src/renderer/features/editor/components/PlanEditor.tsx b/apps/array/src/renderer/features/editor/components/PlanEditor.tsx index 55bb21bf..2f271ec2 100644 --- a/apps/array/src/renderer/features/editor/components/PlanEditor.tsx +++ b/apps/array/src/renderer/features/editor/components/PlanEditor.tsx @@ -4,11 +4,46 @@ import { FloppyDiskIcon } from "@phosphor-icons/react"; import { Box, Button, TextArea } from "@radix-ui/themes"; import { logger } from "@renderer/lib/logger"; import { useQuery, useQueryClient } from "@tanstack/react-query"; -import { useCallback, useEffect, useState } from "react"; +import { useCallback, useEffect, useRef, useState } from "react"; import { useHotkeys } from "react-hotkeys-hook"; const log = logger.scope("plan-editor"); +// Hook to watch for external file changes +function usePlanFileWatcher( + repoPath: string | undefined, + taskId: string, + fileName: string, + onFileChanged: () => void, +) { + const onFileChangedRef = useRef(onFileChanged); + onFileChangedRef.current = onFileChanged; + + useEffect(() => { + if (!repoPath || !window.electronAPI?.onFileChanged) return; + + // Build the expected path for the plan file + const expectedPath = `${repoPath}/.posthog/${taskId}/${fileName}`; + + log.debug("Watching for changes to:", expectedPath); + + const unsubscribe = window.electronAPI.onFileChanged( + ({ repoPath: eventRepoPath, filePath }) => { + // Only process events for our repo + if (eventRepoPath !== repoPath) return; + + // Check if the changed file is our plan file + if (filePath === expectedPath) { + log.debug("Plan file changed externally:", filePath); + onFileChangedRef.current(); + } + }, + ); + + return unsubscribe; + }, [repoPath, taskId, fileName]); +} + interface PlanEditorProps { taskId: string; repoPath: string; @@ -29,11 +64,14 @@ export function PlanEditor({ const [content, setContent] = useState(initialContent || ""); const [isSaving, setIsSaving] = useState(false); const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false); + const [hasInitialized, setHasInitialized] = useState(!!initialContent); + const savedContentRef = useRef(initialContent || ""); const updateTabMetadata = usePanelLayoutStore( (state) => state.updateTabMetadata, ); - const isMarkdownFile = fileName.endsWith(".md"); + // Always use plain textarea for plan files - RichTextEditor's markdown round-trip causes issues with live updates + const useRichEditor = false; const queryClient = useQueryClient(); const { data: fetchedContent } = useQuery({ @@ -56,11 +94,49 @@ export function PlanEditor({ }, }); + // Initialize content from fetched data only once useEffect(() => { - if (!initialContent && fetchedContent && content === "") { + if (!hasInitialized && fetchedContent !== undefined) { setContent(fetchedContent); + savedContentRef.current = fetchedContent; + setHasInitialized(true); } - }, [fetchedContent, initialContent, content]); + }, [fetchedContent, hasInitialized]); + + // Handle external file changes + const handleExternalFileChange = useCallback(async () => { + // Refetch the file content + try { + let newContent: string | null = null; + if (fileName === "plan.md") { + newContent = await window.electronAPI?.readPlanFile(repoPath, taskId); + } else { + newContent = await window.electronAPI?.readTaskArtifact( + repoPath, + taskId, + fileName, + ); + } + + if (newContent !== null && newContent !== savedContentRef.current) { + // Only update if the file content actually changed from what we last saved/loaded + setContent(newContent); + savedContentRef.current = newContent; + // Also reset unsaved changes since we just loaded fresh content + setHasUnsavedChanges(false); + queryClient.setQueryData( + ["task-file", repoPath, taskId, fileName], + newContent, + ); + log.debug("Reloaded plan content from external change"); + } + } catch (error) { + log.error("Failed to reload file after external change:", error); + } + }, [repoPath, taskId, fileName, queryClient]); + + // Watch for external file changes + usePlanFileWatcher(repoPath, taskId, fileName, handleExternalFileChange); const handleSave = useCallback( async (contentToSave: string) => { @@ -80,6 +156,7 @@ export function PlanEditor({ ["task-file", repoPath, taskId, fileName], contentToSave, ); + savedContentRef.current = contentToSave; setHasUnsavedChanges(false); } catch (error) { log.error("Failed to save file:", error); @@ -94,10 +171,10 @@ export function PlanEditor({ handleSave(content); }, [content, handleSave]); - // Track unsaved changes + // Track unsaved changes by comparing to last saved content useEffect(() => { - setHasUnsavedChanges(content !== fetchedContent); - }, [content, fetchedContent]); + setHasUnsavedChanges(content !== savedContentRef.current); + }, [content]); // Update tab metadata when unsaved changes state changes useEffect(() => { @@ -115,7 +192,7 @@ export function PlanEditor({ handleManualSave(); } }, - { enableOnFormTags: ["INPUT", "TEXTAREA"] }, + { enableOnFormTags: ["INPUT", "TEXTAREA"], enableOnContentEditable: true }, [hasUnsavedChanges, isSaving, handleManualSave], ); @@ -134,7 +211,7 @@ export function PlanEditor({ overflow: "hidden", }} > - {isMarkdownFile ? ( + {useRichEditor ? ( ) : (