diff --git a/apps/web/src/components/Sidebar.logic.test.ts b/apps/web/src/components/Sidebar.logic.test.ts index b54ec1cb93..d7f93f371d 100644 --- a/apps/web/src/components/Sidebar.logic.test.ts +++ b/apps/web/src/components/Sidebar.logic.test.ts @@ -429,6 +429,9 @@ describe("getVisibleThreadsForProject", () => { ThreadId.makeUnsafe("thread-6"), ThreadId.makeUnsafe("thread-8"), ]); + expect(result.hiddenThreads.map((thread) => thread.id)).toEqual([ + ThreadId.makeUnsafe("thread-7"), + ]); }); it("returns all threads when the list is expanded", () => { @@ -449,6 +452,7 @@ describe("getVisibleThreadsForProject", () => { expect(result.visibleThreads.map((thread) => thread.id)).toEqual( threads.map((thread) => thread.id), ); + expect(result.hiddenThreads).toEqual([]); }); }); diff --git a/apps/web/src/components/Sidebar.logic.ts b/apps/web/src/components/Sidebar.logic.ts index 1e0871e0d2..f911775446 100644 --- a/apps/web/src/components/Sidebar.logic.ts +++ b/apps/web/src/components/Sidebar.logic.ts @@ -244,6 +244,7 @@ export function getVisibleThreadsForProject(input: { previewLimit: number; }): { hasHiddenThreads: boolean; + hiddenThreads: Thread[]; visibleThreads: Thread[]; } { const { activeThreadId, isThreadListExpanded, previewLimit, threads } = input; @@ -252,6 +253,7 @@ export function getVisibleThreadsForProject(input: { if (!hasHiddenThreads || isThreadListExpanded) { return { hasHiddenThreads, + hiddenThreads: [], visibleThreads: [...threads], }; } @@ -260,6 +262,7 @@ export function getVisibleThreadsForProject(input: { if (!activeThreadId || previewThreads.some((thread) => thread.id === activeThreadId)) { return { hasHiddenThreads: true, + hiddenThreads: threads.slice(previewLimit), visibleThreads: previewThreads, }; } @@ -268,6 +271,7 @@ export function getVisibleThreadsForProject(input: { if (!activeThread) { return { hasHiddenThreads: true, + hiddenThreads: threads.slice(previewLimit), visibleThreads: previewThreads, }; } @@ -276,6 +280,7 @@ export function getVisibleThreadsForProject(input: { return { hasHiddenThreads: true, + hiddenThreads: threads.filter((thread) => !visibleThreadIds.has(thread.id)), visibleThreads: threads.filter((thread) => visibleThreadIds.has(thread.id)), }; } diff --git a/apps/web/src/components/Sidebar.tsx b/apps/web/src/components/Sidebar.tsx index 100d0e3f47..02aa4fb827 100644 --- a/apps/web/src/components/Sidebar.tsx +++ b/apps/web/src/components/Sidebar.tsx @@ -151,6 +151,44 @@ interface PrStatusIndicator { type ThreadPr = GitStatusResult["pr"]; +function ThreadStatusLabel({ + status, + compact = false, +}: { + status: NonNullable>; + compact?: boolean; +}) { + if (compact) { + return ( + + + {status.label} + + ); + } + + return ( + + + {status.label} + + ); +} + function terminalStatusFromRunningIds( runningTerminalIds: string[], ): TerminalStatusIndicator | null { @@ -1023,14 +1061,18 @@ export default function Sidebar() { visibleThreads.filter((thread) => thread.projectId === project.id), appSettings.sidebarThreadSortOrder, ); - const projectStatus = resolveProjectStatusIndicator( - projectThreads.map((thread) => + const threadStatuses = new Map( + projectThreads.map((thread) => [ + thread.id, resolveThreadStatusPill({ thread, hasPendingApprovals: derivePendingApprovals(thread.activities).length > 0, hasPendingUserInput: derivePendingUserInputs(thread.activities).length > 0, }), - ), + ]), + ); + const projectStatus = resolveProjectStatusIndicator( + projectThreads.map((thread) => threadStatuses.get(thread.id) ?? null), ); const activeThreadId = routeThreadId ?? undefined; const isThreadListExpanded = expandedThreadListsByProject.has(project.id); @@ -1039,13 +1081,19 @@ export default function Sidebar() { ? (projectThreads.find((thread) => thread.id === activeThreadId) ?? null) : null; const shouldShowThreadPanel = project.expanded || pinnedCollapsedThread !== null; - const { hasHiddenThreads, visibleThreads: visibleProjectThreads } = - getVisibleThreadsForProject({ - threads: projectThreads, - activeThreadId, - isThreadListExpanded, - previewLimit: THREAD_PREVIEW_LIMIT, - }); + const { + hasHiddenThreads, + hiddenThreads, + visibleThreads: visibleProjectThreads, + } = getVisibleThreadsForProject({ + threads: projectThreads, + activeThreadId, + isThreadListExpanded, + previewLimit: THREAD_PREVIEW_LIMIT, + }); + const hiddenThreadStatus = resolveProjectStatusIndicator( + hiddenThreads.map((thread) => threadStatuses.get(thread.id) ?? null), + ); const orderedProjectThreadIds = projectThreads.map((thread) => thread.id); const renderedThreads = pinnedCollapsedThread ? [pinnedCollapsedThread] @@ -1053,10 +1101,12 @@ export default function Sidebar() { return { hasHiddenThreads, + hiddenThreadStatus, orderedProjectThreadIds, project, projectStatus, projectThreads, + threadStatuses, renderedThreads, shouldShowThreadPanel, isThreadListExpanded, @@ -1198,10 +1248,12 @@ export default function Sidebar() { ) { const { hasHiddenThreads, + hiddenThreadStatus, orderedProjectThreadIds, project, projectStatus, projectThreads, + threadStatuses, renderedThreads, shouldShowThreadPanel, isThreadListExpanded, @@ -1213,11 +1265,7 @@ export default function Sidebar() { const jumpLabel = threadJumpLabelById.get(thread.id) ?? null; const isThreadRunning = thread.session?.status === "running" && thread.session.activeTurnId != null; - const threadStatus = resolveThreadStatusPill({ - thread, - hasPendingApprovals: derivePendingApprovals(thread.activities).length > 0, - hasPendingUserInput: derivePendingUserInputs(thread.activities).length > 0, - }); + const threadStatus = threadStatuses.get(thread.id) ?? null; const prStatus = prStatusIndicator(prByThreadId.get(thread.id) ?? null); const terminalStatus = terminalStatusFromRunningIds( selectThreadTerminalState(terminalStateByThreadId, thread.id).runningTerminalIds, @@ -1291,18 +1339,7 @@ export default function Sidebar() { {prStatus.tooltip} )} - {threadStatus && ( - - - {threadStatus.label} - - )} + {threadStatus && } {renamingThreadId === thread.id ? ( { @@ -1548,7 +1585,10 @@ export default function Sidebar() { expandThreadListForProject(project.id); }} > - Show more + + {hiddenThreadStatus && } + Show more + )}