| ... | ... | @@ -151,6 +151,44 @@ interface PrStatusIndicator { |
| 151 | 151 | |
| 152 | 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 | 192 | function terminalStatusFromRunningIds( |
| 155 | 193 | runningTerminalIds: string[], |
| 156 | 194 | ): TerminalStatusIndicator | null { |
| ... | ... | @@ -1023,14 +1061,18 @@ export default function Sidebar() { |
| 1023 | 1061 | visibleThreads.filter((thread) => thread.projectId === project.id), |
| 1024 | 1062 | appSettings.sidebarThreadSortOrder, |
| 1025 | 1063 | ); |
| 1026 | | const projectStatus = resolveProjectStatusIndicator( |
| 1027 | | projectThreads.map((thread) => |
| 1064 | const threadStatuses = new Map( |
| 1065 | projectThreads.map((thread) => [ |
| 1066 | thread.id, |
| 1028 | 1067 | resolveThreadStatusPill({ |
| 1029 | 1068 | thread, |
| 1030 | 1069 | hasPendingApprovals: derivePendingApprovals(thread.activities).length > 0, |
| 1031 | 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 | 1077 | const activeThreadId = routeThreadId ?? undefined; |
| 1036 | 1078 | const isThreadListExpanded = expandedThreadListsByProject.has(project.id); |
| ... | ... | @@ -1039,13 +1081,19 @@ export default function Sidebar() { |
| 1039 | 1081 | ? (projectThreads.find((thread) => thread.id === activeThreadId) ?? null) |
| 1040 | 1082 | : null; |
| 1041 | 1083 | const shouldShowThreadPanel = project.expanded || pinnedCollapsedThread !== null; |
| 1042 | | const { hasHiddenThreads, visibleThreads: visibleProjectThreads } = |
| 1043 | | getVisibleThreadsForProject({ |
| 1044 | | threads: projectThreads, |
| 1045 | | activeThreadId, |
| 1046 | | isThreadListExpanded, |
| 1047 | | previewLimit: THREAD_PREVIEW_LIMIT, |
| 1048 | | }); |
| 1084 | const { |
| 1085 | hasHiddenThreads, |
| 1086 | hiddenThreads, |
| 1087 | visibleThreads: visibleProjectThreads, |
| 1088 | } = getVisibleThreadsForProject({ |
| 1089 | threads: projectThreads, |
| 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 | 1097 | const orderedProjectThreadIds = projectThreads.map((thread) => thread.id); |
| 1050 | 1098 | const renderedThreads = pinnedCollapsedThread |
| 1051 | 1099 | ? [pinnedCollapsedThread] |
| ... | ... | @@ -1053,10 +1101,12 @@ export default function Sidebar() { |
| 1053 | 1101 | |
| 1054 | 1102 | return { |
| 1055 | 1103 | hasHiddenThreads, |
| 1104 | hiddenThreadStatus, |
| 1056 | 1105 | orderedProjectThreadIds, |
| 1057 | 1106 | project, |
| 1058 | 1107 | projectStatus, |
| 1059 | 1108 | projectThreads, |
| 1109 | threadStatuses, |
| 1060 | 1110 | renderedThreads, |
| 1061 | 1111 | shouldShowThreadPanel, |
| 1062 | 1112 | isThreadListExpanded, |
| ... | ... | @@ -1198,10 +1248,12 @@ export default function Sidebar() { |
| 1198 | 1248 | ) { |
| 1199 | 1249 | const { |
| 1200 | 1250 | hasHiddenThreads, |
| 1251 | hiddenThreadStatus, |
| 1201 | 1252 | orderedProjectThreadIds, |
| 1202 | 1253 | project, |
| 1203 | 1254 | projectStatus, |
| 1204 | 1255 | projectThreads, |
| 1256 | threadStatuses, |
| 1205 | 1257 | renderedThreads, |
| 1206 | 1258 | shouldShowThreadPanel, |
| 1207 | 1259 | isThreadListExpanded, |
| ... | ... | @@ -1213,11 +1265,7 @@ export default function Sidebar() { |
| 1213 | 1265 | const jumpLabel = threadJumpLabelById.get(thread.id) ?? null; |
| 1214 | 1266 | const isThreadRunning = |
| 1215 | 1267 | thread.session?.status === "running" && thread.session.activeTurnId != null; |
| 1216 | | const threadStatus = resolveThreadStatusPill({ |
| 1217 | | thread, |
| 1218 | | hasPendingApprovals: derivePendingApprovals(thread.activities).length > 0, |
| 1219 | | hasPendingUserInput: derivePendingUserInputs(thread.activities).length > 0, |
| 1220 | | }); |
| 1268 | const threadStatus = threadStatuses.get(thread.id) ?? null; |
| 1221 | 1269 | const prStatus = prStatusIndicator(prByThreadId.get(thread.id) ?? null); |
| 1222 | 1270 | const terminalStatus = terminalStatusFromRunningIds( |
| 1223 | 1271 | selectThreadTerminalState(terminalStateByThreadId, thread.id).runningTerminalIds, |
| ... | ... | @@ -1291,18 +1339,7 @@ export default function Sidebar() { |
| 1291 | 1339 | <TooltipPopup side="top">{prStatus.tooltip}</TooltipPopup> |
| 1292 | 1340 | </Tooltip> |
| 1293 | 1341 | )} |
| 1294 | | {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 | | )} |
| 1342 | {threadStatus && <ThreadStatusLabel status={threadStatus} />} |
| 1306 | 1343 | {renamingThreadId === thread.id ? ( |
| 1307 | 1344 | <input |
| 1308 | 1345 | ref={(el) => { |
| ... | ... | @@ -1548,7 +1585,10 @@ export default function Sidebar() { |
| 1548 | 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 | 1592 | </SidebarMenuSubButton> |
| 1553 | 1593 | </SidebarMenuSubItem> |
| 1554 | 1594 | )} |