From 47976305f0ed58a767bebc1ad2e55423b4a49eba Mon Sep 17 00:00:00 2001 From: Kiet Ho Date: Sat, 8 Nov 2025 15:16:52 -0800 Subject: [PATCH 1/5] split --- .../main/components/MainContent/TabGroup.tsx | 9 + .../main/components/MainContent/Terminal.tsx | 23 -- .../components/NewLayout/NewLayoutMain.tsx | 172 ++++++++---- .../main/components/NewLayout/TaskTabs.tsx | 245 +++++++++--------- .../NewLayout/WorktreeTabsSidebar.tsx | 232 +++++++++++------ 5 files changed, 403 insertions(+), 278 deletions(-) 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) + )}
); }; From 57950e3c7866b46c099a78f80eeca5a6e63a3d97 Mon Sep 17 00:00:00 2001 From: Kiet Ho Date: Sat, 8 Nov 2025 15:51:19 -0800 Subject: [PATCH 2/5] changes mode --- .../main/components/DiffView/DiffView.tsx | 80 ++- .../components/NewLayout/NewLayoutMain.tsx | 58 +- .../NewLayout/WorktreeTabsSidebar.tsx | 588 +++++++++++++----- .../TabContent/components/DiffTab/DiffTab.tsx | 7 + 4 files changed, 504 insertions(+), 229 deletions(-) diff --git a/apps/desktop/src/renderer/screens/main/components/DiffView/DiffView.tsx b/apps/desktop/src/renderer/screens/main/components/DiffView/DiffView.tsx index dcdf1c9b4b2..d42f26ae9f6 100644 --- a/apps/desktop/src/renderer/screens/main/components/DiffView/DiffView.tsx +++ b/apps/desktop/src/renderer/screens/main/components/DiffView/DiffView.tsx @@ -28,6 +28,9 @@ interface DiffViewProps { onRefresh?: () => void; isRefreshing?: boolean; onClose?: () => void; + hideFileTree?: boolean; + externalSelectedFile?: string | null; + onFileSelect?: (fileId: string) => void; } export function DiffView({ @@ -35,15 +38,22 @@ export function DiffView({ onRefresh, isRefreshing = false, onClose, + hideFileTree = false, + externalSelectedFile = null, + onFileSelect: externalOnFileSelect, }: DiffViewProps) { const [viewMode, setViewMode] = useState("files"); - const [selectedFile, setSelectedFile] = useState( + const [internalSelectedFile, setInternalSelectedFile] = useState( data.files[0]?.id || null, ); - const [showFileTree, setShowFileTree] = useState(true); + const [showFileTree, setShowFileTree] = useState(!hideFileTree); const scrollContainerRef = useRef(null); const isScrollingProgrammatically = useRef(false); + // Use external selected file if provided, otherwise use internal state + const selectedFile = externalSelectedFile !== null ? externalSelectedFile : internalSelectedFile; + const setSelectedFile = externalOnFileSelect || setInternalSelectedFile; + const getFileIcon = (status: FileDiff["status"]) => { switch (status) { case "added": @@ -338,44 +348,46 @@ export function DiffView({ ) : ( // Files changed view - scrollable list of all files (GitHub style) <> - {/* File tree sidebar - kept mounted, hidden with display:none for instant toggle */} -
-
-
-

Files

-
- - - - - -

Hide file tree

-
-
+ {/* File tree sidebar - hidden when hideFileTree prop is true */} + {!hideFileTree && ( +
+
+
+

Files

+
+ + + + + +

Hide file tree

+
+
+
+
+
+
-
-
-
-
+ )} {/* All files diff content - scrollable */}
- {!showFileTree && ( + {!hideFileTree && !showFileTree && (
@@ -843,11 +803,9 @@ export const NewLayoutMain: React.FC = () => { } }} workspaceId={currentWorkspace?.id || null} - workspaces={workspaces || undefined} - currentWorkspace={currentWorkspace} - onWorkspaceSelect={handleWorkspaceSelect} - onAddWorkspace={handleAddWorkspace} - onRemoveWorkspace={handleRemoveWorkspace} + workspaceName={currentWorkspace?.name} + mainBranch={currentWorkspace?.branch} + onDiffFileSelect={setSelectedDiffFile} /> )} @@ -906,6 +864,8 @@ export const NewLayoutMain: React.FC = () => { worktree={selectedWorktree} workspaceName={currentWorkspace.name} mainBranch={currentWorkspace.branch} + selectedDiffFile={selectedDiffFile} + onDiffFileSelect={setSelectedDiffFile} />
) : ( 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 52deec4926b..b0195239408 100644 --- a/apps/desktop/src/renderer/screens/main/components/NewLayout/WorktreeTabsSidebar.tsx +++ b/apps/desktop/src/renderer/screens/main/components/NewLayout/WorktreeTabsSidebar.tsx @@ -1,11 +1,38 @@ 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 { motion, useMotionValue } from "framer-motion"; +import { + File, + FileEdit, + FilePlus, + FileText, + FileX, + GitBranch, + Monitor, + Plus, + RefreshCw, + Terminal as TerminalIcon, + X +} from "lucide-react"; +import { useCallback, useEffect, useRef, useState } from "react"; import type React from "react"; -import type { Tab, Workspace, Worktree } from "shared/types"; -import { WorkspaceCarousel } from "../Sidebar/components/WorkspaceCarousel"; -import { WorkspaceSwitcher } from "../Sidebar/components/WorkspaceSwitcher"; +import type { Tab, Worktree } from "shared/types"; +import { FileTree } from "../DiffView/FileTree"; +import type { DiffViewData, FileDiff } from "../DiffView/types"; + +// Define sidebar modes +type SidebarMode = "tabs" | "changes"; + +interface Mode { + id: SidebarMode; + name: string; + icon: React.ComponentType<{ size?: number }>; +} + +const SIDEBAR_MODES: Mode[] = [ + { id: "tabs", name: "Tabs", icon: FileText }, + { id: "changes", name: "Changes", icon: GitBranch }, +]; interface WorktreeTabsSidebarProps { worktree: Worktree | null; @@ -15,12 +42,9 @@ 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; + workspaceName?: string; + mainBranch?: string; + onDiffFileSelect?: (fileId: string | null) => void; } export const WorktreeTabsSidebar: React.FC = ({ @@ -31,14 +55,160 @@ export const WorktreeTabsSidebar: React.FC = ({ onCreateTerminal, onCreatePreview, workspaceId, - workspaces, - currentWorkspace, - onWorkspaceSelect, - onAddWorkspace, - onRemoveWorkspace, + workspaceName, + mainBranch, + onDiffFileSelect, }) => { + const [currentMode, setCurrentMode] = useState("tabs"); const scrollProgress = useMotionValue(0); + const scrollContainerRef = useRef(null); + const scrollTimeoutRef = useRef(undefined); + const isInitialMount = useRef(true); + const backgroundX = useMotionValue(0); + + // Diff/changes state + const [diffData, setDiffData] = useState(null); + const [loadingDiff, setLoadingDiff] = useState(false); + const [diffError, setDiffError] = useState(null); + const [selectedFile, setSelectedFile] = useState(null); + + const tabs = worktree?.tabs || []; + const currentModeIndex = SIDEBAR_MODES.findIndex((m) => m.id === currentMode); + + // Track scroll position and update motion value + useEffect(() => { + const scrollContainer = scrollContainerRef.current; + if (!scrollContainer) return; + + let rafId: number | undefined; + + const updateProgress = () => { + const scrollLeft = scrollContainer.scrollLeft; + const containerWidth = scrollContainer.offsetWidth; + const progress = scrollLeft / containerWidth; + scrollProgress.set(progress); + }; + + const handleScroll = () => { + if (rafId !== undefined) { + cancelAnimationFrame(rafId); + } + rafId = requestAnimationFrame(updateProgress); + }; + + scrollContainer.addEventListener("scroll", handleScroll, { passive: true }); + updateProgress(); + + return () => { + scrollContainer.removeEventListener("scroll", handleScroll); + if (rafId !== undefined) { + cancelAnimationFrame(rafId); + } + }; + }, [scrollProgress]); + + // Scroll to current mode when it changes + useEffect(() => { + const scrollContainer = scrollContainerRef.current; + if (!scrollContainer) return; + + const targetScrollX = currentModeIndex * scrollContainer.offsetWidth; + + if (Math.abs(scrollContainer.scrollLeft - targetScrollX) > 10) { + scrollContainer.scrollTo({ + left: targetScrollX, + behavior: isInitialMount.current ? "auto" : "smooth", + }); + } + + isInitialMount.current = false; + }, [currentModeIndex]); + + // Detect when user finishes scrolling and update current mode + useEffect(() => { + const scrollContainer = scrollContainerRef.current; + if (!scrollContainer) return; + + const handleScroll = () => { + if (scrollTimeoutRef.current) { + clearTimeout(scrollTimeoutRef.current); + } + + scrollTimeoutRef.current = setTimeout(() => { + const scrollLeft = scrollContainer.scrollLeft; + const containerWidth = scrollContainer.offsetWidth; + const newIndex = Math.round(scrollLeft / containerWidth); + + if ( + newIndex >= 0 && + newIndex < SIDEBAR_MODES.length && + SIDEBAR_MODES[newIndex].id !== currentMode + ) { + setCurrentMode(SIDEBAR_MODES[newIndex].id); + } + }, 150); + }; + + scrollContainer.addEventListener("scroll", handleScroll); + + return () => { + scrollContainer.removeEventListener("scroll", handleScroll); + if (scrollTimeoutRef.current) { + clearTimeout(scrollTimeoutRef.current); + } + }; + }, [currentMode]); + + // Calculate sliding background position (each button is 40px wide including gap) + useEffect(() => { + const unsubscribe = scrollProgress.on("change", (latest) => { + backgroundX.set(latest * 40); + }); + return unsubscribe; + }, [scrollProgress, backgroundX]); + + // Load diff data when switching to Changes mode + const loadDiff = useCallback(async () => { + if (!workspaceId || !worktree?.id) return; + + setLoadingDiff(true); + setDiffError(null); + + try { + const result = await window.ipcRenderer.invoke("worktree-get-git-diff", { + workspaceId, + worktreeId: worktree.id, + }); + + if (result?.success && result.diff) { + const diffViewData: DiffViewData = { + title: `Changes in ${worktree.branch}`, + description: workspaceName, + timestamp: new Date().toLocaleString(), + files: result.diff.files, + }; + setDiffData(diffViewData); + if (diffViewData.files.length > 0 && !selectedFile) { + setSelectedFile(diffViewData.files[0].id); + } + } else { + setDiffError(result?.error || "Failed to load diff"); + } + } catch (err) { + setDiffError(err instanceof Error ? err.message : "Unknown error"); + } finally { + setLoadingDiff(false); + } + }, [workspaceId, worktree?.id, worktree?.branch, workspaceName, selectedFile]); + + // Load diff when switching to Changes mode + useEffect(() => { + if (currentMode === "changes" && !diffData && !loadingDiff) { + loadDiff(); + } + }, [currentMode, diffData, loadingDiff, loadDiff]); + // Early return after all hooks if (!worktree || !workspaceId) { return (
@@ -47,17 +217,6 @@ 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) { @@ -66,14 +225,31 @@ export const WorktreeTabsSidebar: React.FC = ({ case "preview": return ; case "diff": - return ; + return ; default: return ; } }; + // Helper to get icon for file diff status + const getFileIcon = (status: FileDiff["status"]) => { + switch (status) { + case "added": + return ; + case "deleted": + return ; + case "modified": + return ; + default: + return ; + } + }; + // Flatten tabs recursively (handle group tabs) - const flattenTabs = (tabs: Tab[], level = 0): Array<{ tab: Tab; level: number }> => { + 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 }); @@ -86,132 +262,252 @@ export const WorktreeTabsSidebar: React.FC = ({ const flatTabs = flattenTabs(tabs); - const renderSidebarContent = (workspace: Workspace | null, isActive: boolean) => { - if (!workspace) return null; - - // Find the worktree for this workspace - const workspaceWorktree = workspace.worktrees?.[0] || null; - const workspaceTabs = workspaceWorktree?.tabs || []; - const workspaceFlatTabs = flattenTabs(workspaceTabs); + // Render tabs mode content + const renderTabsMode = () => ( + <> + {/* Header with actions */} +
+

Tabs

+
+ + + + + +

New Terminal

+
+
+ + + + + +

New Preview

+
+
+
+
- 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 }) => ( + + )} + + ))}
+ )} +
+ + ); + + // Render changes mode content + const renderChangesMode = () => ( + <> + {/* Header with actions */} +
+

