import { memo, useCallback, useMemo, useState, type MutableRefObject, type ReactNode, type Ref, } from "react"; import { useTranslation } from "react-i18next"; import { View, Text, Pressable, ScrollView, type GestureResponderEvent, type PressableStateCallbackType, } from "react-native"; import { NestableScrollContainer } from "react-native-draggable-flatlist"; import type { GestureType } from "react-native-gesture-handler"; import { navigateToWorkspace } from "@/stores/navigation-active-workspace-store"; import { useActiveWorkspaceSelection } from "@/stores/navigation-active-workspace-store"; import { type SidebarWorkspaceEntry } from "@/hooks/use-sidebar-workspaces-list"; import type { StatusBucket } from "@/hooks/sidebar-status-view-model"; import type { SidebarWorkspaceGroup } from "@/components/sidebar/sidebar-labels"; import { SidebarFilterEmptyState } from "@/components/sidebar/empty-states"; import type { HostBadgeModel } from "@/hosts/appearance"; import { isWeb as platformIsWeb, isNative as platformIsNative } from "@/constants/platform"; import { useIsCompactFormFactor } from "@/constants/layout"; import { StyleSheet } from "react-native-unistyles"; import type { Theme } from "@/styles/theme"; import type { SidebarSurfaceBackdrop } from "@/styles/surface-backdrop"; import { withUnistyles } from "react-native-unistyles"; import { ChevronDown, ChevronRight, CircleAlert, CircleCheck, CircleDot, CircleX, } from "lucide-react-native"; import { useToast } from "@/contexts/toast-context"; import { useMutation } from "@tanstack/react-query"; import { getHostRuntimeStore } from "@/runtime/host-runtime"; import { AdaptiveRenameModal } from "@/components/rename-modal"; import { requireWorkspaceDirectory } from "@/utils/workspace-directory"; import { redirectIfArchivingActiveWorkspace } from "@/utils/sidebar-workspace-archive-redirect"; import { useWorkspaceArchive } from "@/workspace/use-workspace-archive"; import { toWorktreeArchiveRisk } from "@/git/worktree-archive-warning"; import * as Clipboard from "expo-clipboard"; import type { ShortcutKey } from "@/utils/format-shortcut"; import { useShortcutKeys } from "@/hooks/use-shortcut-keys"; import { useKeyboardActionHandler } from "@/hooks/use-keyboard-action-handler"; import { useClearWorkspaceAttention } from "@/hooks/use-clear-workspace-attention"; import { SidebarWorkspaceRowFrame, SidebarWorkspaceRowContent, resolveTrailingActionVisibility, SidebarWorkspaceTrailingActionBase, SidebarWorkspaceTrailingActionOverlay, SidebarWorkspaceTrailingActionSlot, sidebarWorkspaceRowStyles, } from "@/components/sidebar/sidebar-workspace-row-content"; import { useOpenKebabMenuVisibility } from "@/components/sidebar/use-open-kebab-menu-visibility"; import { getSidebarRowBackdrop } from "@/components/sidebar/sidebar-row-backdrop"; import { getStatusDotColor } from "@/utils/status-dot-color"; import { selectWorkspaceServiceSummary } from "@/components/sidebar/workspace-meta-row"; import { SidebarWorkspaceTrailingContent, useSidebarWorkspaceTrailing, type SidebarWorkspaceTrailing, } from "@/components/sidebar/workspace-trailing"; import { useSidebarCollapsedSectionsStore } from "@/stores/sidebar-collapsed-sections-store"; import { SidebarWorkspaceContextMenu, SidebarWorkspaceMenu, } from "@/components/sidebar/sidebar-workspace-menu"; import { PinnedSectionHeader } from "@/components/sidebar/pinned-section-header"; import { SidebarGroupToggleRow } from "@/components/sidebar/sidebar-group-toggle-row"; import { useLimitedSidebarGroup } from "@/components/sidebar/use-limited-sidebar-group"; import type { ToggleSidebarWorkspacePin } from "@/hooks/use-sidebar-workspace-pin"; import { DraggableList, type DraggableRenderItemInfo } from "@/components/draggable-list"; import type { DraggableListDragHandleProps } from "@/components/draggable-list.types"; import { useLongPressDragInteraction } from "@/components/sidebar/use-long-press-drag-interaction"; // Themed icon wrappers const foregroundMutedColorMapping = (theme: Theme) => ({ color: theme.colors.foregroundMuted, }); // One mapping per bucket, resolved through the status-dot producer so a group header and // the rows under it cannot disagree about what "failed" looks like. const needsInputColorMapping = (theme: Theme) => ({ color: getStatusDotColor({ theme, bucket: "needs_input" }) ?? undefined, }); const failedColorMapping = (theme: Theme) => ({ color: getStatusDotColor({ theme, bucket: "failed" }) ?? undefined, }); const attentionColorMapping = (theme: Theme) => ({ color: getStatusDotColor({ theme, bucket: "attention" }) ?? undefined, }); const runningColorMapping = (theme: Theme) => ({ color: getStatusDotColor({ theme, bucket: "running" }) ?? undefined, }); const ThemedChevronDown = withUnistyles(ChevronDown); const ThemedChevronRight = withUnistyles(ChevronRight); const ThemedCircleAlert = withUnistyles(CircleAlert); const ThemedCircleCheck = withUnistyles(CircleCheck); const ThemedCircleDot = withUnistyles(CircleDot); const ThemedCircleX = withUnistyles(CircleX); const EMPTY_SHORTCUT_INDEX = new Map(); function statusWorkspaceKeyExtractor(workspace: SidebarWorkspaceEntry): string { return workspace.workspaceKey; } interface StatusWorkspaceListProps { groups: SidebarWorkspaceGroup[]; pinnedWorkspaces: SidebarWorkspaceEntry[]; projectIconByProjectViewKey: ReadonlyMap; shortcutIndexByWorkspaceKey: Map; showShortcutBadges: boolean; onWorkspacePress?: () => void; hostBadgeByServerId: ReadonlyMap; supportsPinningByServerId: ReadonlyMap; onToggleWorkspacePin: ToggleSidebarWorkspacePin; onPinnedWorkspaceReorder: (workspaces: SidebarWorkspaceEntry[]) => void; listHeaderComponent?: ReactNode; /** Swaps the group list for the label filter's empty state. Never the header above it. */ sidebarFilterEmpty?: boolean; parentGestureRef?: MutableRefObject; dragGestureHostPresented?: boolean; } export function SidebarStatusWorkspaceList({ groups, pinnedWorkspaces, projectIconByProjectViewKey, shortcutIndexByWorkspaceKey, showShortcutBadges, onWorkspacePress, hostBadgeByServerId, supportsPinningByServerId, onToggleWorkspacePin, onPinnedWorkspaceReorder, listHeaderComponent, sidebarFilterEmpty = false, parentGestureRef, dragGestureHostPresented, }: StatusWorkspaceListProps) { const collapsedWorkspaceGroupKeys = useSidebarCollapsedSectionsStore( (state) => state.collapsedWorkspaceGroupKeys, ); const pinnedCollapsed = useSidebarCollapsedSectionsStore((state) => state.collapsedPinned); const togglePinnedCollapsed = useSidebarCollapsedSectionsStore( (state) => state.togglePinnedCollapsed, ); const { visibleItems: visiblePinnedWorkspaces, expanded: pinnedWorkspacesExpanded, canToggle: canTogglePinnedWorkspaces, toggleExpanded: togglePinnedWorkspacesExpanded, } = useLimitedSidebarGroup(pinnedWorkspaces); const statusShortcutIndex = showShortcutBadges ? shortcutIndexByWorkspaceKey : EMPTY_SHORTCUT_INDEX; const renderPinnedWorkspace = useCallback( ({ item: workspace, drag, isActive, dragHandleProps, }: DraggableRenderItemInfo) => ( ), [ hostBadgeByServerId, onToggleWorkspacePin, onWorkspacePress, projectIconByProjectViewKey, showShortcutBadges, statusShortcutIndex, supportsPinningByServerId, ], ); const content = ( <> {pinnedWorkspaces.length > 0 ? ( {pinnedCollapsed ? null : ( <> {canTogglePinnedWorkspaces ? ( ) : null} )} ) : null} {listHeaderComponent} {sidebarFilterEmpty ? ( ) : ( )} ); return ( {platformIsNative ? ( {content} ) : ( {content} )} ); } function StatusGroupList({ groups, collapsedWorkspaceGroupKeys, projectIconByProjectViewKey, shortcutIndex, showShortcutBadges, onWorkspacePress, hostBadgeByServerId, supportsPinningByServerId, onToggleWorkspacePin, }: { groups: SidebarWorkspaceGroup[]; collapsedWorkspaceGroupKeys: ReadonlySet; projectIconByProjectViewKey: ReadonlyMap; shortcutIndex: Map; showShortcutBadges: boolean; onWorkspacePress?: () => void; hostBadgeByServerId: ReadonlyMap; supportsPinningByServerId: ReadonlyMap; onToggleWorkspacePin: ToggleSidebarWorkspacePin; }) { return ( <> {groups.map((group) => ( ))} ); } function StatusGroupRows({ group, collapsed, projectIconByProjectViewKey, shortcutIndex, showShortcutBadges, onWorkspacePress, hostBadgeByServerId, supportsPinningByServerId, onToggleWorkspacePin, }: { group: SidebarWorkspaceGroup; collapsed: boolean; projectIconByProjectViewKey: ReadonlyMap; shortcutIndex: Map; showShortcutBadges: boolean; onWorkspacePress?: () => void; hostBadgeByServerId: ReadonlyMap; supportsPinningByServerId: ReadonlyMap; onToggleWorkspacePin: ToggleSidebarWorkspacePin; }) { const { visibleItems: visibleWorkspaces, expanded: workspacesExpanded, canToggle: canToggleWorkspaces, toggleExpanded: toggleWorkspacesExpanded, } = useLimitedSidebarGroup(group.rows); return ( {!collapsed ? ( {visibleWorkspaces.map((workspace) => ( ))} {canToggleWorkspaces ? ( ) : null} ) : null} ); } interface StatusRowProjectPresentation { hostBadge: HostBadgeModel | null; projectName: string; projectIconDataUri: string | null; } function buildStatusRowProjectPresentation({ workspace, projectIconByProjectViewKey, hostBadgeByServerId, }: { workspace: SidebarWorkspaceEntry; projectIconByProjectViewKey: ReadonlyMap; hostBadgeByServerId: ReadonlyMap; }): StatusRowProjectPresentation { return { hostBadge: hostBadgeByServerId.get(workspace.serverId) ?? null, projectName: workspace.projectName, projectIconDataUri: projectIconByProjectViewKey.get(workspace.projectViewKey) ?? null, }; } function StatusGroupHeader({ group, collapsed, }: { group: SidebarWorkspaceGroup; collapsed: boolean; }) { const [isHovered, setIsHovered] = useState(false); const toggleWorkspaceGroupCollapsed = useSidebarCollapsedSectionsStore( (state) => state.toggleWorkspaceGroupCollapsed, ); const handlePress = useCallback(() => { toggleWorkspaceGroupCollapsed(group.key); }, [group.key, toggleWorkspaceGroupCollapsed]); const handleHoverIn = useCallback(() => setIsHovered(true), []); const handleHoverOut = useCallback(() => setIsHovered(false), []); const rowStyle = useCallback( ({ pressed }: PressableStateCallbackType) => [ styles.statusGroupRow, isHovered && styles.statusGroupRowHovered, pressed && styles.statusGroupRowPressed, ], [isHovered], ); const accessibilityState = useMemo(() => ({ expanded: !collapsed }), [collapsed]); return ( {group.label} ); } function StatusGroupLeadingVisual({ leading, collapsed, showChevron, }: { leading: SidebarWorkspaceGroup["leading"]; collapsed: boolean; showChevron: boolean; }) { if (!showChevron) { return ; } if (collapsed) { return ; } return ; } function StatusGroupIcon({ bucket }: { bucket: StatusBucket }) { switch (bucket) { case "needs_input": return ; case "failed": return ; case "attention": return ; case "running": return ; case "done": return ; } } const StatusWorkspaceRow = memo(function StatusWorkspaceRow({ workspace, hostBadge, projectName, projectIconDataUri, shortcutNumber, showShortcutBadge, canPin, onToggleWorkspacePin, reserveIdleStatusIndicatorSpace = true, inStatusGroup = true, onWorkspacePress, drag, isDragging = false, dragHandleProps, }: { workspace: SidebarWorkspaceEntry; hostBadge: HostBadgeModel | null; projectName: string; projectIconDataUri: string | null; shortcutNumber: number | null; showShortcutBadge: boolean; canPin: boolean; onToggleWorkspacePin: ToggleSidebarWorkspacePin; reserveIdleStatusIndicatorSpace?: boolean; /** * Whether the row sits under a status header, which is what it indents from. Pinned rows * are a flat list under their own header and sit flush. */ inStatusGroup?: boolean; onWorkspacePress?: () => void; drag?: () => void; isDragging?: boolean; dragHandleProps?: DraggableListDragHandleProps; }) { const activeWorkspaceSelection = useActiveWorkspaceSelection(); const selected = activeWorkspaceSelection?.serverId === workspace.serverId && activeWorkspaceSelection?.workspaceId === workspace.workspaceId; const handlePress = useCallback(() => { if (!workspace.serverId) return; onWorkspacePress?.(); navigateToWorkspace({ serverId: workspace.serverId, workspaceId: workspace.workspaceId }); }, [onWorkspacePress, workspace.serverId, workspace.workspaceId]); return ( ); }); function StatusWorkspaceRowWithMenu({ workspace, hostBadge, projectName, projectIconDataUri, selected, shortcutNumber, showShortcutBadge, canPin, onToggleWorkspacePin, reserveIdleStatusIndicatorSpace = true, inStatusGroup = true, onPress, drag, isDragging = false, dragHandleProps, }: { workspace: SidebarWorkspaceEntry; hostBadge: HostBadgeModel | null; projectName: string; projectIconDataUri: string | null; selected: boolean; shortcutNumber: number | null; showShortcutBadge: boolean; canPin: boolean; onToggleWorkspacePin: ToggleSidebarWorkspacePin; reserveIdleStatusIndicatorSpace?: boolean; /** * Whether the row sits under a status header, which is what it indents from. Pinned rows * are a flat list under their own header and sit flush. */ inStatusGroup?: boolean; onPress: () => void; drag?: () => void; isDragging?: boolean; dragHandleProps?: DraggableListDragHandleProps; }) { const { t } = useTranslation(); const toast = useToast(); const [isHidingWorkspace, setIsHidingWorkspace] = useState(false); const [isRenameOpen, setIsRenameOpen] = useState(false); const isArchiving = workspace.archivingAt !== null || isHidingWorkspace; const redirectAfterArchive = useCallback(() => { redirectIfArchivingActiveWorkspace({ serverId: workspace.serverId, workspaceId: workspace.workspaceId, activeWorkspaceSelection: selected ? { serverId: workspace.serverId, workspaceId: workspace.workspaceId } : null, }); }, [selected, workspace]); const archiveController = useWorkspaceArchive({ serverId: workspace.serverId, workspaceId: workspace.workspaceId, workspaceKind: workspace.workspaceKind, name: workspace.name, ...toWorktreeArchiveRisk(workspace), onArchiveStarted: redirectAfterArchive, onSetHiding: setIsHidingWorkspace, }); const handleArchive = useCallback(() => { if (isArchiving) return; archiveController.archive(); }, [archiveController, isArchiving]); const handleCopyPath = useCallback(() => { let copyTargetDirectory: string; try { copyTargetDirectory = requireWorkspaceDirectory({ workspaceId: workspace.workspaceId, workspaceDirectory: workspace.workspaceDirectory, }); } catch (error) { toast.error(error instanceof Error ? error.message : "Workspace path not available"); return; } void Clipboard.setStringAsync(copyTargetDirectory); toast.copied("Path copied"); }, [toast, workspace.workspaceDirectory, workspace.workspaceId]); const handleCopyBranchName = useCallback(() => { void Clipboard.setStringAsync(workspace.name); toast.copied("Branch name copied"); }, [toast, workspace.name]); const renameMutation = useMutation({ mutationFn: async (title: string) => { const client = getHostRuntimeStore().getClient(workspace.serverId); if (!client) throw new Error(t("workspace.terminal.hostDisconnected")); await client.setWorkspaceTitle(workspace.workspaceId, title.length === 0 ? null : title); }, }); const handleOpenRename = useCallback(() => setIsRenameOpen(true), []); const handleCloseRename = useCallback(() => setIsRenameOpen(false), []); const handleSubmitRename = useCallback( async (value: string) => { await renameMutation.mutateAsync(value.trim()); }, [renameMutation], ); const isPinned = workspace.pinnedAt != null; const handleTogglePin = useCallback(() => { onToggleWorkspacePin(workspace); }, [onToggleWorkspacePin, workspace]); const onTogglePin = canPin ? handleTogglePin : undefined; const archiveShortcutKeys = useShortcutKeys("archive-workspace"); const { hasClearableAttention, clearAttention } = useClearWorkspaceAttention({ serverId: workspace.serverId, workspaceId: workspace.workspaceId, }); const handleMarkAsRead = useCallback(() => { void clearAttention().catch((error) => { toast.error(error instanceof Error ? error.message : "Failed to mark workspace as read"); }); }, [clearAttention, toast]); useKeyboardActionHandler({ handlerId: `workspace-archive-${workspace.workspaceKey}`, actions: ["workspace.archive"], enabled: selected && !isArchiving, priority: 0, handle: () => { handleArchive(); return true; }, }); return ( <> ); } interface StatusWorkspaceRowInnerProps { workspace: SidebarWorkspaceEntry; hostBadge: HostBadgeModel | null; projectName: string; projectIconDataUri: string | null; selected: boolean; shortcutNumber: number | null; showShortcutBadge: boolean; onPress: () => void; isArchiving: boolean; archiveLabel?: string; archiveStatus?: "idle" | "pending" | "success"; archivePendingLabel?: string; onArchive?: () => void; onCopyBranchName?: () => void; onCopyPath?: () => void; onRename?: () => void; onMarkAsRead?: () => void; archiveShortcutKeys?: ShortcutKey[][] | null; isPinned?: boolean; onTogglePin?: () => void; reserveIdleStatusIndicatorSpace?: boolean; /** Pinned rows are flat under their own header; status-group rows indent from theirs. */ inStatusGroup?: boolean; drag?: () => void; isDragging?: boolean; dragHandleProps?: DraggableListDragHandleProps; } function StatusWorkspaceRowInner(props: StatusWorkspaceRowInnerProps) { if (props.drag) { return ; } return ; } function DraggableStatusWorkspaceRowInner( props: StatusWorkspaceRowInnerProps & { drag: () => void }, ) { const dragInteraction = useLongPressDragInteraction({ drag: props.drag, menuController: null, }); return ; } function StatusWorkspaceRowInnerContent({ workspace, hostBadge, projectName, projectIconDataUri, selected, shortcutNumber, showShortcutBadge, onPress, isArchiving, archiveLabel, archiveStatus = "idle", archivePendingLabel, onArchive, onCopyBranchName, onCopyPath, onRename, onMarkAsRead, archiveShortcutKeys, isPinned, onTogglePin, reserveIdleStatusIndicatorSpace = true, inStatusGroup = true, isDragging = false, dragHandleProps, dragInteraction, }: StatusWorkspaceRowInnerProps & { dragInteraction?: ReturnType; }) { const isCompact = useIsCompactFormFactor(); const isTouchPlatform = platformIsNative || isCompact; const [isPressed, setIsPressed] = useState(false); const trailing = useSidebarWorkspaceTrailing(); const { role: _dragRole, tabIndex: _dragTabIndex, "aria-roledescription": _dragRoleDescription, ...dragAttributes } = dragHandleProps?.attributes ?? {}; const isDesktop = !isTouchPlatform; const serviceSummary = isDesktop ? selectWorkspaceServiceSummary(workspace.scripts) : null; const accessibilityState = useMemo(() => ({ selected }), [selected]); const didLongPressRef = dragInteraction?.didLongPressRef; const startDragPress = dragInteraction?.handlePressIn; const moveDragPress = dragInteraction?.handleTouchMove; const endDragPress = dragInteraction?.handlePressOut; const handlePress = useCallback(() => { if (didLongPressRef?.current) { didLongPressRef.current = false; return; } onPress(); }, [didLongPressRef, onPress]); const handlePressIn = useCallback( (event: GestureResponderEvent) => { setIsPressed(true); startDragPress?.(event); }, [startDragPress], ); const handlePressOut = useCallback(() => { setIsPressed(false); endDragPress?.(); }, [endDragPress]); return ( {({ isHovered, contextMenuOpen, onContextMenuOpenChange, hoverHandlers }) => { const showShortcut = showShortcutBadge && shortcutNumber !== null; const { showTrailing, showKebab: showKebabInSlot, showScrim, renderSlot, reserveSlotWidth, } = resolveTrailingActionVisibility({ workspace, trailing, hasArchiveAction: Boolean(onArchive), isHovered, isTouchPlatform, showShortcut, }); const workspaceRowStyle = getStatusWorkspaceRowStyle({ isPressed, selected, isHovered, inStatusGroup, isDragging, }); const backdrop = getSidebarRowBackdrop({ isDragging, isPressed, selected, isHovered }); return ( } style={styles.workspaceRowContainer} {...hoverHandlers} > {renderSlot ? ( ) : null} ); }} ); } function StatusWorkspaceActionSlot({ workspace, backdrop, trailing, showBase, showKebab, showScrim, reserveSlotWidth, isPinned, onTogglePin, onCopyPath, onCopyBranchName, onRename, onMarkAsRead, onArchive, archiveLabel, archiveStatus, archivePendingLabel, archiveShortcutKeys, }: { workspace: SidebarWorkspaceEntry; backdrop: SidebarSurfaceBackdrop; trailing: SidebarWorkspaceTrailing; showBase: boolean; showKebab: boolean; showScrim: boolean; reserveSlotWidth: boolean; isPinned?: boolean; onTogglePin?: () => void; onCopyPath?: () => void; onCopyBranchName?: () => void; onRename?: () => void; onMarkAsRead?: () => void; onArchive?: () => void; archiveLabel?: string; archiveStatus?: "idle" | "pending" | "success"; archivePendingLabel?: string; archiveShortcutKeys?: ShortcutKey[][] | null; }) { const kebab = useOpenKebabMenuVisibility(showKebab); return ( {kebab.showKebab && onArchive ? ( ) : null} ); } function getStatusWorkspaceRowStyle({ isPressed, selected, isHovered, inStatusGroup, isDragging, }: { isPressed: boolean; selected: boolean; isHovered: boolean; inStatusGroup: boolean; isDragging: boolean; }) { return [ styles.workspaceRow, inStatusGroup && sidebarWorkspaceRowStyles.rowIndented, isHovered && styles.workspaceRowHovered, selected && styles.sidebarRowSelected, isDragging && styles.workspaceRowDragging, isPressed && styles.workspaceRowPressed, ]; } const styles = StyleSheet.create((theme) => ({ container: { flex: 1, }, list: { flex: 1, }, listContent: { paddingHorizontal: theme.spacing[2], // Keep status mode's Pinned/Workspaces boundary identical to project mode. paddingTop: 2, paddingBottom: theme.spacing[4], }, pinnedSection: { marginBottom: theme.spacing[1], }, // Matches `projectBlockExpanded` in sidebar-workspace-list.tsx. See the note there. statusGroupBlockExpanded: { paddingBottom: theme.spacing[3], }, statusWorkspaceListContainer: {}, statusGroupRow: { minHeight: 36, paddingVertical: theme.spacing[2], paddingHorizontal: theme.spacing[2], borderRadius: theme.borderRadius.lg, marginBottom: theme.spacing[2], flexDirection: "row", alignItems: "center", justifyContent: "space-between", gap: theme.spacing[2], userSelect: "none", }, statusGroupRowHovered: { backgroundColor: theme.colors.surfaceSidebarHover, }, statusGroupRowPressed: { backgroundColor: theme.colors.surface2, }, statusGroupRowLeft: { flexDirection: "row", alignItems: "center", gap: theme.spacing[2], flex: 1, minWidth: 0, }, statusGroupLeadingVisualSlot: { position: "relative", width: theme.iconSize.md, height: theme.iconSize.md, flexShrink: 0, alignItems: "center", justifyContent: "center", }, statusGroupTitleGroup: { flexDirection: "row", alignItems: "center", gap: theme.spacing[1], flex: 1, minWidth: 0, }, statusGroupTitle: { color: theme.colors.foregroundMuted, fontSize: theme.fontSize.base, fontWeight: "400", minWidth: 0, flexShrink: 1, }, workspaceRowContainer: { position: "relative", }, workspaceRow: { minHeight: 36, marginBottom: theme.spacing[0.5], paddingVertical: theme.spacing[2], paddingLeft: theme.spacing[2], paddingRight: theme.spacing[3], borderRadius: theme.borderRadius.lg, flexDirection: "column", alignItems: "stretch", justifyContent: "flex-start", gap: theme.spacing[1], userSelect: "none", }, workspaceRowHovered: { backgroundColor: theme.colors.surfaceSidebarHover, }, workspaceRowPressed: { backgroundColor: theme.colors.surface2, }, workspaceRowDragging: { backgroundColor: theme.colors.surface2, borderWidth: 1, borderColor: theme.colors.border, transform: [{ scale: 1.02 }], zIndex: 3, ...theme.shadow.md, }, sidebarRowSelected: { backgroundColor: theme.colors.surfaceSidebarSelected, }, }));