diff --git a/.env.example b/.env.example index dda41bee262..17504880b7d 100644 --- a/.env.example +++ b/.env.example @@ -8,7 +8,3 @@ STUB_API_KEY= # Vite dev server port for Electron renderer process # Default: 4927. Auto-increments when creating new worktrees to avoid port conflicts VITE_DEV_SERVER_PORT=4927 - -# Enable new UI layout (workspace tabs at top, worktree sidebar) -# Set to 'true' to enable the new mock UI for visual iteration -ENABLE_NEW_UI=false diff --git a/apps/desktop/electron.vite.config.ts b/apps/desktop/electron.vite.config.ts index f45a88842bf..b782939ab74 100644 --- a/apps/desktop/electron.vite.config.ts +++ b/apps/desktop/electron.vite.config.ts @@ -49,9 +49,6 @@ export default defineConfig({ define: { "process.env.NODE_ENV": JSON.stringify(process.env.NODE_ENV), "process.platform": JSON.stringify(process.platform), - "import.meta.env.ENABLE_NEW_UI": JSON.stringify( - process.env.ENABLE_NEW_UI || "false", - ), "import.meta.env.DEV_SERVER_PORT": JSON.stringify(getPortSync()), }, diff --git a/apps/desktop/src/main/lib/tmux-manager.ts b/apps/desktop/src/main/lib/tmux-manager.ts index 8e2aaf2a3e2..e6b82cb3927 100644 --- a/apps/desktop/src/main/lib/tmux-manager.ts +++ b/apps/desktop/src/main/lib/tmux-manager.ts @@ -1,9 +1,9 @@ +import { spawnSync } from "node:child_process"; import { randomUUID } from "node:crypto"; import { existsSync, readFileSync, writeFileSync } from "node:fs"; import { mkdir } from "node:fs/promises"; import os from "node:os"; import { dirname, join } from "node:path"; -import { spawnSync } from "node:child_process"; import type { BrowserWindow } from "electron"; import { app } from "electron"; import * as pty from "node-pty"; @@ -231,7 +231,9 @@ class TmuxManager { // Resize the tmux window BEFORE attaching to ensure proper dimensions // This is crucial for reconnection after restart - console.log(`[TmuxManager] Resizing tmux window ${sid} to ${cols}x${rows} before attach`); + console.log( + `[TmuxManager] Resizing tmux window ${sid} to ${cols}x${rows} before attach`, + ); const resizeResult = spawnSync("tmux", [ "-L", this.TMUX_SOCKET, @@ -253,13 +255,7 @@ class TmuxManager { // Force tmux to refresh and reflow content at new size // This ensures the pane content is properly wrapped for the new dimensions - spawnSync("tmux", [ - "-L", - this.TMUX_SOCKET, - "refresh-client", - "-t", - sid, - ]); + spawnSync("tmux", ["-L", this.TMUX_SOCKET, "refresh-client", "-t", sid]); // Attach to the session via node-pty console.log(`[TmuxManager] Attaching to session: ${sid}`); @@ -289,10 +285,6 @@ class TmuxManager { // Set up data listener ptyProcess.onData((data: string) => { - // Debug: log what's coming from PTY - if (data.includes("1;2c") || data.includes("0;276")) { - console.log(`[TmuxManager] PTY output from ${sid}:`, JSON.stringify(data), `(length: ${data.length})`); - } this.addTerminalMessage(sid, data); }); @@ -445,7 +437,6 @@ class TmuxManager { const session = this.sessions.get(sid); if (session?.pty) { // Debug: log what's being written - console.log(`[TmuxManager] Writing to ${sid}:`, JSON.stringify(data), `(length: ${data.length})`); session.pty.write(data); return true; } @@ -561,11 +552,8 @@ class TmuxManager { // Return in-memory history if available if (session.outputHistory.length > 0) { - console.log(`[TmuxManager] Returning ${session.outputHistory.length} bytes of cached history for ${sid}`); return session.outputHistory; } - - console.log(`[TmuxManager] No cached history for ${sid}, tmux will send content on attach`); return undefined; } @@ -631,9 +619,7 @@ class TmuxManager { "utf-8", ); - console.log( - `[TmuxManager] Saved ${metadata.length} sessions to disk`, - ); + console.log(`[TmuxManager] Saved ${metadata.length} sessions to disk`); } catch (error) { console.error("[TmuxManager] Failed to save sessions to disk:", error); } diff --git a/apps/desktop/src/renderer/screens/main/MainScreen.tsx b/apps/desktop/src/renderer/screens/main/MainScreen.tsx index 9e60ff27fb4..766661d1e72 100644 --- a/apps/desktop/src/renderer/screens/main/MainScreen.tsx +++ b/apps/desktop/src/renderer/screens/main/MainScreen.tsx @@ -53,9 +53,8 @@ function DroppableMainContent({ return (
{children} {isOver && ( @@ -70,14 +69,11 @@ function DroppableMainContent({ } export function MainScreen() { - // Check if new UI is enabled - const enableNewUI = import.meta.env.ENABLE_NEW_UI === "true"; - - // If new UI is enabled, render the new layout - if (enableNewUI) { - return ; - } + // Use the new layout by default + return ; +} +export function OldMainScreen() { // Otherwise, render the original layout const [isSidebarOpen, setIsSidebarOpen] = useState(true); const [showSidebarOverlay, setShowSidebarOverlay] = useState(false); @@ -1687,18 +1683,16 @@ export function MainScreen() { )} {/* Sidebar overlay when hidden and hovering */} - {!isSidebarOpen && showSidebarOverlay && workspaces && ( + {!isSidebarOpen && showSidebarOverlay && currentWorkspace && (
setShowSidebarOverlay(false)} >
{ @@ -1723,13 +1717,11 @@ export function MainScreen() { onCollapse={() => setIsSidebarOpen(false)} onExpand={() => setIsSidebarOpen(true)} > - {isSidebarOpen && workspaces && ( + {isSidebarOpen && currentWorkspace && ( { @@ -1756,16 +1748,14 @@ export function MainScreen() { panel.expand(); } }} - workspaceName={currentWorkspace?.name} - currentBranch={currentWorkspace?.branch} /> {/* Content Area */} {showDiffView && - diffWorktreeId && - diffWorktree && - currentWorkspace ? ( + diffWorktreeId && + diffWorktree && + currentWorkspace ? ( // Show diff view
void; workspaceName?: string; mainBranch?: string; + isVisibleInMosaic?: boolean; // Whether this tab is visible in a mosaic layout } /** @@ -39,6 +40,7 @@ export default function TabContent({ onTabFocus, workspaceName, mainBranch, + isVisibleInMosaic = false, }: TabContentProps) { const handleFocus = () => { onTabFocus(tab.id); @@ -71,6 +73,7 @@ export default function TabContent({ groupTabId={groupTabId} selectedTabId={selectedTabId} onFocus={handleFocus} + isVisibleInMosaic={isVisibleInMosaic} /> ); @@ -165,6 +168,7 @@ interface TerminalTabContentProps { groupTabId: string; // ID of the parent group tab selectedTabId?: string; // Currently selected tab ID onFocus: () => void; + isVisibleInMosaic?: boolean; // Whether this tab is visible in a mosaic layout } function TerminalTabContent({ @@ -175,10 +179,13 @@ function TerminalTabContent({ groupTabId, selectedTabId, onFocus, + isVisibleInMosaic = false, }: TerminalTabContentProps) { const terminalId = tab.id; const terminalCreatedRef = useRef(false); const isSelected = selectedTabId === tab.id; + // Terminal should be visible if it's either selected OR visible in a mosaic layout + const isVisible = isSelected || isVisibleInMosaic; // Terminal creation and lifecycle // NOTE: Actual terminal-create is now deferred to the Terminal component @@ -234,7 +241,7 @@ function TerminalTabContent({
@@ -259,16 +260,20 @@ export default function TabGroup({ .mosaic-theme-dark .mosaic-window { background: #1a1a1a; border: 1px solid #333; + outline: none; + transition: outline 0.15s ease; } .mosaic-theme-dark .mosaic-window .mosaic-window-toolbar { background: #262626; border-bottom: 1px solid #333; height: 32px; padding: 0 8px; + transition: background-color 0.15s ease; } .mosaic-theme-dark .mosaic-window .mosaic-window-title { color: #e5e5e5; font-size: 12px; + transition: color 0.15s ease; } .mosaic-theme-dark .mosaic-window-body { background: #1a1a1a; @@ -279,9 +284,8 @@ export default function TabGroup({ .mosaic-theme-dark .mosaic-split:hover { background: #444; } - .active-mosaic-window .mosaic-window { - border: 1px solid #3b82f6 !important; - box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.3); + .active-mosaic-window .mosaic-window-toolbar { + background: #3a3a3a !important; } `}
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 7ee9236e4d4..77059b33b1d 100644 --- a/apps/desktop/src/renderer/screens/main/components/NewLayout/NewLayoutMain.tsx +++ b/apps/desktop/src/renderer/screens/main/components/NewLayout/NewLayoutMain.tsx @@ -12,11 +12,11 @@ import { Background } from "../Background"; import TabContent from "../MainContent/TabContent"; import TabGroup from "../MainContent/TabGroup"; import { PlaceholderState } from "../PlaceholderState"; -import { Sidebar } from "../Sidebar"; import { DiffTab } from "../TabContent/components/DiffTab"; import { AddTaskModal } from "./AddTaskModal"; import { TaskTabs } from "./TaskTabs"; import { WorktreeTabView } from "./WorktreeTabView"; +import { WorktreeTabsSidebar } from "./WorktreeTabsSidebar"; // Mock tasks data - TODO: Replace with actual task data from backend const MOCK_TASKS = [ @@ -551,6 +551,13 @@ export const NewLayoutMain: React.FC = () => { if (activeSelection?.worktreeId && activeSelection?.tabId) { setSelectedWorktreeId(activeSelection.worktreeId); setSelectedTabId(activeSelection.tabId); + } else if (workspace.worktrees && workspace.worktrees.length > 0) { + // Auto-select first worktree and its first tab if no selection exists + const firstWorktree = workspace.worktrees[0]; + setSelectedWorktreeId(firstWorktree.id); + if (firstWorktree.tabs && firstWorktree.tabs.length > 0) { + setSelectedTabId(firstWorktree.tabs[0].id); + } } } } @@ -585,6 +592,15 @@ export const NewLayoutMain: React.FC = () => { ); if (refreshedWorkspace) { setCurrentWorkspace(refreshedWorkspace); + + // Auto-select first worktree and tab if available + if (refreshedWorkspace.worktrees && refreshedWorkspace.worktrees.length > 0) { + const firstWorktree = refreshedWorkspace.worktrees[0]; + setSelectedWorktreeId(firstWorktree.id); + if (firstWorktree.tabs && firstWorktree.tabs.length > 0) { + setSelectedTabId(firstWorktree.tabs[0].id); + } + } } }; @@ -612,18 +628,61 @@ export const NewLayoutMain: React.FC = () => { onMouseLeave={() => setShowSidebarOverlay(false)} >
- { + { + if (selectedWorktreeId) { + handleTabSelect(selectedWorktreeId, tabId); + } setShowSidebarOverlay(false); }} - onShowDiff={handleShowDiff} + onTabClose={async (tabId) => { + if (!currentWorkspace || !selectedWorktreeId) return; + + const result = await window.ipcRenderer.invoke("tab-delete", { + workspaceId: currentWorkspace.id, + worktreeId: selectedWorktreeId, + tabId, + }); + + if (result.success) { + await handleWorktreeCreated(); + } + }} + onCreateTerminal={async () => { + if (!currentWorkspace || !selectedWorktreeId) return; + + const result = await window.ipcRenderer.invoke("tab-create", { + workspaceId: currentWorkspace.id, + worktreeId: selectedWorktreeId, + name: "Terminal", + type: "terminal", + }); + + if (result.success && result.tab) { + handleTabSelect(selectedWorktreeId, result.tab.id); + await handleWorktreeCreated(); + } + setShowSidebarOverlay(false); + }} + onCreatePreview={async () => { + if (!currentWorkspace || !selectedWorktreeId) return; + + const result = await window.ipcRenderer.invoke("tab-create", { + workspaceId: currentWorkspace.id, + worktreeId: selectedWorktreeId, + name: "Preview", + type: "preview", + }); + + if (result.success && result.tab) { + handleTabSelect(selectedWorktreeId, result.tab.id); + await handleWorktreeCreated(); + } + setShowSidebarOverlay(false); + }} + workspaceId={currentWorkspace?.id || null} />
@@ -631,15 +690,21 @@ export const NewLayoutMain: React.FC = () => {
- {/* Task tabs at the top */} + {/* Worktree tabs at the top */} { + setSelectedWorktreeId(worktreeId); + // Select first tab in the worktree + const worktree = currentWorkspace?.worktrees?.find(wt => wt.id === worktreeId); + if (worktree && worktree.tabs && worktree.tabs.length > 0) { + handleTabSelect(worktreeId, worktree.tabs[0].id); + } + }} /> {/* Main content area with resizable sidebar */} @@ -658,22 +723,59 @@ export const NewLayoutMain: React.FC = () => { onCollapse={() => setIsSidebarOpen(false)} onExpand={() => setIsSidebarOpen(true)} > - {isSidebarOpen && workspaces && ( - { - const panel = sidebarPanelRef.current; - if (panel && !panel.isCollapsed()) { - panel.collapse(); + {isSidebarOpen && ( + { + if (selectedWorktreeId) { + handleTabSelect(selectedWorktreeId, tabId); + } + }} + onTabClose={async (tabId) => { + if (!currentWorkspace || !selectedWorktreeId) return; + + const result = await window.ipcRenderer.invoke("tab-delete", { + workspaceId: currentWorkspace.id, + worktreeId: selectedWorktreeId, + tabId, + }); + + if (result.success) { + await handleWorktreeCreated(); + } + }} + onCreateTerminal={async () => { + if (!currentWorkspace || !selectedWorktreeId) return; + + const result = await window.ipcRenderer.invoke("tab-create", { + workspaceId: currentWorkspace.id, + worktreeId: selectedWorktreeId, + name: "Terminal", + type: "terminal", + }); + + if (result.success && result.tab) { + handleTabSelect(selectedWorktreeId, result.tab.id); + await handleWorktreeCreated(); + } + }} + onCreatePreview={async () => { + if (!currentWorkspace || !selectedWorktreeId) return; + + const result = await window.ipcRenderer.invoke("tab-create", { + workspaceId: currentWorkspace.id, + worktreeId: selectedWorktreeId, + name: "Preview", + type: "preview", + }); + + if (result.success && result.tab) { + handleTabSelect(selectedWorktreeId, result.tab.id); + await handleWorktreeCreated(); } }} - onShowDiff={handleShowDiff} + workspaceId={currentWorkspace?.id || null} /> )} diff --git a/apps/desktop/src/renderer/screens/main/components/NewLayout/TaskTabs.tsx b/apps/desktop/src/renderer/screens/main/components/NewLayout/TaskTabs.tsx index aa3bcb8d2d7..701fb50eb7c 100644 --- a/apps/desktop/src/renderer/screens/main/components/NewLayout/TaskTabs.tsx +++ b/apps/desktop/src/renderer/screens/main/components/NewLayout/TaskTabs.tsx @@ -5,91 +5,27 @@ import { HoverCardTrigger, } from "@superset/ui/hover-card"; import { Tooltip, TooltipContent, TooltipTrigger } from "@superset/ui/tooltip"; -import { PanelLeftClose, PanelLeftOpen, Plus } from "lucide-react"; +import { PanelLeftClose, PanelLeftOpen } from "lucide-react"; import type React from "react"; -import { useState } from "react"; -import { TaskAssignee } from "./TaskAssignee"; -import { StatusIndicator, type TaskStatus } from "./StatusIndicator"; +import type { Worktree } from "shared/types"; -interface MockTask { - id: string; - slug: string; - name: string; - status: TaskStatus; - branch: string; - description: string; - assignee: string; - assigneeAvatarUrl: string; - lastUpdated: string; -} - -const MOCK_TASKS: MockTask[] = [ - { - id: "1", - slug: "SSET-1", - name: "Homepage Redesign", - status: "working", - branch: "feature/homepage-redesign", - description: "Redesigning the homepage with new branding and improved UX", - assignee: "Alice", - assigneeAvatarUrl: "https://i.pravatar.cc/150?img=1", - lastUpdated: "2 hours ago", - }, - { - id: "2", - slug: "SSET-2", - name: "API Integration", - status: "needs-feedback", - branch: "feature/api-integration", - description: "Integrate new REST API endpoints for user management", - assignee: "Bob", - assigneeAvatarUrl: "https://i.pravatar.cc/150?img=12", - lastUpdated: "1 day ago", - }, - { - id: "3", - slug: "SSET-3", - name: "Bug Fixes", - status: "planning", - branch: "fix/various-bugs", - description: "Collection of bug fixes reported by users", - assignee: "Charlie", - assigneeAvatarUrl: "https://i.pravatar.cc/150?img=33", - lastUpdated: "3 days ago", - }, - { - id: "4", - slug: "SSET-4", - name: "Performance Optimization", - status: "ready-to-merge", - branch: "perf/optimize-queries", - description: "Optimize database queries for faster page loads", - assignee: "Diana", - assigneeAvatarUrl: "https://i.pravatar.cc/150?img=9", - lastUpdated: "5 minutes ago", - }, -]; - -interface TaskTabsProps { +interface WorktreeTabsProps { onCollapseSidebar: () => void; onExpandSidebar: () => void; isSidebarOpen: boolean; - onAddTask: () => void; - activeTaskId: string; - onActiveTaskChange: (taskId: string) => void; - openTasks: MockTask[]; + worktrees: Worktree[]; + selectedWorktreeId: string | null; + onWorktreeSelect: (worktreeId: string) => void; } -export const TaskTabs: React.FC = ({ +export const TaskTabs: React.FC = ({ onCollapseSidebar, onExpandSidebar, isSidebarOpen, - onAddTask, - activeTaskId, - onActiveTaskChange, - openTasks, + worktrees, + selectedWorktreeId, + onWorktreeSelect, }) => { - return (
= ({ )}
- {/* Task tabs */} - {openTasks.map((task) => { - const statusLabel = task.status === "planning" ? "Planning" : - task.status === "working" ? "Working" : - task.status === "needs-feedback" ? "Needs Feedback" : - "Ready to Merge"; + {/* Worktree tabs */} + {worktrees.map((worktree) => { + // Use description as title if available, otherwise use branch name + const displayTitle = worktree.description || worktree.branch; + + // Determine status color based on worktree state + const hasActivity = worktree.tabs && worktree.tabs.length > 0; + const hasPorts = + worktree.detectedPorts && + Object.keys(worktree.detectedPorts).length > 0; + const statusColor = hasPorts + ? "rgb(34, 197, 94)" // green - has running services + : hasActivity + ? "rgb(234, 179, 8)" // yellow - has tabs/activity + : "rgb(156, 163, 175)"; // gray - inactive return ( - +
- {/* Header with task slug/name and assignee */} + {/* Header with title */}
-

- [{task.slug}] {task.name} -

-

- {task.description} -

-
- - {/* Assignee in top-right */} -
- + {worktree.description ? ( + <> +

+ {worktree.description} +

+

+ Branch: {worktree.branch} +

+ + ) : ( +

+ {worktree.branch} +

+ )}
@@ -195,26 +163,52 @@ export const TaskTabs: React.FC = ({
Status
- - {statusLabel} +
+ + + +
+ + {hasPorts + ? "Running" + : hasActivity + ? "Active" + : "Inactive"} +
-
- Updated - - {task.lastUpdated} - -
+ {worktree.tabs && worktree.tabs.length > 0 && ( +
+ Tabs + + {worktree.tabs.length} + +
+ )} + + {!worktree.description && ( +
+ Branch + + {worktree.branch} + +
+ )}
- Branch + Path - {task.branch} + {worktree.path}
@@ -223,16 +217,6 @@ export const TaskTabs: React.FC = ({
); })} - - - - - -

Open task

-
-
); diff --git a/apps/desktop/src/renderer/screens/main/components/NewLayout/WorktreeTabsSidebar.tsx b/apps/desktop/src/renderer/screens/main/components/NewLayout/WorktreeTabsSidebar.tsx new file mode 100644 index 00000000000..bfe83dc7732 --- /dev/null +++ b/apps/desktop/src/renderer/screens/main/components/NewLayout/WorktreeTabsSidebar.tsx @@ -0,0 +1,149 @@ +import { Button } from "@superset/ui/button"; +import { Tooltip, TooltipContent, TooltipTrigger } from "@superset/ui/tooltip"; +import { Monitor, Plus, Terminal as TerminalIcon, X } from "lucide-react"; +import type React from "react"; +import type { Tab, Worktree } from "shared/types"; + +interface WorktreeTabsSidebarProps { + worktree: Worktree | null; + selectedTabId: string | null; + onTabSelect: (tabId: string) => void; + onTabClose: (tabId: string) => void; + onCreateTerminal: () => void; + onCreatePreview: () => void; + workspaceId: string | null; +} + +export const WorktreeTabsSidebar: React.FC = ({ + worktree, + selectedTabId, + onTabSelect, + onTabClose, + onCreateTerminal, + onCreatePreview, + workspaceId, +}) => { + if (!worktree || !workspaceId) { + return ( +
+

No worktree selected

+
+ ); + } + + const tabs = worktree.tabs || []; + + // Helper to get icon for tab type + const getTabIcon = (tab: Tab) => { + switch (tab.type) { + case "terminal": + return ; + case "preview": + return ; + case "diff": + return ; + default: + return ; + } + }; + + // Flatten tabs recursively (handle group tabs) + const flattenTabs = (tabs: Tab[], level = 0): Array<{ tab: Tab; level: number }> => { + const result: Array<{ tab: Tab; level: number }> = []; + for (const tab of tabs) { + result.push({ tab, level }); + if (tab.type === "group" && tab.tabs) { + result.push(...flattenTabs(tab.tabs, level + 1)); + } + } + return result; + }; + + const flatTabs = flattenTabs(tabs); + + return ( +
+ {/* Header with actions */} +
+

Tabs

+
+ + + + + +

New Terminal

+
+
+ + + + + +

New Preview

+
+
+
+
+ + {/* Tab list */} +
+ {flatTabs.length === 0 ? ( +
+ No tabs yet. Create a terminal or preview to get started. +
+ ) : ( +
+ {flatTabs.map(({ tab, level }) => ( + + )} + + ))} +
+ )} +
+
+ ); +}; diff --git a/apps/desktop/src/renderer/screens/main/components/Sidebar/Sidebar.tsx b/apps/desktop/src/renderer/screens/main/components/Sidebar/Sidebar.tsx index bc311ad67d7..3d842e906f5 100644 --- a/apps/desktop/src/renderer/screens/main/components/Sidebar/Sidebar.tsx +++ b/apps/desktop/src/renderer/screens/main/components/Sidebar/Sidebar.tsx @@ -1,23 +1,17 @@ -import { type MotionValue, useMotionValue } from "framer-motion"; import { useEffect, useState } from "react"; import type { Workspace, Worktree } from "shared/types"; import { CreateWorktreeButton, CreateWorktreeModal, SidebarHeader, - WorkspaceCarousel, - WorkspacePortIndicator, - WorkspaceSwitcher, WorktreeList, } from "./components"; interface SidebarProps { - workspaces: Workspace[]; currentWorkspace: Workspace | null; onCollapse: () => void; onTabSelect: (worktreeId: string, tabId: string) => void; onWorktreeCreated: () => void; - onWorkspaceSelect: (workspaceId: string) => void; onUpdateWorktree: (worktreeId: string, updatedWorktree: Worktree) => void; selectedTabId: string | undefined; isDragging?: boolean; @@ -25,12 +19,10 @@ interface SidebarProps { } export function Sidebar({ - workspaces, currentWorkspace, onCollapse, onTabSelect, onWorktreeCreated, - onWorkspaceSelect, onUpdateWorktree, selectedTabId, isDragging = false, @@ -51,16 +43,6 @@ export function Sidebar({ const [setupStatus, setSetupStatus] = useState(undefined); const [setupOutput, setSetupOutput] = useState(undefined); - // Initialize with current workspace index - const currentIndex = workspaces.findIndex( - (w) => w.id === currentWorkspace?.id, - ); - const initialIndex = currentIndex >= 0 ? currentIndex : 0; - const defaultScrollProgress = useMotionValue(initialIndex); - const [scrollProgress, setScrollProgress] = useState>( - defaultScrollProgress, - ); - // Auto-expand worktree if it contains the selected tab useEffect(() => { if (currentWorkspace && selectedTabId) { @@ -230,43 +212,6 @@ export function Sidebar({ setSetupOutput(undefined); }; - const handleAddWorkspace = () => { - // Trigger the File -> Open Repository menu action - window.ipcRenderer.send("open-repository"); - }; - - const handleRemoveWorkspace = async ( - workspaceId: string, - workspaceName: string, - ) => { - // Confirm deletion - const confirmed = window.confirm( - `Remove workspace "${workspaceName}"?\n\nAll terminal sessions for this workspace will be closed.`, - ); - - if (!confirmed) return; - - try { - const result = await window.ipcRenderer.invoke("workspace-delete", { - id: workspaceId, - removeWorktree: false, - }); - if (result.success) { - // If we deleted the current workspace, clear selection - if (currentWorkspace?.id === workspaceId) { - onWorkspaceSelect(""); - } - // Refresh will happen via workspace-opened event - window.location.reload(); - } else { - alert(`Failed to remove workspace: ${result.error || "Unknown error"}`); - } - } catch (error) { - console.error("Error removing workspace:", error); - alert(`Error: ${error instanceof Error ? error.message : String(error)}`); - } - }; - const handleScanWorktrees = async () => { if (!currentWorkspace) return; @@ -299,38 +244,35 @@ export function Sidebar({ return (
- - {(workspace, isActive) => ( - <> - + + +
+ - {workspace && ( - - )} - + {currentWorkspace && ( + )} - +
void; - workspaceName?: string; - currentBranch?: string; } export function TopBar({ isSidebarOpen, onOpenSidebar, - workspaceName, - currentBranch, }: TopBarProps) { return (