From 569129ea3fab71b3c0b2409c3fe46bbef83ec860 Mon Sep 17 00:00:00 2001 From: AndyMik90 Date: Sat, 13 Dec 2025 22:51:49 +0100 Subject: [PATCH] feat(terminal): Add task selection dropdown with auto-context loading MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add associatedTaskId field to Terminal interface in terminal-store.ts - Add setAssociatedTask action to update terminal task association - Update TerminalGrid to pass tasks from task store to Terminal components - Add task selection dropdown in Terminal header (shows when Claude active) - Add tooltip showing task description when hovering over terminal title - Implement handleTaskSelect callback that: - Updates terminal title to match selected task - Associates task with terminal - Sends formatted context message to Claude The dropdown filters to only show tasks in 'backlog' (Planning) status. When a task is selected, Claude receives a message with the task context and prompts for confirmation. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .../src/renderer/components/Terminal.tsx | 100 ++++++++++++++++-- .../src/renderer/components/TerminalGrid.tsx | 5 + .../src/renderer/stores/terminal-store.ts | 10 ++ 3 files changed, 109 insertions(+), 6 deletions(-) diff --git a/auto-claude-ui/src/renderer/components/Terminal.tsx b/auto-claude-ui/src/renderer/components/Terminal.tsx index 77a232a6..0f6cecd3 100644 --- a/auto-claude-ui/src/renderer/components/Terminal.tsx +++ b/auto-claude-ui/src/renderer/components/Terminal.tsx @@ -3,10 +3,24 @@ import { Terminal as XTerm } from '@xterm/xterm'; import { FitAddon } from '@xterm/addon-fit'; import { WebLinksAddon } from '@xterm/addon-web-links'; import '@xterm/xterm/css/xterm.css'; -import { X, Sparkles, TerminalSquare } from 'lucide-react'; +import { X, Sparkles, TerminalSquare, ListTodo } from 'lucide-react'; import { Button } from './ui/button'; import { cn } from '../lib/utils'; import { useTerminalStore, type TerminalStatus } from '../stores/terminal-store'; +import type { Task } from '../../shared/types'; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from './ui/select'; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from './ui/tooltip'; interface TerminalProps { id: string; @@ -15,6 +29,7 @@ interface TerminalProps { isActive: boolean; onClose: () => void; onActivate: () => void; + tasks?: Task[]; // Tasks for task selection dropdown } const STATUS_COLORS: Record = { @@ -24,7 +39,7 @@ const STATUS_COLORS: Record = { exited: 'bg-destructive', }; -export function Terminal({ id, cwd, projectPath, isActive, onClose, onActivate }: TerminalProps) { +export function Terminal({ id, cwd, projectPath, isActive, onClose, onActivate, tasks = [] }: TerminalProps) { const terminalRef = useRef(null); const xtermRef = useRef(null); const fitAddonRef = useRef(null); @@ -37,6 +52,15 @@ export function Terminal({ id, cwd, projectPath, isActive, onClose, onActivate } const setClaudeMode = useTerminalStore((state) => state.setClaudeMode); const setClaudeSessionId = useTerminalStore((state) => state.setClaudeSessionId); const updateTerminal = useTerminalStore((state) => state.updateTerminal); + const setAssociatedTask = useTerminalStore((state) => state.setAssociatedTask); + + // Filter tasks to only show backlog (Planning) status tasks for dropdown + const backlogTasks = tasks.filter((t) => t.status === 'backlog'); + + // Find the currently associated task for tooltip + const associatedTask = terminal?.associatedTaskId + ? tasks.find((t) => t.id === terminal.associatedTaskId) + : undefined; const appendOutput = useTerminalStore((state) => state.appendOutput); const clearOutputBuffer = useTerminalStore((state) => state.clearOutputBuffer); @@ -96,13 +120,18 @@ export function Terminal({ id, cwd, projectPath, isActive, onClose, onActivate } fitAddonRef.current = fitAddon; // Replay buffered output if this is a remount (output exists in store) + // Skip replay for restored Claude sessions - they'll clear and resume fresh // Then clear the buffer to prevent duplicate content on subsequent remounts const terminalState = useTerminalStore.getState().terminals.find((t) => t.id === id); - if (terminalState?.outputBuffer) { + if (terminalState?.outputBuffer && !(terminalState.isRestored && terminalState.isClaudeMode)) { xterm.write(terminalState.outputBuffer); // Clear buffer after replay - new output will accumulate fresh // This prevents duplicates when combined with full-screen redraws from TUI apps useTerminalStore.getState().clearOutputBuffer(id); + } else if (terminalState?.isRestored && terminalState.isClaudeMode) { + // For restored Claude sessions, just clear the buffer without replay + // The session will clear screen and start fresh + useTerminalStore.getState().clearOutputBuffer(id); } // Handle terminal input - send to main process @@ -313,6 +342,27 @@ export function Terminal({ id, cwd, projectPath, isActive, onClose, onActivate } } }, [onActivate]); + // Handle task selection from dropdown + const handleTaskSelect = useCallback((taskId: string) => { + const selectedTask = tasks.find((t) => t.id === taskId); + if (!selectedTask) return; + + // Update terminal with task association and title + setAssociatedTask(id, taskId); + updateTerminal(id, { title: selectedTask.title }); + + // Format and send context message to Claude + const contextMessage = `I'm working on: ${selectedTask.title} + +Description: +${selectedTask.description} + +Please confirm you're ready by saying: I'm ready to work on ${selectedTask.title} - Context is loaded.`; + + // Send the context message to the terminal + window.electronAPI.sendTerminalInput(id, contextMessage + '\r'); + }, [id, tasks, setAssociatedTask, updateTerminal]); + return (
- - {terminal?.title || 'Terminal'} - + {/* Terminal title with optional tooltip showing task description */} + {associatedTask ? ( + + + + + {terminal?.title || 'Terminal'} + + + +

{associatedTask.description}

+
+
+
+ ) : ( + + {terminal?.title || 'Terminal'} + + )}
{terminal?.isClaudeMode && ( @@ -337,6 +403,28 @@ export function Terminal({ id, cwd, projectPath, isActive, onClose, onActivate } Claude )} + {/* Task selection dropdown - only show when Claude is active and there are backlog tasks */} + {terminal?.isClaudeMode && backlogTasks.length > 0 && ( + + )}
{!terminal?.isClaudeMode && terminal?.status !== 'exited' && ( diff --git a/auto-claude-ui/src/renderer/components/TerminalGrid.tsx b/auto-claude-ui/src/renderer/components/TerminalGrid.tsx index 797db41c..48ccf5ca 100644 --- a/auto-claude-ui/src/renderer/components/TerminalGrid.tsx +++ b/auto-claude-ui/src/renderer/components/TerminalGrid.tsx @@ -9,6 +9,7 @@ import { Terminal } from './Terminal'; import { Button } from './ui/button'; import { cn } from '../lib/utils'; import { useTerminalStore } from '../stores/terminal-store'; +import { useTaskStore } from '../stores/task-store'; interface TerminalGridProps { projectPath?: string; @@ -23,6 +24,9 @@ export function TerminalGrid({ projectPath }: TerminalGridProps) { const canAddTerminal = useTerminalStore((state) => state.canAddTerminal); const setClaudeMode = useTerminalStore((state) => state.setClaudeMode); + // Get tasks from task store for task selection dropdown in terminals + const tasks = useTaskStore((state) => state.tasks); + // Handle keyboard shortcut for new terminal useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { @@ -167,6 +171,7 @@ export function TerminalGrid({ projectPath }: TerminalGridProps) { isActive={terminal.id === activeTerminalId} onClose={() => handleCloseTerminal(terminal.id)} onActivate={() => setActiveTerminal(terminal.id)} + tasks={tasks} />
diff --git a/auto-claude-ui/src/renderer/stores/terminal-store.ts b/auto-claude-ui/src/renderer/stores/terminal-store.ts index 87579451..09af0e83 100644 --- a/auto-claude-ui/src/renderer/stores/terminal-store.ts +++ b/auto-claude-ui/src/renderer/stores/terminal-store.ts @@ -14,6 +14,7 @@ export interface Terminal { claudeSessionId?: string; // Claude Code session ID for resume outputBuffer: string; // Store terminal output for replay on remount isRestored?: boolean; // Whether this terminal was restored from a saved session + associatedTaskId?: string; // ID of task associated with this terminal (for context loading) } interface TerminalLayout { @@ -40,6 +41,7 @@ interface TerminalState { setTerminalStatus: (id: string, status: TerminalStatus) => void; setClaudeMode: (id: string, isClaudeMode: boolean) => void; setClaudeSessionId: (id: string, sessionId: string) => void; + setAssociatedTask: (id: string, taskId: string | undefined) => void; appendOutput: (id: string, data: string) => void; clearOutputBuffer: (id: string) => void; clearAllTerminals: () => void; @@ -163,6 +165,14 @@ export const useTerminalStore = create((set, get) => ({ })); }, + setAssociatedTask: (id: string, taskId: string | undefined) => { + set((state) => ({ + terminals: state.terminals.map((t) => + t.id === id ? { ...t, associatedTaskId: taskId } : t + ), + })); + }, + appendOutput: (id: string, data: string) => { set((state) => ({ terminals: state.terminals.map((t) =>