diff --git a/apps/desktop/src/main/lib/workspace/tab-operations.ts b/apps/desktop/src/main/lib/workspace/tab-operations.ts index 6ad3c03dd2a..6e74dee9f0a 100644 --- a/apps/desktop/src/main/lib/workspace/tab-operations.ts +++ b/apps/desktop/src/main/lib/workspace/tab-operations.ts @@ -100,6 +100,24 @@ export async function createTab( configManager.write(config); } + // Create terminal process immediately for terminal tabs with commands + if (tab.type === "terminal" && tab.command) { + const tmuxManager = await import("../tmux-manager").then( + (m) => m.default, + ); + const workingDir = worktree.path || workspace.repoPath; + + // Create terminal with command - it will be executed automatically + await tmuxManager.create({ + id: tab.id, + cwd: workingDir, + command: tab.command, + // Use default dimensions, Terminal component will resize when it loads + cols: 80, + rows: 30, + }); + } + return { success: true, tab }; } catch (error) { console.error("Failed to create tab:", error); diff --git a/apps/desktop/src/renderer/screens/main/components/MainContent/TabContent.tsx b/apps/desktop/src/renderer/screens/main/components/MainContent/TabContent.tsx index 59fdf7e9db9..052c44d5ba3 100644 --- a/apps/desktop/src/renderer/screens/main/components/MainContent/TabContent.tsx +++ b/apps/desktop/src/renderer/screens/main/components/MainContent/TabContent.tsx @@ -177,31 +177,8 @@ function TerminalTabContent({ onFocus, }: TerminalTabContentProps) { const terminalId = tab.id; - const terminalCreatedRef = useRef(false); const isSelected = selectedTabId === tab.id; - // Terminal creation and lifecycle - // NOTE: Actual terminal-create is now deferred to the Terminal component - // so it can pass the correct dimensions when ready - useEffect(() => { - // Execute startup command if specified (only after terminal is created) - if ( - tab.command && - tab.command.trim() !== "" && - !terminalCreatedRef.current - ) { - terminalCreatedRef.current = true; - const commandToExecute = tab.command; - // Wait for terminal to be created and attached - setTimeout(() => { - window.ipcRenderer.invoke("terminal-execute-command", { - id: tab.id, - command: commandToExecute, - }); - }, 1000); - } - }, [tab.id, tab.command]); - // Listen for CWD changes from the main process useEffect(() => { if (!terminalId || !workspaceId || !worktreeId) return; @@ -241,7 +218,6 @@ function TerminalTabContent({ hidden={!isSelected} onFocus={onFocus} cwd={terminalCwd} - command={tab.command} /> ); diff --git a/apps/desktop/src/renderer/screens/main/components/MainContent/Terminal.tsx b/apps/desktop/src/renderer/screens/main/components/MainContent/Terminal.tsx index 9790929e91c..110092e2588 100644 --- a/apps/desktop/src/renderer/screens/main/components/MainContent/Terminal.tsx +++ b/apps/desktop/src/renderer/screens/main/components/MainContent/Terminal.tsx @@ -15,7 +15,6 @@ interface TerminalProps { hidden?: boolean; onFocus?: () => void; cwd?: string; - command?: string | null; } interface TerminalMessage { @@ -76,7 +75,6 @@ export default function TerminalComponent({ hidden = false, onFocus, cwd, - command, }: TerminalProps) { const terminalRef = useRef(null); const [terminal, setTerminal] = useState(null); @@ -301,7 +299,6 @@ export default function TerminalComponent({ cwd, cols, rows, - command: command || undefined, }) .catch((error: Error) => { console.error("Failed to create terminal:", error); diff --git a/apps/desktop/src/renderer/screens/main/components/NewLayout/NewLayoutMain.tsx b/apps/desktop/src/renderer/screens/main/components/NewLayout/NewLayoutMain.tsx index 3cc44e509d0..019ea6f6862 100644 --- a/apps/desktop/src/renderer/screens/main/components/NewLayout/NewLayoutMain.tsx +++ b/apps/desktop/src/renderer/screens/main/components/NewLayout/NewLayoutMain.tsx @@ -374,6 +374,40 @@ export const NewLayoutMain: React.FC = () => { } }; + // Optimistically add a tab to the current workspace + const handleTabCreated = (worktreeId: string, tab: Tab) => { + if (!currentWorkspace) return; + + // Find the worktree and add the tab + const updatedWorktrees = currentWorkspace.worktrees.map((wt) => { + if (wt.id === worktreeId) { + return { + ...wt, + tabs: [...wt.tabs, tab], + }; + } + return wt; + }); + + const updatedWorkspace = { + ...currentWorkspace, + worktrees: updatedWorktrees, + activeWorktreeId: worktreeId, + activeTabId: tab.id, + }; + + setCurrentWorkspace(updatedWorkspace); + + // Also update in workspaces array + if (workspaces) { + setWorkspaces( + workspaces.map((ws) => + ws.id === currentWorkspace.id ? updatedWorkspace : ws, + ), + ); + } + }; + // Handle tab selection const handleTabSelect = (worktreeId: string, tabId: string) => { setSelectedWorktreeId(worktreeId); @@ -949,7 +983,7 @@ export const NewLayoutMain: React.FC = () => { currentWorkspace={currentWorkspace} selectedWorktreeId={selectedWorktreeId} onTabSelect={handleTabSelect} - onReload={loadAllWorkspaces} + onTabCreated={handleTabCreated} /> ) : ( // Edit mode - show workspace/terminal view diff --git a/apps/desktop/src/renderer/screens/main/components/PlanView/KanbanColumn.tsx b/apps/desktop/src/renderer/screens/main/components/PlanView/KanbanColumn.tsx index a9eb4b00733..9868a0df91c 100644 --- a/apps/desktop/src/renderer/screens/main/components/PlanView/KanbanColumn.tsx +++ b/apps/desktop/src/renderer/screens/main/components/PlanView/KanbanColumn.tsx @@ -1,6 +1,6 @@ import type { RouterOutputs } from "@superset/api"; import type React from "react"; -import type { Workspace } from "shared/types"; +import type { Tab, Workspace } from "shared/types"; import { TaskCard } from "./TaskCard"; type Task = RouterOutputs["task"]["all"][number]; @@ -13,7 +13,7 @@ interface KanbanColumnProps { currentWorkspace: Workspace | null; selectedWorktreeId: string | null; onTabSelect: (worktreeId: string, tabId: string) => void; - onReload: () => void; + onTabCreated: (worktreeId: string, tab: Tab) => void; onUpdateTask: ( taskId: string, updates: { @@ -33,7 +33,7 @@ export const KanbanColumn: React.FC = ({ currentWorkspace, selectedWorktreeId, onTabSelect, - onReload, + onTabCreated, onUpdateTask, }) => { return ( @@ -61,7 +61,7 @@ export const KanbanColumn: React.FC = ({ currentWorkspace={currentWorkspace} selectedWorktreeId={selectedWorktreeId} onTabSelect={onTabSelect} - onReload={onReload} + onTabCreated={onTabCreated} onUpdateTask={onUpdateTask} /> ))} diff --git a/apps/desktop/src/renderer/screens/main/components/PlanView/PlanView.tsx b/apps/desktop/src/renderer/screens/main/components/PlanView/PlanView.tsx index a76c3e60e8e..69368e87a49 100644 --- a/apps/desktop/src/renderer/screens/main/components/PlanView/PlanView.tsx +++ b/apps/desktop/src/renderer/screens/main/components/PlanView/PlanView.tsx @@ -2,7 +2,7 @@ import type { RouterOutputs } from "@superset/api"; import { Plus } from "lucide-react"; import type React from "react"; import { useMemo, useState } from "react"; -import type { Workspace } from "shared/types"; +import type { Tab, Workspace } from "shared/types"; import { mockTasks, mockUsers } from "../../../../../lib/mock-data"; import { CreateTaskModal } from "./CreateTaskModal"; import { KanbanColumn } from "./KanbanColumn"; @@ -15,14 +15,14 @@ interface PlanViewProps { currentWorkspace: Workspace | null; selectedWorktreeId: string | null; onTabSelect: (worktreeId: string, tabId: string) => void; - onReload: () => void; + onTabCreated: (worktreeId: string, tab: Tab) => void; } export const PlanView: React.FC = ({ currentWorkspace, selectedWorktreeId, onTabSelect, - onReload, + onTabCreated, }) => { // Initialize with mock tasks and add some variety to statuses const [tasks, setTasks] = useState(() => { @@ -144,7 +144,7 @@ export const PlanView: React.FC = ({ currentWorkspace={currentWorkspace} selectedWorktreeId={selectedWorktreeId} onTabSelect={onTabSelect} - onReload={onReload} + onTabCreated={onTabCreated} /> ); } @@ -183,7 +183,7 @@ export const PlanView: React.FC = ({ currentWorkspace={currentWorkspace} selectedWorktreeId={selectedWorktreeId} onTabSelect={onTabSelect} - onReload={onReload} + onTabCreated={onTabCreated} onUpdateTask={handleUpdateTask} /> = ({ currentWorkspace={currentWorkspace} selectedWorktreeId={selectedWorktreeId} onTabSelect={onTabSelect} - onReload={onReload} + onTabCreated={onTabCreated} onUpdateTask={handleUpdateTask} /> = ({ currentWorkspace={currentWorkspace} selectedWorktreeId={selectedWorktreeId} onTabSelect={onTabSelect} - onReload={onReload} + onTabCreated={onTabCreated} onUpdateTask={handleUpdateTask} /> = ({ currentWorkspace={currentWorkspace} selectedWorktreeId={selectedWorktreeId} onTabSelect={onTabSelect} - onReload={onReload} + onTabCreated={onTabCreated} onUpdateTask={handleUpdateTask} /> = ({ currentWorkspace={currentWorkspace} selectedWorktreeId={selectedWorktreeId} onTabSelect={onTabSelect} - onReload={onReload} + onTabCreated={onTabCreated} onUpdateTask={handleUpdateTask} /> diff --git a/apps/desktop/src/renderer/screens/main/components/PlanView/TaskCard.tsx b/apps/desktop/src/renderer/screens/main/components/PlanView/TaskCard.tsx index c3b107d46b5..d5540f0e1eb 100644 --- a/apps/desktop/src/renderer/screens/main/components/PlanView/TaskCard.tsx +++ b/apps/desktop/src/renderer/screens/main/components/PlanView/TaskCard.tsx @@ -2,7 +2,7 @@ import type { RouterOutputs } from "@superset/api"; import { Play } from "lucide-react"; import type React from "react"; import { useState } from "react"; -import type { Workspace } from "shared/types"; +import type { Tab, Workspace } from "shared/types"; type Task = RouterOutputs["task"]["all"][number]; @@ -12,7 +12,7 @@ interface TaskCardProps { currentWorkspace: Workspace | null; selectedWorktreeId: string | null; onTabSelect: (worktreeId: string, tabId: string) => void; - onReload: () => void; + onTabCreated: (worktreeId: string, tab: Tab) => void; onUpdateTask: ( taskId: string, updates: { @@ -41,7 +41,7 @@ export const TaskCard: React.FC = ({ currentWorkspace, selectedWorktreeId, onTabSelect, - onReload, + onTabCreated, onUpdateTask, }) => { const statusColor = statusColors[task.status] || "bg-neutral-500"; @@ -79,15 +79,23 @@ export const TaskCard: React.FC = ({ try { // Create a new terminal with claude command + const taskPrompt = `${task.title}\n\n${task.description || ""}`.trim(); + // Escape quotes and newlines for shell command + const escapedPrompt = taskPrompt + .replace(/\\/g, '\\\\') // Escape backslashes first + .replace(/"/g, '\\"') // Escape double quotes + .replace(/\n/g, '\\n'); // Escape newlines const result = await window.ipcRenderer.invoke("tab-create", { workspaceId: currentWorkspace.id, worktreeId: targetWorktreeId, name: `Task: ${task.slug}`, type: "terminal", - command: `claude "hi"`, + command: `claude "${escapedPrompt}"`, }); - if (result.success) { + if (result.success && result.tab) { + const newTabId = result.tab.id; + // Update task status to planning (pending) onUpdateTask(task.id, { title: task.title, @@ -95,17 +103,11 @@ export const TaskCard: React.FC = ({ status: "planning", }); - // Reload workspace to get updated tab data - await onReload(); - - // Select the new tab after reload - const newTabId = result.tab?.id; - if (newTabId) { - // Small delay to ensure workspace is reloaded - setTimeout(() => { - onTabSelect(targetWorktreeId, newTabId); - }, 100); - } + // Optimistically add the tab to state + onTabCreated(targetWorktreeId, result.tab); + + // Select the new tab immediately + onTabSelect(targetWorktreeId, newTabId); } } catch (error) { console.error("Error starting task:", error); diff --git a/apps/desktop/src/renderer/screens/main/components/PlanView/TaskDetailModal.tsx b/apps/desktop/src/renderer/screens/main/components/PlanView/TaskDetailModal.tsx index b88e24fa592..cb482331282 100644 --- a/apps/desktop/src/renderer/screens/main/components/PlanView/TaskDetailModal.tsx +++ b/apps/desktop/src/renderer/screens/main/components/PlanView/TaskDetailModal.tsx @@ -36,7 +36,7 @@ export const TaskDetailModal: React.FC = ({ const statusLabels: Record = { backlog: "Backlog", todo: "Todo", - planning: "Pending", + planning: "In Progress", working: "Working", "needs-feedback": "Needs Feedback", "ready-to-merge": "Ready to Merge", diff --git a/apps/desktop/src/renderer/screens/main/components/PlanView/TaskPage.tsx b/apps/desktop/src/renderer/screens/main/components/PlanView/TaskPage.tsx index f6b54cfdca8..4bc6d4705fb 100644 --- a/apps/desktop/src/renderer/screens/main/components/PlanView/TaskPage.tsx +++ b/apps/desktop/src/renderer/screens/main/components/PlanView/TaskPage.tsx @@ -2,7 +2,7 @@ import type { RouterOutputs } from "@superset/api"; import { ChevronDown, ChevronLeft, Play, User as UserIcon } from "lucide-react"; import type React from "react"; import { useEffect, useRef, useState } from "react"; -import type { Workspace } from "shared/types"; +import type { Tab, Workspace } from "shared/types"; type Task = RouterOutputs["task"]["all"][number]; type User = RouterOutputs["user"]["all"][number]; @@ -23,7 +23,7 @@ interface TaskPageProps { currentWorkspace: Workspace | null; selectedWorktreeId: string | null; onTabSelect: (worktreeId: string, tabId: string) => void; - onReload: () => void; + onTabCreated: (worktreeId: string, tab: Tab) => void; } const statusColors: Record = { @@ -56,7 +56,7 @@ export const TaskPage: React.FC = ({ currentWorkspace, selectedWorktreeId, onTabSelect, - onReload, + onTabCreated, }) => { const statusColor = statusColors[task.status] || "bg-neutral-500"; const [title, setTitle] = useState(task.title); @@ -175,7 +175,7 @@ export const TaskPage: React.FC = ({ }); // Reload workspace to get updated tab data - await onReload(); + await onTabCreated(); // Select the new tab after reload const newTabId = result.tab?.id;