From 0b01a7e84398a7d5414543335fdd4e02f1431a9e Mon Sep 17 00:00:00 2001 From: Kiet Ho Date: Sun, 9 Nov 2025 12:41:52 -0800 Subject: [PATCH 1/4] start command --- .../screens/main/components/PlanView/TaskCard.tsx | 8 +++++++- .../screens/main/components/PlanView/TaskDetailModal.tsx | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) 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..4ea51bfd841 100644 --- a/apps/desktop/src/renderer/screens/main/components/PlanView/TaskCard.tsx +++ b/apps/desktop/src/renderer/screens/main/components/PlanView/TaskCard.tsx @@ -79,12 +79,18 @@ 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) { 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", From daa75200cf666deea8b7616d54830cf38b8991e1 Mon Sep 17 00:00:00 2001 From: Kiet Ho Date: Sun, 9 Nov 2025 12:48:07 -0800 Subject: [PATCH 2/4] remove the command in terminal view --- .../main/components/NewLayout/NewLayoutMain.tsx | 16 +++++++++++++++- .../main/components/PlanView/TaskCard.tsx | 14 +++++--------- 2 files changed, 20 insertions(+), 10 deletions(-) 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..a00c9c37631 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,20 @@ export const NewLayoutMain: React.FC = () => { } }; + // Reload both workspaces and current workspace + const reloadWorkspaceData = async () => { + await loadAllWorkspaces(); + if (currentWorkspace) { + const refreshedWorkspace = await window.ipcRenderer.invoke( + "workspace-get", + currentWorkspace.id, + ); + if (refreshedWorkspace) { + setCurrentWorkspace(refreshedWorkspace); + } + } + }; + // Handle tab selection const handleTabSelect = (worktreeId: string, tabId: string) => { setSelectedWorktreeId(worktreeId); @@ -949,7 +963,7 @@ export const NewLayoutMain: React.FC = () => { currentWorkspace={currentWorkspace} selectedWorktreeId={selectedWorktreeId} onTabSelect={handleTabSelect} - onReload={loadAllWorkspaces} + onReload={reloadWorkspaceData} /> ) : ( // Edit mode - show workspace/terminal view 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 4ea51bfd841..7a24b7e3e9c 100644 --- a/apps/desktop/src/renderer/screens/main/components/PlanView/TaskCard.tsx +++ b/apps/desktop/src/renderer/screens/main/components/PlanView/TaskCard.tsx @@ -93,7 +93,9 @@ export const TaskCard: React.FC = ({ 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, @@ -104,14 +106,8 @@ export const TaskCard: React.FC = ({ // 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); - } + // Select the new tab immediately after reload completes + onTabSelect(targetWorktreeId, newTabId); } } catch (error) { console.error("Error starting task:", error); From 26fe60c17c95f3906a637cdad3b5b178542c23be Mon Sep 17 00:00:00 2001 From: Kiet Ho Date: Sun, 9 Nov 2025 12:50:45 -0800 Subject: [PATCH 3/4] run command on create --- .../src/main/lib/workspace/tab-operations.ts | 18 ++++++++++++++ .../components/MainContent/TabContent.tsx | 24 ------------------- .../main/components/MainContent/Terminal.tsx | 3 --- 3 files changed, 18 insertions(+), 27 deletions(-) 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); From 142c07f9540da141db9ac39ef52a26ec88cab1ab Mon Sep 17 00:00:00 2001 From: Kiet Ho Date: Sun, 9 Nov 2025 12:55:20 -0800 Subject: [PATCH 4/4] fix for demo --- .../components/NewLayout/NewLayoutMain.tsx | 42 ++++++++++++++----- .../main/components/PlanView/KanbanColumn.tsx | 8 ++-- .../main/components/PlanView/PlanView.tsx | 18 ++++---- .../main/components/PlanView/TaskCard.tsx | 12 +++--- .../main/components/PlanView/TaskPage.tsx | 8 ++-- 5 files changed, 54 insertions(+), 34 deletions(-) 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 a00c9c37631..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,17 +374,37 @@ export const NewLayoutMain: React.FC = () => { } }; - // Reload both workspaces and current workspace - const reloadWorkspaceData = async () => { - await loadAllWorkspaces(); - if (currentWorkspace) { - const refreshedWorkspace = await window.ipcRenderer.invoke( - "workspace-get", - currentWorkspace.id, - ); - if (refreshedWorkspace) { - setCurrentWorkspace(refreshedWorkspace); + // 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, + ), + ); } }; @@ -963,7 +983,7 @@ export const NewLayoutMain: React.FC = () => { currentWorkspace={currentWorkspace} selectedWorktreeId={selectedWorktreeId} onTabSelect={handleTabSelect} - onReload={reloadWorkspaceData} + 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 7a24b7e3e9c..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"; @@ -103,10 +103,10 @@ export const TaskCard: React.FC = ({ status: "planning", }); - // Reload workspace to get updated tab data - await onReload(); + // Optimistically add the tab to state + onTabCreated(targetWorktreeId, result.tab); - // Select the new tab immediately after reload completes + // Select the new tab immediately onTabSelect(targetWorktreeId, newTabId); } } catch (error) { 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;