@@ -85,6 +85,7 @@ import { useThreadSelectionStore } from "../threadSelectionStore";
8585import { formatWorktreePathForDisplay , getOrphanedWorktreePathForThread } from "../worktreeCleanup" ;
8686import { isNonEmpty as isNonEmptyString } from "effect/String" ;
8787import {
88+ collectSidebarNonIdleProjectIds ,
8889 resolveSidebarNewThreadEnvMode ,
8990 resolveThreadRowClassName ,
9091 resolveThreadStatusPill ,
@@ -105,12 +106,6 @@ function formatRelativeTime(iso: string): string {
105106 return `${ Math . floor ( hours / 24 ) } d ago` ;
106107}
107108
108- interface TerminalStatusIndicator {
109- label : "Terminal process running" ;
110- colorClass : string ;
111- pulse : boolean ;
112- }
113-
114109interface PrStatusIndicator {
115110 label : "PR open" | "PR closed" | "PR merged" ;
116111 colorClass : string ;
@@ -120,19 +115,6 @@ interface PrStatusIndicator {
120115
121116type ThreadPr = GitStatusResult [ "pr" ] ;
122117
123- function terminalStatusFromRunningIds (
124- runningTerminalIds : string [ ] ,
125- ) : TerminalStatusIndicator | null {
126- if ( runningTerminalIds . length === 0 ) {
127- return null ;
128- }
129- return {
130- label : "Terminal process running" ,
131- colorClass : "text-teal-600 dark:text-teal-300/90" ,
132- pulse : true ,
133- } ;
134- }
135-
136118function prStatusIndicator ( pr : ThreadPr ) : PrStatusIndicator | null {
137119 if ( ! pr ) return null ;
138120
@@ -377,27 +359,35 @@ export default function Sidebar() {
377359 return map ;
378360 } , [ threads ] ) ;
379361
362+ const runningTerminalThreadIds = useMemo ( ( ) => {
363+ const ids = new Set < ThreadId > ( ) ;
364+ for ( const thread of threads ) {
365+ if ( selectThreadTerminalState ( terminalStateByThreadId , thread . id ) . runningTerminalIds . length ) {
366+ ids . add ( thread . id ) ;
367+ }
368+ }
369+ return ids ;
370+ } , [ terminalStateByThreadId , threads ] ) ;
371+
380372 const activeThread = routeThreadId
381373 ? threads . find ( ( thread ) => thread . id === routeThreadId )
382374 : undefined ;
383375 const activeDraftThread = useComposerDraftStore ( ( store ) =>
384376 routeThreadId ? store . draftThreadsByThreadId [ routeThreadId ] : undefined ,
385377 ) ;
386- const activeProjectId = activeThread ?. projectId ?? activeDraftThread ?. projectId ;
378+ const activeProjectId = activeThread ?. projectId ?? activeDraftThread ?. projectId ?? null ;
387379
388- // Currently active project and projects with a thread status
389- const activeProjectIds = useMemo ( ( ) => {
390- const ids = new Set < ProjectId > ( ) ;
391- if ( activeProjectId ) {
392- ids . add ( activeProjectId ) ;
393- }
394- for ( const thread of threads ) {
395- if ( threadStatusById . get ( thread . id ) !== null ) {
396- ids . add ( thread . projectId ) ;
397- }
398- }
399- return ids ;
400- } , [ activeProjectId , threadStatusById , threads ] ) ;
380+ // Currently active project and projects with a thread status or running terminal
381+ const nonIdleProjectIds = useMemo (
382+ ( ) =>
383+ collectSidebarNonIdleProjectIds ( {
384+ activeProjectId,
385+ threads,
386+ threadStatusById,
387+ runningTerminalThreadIds,
388+ } ) ,
389+ [ activeProjectId , runningTerminalThreadIds , threadStatusById , threads ] ,
390+ ) ;
401391
402392 const openPrLink = useCallback ( ( event : React . MouseEvent < HTMLElement > , prUrl : string ) => {
403393 event . preventDefault ( ) ;
@@ -1029,12 +1019,12 @@ export default function Sidebar() {
10291019
10301020 const handleCollapseIdleProjects = useCallback ( ( ) => {
10311021 for ( const project of projects ) {
1032- if ( ! project . expanded || activeProjectIds . has ( project . id ) ) {
1022+ if ( ! project . expanded || nonIdleProjectIds . has ( project . id ) ) {
10331023 continue ;
10341024 }
10351025 setProjectExpanded ( project . id , false ) ;
10361026 }
1037- } , [ projects , activeProjectIds , setProjectExpanded ] ) ;
1027+ } , [ projects , nonIdleProjectIds , setProjectExpanded ] ) ;
10381028
10391029 useEffect ( ( ) => {
10401030 const onMouseDown = ( event : globalThis . MouseEvent ) => {
@@ -1489,10 +1479,13 @@ export default function Sidebar() {
14891479 const prStatus = prStatusIndicator (
14901480 prByThreadId . get ( thread . id ) ?? null ,
14911481 ) ;
1492- const terminalStatus = terminalStatusFromRunningIds (
1493- selectThreadTerminalState ( terminalStateByThreadId , thread . id )
1494- . runningTerminalIds ,
1495- ) ;
1482+ const terminalStatus = runningTerminalThreadIds . has ( thread . id )
1483+ ? {
1484+ label : "Terminal process running" ,
1485+ colorClass : "text-teal-600 dark:text-teal-300/90" ,
1486+ pulse : true ,
1487+ }
1488+ : null ;
14961489
14971490 return (
14981491 < SidebarMenuSubItem
0 commit comments