diff --git a/apps/web/src/components/Sidebar.tsx b/apps/web/src/components/Sidebar.tsx index bf129eca..0b289e0b 100644 --- a/apps/web/src/components/Sidebar.tsx +++ b/apps/web/src/components/Sidebar.tsx @@ -1,14 +1,19 @@ import { ArrowLeftIcon, ArrowUpDownIcon, + CheckCircleIcon, ChevronRightIcon, + CircleDotIcon, + CloudUploadIcon, EyeIcon, EyeOffIcon, - ZapIcon, + ExternalLinkIcon, FolderIcon, GitBranchIcon, GitMergeIcon, GitPullRequestIcon, + LinkIcon, + UserIcon, XCircleIcon, PanelLeftCloseIcon, PlusIcon, @@ -17,7 +22,6 @@ import { TerminalIcon, TriangleAlertIcon, } from "lucide-react"; -import { ThemeModeSwitcher } from "./ThemeModeSwitcher"; import { OkCodeMark } from "./OkCodeMark"; import { ConnectionIndicator } from "./ConnectionIndicator"; import { memo, useCallback, useEffect, useMemo, useRef, useState, type MouseEvent } from "react"; @@ -110,7 +114,6 @@ import { isActionableThreadStatus, resolveProjectStatusIndicator, resolveSidebarNewThreadEnvMode, - resolveThreadRowClassName, resolveThreadStatusPill, shouldClearThreadSelectionOnMouseDown, sortProjectsForSidebar, @@ -350,7 +353,7 @@ function SortableProjectItem({ transform: CSS.Translate.toString(transform), transition, }} - className={`group/menu-item relative rounded-md mt-1.5 first:mt-0 ${ + className={`group/menu-item relative rounded-md mt-0.5 first:mt-0 ${ isDragging ? "z-20 opacity-80" : "" } ${isOver && !isDragging ? "ring-1 ring-primary/40" : ""}`} data-sidebar="menu-item" @@ -441,25 +444,41 @@ const MemoizedThreadRow = memo( selectThreadTerminalState(terminalStateByThreadId, thread.id).runningTerminalIds, ); + // Derive a type-based icon for the thread row + const ThreadIcon = prStatus + ? prStatus.icon + : threadStatus?.label === "Completed" + ? CheckCircleIcon + : threadStatus?.label === "Error" + ? XCircleIcon + : threadStatus?.label === "Working" || threadStatus?.label === "Connecting" + ? CircleDotIcon + : threadStatus?.label === "Pending Approval" || threadStatus?.label === "Awaiting Input" + ? UserIcon + : threadStatus?.label === "Plan Ready" + ? LinkIcon + : CircleDotIcon; + + const threadIconColor = prStatus + ? prStatus.colorClass + : threadStatus + ? threadStatus.colorClass + : "text-muted-foreground/50"; + return ( - {/* Project color accent bar */} - } size="sm" isActive={isActive} - className={resolveThreadRowClassName({ - isActive, - isSelected, - })} + className={cn( + "h-auto min-h-7 translate-x-0 items-center gap-2 rounded-md px-2 py-1 text-left", + isActive + ? "bg-accent/60 text-foreground" + : isSelected + ? "bg-accent/40 text-foreground" + : "text-muted-foreground hover:bg-accent/40 hover:text-foreground", + )} onClick={(event) => { handleThreadClick(event, thread.id, orderedProjectThreadIds); }} @@ -493,38 +512,8 @@ const MemoizedThreadRow = memo( } }} > + - {prStatus && ( - - { - openPrLink(event, prStatus.url); - }} - > - - - } - /> - {prStatus.tooltip} - - )} - {threadStatus && ( - - - {threadStatus.label} - - )} - - {terminalStatus && ( - - - - )} - - {formatRelativeTimeFn(thread.updatedAt ?? thread.createdAt)} - - + ); @@ -1447,39 +1416,17 @@ export default function Sidebar() { const orderedProjectThreadIds = projectThreads.map((thread) => thread.id); const renderedThreads = pinnedCollapsedThread ? [pinnedCollapsedThread] : visibleThreads; const pColor = getProjectColor(project.id); - const isDark = resolvedTheme === "dark"; return ( - + { - const hoverBg = appSettings.sidebarAccentBgColorOverride - ? `color-mix(in srgb, ${appSettings.sidebarAccentBgColorOverride} 25%, transparent)` - : isDark - ? pColor.bgDark.replace("0.12)", "0.20)") - : pColor.bg.replace("0.08)", "0.14)"); - e.currentTarget.style.backgroundColor = hoverBg; - }} - onMouseLeave={(e) => { - e.currentTarget.style.backgroundColor = appSettings.sidebarAccentBgColorOverride - ? `color-mix(in srgb, ${appSettings.sidebarAccentBgColorOverride} 15%, transparent)` - : isDark - ? pColor.bgDark - : pColor.bg; - }} + className={cn( + "min-w-0 flex-1 gap-0 rounded-md px-2 py-1.5 text-left hover:bg-transparent", + isManualProjectSorting ? "cursor-grab active:cursor-grabbing" : "cursor-pointer", + )} {...(isManualProjectSorting && dragHandleProps ? dragHandleProps.attributes : {})} {...(isManualProjectSorting && dragHandleProps ? dragHandleProps.listeners : {})} onPointerDownCapture={handleProjectTitlePointerDownCapture} @@ -1493,35 +1440,12 @@ export default function Sidebar() { }); }} > - {!project.expanded && projectStatus ? ( - - - - - - - ) : ( - - )} - {editingProjectId === project.id ? ( setDraftProjectTitle(e.target.value)} onKeyDown={(e) => { if (e.key === "Enter") { @@ -1538,18 +1462,7 @@ export default function Sidebar() { ) : ( { e.stopPropagation(); startProjectEditing({ @@ -1560,42 +1473,13 @@ export default function Sidebar() { > {project.name} - {projectDisambiguationPaths.has(project.id) && ( - - {projectDisambiguationPaths.get(project.id)} - - )} )} - - { - event.preventDefault(); - event.stopPropagation(); - void createNewThreadForProject(project.id); - }} - > - - - } - /> - - {newThreadShortcutLabel ? `New thread (${newThreadShortcutLabel})` : "New thread"} - - - + {renderedThreads.map((thread) => ( {project.expanded && !appSettings.sidebarHideFiles ? ( - + setFilesCollapsedByProject((current) => { const next = new Set(current); @@ -2299,9 +2183,9 @@ export default function Sidebar() { ) : ( - + {sortedProjects.map((project) => ( - + {renderProjectItem(project, null)} ))} @@ -2338,33 +2222,8 @@ export default function Sidebar() { className="gap-2 px-2 py-1.5 text-muted-foreground/70 hover:bg-accent hover:text-foreground" onClick={() => void navigate({ to: "/pr-review" })} > - - PR Review - - - - void navigate({ to: "/merge-conflicts" })} - > - - Merge Conflicts - - - - - void navigate({ - to: "/skills", - search: { create: undefined, name: undefined }, - }) - } - > - - Skills + + Open Workspace @@ -2377,9 +2236,6 @@ export default function Sidebar() { Settings - - - > )}