| ... | @@ -151,6 +151,44 @@ interface PrStatusIndicator { | ... | @@ -151,6 +151,44 @@ interface PrStatusIndicator { |
| 151 | | 151 | |
| 152 | type ThreadPr = GitStatusResult["pr"]; | 152 | type ThreadPr = GitStatusResult["pr"]; |
| 153 | | 153 | |
| | 154 | function ThreadStatusLabel({ |
| | 155 | status, |
| | 156 | compact = false, |
| | 157 | }: { |
| | 158 | status: NonNullable<ReturnType<typeof resolveThreadStatusPill>>; |
| | 159 | compact?: boolean; |
| | 160 | }) { |
| | 161 | if (compact) { |
| | 162 | return ( |
| | 163 | <span |
| | 164 | title={status.label} |
| | 165 | className={`inline-flex size-3.5 shrink-0 items-center justify-center ${status.colorClass}`} |
| | 166 | > |
| | 167 | <span |
| | 168 | className={`size-[9px] rounded-full ${status.dotClass} ${ |
| | 169 | status.pulse ? "animate-pulse" : "" |
| | 170 | }`} |
| | 171 | /> |
| | 172 | <span className="sr-only">{status.label}</span> |
| | 173 | </span> |
| | 174 | ); |
| | 175 | } |
| | 176 | |
| | 177 | return ( |
| | 178 | <span |
| | 179 | title={status.label} |
| | 180 | className={`inline-flex items-center gap-1 text-[10px] ${status.colorClass}`} |
| | 181 | > |
| | 182 | <span |
| | 183 | className={`h-1.5 w-1.5 rounded-full ${status.dotClass} ${ |
| | 184 | status.pulse ? "animate-pulse" : "" |
| | 185 | }`} |
| | 186 | /> |
| | 187 | <span className="hidden md:inline">{status.label}</span> |
| | 188 | </span> |
| | 189 | ); |
| | 190 | } |
| | 191 | |
| 154 | function terminalStatusFromRunningIds( | 192 | function terminalStatusFromRunningIds( |
| 155 | runningTerminalIds: string[], | 193 | runningTerminalIds: string[], |
| 156 | ): TerminalStatusIndicator | null { | 194 | ): TerminalStatusIndicator | null { |
| ... | @@ -1023,14 +1061,18 @@ export default function Sidebar() { | ... | @@ -1023,14 +1061,18 @@ export default function Sidebar() { |
| 1023 | visibleThreads.filter((thread) => thread.projectId === project.id), | 1061 | visibleThreads.filter((thread) => thread.projectId === project.id), |
| 1024 | appSettings.sidebarThreadSortOrder, | 1062 | appSettings.sidebarThreadSortOrder, |
| 1025 | ); | 1063 | ); |
| 1026 | const projectStatus = resolveProjectStatusIndicator( | 1064 | const threadStatuses = new Map( |
| 1027 | projectThreads.map((thread) => | 1065 | projectThreads.map((thread) => [ |
| | 1066 | thread.id, |
| 1028 | resolveThreadStatusPill({ | 1067 | resolveThreadStatusPill({ |
| 1029 | thread, | 1068 | thread, |
| 1030 | hasPendingApprovals: derivePendingApprovals(thread.activities).length > 0, | 1069 | hasPendingApprovals: derivePendingApprovals(thread.activities).length > 0, |
| 1031 | hasPendingUserInput: derivePendingUserInputs(thread.activities).length > 0, | 1070 | hasPendingUserInput: derivePendingUserInputs(thread.activities).length > 0, |
| 1032 | }), | 1071 | }), |
| 1033 | ), | 1072 | ]), |
| | 1073 | ); |
| | 1074 | const projectStatus = resolveProjectStatusIndicator( |
| | 1075 | projectThreads.map((thread) => threadStatuses.get(thread.id) ?? null), |
| 1034 | ); | 1076 | ); |
| 1035 | const activeThreadId = routeThreadId ?? undefined; | 1077 | const activeThreadId = routeThreadId ?? undefined; |
| 1036 | const isThreadListExpanded = expandedThreadListsByProject.has(project.id); | 1078 | const isThreadListExpanded = expandedThreadListsByProject.has(project.id); |
| ... | @@ -1039,13 +1081,19 @@ export default function Sidebar() { | ... | @@ -1039,13 +1081,19 @@ export default function Sidebar() { |
| 1039 | ? (projectThreads.find((thread) => thread.id === activeThreadId) ?? null) | 1081 | ? (projectThreads.find((thread) => thread.id === activeThreadId) ?? null) |
| 1040 | : null; | 1082 | : null; |
| 1041 | const shouldShowThreadPanel = project.expanded || pinnedCollapsedThread !== null; | 1083 | const shouldShowThreadPanel = project.expanded || pinnedCollapsedThread !== null; |
| 1042 | const { hasHiddenThreads, visibleThreads: visibleProjectThreads } = | 1084 | const { |
| 1043 | getVisibleThreadsForProject({ | 1085 | hasHiddenThreads, |
| 1044 | threads: projectThreads, | 1086 | hiddenThreads, |
| 1045 | activeThreadId, | 1087 | visibleThreads: visibleProjectThreads, |
| 1046 | isThreadListExpanded, | 1088 | } = getVisibleThreadsForProject({ |
| 1047 | previewLimit: THREAD_PREVIEW_LIMIT, | 1089 | threads: projectThreads, |
| 1048 | }); | 1090 | activeThreadId, |
| | 1091 | isThreadListExpanded, |
| | 1092 | previewLimit: THREAD_PREVIEW_LIMIT, |
| | 1093 | }); |
| | 1094 | const hiddenThreadStatus = resolveProjectStatusIndicator( |
| | 1095 | hiddenThreads.map((thread) => threadStatuses.get(thread.id) ?? null), |
| | 1096 | ); |
| 1049 | const orderedProjectThreadIds = projectThreads.map((thread) => thread.id); | 1097 | const orderedProjectThreadIds = projectThreads.map((thread) => thread.id); |
| 1050 | const renderedThreads = pinnedCollapsedThread | 1098 | const renderedThreads = pinnedCollapsedThread |
| 1051 | ? [pinnedCollapsedThread] | 1099 | ? [pinnedCollapsedThread] |
| ... | @@ -1053,10 +1101,12 @@ export default function Sidebar() { | ... | @@ -1053,10 +1101,12 @@ export default function Sidebar() { |
| 1053 | | 1101 | |
| 1054 | return { | 1102 | return { |
| 1055 | hasHiddenThreads, | 1103 | hasHiddenThreads, |
| | 1104 | hiddenThreadStatus, |
| 1056 | orderedProjectThreadIds, | 1105 | orderedProjectThreadIds, |
| 1057 | project, | 1106 | project, |
| 1058 | projectStatus, | 1107 | projectStatus, |
| 1059 | projectThreads, | 1108 | projectThreads, |
| | 1109 | threadStatuses, |
| 1060 | renderedThreads, | 1110 | renderedThreads, |
| 1061 | shouldShowThreadPanel, | 1111 | shouldShowThreadPanel, |
| 1062 | isThreadListExpanded, | 1112 | isThreadListExpanded, |
| ... | @@ -1198,10 +1248,12 @@ export default function Sidebar() { | ... | @@ -1198,10 +1248,12 @@ export default function Sidebar() { |
| 1198 | ) { | 1248 | ) { |
| 1199 | const { | 1249 | const { |
| 1200 | hasHiddenThreads, | 1250 | hasHiddenThreads, |
| | 1251 | hiddenThreadStatus, |
| 1201 | orderedProjectThreadIds, | 1252 | orderedProjectThreadIds, |
| 1202 | project, | 1253 | project, |
| 1203 | projectStatus, | 1254 | projectStatus, |
| 1204 | projectThreads, | 1255 | projectThreads, |
| | 1256 | threadStatuses, |
| 1205 | renderedThreads, | 1257 | renderedThreads, |
| 1206 | shouldShowThreadPanel, | 1258 | shouldShowThreadPanel, |
| 1207 | isThreadListExpanded, | 1259 | isThreadListExpanded, |
| ... | @@ -1213,11 +1265,7 @@ export default function Sidebar() { | ... | @@ -1213,11 +1265,7 @@ export default function Sidebar() { |
| 1213 | const jumpLabel = threadJumpLabelById.get(thread.id) ?? null; | 1265 | const jumpLabel = threadJumpLabelById.get(thread.id) ?? null; |
| 1214 | const isThreadRunning = | 1266 | const isThreadRunning = |
| 1215 | thread.session?.status === "running" && thread.session.activeTurnId != null; | 1267 | thread.session?.status === "running" && thread.session.activeTurnId != null; |
| 1216 | const threadStatus = resolveThreadStatusPill({ | 1268 | const threadStatus = threadStatuses.get(thread.id) ?? null; |
| 1217 | thread, | | |
| 1218 | hasPendingApprovals: derivePendingApprovals(thread.activities).length > 0, | | |
| 1219 | hasPendingUserInput: derivePendingUserInputs(thread.activities).length > 0, | | |
| 1220 | }); | | |
| 1221 | const prStatus = prStatusIndicator(prByThreadId.get(thread.id) ?? null); | 1269 | const prStatus = prStatusIndicator(prByThreadId.get(thread.id) ?? null); |
| 1222 | const terminalStatus = terminalStatusFromRunningIds( | 1270 | const terminalStatus = terminalStatusFromRunningIds( |
| 1223 | selectThreadTerminalState(terminalStateByThreadId, thread.id).runningTerminalIds, | 1271 | selectThreadTerminalState(terminalStateByThreadId, thread.id).runningTerminalIds, |
| ... | @@ -1291,18 +1339,7 @@ export default function Sidebar() { | ... | @@ -1291,18 +1339,7 @@ export default function Sidebar() { |
| 1291 | <TooltipPopup side="top">{prStatus.tooltip}</TooltipPopup> | 1339 | <TooltipPopup side="top">{prStatus.tooltip}</TooltipPopup> |
| 1292 | </Tooltip> | 1340 | </Tooltip> |
| 1293 | )} | 1341 | )} |
| 1294 | {threadStatus && ( | 1342 | {threadStatus && <ThreadStatusLabel status={threadStatus} />} |
| 1295 | <span | | |
| 1296 | className={`inline-flex items-center gap-1 text-[10px] ${threadStatus.colorClass}`} | | |
| 1297 | > | | |
| 1298 | <span | | |
| 1299 | className={`h-1.5 w-1.5 rounded-full ${threadStatus.dotClass} ${ | | |
| 1300 | threadStatus.pulse ? "animate-pulse" : "" | | |
| 1301 | }`} | | |
| 1302 | /> | | |
| 1303 | <span className="hidden md:inline">{threadStatus.label}</span> | | |
| 1304 | </span> | | |
| 1305 | )} | | |
| 1306 | {renamingThreadId === thread.id ? ( | 1343 | {renamingThreadId === thread.id ? ( |
| 1307 | <input | 1344 | <input |
| 1308 | ref={(el) => { | 1345 | ref={(el) => { |
| ... | @@ -1548,7 +1585,10 @@ export default function Sidebar() { | ... | @@ -1548,7 +1585,10 @@ export default function Sidebar() { |
| 1548 | expandThreadListForProject(project.id); | 1585 | expandThreadListForProject(project.id); |
| 1549 | }} | 1586 | }} |
| 1550 | > | 1587 | > |
| 1551 | <span>Show more</span> | 1588 | <span className="flex min-w-0 flex-1 items-center gap-2"> |
| | 1589 | {hiddenThreadStatus && <ThreadStatusLabel status={hiddenThreadStatus} compact />} |
| | 1590 | <span>Show more</span> |
| | 1591 | </span> |
| 1552 | </SidebarMenuSubButton> | 1592 | </SidebarMenuSubButton> |
| 1553 | </SidebarMenuSubItem> | 1593 | </SidebarMenuSubItem> |
| 1554 | )} | 1594 | )} |