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({
diff --git a/apps/desktop/src/renderer/screens/main/components/MainContent/TabGroup.tsx b/apps/desktop/src/renderer/screens/main/components/MainContent/TabGroup.tsx
index e530b87932a..16db9e3e233 100644
--- a/apps/desktop/src/renderer/screens/main/components/MainContent/TabGroup.tsx
+++ b/apps/desktop/src/renderer/screens/main/components/MainContent/TabGroup.tsx
@@ -193,6 +193,7 @@ export default function TabGroup({
onTabFocus={onTabFocus}
workspaceName={workspaceName}
mainBranch={mainBranch}
+ isVisibleInMosaic={true}
/>
@@ -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 (
+
+ );
+ }
+
+ 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 (