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 16db9e3e233..a390f477095 100644 --- a/apps/desktop/src/renderer/screens/main/components/MainContent/TabGroup.tsx +++ b/apps/desktop/src/renderer/screens/main/components/MainContent/TabGroup.tsx @@ -280,9 +280,18 @@ export default function TabGroup({ } .mosaic-theme-dark .mosaic-split { background: #333; + opacity: 0; + border-radius: 25px; + transition: opacity 0.2s ease, background-color 0.2s ease; } .mosaic-theme-dark .mosaic-split:hover { background: #444; + opacity: 1; + } + .mosaic-theme-dark .mosaic-split:active, + .mosaic-theme-dark .mosaic-split.mosaic-split-dragging { + background: #555; + opacity: 1; } .active-mosaic-window .mosaic-window-toolbar { background: #3a3a3a !important; 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 bc26a27cd9e..3f1883fa5c0 100644 --- a/apps/desktop/src/renderer/screens/main/components/MainContent/Terminal.tsx +++ b/apps/desktop/src/renderer/screens/main/components/MainContent/Terminal.tsx @@ -84,9 +84,6 @@ export default function TerminalComponent({ const fitFunctionRef = useRef<(() => void) | null>(null); const hasBeenVisibleRef = useRef(false); - // Log visibility changes for debugging - console.log(`[Terminal] Render: ${terminalId?.slice(0, 8)} hidden=${hidden} terminal=${!!terminal}`); - // Update the ref when onFocus changes useEffect(() => { onFocusRef.current = onFocus; @@ -101,8 +98,6 @@ export default function TerminalComponent({ // Handle visibility changes - fit terminal when it becomes visible useEffect(() => { - console.log(`[Terminal] Visibility effect fired: ${terminalIdRef.current?.slice(0, 8)} hidden=${hidden} terminal=${!!terminal} hasBeenVisible=${hasBeenVisibleRef.current}`); - if (!hidden && terminal && fitFunctionRef.current && terminalRef.current) { const isFirstTimeVisible = !hasBeenVisibleRef.current; hasBeenVisibleRef.current = true; @@ -116,14 +111,10 @@ export default function TerminalComponent({ const tryFit = () => { const rect = terminalRef.current?.getBoundingClientRect(); if (rect && rect.width > 0 && rect.height > 0) { - console.log(`[Terminal] Fitting terminal ${terminalIdRef.current?.slice(0, 8)} after becoming visible (attempt ${attempts + 1}, firstTime=${isFirstTimeVisible})`); fitFunctionRef.current?.(); } else if (attempts < maxAttempts) { attempts++; - console.log(`[Terminal] Container not ready for ${terminalIdRef.current?.slice(0, 8)}, retrying... (${rect?.width}x${rect?.height})`); setTimeout(tryFit, retryDelay); - } else { - console.warn(`[Terminal] Failed to fit ${terminalIdRef.current} after ${maxAttempts} attempts`); } }; @@ -145,16 +136,12 @@ export default function TerminalComponent({ return; } - console.log(`[Terminal] Initializing terminal: ${terminalId.slice(0, 8)}`); - // Set terminalIdRef immediately to prevent race conditions terminalIdRef.current = terminalId; const { term } = initTerminal(terminalRef.current, theme, onFocusRef); setTerminal(term); - console.log(`[Terminal] Initialized terminal: ${terminalId.slice(0, 8)}`); - return () => { // Don't dispose XTerm or cleanup on unmount // XTerm instances should persist through reordering @@ -232,7 +219,6 @@ export default function TerminalComponent({ const height = rect.height; if (width <= 0 || height <= 0) { - console.log(`[Terminal] Skipping fit for ${terminalIdRef.current} - container has no dimensions (${width}x${height})`); return; // Skip if container has no dimensions yet } @@ -240,7 +226,6 @@ export default function TerminalComponent({ // Then manually resize to ensure PTY gets the correct dimensions const dimensions = fitAddon.proposeDimensions(); if (dimensions) { - console.log(`[Terminal] Fitting ${terminalIdRef.current} to ${dimensions.cols}x${dimensions.rows}`); term.resize(dimensions.cols, dimensions.rows); } } catch (e) { @@ -276,7 +261,6 @@ export default function TerminalComponent({ const cols = dimensions?.cols || 80; const rows = dimensions?.rows || 30; - console.log(`[Terminal] Creating terminal ${terminalId.slice(0, 8)} with dimensions ${cols}x${rows}`); window.ipcRenderer.invoke("terminal-create", { id: terminalId, cwd, @@ -373,12 +357,9 @@ export default function TerminalComponent({ data === '\x1b[O'; // Focus out event if (isTerminalResponse) { - console.log(`[Terminal] Filtered terminal response for ${terminalIdRef.current}:`, JSON.stringify(data)); return; } - // Debug: log user input - console.log(`[Terminal] User input for ${terminalIdRef.current}:`, JSON.stringify(data), `(length: ${data.length})`); window.ipcRenderer.send("terminal-input", { id: terminalIdRef.current, data, @@ -421,10 +402,6 @@ export default function TerminalComponent({ const terminalDataListener = (message: TerminalMessage) => { if (message?.id === terminalIdRef.current) { - // Debug: log data being written to xterm - if (message.data.includes("1;2c") || message.data.includes("0;276")) { - console.log(`[Terminal] Received data for ${terminalIdRef.current}:`, JSON.stringify(message.data), `(length: ${message.data.length})`); - } // If we're in the middle of a resize, queue the write if (isResizing) { writeQueue.push(message.data); 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 77059b33b1d..a35c98e9cab 100644 --- a/apps/desktop/src/renderer/screens/main/components/NewLayout/NewLayoutMain.tsx +++ b/apps/desktop/src/renderer/screens/main/components/NewLayout/NewLayoutMain.tsx @@ -15,8 +15,8 @@ import { PlaceholderState } from "../PlaceholderState"; import { DiffTab } from "../TabContent/components/DiffTab"; import { AddTaskModal } from "./AddTaskModal"; import { TaskTabs } from "./TaskTabs"; -import { WorktreeTabView } from "./WorktreeTabView"; import { WorktreeTabsSidebar } from "./WorktreeTabsSidebar"; +import { WorktreeTabView } from "./WorktreeTabView"; // Mock tasks data - TODO: Replace with actual task data from backend const MOCK_TASKS = [ @@ -29,7 +29,7 @@ const MOCK_TASKS = [ description: "Redesigning the homepage with new branding and improved UX", assignee: "Alice", assigneeAvatarUrl: "https://i.pravatar.cc/150?img=1", - lastUpdated: "2 hours ago" + lastUpdated: "2 hours ago", }, { id: "2", @@ -40,7 +40,7 @@ const MOCK_TASKS = [ description: "Integrate new REST API endpoints for user management", assignee: "Bob", assigneeAvatarUrl: "https://i.pravatar.cc/150?img=12", - lastUpdated: "1 day ago" + lastUpdated: "1 day ago", }, { id: "3", @@ -51,7 +51,7 @@ const MOCK_TASKS = [ description: "Collection of bug fixes reported by users", assignee: "Charlie", assigneeAvatarUrl: "https://i.pravatar.cc/150?img=33", - lastUpdated: "3 days ago" + lastUpdated: "3 days ago", }, { id: "4", @@ -62,7 +62,7 @@ const MOCK_TASKS = [ description: "Optimize database queries for faster page loads", assignee: "Diana", assigneeAvatarUrl: "https://i.pravatar.cc/150?img=9", - lastUpdated: "5 minutes ago" + lastUpdated: "5 minutes ago", }, { id: "5", @@ -70,10 +70,11 @@ const MOCK_TASKS = [ name: "User Authentication System", status: "working" as const, branch: "feature/auth-system", - description: "Implement OAuth2 and JWT-based authentication system with refresh tokens", + description: + "Implement OAuth2 and JWT-based authentication system with refresh tokens", assignee: "Eve", assigneeAvatarUrl: "https://i.pravatar.cc/150?img=5", - lastUpdated: "3 hours ago" + lastUpdated: "3 hours ago", }, { id: "6", @@ -84,7 +85,7 @@ const MOCK_TASKS = [ description: "Add dark mode theme support across the entire application", assignee: "Frank", assigneeAvatarUrl: "https://i.pravatar.cc/150?img=13", - lastUpdated: "2 days ago" + lastUpdated: "2 days ago", }, { id: "7", @@ -92,10 +93,11 @@ const MOCK_TASKS = [ name: "Database Migration Scripts", status: "ready-to-merge" as const, branch: "db/migration-scripts", - description: "Create automated migration scripts for production database updates", + description: + "Create automated migration scripts for production database updates", assignee: "Grace", assigneeAvatarUrl: "https://i.pravatar.cc/150?img=20", - lastUpdated: "1 hour ago" + lastUpdated: "1 hour ago", }, { id: "8", @@ -103,10 +105,11 @@ const MOCK_TASKS = [ name: "Email Notification Service", status: "needs-feedback" as const, branch: "feature/email-notifications", - description: "Build email notification service using SendGrid for transactional emails", + description: + "Build email notification service using SendGrid for transactional emails", assignee: "Henry", assigneeAvatarUrl: "https://i.pravatar.cc/150?img=8", - lastUpdated: "4 hours ago" + lastUpdated: "4 hours ago", }, { id: "9", @@ -114,10 +117,11 @@ const MOCK_TASKS = [ name: "Mobile Responsive Design", status: "working" as const, branch: "feature/mobile-responsive", - description: "Make the application fully responsive for mobile and tablet devices", + description: + "Make the application fully responsive for mobile and tablet devices", assignee: "Iris", assigneeAvatarUrl: "https://i.pravatar.cc/150?img=16", - lastUpdated: "6 hours ago" + lastUpdated: "6 hours ago", }, { id: "10", @@ -125,10 +129,11 @@ const MOCK_TASKS = [ name: "Analytics Dashboard", status: "planning" as const, branch: "feature/analytics-dashboard", - description: "Create admin dashboard with charts and metrics for user analytics", + description: + "Create admin dashboard with charts and metrics for user analytics", assignee: "Jack", assigneeAvatarUrl: "https://i.pravatar.cc/150?img=11", - lastUpdated: "1 week ago" + lastUpdated: "1 week ago", }, { id: "11", @@ -136,10 +141,11 @@ const MOCK_TASKS = [ name: "CI/CD Pipeline", status: "ready-to-merge" as const, branch: "devops/ci-cd-pipeline", - description: "Set up automated CI/CD pipeline with GitHub Actions and Docker", + description: + "Set up automated CI/CD pipeline with GitHub Actions and Docker", assignee: "Kate", assigneeAvatarUrl: "https://i.pravatar.cc/150?img=25", - lastUpdated: "30 minutes ago" + lastUpdated: "30 minutes ago", }, { id: "12", @@ -150,7 +156,7 @@ const MOCK_TASKS = [ description: "Implement full-text search with Elasticsearch integration", assignee: "Liam", assigneeAvatarUrl: "https://i.pravatar.cc/150?img=14", - lastUpdated: "5 hours ago" + lastUpdated: "5 hours ago", }, { id: "13", @@ -158,10 +164,11 @@ const MOCK_TASKS = [ name: "File Upload System", status: "needs-feedback" as const, branch: "feature/file-uploads", - description: "Build secure file upload system with S3 storage and virus scanning", + description: + "Build secure file upload system with S3 storage and virus scanning", assignee: "Mia", assigneeAvatarUrl: "https://i.pravatar.cc/150?img=27", - lastUpdated: "2 hours ago" + lastUpdated: "2 hours ago", }, { id: "14", @@ -172,7 +179,7 @@ const MOCK_TASKS = [ description: "Implement rate limiting and throttling for API endpoints", assignee: "Noah", assigneeAvatarUrl: "https://i.pravatar.cc/150?img=17", - lastUpdated: "4 days ago" + lastUpdated: "4 days ago", }, { id: "15", @@ -180,10 +187,11 @@ const MOCK_TASKS = [ name: "Internationalization", status: "working" as const, branch: "feature/i18n", - description: "Add multi-language support with i18next for English, Spanish, and French", + description: + "Add multi-language support with i18next for English, Spanish, and French", assignee: "Olivia", assigneeAvatarUrl: "https://i.pravatar.cc/150?img=32", - lastUpdated: "8 hours ago" + lastUpdated: "8 hours ago", }, ]; @@ -194,7 +202,7 @@ export const NewLayoutMain: React.FC = () => { const [isAddTaskModalOpen, setIsAddTaskModalOpen] = useState(false); // Initialize with first 4 tasks to match the tabs currently displayed const [openTasks, setOpenTasks] = useState( - MOCK_TASKS.slice(0, 4) + MOCK_TASKS.slice(0, 4), ); const [activeTaskId, setActiveTaskId] = useState(MOCK_TASKS[0].id); const [allTasks, setAllTasks] = useState(MOCK_TASKS); @@ -464,6 +472,46 @@ export const NewLayoutMain: React.FC = () => { } }; + // Workspace handlers + const handleAddWorkspace = () => { + // TODO: Implement workspace creation dialog + console.log("Add workspace - not yet implemented"); + }; + + const handleRemoveWorkspace = async ( + workspaceId: string, + workspaceName: string, + ) => { + // Show confirmation dialog + const confirmed = confirm( + `Are you sure you want to remove workspace "${workspaceName}"? This will not delete any files.`, + ); + + if (confirmed) { + try { + await window.ipcRenderer.invoke("workspace-delete", { + id: workspaceId, + removeWorktree: false, + }); + // Reload workspaces + await loadAllWorkspaces(); + // If we removed the current workspace, switch to another one + if (currentWorkspace?.id === workspaceId && workspaces) { + const remainingWorkspaces = workspaces.filter( + (ws) => ws.id !== workspaceId, + ); + if (remainingWorkspaces.length > 0) { + await handleWorkspaceSelect(remainingWorkspaces[0].id); + } else { + setCurrentWorkspace(null); + } + } + } catch (error) { + console.error("Failed to remove workspace:", error); + } + } + }; + // Task handlers const handleOpenAddTaskModal = () => { setIsAddTaskModalOpen(true); @@ -473,7 +521,7 @@ export const NewLayoutMain: React.FC = () => { setIsAddTaskModalOpen(false); }; - const handleSelectTask = (task: typeof MOCK_TASKS[0]) => { + const handleSelectTask = (task: (typeof MOCK_TASKS)[0]) => { // Check if task is already open const isAlreadyOpen = openTasks.some((t) => t.id === task.id); if (!isAlreadyOpen) { @@ -594,7 +642,10 @@ export const NewLayoutMain: React.FC = () => { setCurrentWorkspace(refreshedWorkspace); // Auto-select first worktree and tab if available - if (refreshedWorkspace.worktrees && refreshedWorkspace.worktrees.length > 0) { + if ( + refreshedWorkspace.worktrees && + refreshedWorkspace.worktrees.length > 0 + ) { const firstWorktree = refreshedWorkspace.worktrees[0]; setSelectedWorktreeId(firstWorktree.id); if (firstWorktree.tabs && firstWorktree.tabs.length > 0) { @@ -683,6 +734,11 @@ export const NewLayoutMain: React.FC = () => { setShowSidebarOverlay(false); }} workspaceId={currentWorkspace?.id || null} + workspaces={workspaces || undefined} + currentWorkspace={currentWorkspace} + onWorkspaceSelect={handleWorkspaceSelect} + onAddWorkspace={handleAddWorkspace} + onRemoveWorkspace={handleRemoveWorkspace} /> @@ -700,7 +756,9 @@ export const NewLayoutMain: React.FC = () => { onWorktreeSelect={(worktreeId) => { setSelectedWorktreeId(worktreeId); // Select first tab in the worktree - const worktree = currentWorkspace?.worktrees?.find(wt => wt.id === worktreeId); + const worktree = currentWorkspace?.worktrees?.find( + (wt) => wt.id === worktreeId, + ); if (worktree && worktree.tabs && worktree.tabs.length > 0) { handleTabSelect(worktreeId, worktree.tabs[0].id); } @@ -708,7 +766,7 @@ export const NewLayoutMain: React.FC = () => { /> {/* Main content area with resizable sidebar */} -
+
{ onTabClose={async (tabId) => { if (!currentWorkspace || !selectedWorktreeId) return; - const result = await window.ipcRenderer.invoke("tab-delete", { - workspaceId: currentWorkspace.id, - worktreeId: selectedWorktreeId, - tabId, - }); + const result = await window.ipcRenderer.invoke( + "tab-delete", + { + workspaceId: currentWorkspace.id, + worktreeId: selectedWorktreeId, + tabId, + }, + ); if (result.success) { await handleWorktreeCreated(); @@ -748,12 +809,15 @@ export const NewLayoutMain: React.FC = () => { onCreateTerminal={async () => { if (!currentWorkspace || !selectedWorktreeId) return; - const result = await window.ipcRenderer.invoke("tab-create", { - workspaceId: currentWorkspace.id, - worktreeId: selectedWorktreeId, - name: "Terminal", - type: "terminal", - }); + 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); @@ -763,12 +827,15 @@ export const NewLayoutMain: React.FC = () => { onCreatePreview={async () => { if (!currentWorkspace || !selectedWorktreeId) return; - const result = await window.ipcRenderer.invoke("tab-create", { - workspaceId: currentWorkspace.id, - worktreeId: selectedWorktreeId, - name: "Preview", - type: "preview", - }); + 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); @@ -776,6 +843,11 @@ export const NewLayoutMain: React.FC = () => { } }} workspaceId={currentWorkspace?.id || null} + workspaces={workspaces || undefined} + currentWorkspace={currentWorkspace} + onWorkspaceSelect={handleWorkspaceSelect} + onAddWorkspace={handleAddWorkspace} + onRemoveWorkspace={handleRemoveWorkspace} /> )} @@ -785,10 +857,10 @@ export const NewLayoutMain: React.FC = () => { {/* Main content panel */} {loading || - error || - !currentWorkspace || - !selectedTab || - !selectedWorktree ? ( + error || + !currentWorkspace || + !selectedTab || + !selectedWorktree ? ( = ({ }) => { return (
{/* Sidebar collapse/expand toggle */} @@ -76,147 +74,148 @@ export const TaskTabs: React.FC = ({ )}
- {/* Worktree tabs */} - {worktrees.map((worktree) => { - // Use description as title if available, otherwise use branch name - const displayTitle = worktree.description || worktree.branch; +
+ {/* 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 + // 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 title */} -
-
- {worktree.description ? ( + > + {/* Status indicator dot */} +
+ + + + {hasPorts && ( <> -

- {worktree.description} -

-

- Branch: {worktree.branch} -

+ + - ) : ( -

- {worktree.branch} -

)}
-
- - {/* Metadata grid */} -
-
- Status -
-
- - - -
- - {hasPorts - ? "Running" - : hasActivity - ? "Active" - : "Inactive"} - + + {displayTitle} + + + + +
+ {/* Header with title */} +
+
+ {worktree.description ? ( + <> +

+ {worktree.description} +

+

+ Branch: {worktree.branch} +

+ + ) : ( +

+ {worktree.branch} +

+ )}
- {worktree.tabs && worktree.tabs.length > 0 && ( + {/* Metadata grid */} +
- Tabs - - {worktree.tabs.length} - + Status +
+
+ + + +
+ + {hasPorts + ? "Running" + : hasActivity + ? "Active" + : "Inactive"} + +
- )} - {!worktree.description && ( + {worktree.tabs && worktree.tabs.length > 0 && ( +
+ Tabs + + {worktree.tabs.length} + +
+ )} + + {!worktree.description && ( +
+ Branch + + {worktree.branch} + +
+ )} +
- Branch + Path - {worktree.branch} + {worktree.path}
- )} - -
- Path - - {worktree.path} -
-
-
- - ); - })} + + + ); + })} +
); diff --git a/apps/desktop/src/renderer/screens/main/components/NewLayout/WorktreeTabsSidebar.tsx b/apps/desktop/src/renderer/screens/main/components/NewLayout/WorktreeTabsSidebar.tsx index bfe83dc7732..52deec4926b 100644 --- a/apps/desktop/src/renderer/screens/main/components/NewLayout/WorktreeTabsSidebar.tsx +++ b/apps/desktop/src/renderer/screens/main/components/NewLayout/WorktreeTabsSidebar.tsx @@ -1,8 +1,11 @@ import { Button } from "@superset/ui/button"; import { Tooltip, TooltipContent, TooltipTrigger } from "@superset/ui/tooltip"; +import { type MotionValue, useMotionValue } from "framer-motion"; import { Monitor, Plus, Terminal as TerminalIcon, X } from "lucide-react"; import type React from "react"; -import type { Tab, Worktree } from "shared/types"; +import type { Tab, Workspace, Worktree } from "shared/types"; +import { WorkspaceCarousel } from "../Sidebar/components/WorkspaceCarousel"; +import { WorkspaceSwitcher } from "../Sidebar/components/WorkspaceSwitcher"; interface WorktreeTabsSidebarProps { worktree: Worktree | null; @@ -12,6 +15,12 @@ interface WorktreeTabsSidebarProps { onCreateTerminal: () => void; onCreatePreview: () => void; workspaceId: string | null; + // New props for workspace carousel + workspaces?: Workspace[]; + currentWorkspace?: Workspace | null; + onWorkspaceSelect?: (workspaceId: string) => void; + onAddWorkspace?: () => void; + onRemoveWorkspace?: (workspaceId: string, workspaceName: string) => void; } export const WorktreeTabsSidebar: React.FC = ({ @@ -22,7 +31,14 @@ export const WorktreeTabsSidebar: React.FC = ({ onCreateTerminal, onCreatePreview, workspaceId, + workspaces, + currentWorkspace, + onWorkspaceSelect, + onAddWorkspace, + onRemoveWorkspace, }) => { + const scrollProgress = useMotionValue(0); + if (!worktree || !workspaceId) { return (
@@ -33,6 +49,15 @@ export const WorktreeTabsSidebar: React.FC = ({ const tabs = worktree.tabs || []; + // Check if workspace carousel should be shown + const showWorkspaceCarousel = + workspaces && + workspaces.length > 1 && + currentWorkspace && + onWorkspaceSelect && + onAddWorkspace && + onRemoveWorkspace; + // Helper to get icon for tab type const getTabIcon = (tab: Tab) => { switch (tab.type) { @@ -61,89 +86,132 @@ export const WorktreeTabsSidebar: React.FC = ({ const flatTabs = flattenTabs(tabs); - return ( -
- {/* Header with actions */} -
-

Tabs

-
- - - - - -

New Terminal

-
-
- - - - - -

New Preview

-
-
-
-
+ const renderSidebarContent = (workspace: Workspace | null, isActive: boolean) => { + if (!workspace) return null; - {/* Tab list */} -
- {flatTabs.length === 0 ? ( -
- No tabs yet. Create a terminal or preview to get started. -
- ) : ( -
- {flatTabs.map(({ tab, level }) => ( - - )} - - ))} + // Find the worktree for this workspace + const workspaceWorktree = workspace.worktrees?.[0] || null; + const workspaceTabs = workspaceWorktree?.tabs || []; + const workspaceFlatTabs = flattenTabs(workspaceTabs); + + return ( + <> + {/* Header with actions */} +
+

Tabs

+
+ + + + + +

New Terminal

+
+
+ + + + + +

New Preview

+
+
- )} -
+
+ + {/* Tab list */} +
+ {workspaceFlatTabs.length === 0 ? ( +
+ No tabs yet. Create a terminal or preview to get started. +
+ ) : ( +
+ {workspaceFlatTabs.map(({ tab, level }) => ( + + )} + + ))} +
+ )} +
+ + ); + }; + + return ( +
+ {/* Workspace Switcher - only show if multiple workspaces */} + {showWorkspaceCarousel && workspaces && currentWorkspace && onWorkspaceSelect && onAddWorkspace && onRemoveWorkspace && ( + + )} + + {/* Workspace Carousel - horizontal scroll between workspaces */} + {showWorkspaceCarousel && workspaces && currentWorkspace && onWorkspaceSelect ? ( + ) => { + // Update scroll progress for the switcher + scrollProgress.set(progress.get()); + }} + > + {renderSidebarContent} + + ) : ( + // Single workspace - no carousel needed + renderSidebarContent(currentWorkspace || null, true) + )}
); };