Changes

+
+ + + + + +

Refresh changes

+
+
+
- {/* Tab list */} -
- {workspaceFlatTabs.length === 0 ? ( -
- No tabs yet. Create a terminal or preview to get started. + {/* Changes content */} +
+ {loadingDiff ? ( +
+
+
+

Loading changes...

- ) : ( -
- {workspaceFlatTabs.map(({ tab, level }) => ( - - )} - - ))} +
+ ) : diffError ? ( +
+

{diffError}

+ +
+ ) : !diffData || diffData.files.length === 0 ? ( +
+

No changes

+

+ No uncommitted changes in this worktree +

+
+ ) : ( +
+
+

+ Files Changed • {diffData.files.length} +

- )} -
- - ); - }; +
+ { + setSelectedFile(fileId); + onDiffFileSelect?.(fileId); + }} + getFileIcon={getFileIcon} + /> +
+
+ )} +
+ + ); 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) - )} + {/* Mode Switcher */} +
+
+ {/* Sliding background indicator */} + + + {SIDEBAR_MODES.map((mode) => { + const Icon = mode.icon; + return ( + + + + + +

{mode.name}

+
+
+ ); + })} +
+
+ + {/* Mode Content Carousel */} +
+
+ {/* Tabs Mode */} +
+ {renderTabsMode()} +
+ + {/* Changes Mode */} +
+ {renderChangesMode()} +
+
+
); }; diff --git a/apps/desktop/src/renderer/screens/main/components/TabContent/components/DiffTab/DiffTab.tsx b/apps/desktop/src/renderer/screens/main/components/TabContent/components/DiffTab/DiffTab.tsx index b30a6ef9f62..a38ced19469 100644 --- a/apps/desktop/src/renderer/screens/main/components/TabContent/components/DiffTab/DiffTab.tsx +++ b/apps/desktop/src/renderer/screens/main/components/TabContent/components/DiffTab/DiffTab.tsx @@ -13,6 +13,8 @@ interface DiffTabProps { workspaceName?: string; mainBranch?: string; onClose?: () => void; + selectedDiffFile?: string | null; + onDiffFileSelect?: (fileId: string) => void; } export function DiffTab({ @@ -23,6 +25,8 @@ export function DiffTab({ workspaceName, mainBranch, onClose, + selectedDiffFile, + onDiffFileSelect, }: DiffTabProps) { const [loading, setLoading] = useState(true); const [refreshing, setRefreshing] = useState(false); @@ -159,6 +163,9 @@ export function DiffTab({ onRefresh={handleRefresh} isRefreshing={refreshing} onClose={onClose} + hideFileTree={!!selectedDiffFile} + externalSelectedFile={selectedDiffFile || null} + onFileSelect={onDiffFileSelect} />
); From 24651cf0beb1a4885c40f982c83f2c1e57c1087f Mon Sep 17 00:00:00 2001 From: Kiet Ho Date: Sat, 8 Nov 2025 15:58:10 -0800 Subject: [PATCH 3/5] changes mode --- .../NewLayout/WorktreeTabsSidebar.tsx | 36 ++++++++++++------- 1 file changed, 24 insertions(+), 12 deletions(-) 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 b0195239408..1fe2c3c7ca0 100644 --- a/apps/desktop/src/renderer/screens/main/components/NewLayout/WorktreeTabsSidebar.tsx +++ b/apps/desktop/src/renderer/screens/main/components/NewLayout/WorktreeTabsSidebar.tsx @@ -1,6 +1,6 @@ import { Button } from "@superset/ui/button"; import { Tooltip, TooltipContent, TooltipTrigger } from "@superset/ui/tooltip"; -import { motion, useMotionValue } from "framer-motion"; +import { motion, useMotionValue, useTransform } from "framer-motion"; import { File, FileEdit, @@ -167,6 +167,20 @@ export const WorktreeTabsSidebar: React.FC = ({ return unsubscribe; }, [scrollProgress, backgroundX]); + // Create opacity transforms for each mode (must be outside render loop) + const mode0Opacity = useTransform( + scrollProgress, + [-0.5, 0, 0.5], + [0.4, 1, 0.4] + ); + const mode1Opacity = useTransform( + scrollProgress, + [0.5, 1, 1.5], + [0.4, 1, 0.4] + ); + + const modeOpacities = [mode0Opacity, mode1Opacity]; + // Load diff data when switching to Changes mode const loadDiff = useCallback(async () => { if (!workspaceId || !worktree?.id) return; @@ -444,23 +458,21 @@ export const WorktreeTabsSidebar: React.FC = ({ }} /> - {SIDEBAR_MODES.map((mode) => { + {SIDEBAR_MODES.map((mode, index) => { const Icon = mode.icon; + const opacity = modeOpacities[index]; + return ( - + +

{mode.name}

From 61a9554a567ef527e53ee83eefb25fefd50dc0cf Mon Sep 17 00:00:00 2001 From: Kiet Ho Date: Sat, 8 Nov 2025 16:06:57 -0800 Subject: [PATCH 4/5] changes mode --- .../components/NewLayout/NewLayoutMain.tsx | 24 +++++++++++- .../NewLayout/WorktreeTabsSidebar.tsx | 38 ++++++++++++++----- 2 files changed, 51 insertions(+), 11 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 e505d7f5134..3b5c2c10f29 100644 --- a/apps/desktop/src/renderer/screens/main/components/NewLayout/NewLayoutMain.tsx +++ b/apps/desktop/src/renderer/screens/main/components/NewLayout/NewLayoutMain.tsx @@ -219,6 +219,7 @@ export const NewLayoutMain: React.FC = () => { const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [selectedDiffFile, setSelectedDiffFile] = useState(null); + const [sidebarMode, setSidebarMode] = useState<"tabs" | "changes">("tabs"); const handleCollapseSidebar = () => { const panel = sidebarPanelRef.current; @@ -699,6 +700,7 @@ export const NewLayoutMain: React.FC = () => { workspaceName={currentWorkspace?.name} mainBranch={currentWorkspace?.branch} onDiffFileSelect={setSelectedDiffFile} + onModeChange={setSidebarMode} />
@@ -806,6 +808,7 @@ export const NewLayoutMain: React.FC = () => { workspaceName={currentWorkspace?.name} mainBranch={currentWorkspace?.branch} onDiffFileSelect={setSelectedDiffFile} + onModeChange={setSidebarMode} /> )} @@ -817,13 +820,32 @@ export const NewLayoutMain: React.FC = () => { {loading || error || !currentWorkspace || - !selectedTab || !selectedWorktree ? ( + ) : sidebarMode === "changes" ? ( + // Changes mode - always show diff view +
+ +
+ ) : !selectedTab ? ( + ) : parentGroupTab ? ( // Selected tab is a sub-tab of a group → display the parent group's mosaic void; + onModeChange?: (mode: SidebarMode) => void; } export const WorktreeTabsSidebar: React.FC = ({ @@ -58,6 +59,7 @@ export const WorktreeTabsSidebar: React.FC = ({ workspaceName, mainBranch, onDiffFileSelect, + onModeChange, }) => { const [currentMode, setCurrentMode] = useState("tabs"); const scrollProgress = useMotionValue(0); @@ -144,7 +146,9 @@ export const WorktreeTabsSidebar: React.FC = ({ newIndex < SIDEBAR_MODES.length && SIDEBAR_MODES[newIndex].id !== currentMode ) { - setCurrentMode(SIDEBAR_MODES[newIndex].id); + const newMode = SIDEBAR_MODES[newIndex].id; + setCurrentMode(newMode); + onModeChange?.(newMode); } }, 150); }; @@ -157,7 +161,7 @@ export const WorktreeTabsSidebar: React.FC = ({ clearTimeout(scrollTimeoutRef.current); } }; - }, [currentMode]); + }, [currentMode, onModeChange]); // Calculate sliding background position (each button is 40px wide including gap) useEffect(() => { @@ -276,6 +280,9 @@ export const WorktreeTabsSidebar: React.FC = ({ const flatTabs = flattenTabs(tabs); + // Filter out diff tabs - they should only be accessed through Changes mode + const nonDiffTabs = flatTabs.filter(({ tab }) => tab.type !== "diff"); + // Render tabs mode content const renderTabsMode = () => ( <> @@ -318,13 +325,13 @@ export const WorktreeTabsSidebar: React.FC = ({ {/* Tab list */}
- {flatTabs.length === 0 ? ( + {nonDiffTabs.length === 0 ? (
No tabs yet. Create a terminal or preview to get started.
) : (
- {flatTabs.map(({ tab, level }) => ( + {nonDiffTabs.map(({ tab, level }) => ( @@ -453,44 +460,34 @@ export const WorktreeTabsSidebar: React.FC = ({ {/* Mode Switcher */}
- {/* Sliding background indicator */} - - - {SIDEBAR_MODES.map((mode, index) => { + {SIDEBAR_MODES.map((mode) => { const Icon = mode.icon; - const opacity = modeOpacities[index]; const isActive = currentMode === mode.id; return ( - { e.preventDefault(); e.stopPropagation(); - console.log("Mode button clicked:", mode.id); + console.log( + "Mode clicked:", + mode.id, + "Current:", + currentMode, + ); setCurrentMode(mode.id); onModeChange?.(mode.id); }} - className={`relative z-20 flex h-8 w-8 items-center justify-center rounded-sm transition-all cursor-pointer ${ - isActive - ? "text-neutral-100" - : "text-neutral-500 hover:text-neutral-300" - } hover:bg-white/10 active:bg-white/20`} - style={{ opacity: isActive ? 1 : opacity }} + className={`relative z-20 flex h-8 w-8 items-center justify-center rounded-sm transition-all cursor-pointer ${isActive + ? "text-white bg-neutral-800" + : "text-neutral-500 hover:text-neutral-300 hover:bg-white/5" + }`} > - - + +

{mode.name}

@@ -512,7 +509,10 @@ export const WorktreeTabsSidebar: React.FC = ({ msOverflowStyle: "none", }} > -
+
{/* Tabs Mode */}