import { LoadingSpinner } from "@/components/ui/loading-spinner"; import React, { useCallback, useEffect, useMemo, useRef, useState, type Dispatch, type SetStateAction, } from "react"; import { Pressable, ScrollView, Text, View, type LayoutChangeEvent, type PressableStateCallbackType, } from "react-native"; import { CopyX, ArrowLeftToLine, ArrowRightToLine, ChevronDown, Columns2, Copy, Pencil, RotateCw, Rows2, Globe, Plus, SquarePen, SquareTerminal, X, } from "lucide-react-native"; import { StyleSheet, withUnistyles } from "react-native-unistyles"; import { useTranslation } from "react-i18next"; import { useRouter, type Href } from "expo-router"; import { SortableInlineList } from "@/components/sortable-inline-list"; import type { DraggableListDragHandleProps, DraggableRenderItemInfo, } from "@/components/draggable-list.types"; import { isNative, isWeb } from "@/constants/platform"; import { ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuSeparator, ContextMenuTrigger, } from "@/components/ui/context-menu"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuLabel, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { Shortcut } from "@/components/ui/shortcut"; import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; import { useShortcutKeys } from "@/hooks/use-shortcut-keys"; import { WORKSPACE_SECONDARY_HEADER_HEIGHT } from "@/constants/layout"; import type { ShortcutKey } from "@/utils/format-shortcut"; import { useWorkspaceTabLayout } from "@/screens/workspace/use-workspace-tab-layout"; import { WorkspaceTabPresentationResolver, WorkspaceTabIcon, type WorkspaceTabPresentation, } from "@/screens/workspace/workspace-tab-presentation"; import { buildDeterministicWorkspaceTabId } from "@/workspace-tabs/identity"; import { buildWorkspaceDesktopTabActions, type WorkspaceDesktopTabActions, type WorkspaceTabMenuEntry, type WorkspaceTabMenuLabels, } from "@/screens/workspace/workspace-tab-menu"; import type { WorkspaceTabDescriptor } from "@/screens/workspace/workspace-tabs-types"; import type { Theme } from "@/styles/theme"; import { RenderProfile } from "@/utils/render-profiler"; import { useDaemonConfig } from "@/hooks/use-daemon-config"; import { getTerminalProfileIcon, resolveTerminalProfiles, } from "@getpaseo/protocol/terminal-profiles"; import { buildSettingsHostSectionRoute } from "@/utils/host-routes"; import type { TerminalProfileInput } from "@/screens/workspace/terminals/use-workspace-terminals"; import { ProfileIcon, usePinnedLaunchers } from "@/workspace-pins/launch"; import { runPinnedTabTarget, type TabTargetHandlers } from "@/workspace-pins/run"; import type { PinnedTabTarget } from "@/workspace-pins/target"; import { PinnedTargetsRow } from "@/workspace-pins/pinned-targets-row"; import { PinnableMenuItem } from "@/workspace-pins/pinnable-menu-item"; const DROPDOWN_WIDTH = 220; const LOADING_TAB_LABEL_SKELETON_WIDTH = 80; const DEFAULT_INLINE_ADD_BUTTON_RESERVED_WIDTH = 36; const ThemedLoadingSpinner = withUnistyles(LoadingSpinner); const ThemedX = withUnistyles(X); const ThemedCopy = withUnistyles(Copy); const ThemedRotateCw = withUnistyles(RotateCw); const ThemedArrowLeftToLine = withUnistyles(ArrowLeftToLine); const ThemedArrowRightToLine = withUnistyles(ArrowRightToLine); const ThemedCopyX = withUnistyles(CopyX); const ThemedPencil = withUnistyles(Pencil); const ThemedSquarePen = withUnistyles(SquarePen); const ThemedSquareTerminal = withUnistyles(SquareTerminal); const ThemedChevronDown = withUnistyles(ChevronDown); const ThemedGlobe = withUnistyles(Globe); const ThemedColumns2 = withUnistyles(Columns2); const ThemedRows2 = withUnistyles(Rows2); const ThemedPlus = withUnistyles(Plus); const foregroundColorMapping = (theme: Theme) => ({ color: theme.colors.foreground }); const mutedColorMapping = (theme: Theme) => ({ color: theme.colors.foregroundMuted }); const AGENT_ICON = ; const TERMINAL_ICON = ; const BROWSER_ICON = ; const DRAFT_TARGET: PinnedTabTarget = { kind: "draft" }; const TERMINAL_TARGET: PinnedTabTarget = { kind: "terminal" }; const BROWSER_TARGET: PinnedTabTarget = { kind: "browser" }; function newTabActionButtonStyle({ hovered, pressed }: PressableStateCallbackType) { return [styles.newTabActionButton, (hovered || pressed) && styles.newTabActionButtonHovered]; } function inlineAddActionButtonStyle({ hovered, pressed }: PressableStateCallbackType) { return [styles.inlineAddActionButton, (hovered || pressed) && styles.newTabActionButtonHovered]; } function updateMeasuredWidth(setWidth: Dispatch>, event: LayoutChangeEvent) { const nextWidth = Math.round(event.nativeEvent.layout.width); setWidth((current) => (Math.abs(current - nextWidth) > 1 ? nextWidth : current)); } function ProfileLeadingIcon({ iconKey }: { iconKey: string | undefined }) { return ( ); } interface PinnableProfileMenuItemProps { profile: { id: string; name: string; command: string; args?: string[]; icon?: string }; disabled?: boolean; onLaunch: (target: PinnedTabTarget) => void; } function PinnableProfileMenuItem({ profile, disabled, onLaunch }: PinnableProfileMenuItemProps) { const target = useMemo( () => ({ kind: "profile", profileId: profile.id }), [profile.id], ); const leading = useMemo( () => , [profile], ); const handleSelect = useCallback(() => onLaunch(target), [onLaunch, target]); return ( ); } interface WorkspaceInlineAddTabButtonProps { shortcutKeys: ShortcutKey[][] | null; onCreateAgentTab: () => void; onLayout: (event: LayoutChangeEvent) => void; } function WorkspaceInlineAddTabButton({ shortcutKeys, onCreateAgentTab, onLayout, }: WorkspaceInlineAddTabButtonProps) { const { t } = useTranslation(); const tooltipText = t("workspace.tabs.actions.newAgent"); return ( {tooltipText} {shortcutKeys ? ( ) : null} ); } interface WorkspaceTabRowExtrasProps { onCreateAgentTab: () => void; onCreateTerminal: () => void; onCreateBrowser: () => void; onCreateTerminalWithProfile: (profile: TerminalProfileInput) => void; onEditProfiles: () => void; normalizedServerId: string; showCreateBrowserTab: boolean; terminalDisabled: boolean; } function WorkspaceTabRowExtras({ onCreateAgentTab, onCreateTerminal, onCreateBrowser, onCreateTerminalWithProfile, onEditProfiles, normalizedServerId, showCreateBrowserTab, terminalDisabled, }: WorkspaceTabRowExtrasProps) { const { t } = useTranslation(); const { config } = useDaemonConfig(normalizedServerId); const profiles = useMemo( () => resolveTerminalProfiles(config?.terminalProfiles), [config?.terminalProfiles], ); const handlers = useMemo( () => ({ createDraft: onCreateAgentTab, createTerminal: onCreateTerminal, createBrowser: onCreateBrowser, createTerminalWithProfile: onCreateTerminalWithProfile, }), [onCreateAgentTab, onCreateBrowser, onCreateTerminal, onCreateTerminalWithProfile], ); const onLaunch = useCallback( (target: PinnedTabTarget) => { runPinnedTabTarget(target, profiles, handlers); }, [handlers, profiles], ); const launchers = usePinnedLaunchers({ serverId: normalizedServerId, onLaunch }); return ( <> {t("workspace.tabs.actions.moreActions")} {showCreateBrowserTab ? ( ) : null} {t("workspace.tabs.actions.terminalProfilesMenu")} {profiles.map((profile) => ( ))} {t("workspace.tabs.actions.editTerminalProfiles")} ); } function TabContextMenuItem({ entry, }: { entry: Extract; }) { const leading = useMemo(() => { switch (entry.icon) { case "copy": return ; case "rotate-cw": return ; case "arrow-left-to-line": return ; case "arrow-right-to-line": return ; case "copy-x": return ; case "pencil": return ; case "x": return ; default: return undefined; } }, [entry.icon]); const trailing = useMemo( () => (entry.hint ? {entry.hint} : undefined), [entry.hint], ); return ( {entry.label} ); } function tabKeyExtractor(tab: WorkspaceDesktopTabRowItem) { return `${tab.tab.key}:${tab.tab.kind}`; } export interface WorkspaceDesktopTabRowItem { tab: WorkspaceTabDescriptor; isActive: boolean; isCloseHovered: boolean; isClosingTab: boolean; } interface SplitActionButtonProps { onPress: () => void; label: string; shortcutKeys: ShortcutKey[][] | null; icon: "split-right" | "split-down"; } function SplitActionButton({ onPress, label, shortcutKeys, icon }: SplitActionButtonProps) { return ( {icon === "split-right" ? ( ) : ( )} {label} {shortcutKeys ? ( ) : null} ); } interface WorkspaceDesktopTabsRowProps { paneId?: string; isFocused?: boolean; tabs: WorkspaceDesktopTabRowItem[]; normalizedServerId: string; normalizedWorkspaceId: string; setHoveredCloseTabKey: Dispatch>; onNavigateTab: (tabId: string) => void; onCloseTab: (tabId: string) => Promise | void; onCopyResumeCommand: (agentId: string) => Promise | void; onCopyAgentId: (agentId: string) => Promise | void; onCopyTerminalId: (terminalId: string) => Promise | void; onCopyFilePath: (path: string) => Promise | void; onReloadAgent: (agentId: string) => Promise | void; onRenameTab: (tab: WorkspaceTabDescriptor) => void; onCloseTabsToLeft: (tabId: string) => Promise | void; onCloseTabsToRight: (tabId: string) => Promise | void; onCloseOtherTabs: (tabId: string) => Promise | void; onCreateDraftTab: (input: { paneId?: string }) => void; onCreateTerminalTab: (input: { paneId?: string; profile?: TerminalProfileInput }) => void; onCreateBrowserTab: (input: { paneId?: string }) => void; showCreateBrowserTab?: boolean; disableCreateTerminal?: boolean; isWaitingOnTerminalReadiness?: boolean; onReorderTabs: (nextTabs: WorkspaceTabDescriptor[]) => void; onSplitRight: () => void; onSplitDown: () => void; externalDndContext?: boolean; activeDragTabId?: string | null; tabDropPreviewIndex?: number | null; showPaneSplitActions?: boolean; focusModeEnabled: boolean; onExitFocusMode: () => void; } function getFallbackTabLabel( tab: WorkspaceTabDescriptor, labels: { newAgent: string; setup: string; terminal: string; agent: string; changes: string }, ): string { if (tab.target.kind === "draft") { return labels.newAgent; } if (tab.target.kind === "setup") { return labels.setup; } if (tab.target.kind === "terminal") { return labels.terminal; } if (tab.target.kind === "file") { return tab.target.path.split("/").findLast(Boolean) ?? tab.target.path; } if (tab.target.kind === "working_diff") { return labels.changes; } return labels.agent; } function useMiddleClickClose(onClose: () => void) { const ref = useRef(null); useEffect(() => { if (isNative) return; const node = ref.current as unknown as HTMLElement | null; if (!node) return; function handleAuxClick(event: MouseEvent) { if (event.button === 1) { event.preventDefault(); onClose(); } } node.addEventListener("auxclick", handleAuxClick); return () => node.removeEventListener("auxclick", handleAuxClick); }, [onClose]); return ref; } function TabHandleContent({ presentation, isHighlighted, showLabel, tabLabelSkeletonStyle, tabLabelStyle, }: { presentation: WorkspaceTabPresentation; isHighlighted: boolean; showLabel: boolean; tabLabelSkeletonStyle: React.ComponentProps["style"]; tabLabelStyle: React.ComponentProps["style"]; }) { const tabHandleDataSet = useMemo( () => ({ statusBucket: presentation.statusBucket ?? "none" }), [presentation.statusBucket], ); return ( {showLabel && presentation.titleState === "loading" ? ( ) : null} {showLabel && presentation.titleState !== "loading" ? ( {presentation.label} ) : null} ); } function TabChip({ tab, isActive, isDragging, isFocused, resolvedTabWidth, showLabel, showCloseButton, isCloseHovered, isClosingTab, presentation, tooltipLabel, resolvedTab, setHoveredCloseTabKey, onNavigateTab, onCloseTab, dragHandleProps, }: { tab: WorkspaceTabDescriptor; isActive: boolean; isDragging: boolean; isFocused: boolean; resolvedTabWidth: number; showLabel: boolean; showCloseButton: boolean; isCloseHovered: boolean; isClosingTab: boolean; presentation: WorkspaceTabPresentation; tooltipLabel: string; resolvedTab: WorkspaceDesktopTabActions; setHoveredCloseTabKey: Dispatch>; onNavigateTab: (tabId: string) => void; onCloseTab: (tabId: string) => Promise | void; dragHandleProps: DraggableListDragHandleProps | undefined; }) { const { t } = useTranslation(); const { closeButtonTestId, contextMenuTestId, menuEntries } = resolvedTab; const middleClickRef = useMiddleClickClose( useCallback(() => void onCloseTab(tab.tabId), [onCloseTab, tab.tabId]), ); const [hovered, setHovered] = useState(false); const isHighlighted = isActive || hovered || isCloseHovered; const showTrailingAffordance = showCloseButton || presentation.modified; const closeButtonDragBlockers = isWeb ? ({ onPointerDown: (event: { stopPropagation?: () => void }) => { event.stopPropagation?.(); }, onMouseDown: (event: { stopPropagation?: () => void }) => { event.stopPropagation?.(); }, } as const) : undefined; const tabChipStyle = useCallback( () => [ styles.tab, isWeb && isDragging && ({ cursor: "grabbing" } as object), { minWidth: resolvedTabWidth, width: resolvedTabWidth, maxWidth: resolvedTabWidth, }, ], [isDragging, resolvedTabWidth], ); const handleTabHoverIn = useCallback(() => { setHovered(true); }, []); const handleTabHoverOut = useCallback(() => { setHovered(false); }, []); const handleNavigateTab = useCallback(() => { onNavigateTab(tab.tabId); }, [onNavigateTab, tab.tabId]); const handleCloseButtonPressIn = useCallback((event: { stopPropagation?: () => void }) => { event.stopPropagation?.(); }, []); const handleCloseButtonHoverIn = useCallback(() => { setHoveredCloseTabKey(tab.key); }, [setHoveredCloseTabKey, tab.key]); const handleCloseButtonHoverOut = useCallback(() => { setHoveredCloseTabKey((current) => (current === tab.key ? null : current)); }, [setHoveredCloseTabKey, tab.key]); const handleCloseButtonPress = useCallback( (event: { stopPropagation?: () => void }) => { event.stopPropagation?.(); void onCloseTab(tab.tabId); }, [onCloseTab, tab.tabId], ); const closeButtonStyle = useCallback( ({ hovered: isButtonHovered, pressed }: PressableStateCallbackType & { hovered?: boolean }) => [ styles.tabCloseButton, styles.tabCloseButtonShown, (Boolean(isButtonHovered) || pressed) && styles.tabCloseButtonActive, ], [], ); const tabAccessibilityState = useMemo(() => ({ selected: isActive }), [isActive]); const tabFocusIndicatorStyle = useMemo( () => [styles.tabFocusIndicator, !isFocused && styles.tabFocusIndicatorUnfocused], [isFocused], ); const tabLabelSkeletonStyle = useMemo( () => [ styles.tabLabelSkeleton, showTrailingAffordance && styles.tabLabelSkeletonWithCloseButton, ], [showTrailingAffordance], ); const tabLabelStyle = useMemo( () => [ styles.tabLabel, isHighlighted && styles.tabLabelActive, showTrailingAffordance && styles.tabLabelWithCloseButton, ], [isHighlighted, showTrailingAffordance], ); return ( {isActive && } {showTrailingAffordance ? ( {({ hovered: closeHovered, pressed }) => { const highlighted = closeHovered || pressed; if (isClosingTab) { return ( ); } if (highlighted || !presentation.modified) { return ( ); } return ( ); }} ) : null} {tab.target.kind === "agent" ? ( {tooltipLabel} {tab.target.agentId.slice(0, 7)} ) : ( {tooltipLabel} )} {menuEntries.map((entry) => entry.kind === "separator" ? ( ) : ( ), )} ); } export function WorkspaceDesktopTabsRow({ paneId, isFocused = false, tabs, normalizedServerId, normalizedWorkspaceId, setHoveredCloseTabKey, onNavigateTab, onCloseTab, onCopyResumeCommand, onCopyAgentId, onCopyTerminalId, onCopyFilePath, onReloadAgent, onRenameTab, onCloseTabsToLeft, onCloseTabsToRight, onCloseOtherTabs, onCreateDraftTab, onCreateTerminalTab, onCreateBrowserTab, showCreateBrowserTab = false, disableCreateTerminal = false, isWaitingOnTerminalReadiness = false, onReorderTabs, onSplitRight, onSplitDown, externalDndContext = false, activeDragTabId = null, tabDropPreviewIndex = null, showPaneSplitActions = true, focusModeEnabled, onExitFocusMode, }: WorkspaceDesktopTabsRowProps) { const { t } = useTranslation(); const router = useRouter(); const newTabKeys = useShortcutKeys("workspace-tab-new"); const focusModeKeys = useShortcutKeys("toggle-focus"); const splitRightKeys = useShortcutKeys("workspace-pane-split-right"); const splitDownKeys = useShortcutKeys("workspace-pane-split-down"); const [tabsContainerWidth, setTabsContainerWidth] = useState(0); const [tabsActionsWidth, setTabsActionsWidth] = useState(0); const [inlineAddButtonWidth, setInlineAddButtonWidth] = useState(0); const [exitFocusModeWidth, setExitFocusModeWidth] = useState(0); const handleTabsContainerLayout = useCallback((event: LayoutChangeEvent) => { updateMeasuredWidth(setTabsContainerWidth, event); }, []); const handleTabsActionsLayout = useCallback((event: LayoutChangeEvent) => { updateMeasuredWidth(setTabsActionsWidth, event); }, []); const handleInlineAddButtonLayout = useCallback((event: LayoutChangeEvent) => { updateMeasuredWidth(setInlineAddButtonWidth, event); }, []); const handleExitFocusModeLayout = useCallback((event: LayoutChangeEvent) => { updateMeasuredWidth(setExitFocusModeWidth, event); }, []); const layoutMetrics = useMemo( () => ({ rowHorizontalInset: 0, actionsReservedWidth: Math.max( 0, tabsActionsWidth + (inlineAddButtonWidth || DEFAULT_INLINE_ADD_BUTTON_RESERVED_WIDTH) + (focusModeEnabled ? exitFocusModeWidth : 0), ), rowPaddingHorizontal: 0, tabGap: 0, maxTabWidth: 200, tabIconWidth: 14, tabHorizontalPadding: 12, estimatedCharWidth: 7, closeButtonWidth: 22, }), [exitFocusModeWidth, focusModeEnabled, inlineAddButtonWidth, tabsActionsWidth], ); const fallbackTabLabels = useMemo( () => ({ newAgent: t("workspace.tabs.fallback.newAgent"), setup: t("workspace.tabs.fallback.setup"), terminal: t("workspace.tabs.fallback.terminal"), agent: t("workspace.tabs.fallback.agent"), changes: t("panels.diff.changesLabel"), }), [t], ); const tabMenuLabels = useMemo( () => ({ copyResumeCommand: t("workspace.tabs.menu.copyResumeCommand"), copyAgentId: t("workspace.tabs.menu.copyAgentId"), copyTerminalId: t("workspace.tabs.menu.copyTerminalId"), copyFilePath: t("workspace.tabs.menu.copyFilePath"), rename: t("workspace.tabs.menu.rename"), closeAbove: t("workspace.tabs.menu.closeAbove"), closeBelow: t("workspace.tabs.menu.closeBelow"), closeLeft: t("workspace.tabs.menu.closeLeft"), closeRight: t("workspace.tabs.menu.closeRight"), closeOthers: t("workspace.tabs.menu.closeOthers"), reloadAgent: t("workspace.tabs.menu.reloadAgent"), reloadAgentTooltip: t("workspace.tabs.menu.reloadAgentTooltip"), close: t("workspace.tabs.menu.close"), }), [t], ); const tabLabelLengths = useMemo( () => tabs.map((tab) => { const label = getFallbackTabLabel(tab.tab, fallbackTabLabels); return label.length; }), [fallbackTabLabels, tabs], ); const { layout } = useWorkspaceTabLayout({ tabLabelLengths, viewportWidthOverride: tabsContainerWidth > 0 ? tabsContainerWidth : null, metrics: layoutMetrics, }); const handleDragEnd = useCallback( (nextTabs: WorkspaceDesktopTabRowItem[]) => { onReorderTabs(nextTabs.map((tab) => tab.tab)); }, [onReorderTabs], ); const getTabDragData = useMemo(() => { if (!paneId) return undefined; return (tab: WorkspaceDesktopTabRowItem) => ({ kind: "workspace-tab" as const, paneId, tabId: tab.tab.tabId, }); }, [paneId]); const handleCreateAgentTab = useCallback(() => { onCreateDraftTab({ paneId }); }, [onCreateDraftTab, paneId]); const handleCreateTerminal = useCallback(() => { onCreateTerminalTab({ paneId }); }, [onCreateTerminalTab, paneId]); const handleCreateTerminalWithProfile = useCallback( (profile: TerminalProfileInput) => { onCreateTerminalTab({ paneId, profile }); }, [onCreateTerminalTab, paneId], ); const handleEditProfiles = useCallback(() => { router.push(buildSettingsHostSectionRoute(normalizedServerId, "terminals") as Href); }, [normalizedServerId, router]); const handleCreateBrowser = useCallback(() => { onCreateBrowserTab({ paneId }); }, [onCreateBrowserTab, paneId]); const terminalDisabled = disableCreateTerminal || isWaitingOnTerminalReadiness; const renderTab = useCallback( ({ item, index, dragHandleProps, isActive, }: DraggableRenderItemInfo) => { const shouldShowCloseButton = layout.closeButtonPolicy === "all"; const layoutItem = layout.items[index] ?? null; const resolvedTabWidth = layoutItem?.width ?? 150; const showLabel = layoutItem?.showLabel ?? true; const showDropIndicatorBefore = activeDragTabId !== null && tabDropPreviewIndex === index; const showDropIndicatorAfter = activeDragTabId !== null && tabDropPreviewIndex === tabs.length && index === tabs.length - 1; return ( ); }, [ activeDragTabId, isFocused, layout.closeButtonPolicy, layout.items, normalizedServerId, normalizedWorkspaceId, onCloseOtherTabs, onCloseTab, onCloseTabsToLeft, onCloseTabsToRight, onCopyAgentId, onCopyTerminalId, onCopyFilePath, onCopyResumeCommand, onNavigateTab, onReloadAgent, onRenameTab, setHoveredCloseTabKey, tabMenuLabels, tabDropPreviewIndex, tabs.length, ], ); const tabsScrollStyle = useMemo( () => [ styles.tabsScroll, layout.requiresHorizontalScrollFallback ? styles.tabsScrollOverflow : styles.tabsScrollFitContent, ], [layout.requiresHorizontalScrollFallback], ); const row = ( {focusModeEnabled ? ( {t("workspace.tabs.actions.exitFocusMode")} {focusModeKeys ? ( ) : null} ) : null} {showPaneSplitActions ? ( <> ) : null} ); return {row}; } function ResolvedDesktopTabChip({ item, isFocused, isDragging, index, tabCount, normalizedServerId, normalizedWorkspaceId, onCopyResumeCommand, onCopyAgentId, onCopyTerminalId, onCopyFilePath, onReloadAgent, onRenameTab, onCloseTabsToLeft, onCloseTabsToRight, onCloseOtherTabs, resolvedTabWidth, showLabel, showCloseButton, setHoveredCloseTabKey, onNavigateTab, onCloseTab, labels, dragHandleProps, showDropIndicatorBefore, showDropIndicatorAfter, }: { item: WorkspaceDesktopTabRowItem; isFocused: boolean; isDragging: boolean; index: number; tabCount: number; normalizedServerId: string; normalizedWorkspaceId: string; onCopyResumeCommand: (agentId: string) => Promise | void; onCopyAgentId: (agentId: string) => Promise | void; onCopyTerminalId: (terminalId: string) => Promise | void; onCopyFilePath: (path: string) => Promise | void; onReloadAgent: (agentId: string) => Promise | void; onRenameTab: (tab: WorkspaceTabDescriptor) => void; onCloseTabsToLeft: (tabId: string) => Promise | void; onCloseTabsToRight: (tabId: string) => Promise | void; onCloseOtherTabs: (tabId: string) => Promise | void; resolvedTabWidth: number; showLabel: boolean; showCloseButton: boolean; setHoveredCloseTabKey: Dispatch>; onNavigateTab: (tabId: string) => void; onCloseTab: (tabId: string) => Promise | void; labels: WorkspaceTabMenuLabels; dragHandleProps: DraggableListDragHandleProps | undefined; showDropIndicatorBefore: boolean; showDropIndicatorAfter: boolean; }) { const { t } = useTranslation(); const resolvedTab = useMemo( () => buildWorkspaceDesktopTabActions({ tab: item.tab, index, tabCount, onCopyResumeCommand, onCopyAgentId, onCopyTerminalId, onCopyFilePath, onReloadAgent, onRenameTab, onCloseTab, onCloseTabsToLeft, onCloseTabsToRight, onCloseOtherTabs, labels, }), [ index, item.tab, onCloseOtherTabs, onCloseTab, onCloseTabsToLeft, onCloseTabsToRight, onCopyAgentId, onCopyTerminalId, onCopyFilePath, onCopyResumeCommand, labels, onReloadAgent, onRenameTab, tabCount, ], ); return ( {(presentation) => { const tooltipLabel = presentation.titleState === "loading" ? t("workspace.tabs.loadingAgentTitle") : presentation.tooltip; return ( {showDropIndicatorBefore ? ( ) : null} {showDropIndicatorAfter ? ( ) : null} ); }} ); } const styles = StyleSheet.create((theme) => ({ tabsContainer: { minWidth: 0, height: WORKSPACE_SECONDARY_HEADER_HEIGHT, borderBottomWidth: 1, borderBottomColor: theme.colors.border, backgroundColor: theme.colors.surface0, flexDirection: "row", alignItems: "center", overflow: "visible", }, tabsScroll: { minWidth: 0, }, tabsScrollFitContent: { flex: 1, }, tabsScrollOverflow: { flex: 1, }, tabsContent: { flexDirection: "row", alignItems: "stretch", }, tabsActions: { flexDirection: "row", alignItems: "center", paddingHorizontal: theme.spacing[2], }, exitFocusModeSlot: { alignSelf: "stretch", flexDirection: "row", alignItems: "center", paddingHorizontal: theme.spacing[1], borderRightWidth: 1, borderRightColor: theme.colors.border, }, inlineAddButton: { flexDirection: "row", alignItems: "center", paddingHorizontal: theme.spacing[1], }, tab: { paddingHorizontal: theme.spacing[3], paddingVertical: theme.spacing[2], borderRightWidth: 1, borderRightColor: theme.colors.border, flexDirection: "row", alignItems: "center", gap: theme.spacing[1], userSelect: "none", }, tabSlot: { position: "relative", overflow: "visible", }, tabHandle: { flexDirection: "row", alignItems: "center", gap: theme.spacing[1], flex: 1, minWidth: 0, userSelect: "none", }, tabIcon: { flexShrink: 0, }, tabFocusIndicator: { position: "absolute", top: 0, left: 0, right: 0, height: 2, backgroundColor: theme.colors.accent, }, tabFocusIndicatorUnfocused: { backgroundColor: theme.colors.borderAccent, }, tabDropIndicator: { position: "absolute", top: theme.spacing[2], bottom: theme.spacing[2], width: 5, borderRadius: theme.borderRadius.full, backgroundColor: theme.colors.accent, zIndex: 10, pointerEvents: "none", }, tabDropIndicatorBefore: { left: -3, }, tabDropIndicatorAfter: { right: -3, }, tabLabel: { flexShrink: 1, minWidth: 0, color: theme.colors.foregroundMuted, fontSize: theme.fontSize.sm, fontWeight: theme.fontWeight.normal, userSelect: "none", }, tabLabelSkeleton: { width: 96, maxWidth: "100%", flexShrink: 1, minWidth: 0, height: 10, borderRadius: theme.borderRadius.full, backgroundColor: theme.colors.surface3, opacity: 0.9, }, tabLabelSkeletonWithCloseButton: { width: LOADING_TAB_LABEL_SKELETON_WIDTH, }, tabLabelWithCloseButton: { paddingRight: 0, }, tabLabelActive: { color: theme.colors.foreground, }, tabCloseButton: { width: 18, height: 18, marginLeft: 0, borderRadius: theme.borderRadius.sm, alignItems: "center", justifyContent: "center", }, tabCloseButtonShown: { opacity: 1, }, tabCloseButtonActive: { backgroundColor: theme.colors.surface3, }, tabModifiedDot: { width: 8, height: 8, borderRadius: theme.borderRadius.full, backgroundColor: theme.colors.foregroundMuted, }, newTabActionButton: { width: 22, height: 22, borderRadius: theme.borderRadius.md, alignItems: "center", justifyContent: "center", }, inlineAddActionButton: { width: 28, height: 28, borderRadius: theme.borderRadius.md, alignItems: "center", justifyContent: "center", }, newTabActionButtonDisabled: { opacity: 0.5, }, newTabActionButtonHovered: { backgroundColor: theme.colors.surface2, }, newTabTooltipText: { color: theme.colors.foreground, fontSize: theme.fontSize.sm, }, newTabTooltipRow: { flexDirection: "row", alignItems: "center", gap: theme.spacing[2], }, newTabTooltipShortcut: {}, tooltipAgentRow: { flexDirection: "row", alignItems: "center", gap: theme.spacing[2], }, tooltipAgentId: { color: theme.colors.foregroundMuted, fontSize: theme.fontSize.xs, }, menuItemHint: { color: theme.colors.foregroundMuted, fontSize: theme.fontSize.xs, }, terminalProfileIconWrapper: { width: 14, height: 14, }, }));