| ... | ... | @@ -11,6 +11,7 @@ import { |
| 11 | 11 | use, |
| 12 | 12 | useCallback, |
| 13 | 13 | useEffect, |
| 14 | useLayoutEffect, |
| 14 | 15 | useMemo, |
| 15 | 16 | useRef, |
| 16 | 17 | useState, |
| ... | ... | @@ -44,7 +45,6 @@ import { |
| 44 | 45 | EyeIcon, |
| 45 | 46 | GlobeIcon, |
| 46 | 47 | HammerIcon, |
| 47 | | type LucideIcon, |
| 48 | 48 | MessageCircleIcon, |
| 49 | 49 | MinusIcon, |
| 50 | 50 | SquarePenIcon, |
| ... | ... | @@ -396,7 +396,8 @@ const TimelineRowContent = memo(function TimelineRowContent({ row }: { row: Time |
| 396 | 396 | className={cn( |
| 397 | 397 | // Commentary (non-terminal assistant) rows carry no metadata row, so |
| 398 | 398 | // they sit closer to the work that follows them. |
| 399 | | row.kind === "message" && row.message.role === "assistant" && !row.showAssistantMeta |
| 399 | (row.kind === "message" && row.message.role === "assistant" && !row.showAssistantMeta) || |
| 400 | row.kind === "work" |
| 400 | 401 | ? "pb-2" |
| 401 | 402 | : "pb-4", |
| 402 | 403 | row.kind === "message" && row.message.role === "assistant" ? "group/assistant" : null, |
| ... | ... | @@ -526,7 +527,7 @@ function TurnFoldTimelineRow({ row }: { row: Extract<TimelineRow, { kind: "turn- |
| 526 | 527 | aria-expanded={row.expanded} |
| 527 | 528 | data-scroll-anchor-ignore |
| 528 | 529 | onClick={() => ctx.onToggleTurnFold(row.turnId)} |
| 529 | | className="flex items-center gap-1 px-1 text-xs text-muted-foreground tabular-nums transition-colors hover:text-foreground" |
| 530 | className="flex cursor-pointer select-none items-center gap-1 rounded-md px-1 text-xs text-muted-foreground tabular-nums transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring/70" |
| 530 | 531 | > |
| 531 | 532 | <span>{row.label}</span> |
| 532 | 533 | <Icon className="size-3.5" /> |
| ... | ... | @@ -679,6 +680,8 @@ const WorkGroupSection = memo(function WorkGroupSection({ |
| 679 | 680 | }) { |
| 680 | 681 | const { workspaceRoot } = use(TimelineRowCtx); |
| 681 | 682 | const [isExpanded, setIsExpanded] = useState(false); |
| 683 | const sectionRef = useRef<HTMLElement>(null); |
| 684 | const anchorBottomBeforeToggleRef = useRef<number | null>(null); |
| 682 | 685 | const nonEmptyEntries = useMemo( |
| 683 | 686 | () => groupedEntries.filter((entry) => !workEntryIndicatesToolNeutralStatus(entry)), |
| 684 | 687 | [groupedEntries], |
| ... | ... | @@ -690,39 +693,54 @@ const WorkGroupSection = memo(function WorkGroupSection({ |
| 690 | 693 | : nonEmptyEntries; |
| 691 | 694 | const hiddenCount = nonEmptyEntries.length - visibleEntries.length; |
| 692 | 695 | const onlyToolEntries = nonEmptyEntries.every((entry) => workLogEntryIsToolLike(entry)); |
| 693 | | const headerTitle = onlyToolEntries |
| 696 | const groupLabel = onlyToolEntries |
| 694 | 697 | ? nonEmptyEntries.length === 1 |
| 695 | 698 | ? "1 tool call" |
| 696 | 699 | : `${nonEmptyEntries.length} tool calls` |
| 697 | 700 | : "work log"; |
| 698 | 701 | |
| 702 | useLayoutEffect(() => { |
| 703 | const anchorBottomBeforeToggle = anchorBottomBeforeToggleRef.current; |
| 704 | anchorBottomBeforeToggleRef.current = null; |
| 705 | |
| 706 | if (anchorBottomBeforeToggle === null) { |
| 707 | return; |
| 708 | } |
| 709 | |
| 710 | const section = sectionRef.current; |
| 711 | if (!section) { |
| 712 | return; |
| 713 | } |
| 714 | |
| 715 | const delta = section.getBoundingClientRect().bottom - anchorBottomBeforeToggle; |
| 716 | if (Math.abs(delta) < 0.5) { |
| 717 | return; |
| 718 | } |
| 719 | |
| 720 | const scroller = findNearestVerticalScroller(section); |
| 721 | if (scroller) { |
| 722 | scroller.scrollTop += delta; |
| 723 | } else { |
| 724 | window.scrollBy(0, delta); |
| 725 | } |
| 726 | }, [isExpanded]); |
| 727 | |
| 728 | const toggleExpanded = () => { |
| 729 | anchorBottomBeforeToggleRef.current = |
| 730 | sectionRef.current?.getBoundingClientRect().bottom ?? null; |
| 731 | setIsExpanded((v) => !v); |
| 732 | }; |
| 733 | |
| 699 | 734 | if (nonEmptyEntries.length === 0) return null; |
| 700 | 735 | |
| 701 | 736 | return ( |
| 702 | | <div className="rounded-2xl border border-input bg-background p-2 pt-3 shadow-xs/5 not-dark:bg-clip-padding dark:bg-input/32"> |
| 703 | | <div className="mb-2 flex items-center justify-between gap-2 px-2"> |
| 704 | | <p className="font-medium text-foreground text-xs">{headerTitle}</p> |
| 705 | | {hasOverflow && ( |
| 706 | | <button |
| 707 | | type="button" |
| 708 | | className="inline-flex cursor-pointer items-center gap-1 text-muted-foreground text-xs transition-colors duration-150 hover:text-foreground/80" |
| 709 | | onClick={() => setIsExpanded((v) => !v)} |
| 710 | | > |
| 711 | | {isExpanded ? ( |
| 712 | | <> |
| 713 | | Show less |
| 714 | | <ChevronUpIcon className="size-3.5 shrink-0 opacity-80" /> |
| 715 | | </> |
| 716 | | ) : ( |
| 717 | | <> |
| 718 | | Show {hiddenCount} more |
| 719 | | <ChevronDownIcon className="size-3.5 shrink-0 opacity-80" /> |
| 720 | | </> |
| 721 | | )} |
| 722 | | </button> |
| 723 | | )} |
| 724 | | </div> |
| 725 | | <div className="space-y-0.5"> |
| 737 | <section ref={sectionRef} className="-mx-1 space-y-0.5 px-1 py-0.5" aria-label={groupLabel}> |
| 738 | {!onlyToolEntries && ( |
| 739 | <p className="px-0.5 pb-0.5 font-medium text-[11px] text-muted-foreground/65"> |
| 740 | {groupLabel} |
| 741 | </p> |
| 742 | )} |
| 743 | <div className="space-y-px"> |
| 726 | 744 | {visibleEntries.map((workEntry) => ( |
| 727 | 745 | <SimpleWorkEntryRow |
| 728 | 746 | key={workEntry.id} |
| ... | ... | @@ -731,10 +749,47 @@ const WorkGroupSection = memo(function WorkGroupSection({ |
| 731 | 749 | /> |
| 732 | 750 | ))} |
| 733 | 751 | </div> |
| 734 | | </div> |
| 752 | {hasOverflow && ( |
| 753 | <button |
| 754 | type="button" |
| 755 | className="flex w-full cursor-pointer items-center gap-1.5 rounded-md px-0.5 py-0.5 text-left text-[12px] leading-5 transition-colors duration-150 hover:bg-accent/20 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring/70" |
| 756 | onClick={toggleExpanded} |
| 757 | > |
| 758 | <span className="flex size-5 shrink-0 items-center justify-center text-muted-foreground/65"> |
| 759 | {isExpanded ? ( |
| 760 | <ChevronUpIcon className="size-3.5 shrink-0 opacity-70" /> |
| 761 | ) : ( |
| 762 | <ChevronDownIcon className="size-3.5 shrink-0 opacity-70" /> |
| 763 | )} |
| 764 | </span> |
| 765 | {isExpanded ? ( |
| 766 | <span className="font-medium text-foreground/82">Show fewer tool calls</span> |
| 767 | ) : ( |
| 768 | <span className="font-medium text-foreground/82"> |
| 769 | +{hiddenCount} previous tool {hiddenCount === 1 ? "call" : "calls"} |
| 770 | </span> |
| 771 | )} |
| 772 | </button> |
| 773 | )} |
| 774 | </section> |
| 735 | 775 | ); |
| 736 | 776 | }); |
| 737 | 777 | |
| 778 | function findNearestVerticalScroller(element: HTMLElement): HTMLElement | null { |
| 779 | let parent = element.parentElement; |
| 780 | while (parent) { |
| 781 | const { overflowY } = window.getComputedStyle(parent); |
| 782 | if ( |
| 783 | (overflowY === "auto" || overflowY === "scroll" || overflowY === "overlay") && |
| 784 | parent.scrollHeight > parent.clientHeight |
| 785 | ) { |
| 786 | return parent; |
| 787 | } |
| 788 | parent = parent.parentElement; |
| 789 | } |
| 790 | return null; |
| 791 | } |
| 792 | |
| 738 | 793 | /** Subscribes directly to the UI state store for expand/collapse state, |
| 739 | 794 | * so toggling re-renders only this component — not the entire list. */ |
| 740 | 795 | const AssistantChangedFilesSection = memo(function AssistantChangedFilesSection({ |
| ... | ... | @@ -1183,30 +1238,73 @@ function formatWorkingTimerNow(startIso: string): string { |
| 1183 | 1238 | return formatWorkingTimer(startIso, new Date().toISOString()) ?? "0s"; |
| 1184 | 1239 | } |
| 1185 | 1240 | |
| 1241 | type WorkEntryIconName = |
| 1242 | | "bot" |
| 1243 | | "check" |
| 1244 | | "circle-alert" |
| 1245 | | "eye" |
| 1246 | | "globe" |
| 1247 | | "hammer" |
| 1248 | | "message-circle" |
| 1249 | | "square-pen" |
| 1250 | | "terminal" |
| 1251 | | "wrench" |
| 1252 | | "x" |
| 1253 | | "zap"; |
| 1254 | |
| 1255 | function WorkEntryIconSvg({ name, className }: { name: WorkEntryIconName; className: string }) { |
| 1256 | switch (name) { |
| 1257 | case "bot": |
| 1258 | return <BotIcon className={className} aria-hidden />; |
| 1259 | case "check": |
| 1260 | return <CheckIcon className={className} aria-hidden />; |
| 1261 | case "circle-alert": |
| 1262 | return <CircleAlertIcon className={className} aria-hidden />; |
| 1263 | case "eye": |
| 1264 | return <EyeIcon className={className} aria-hidden />; |
| 1265 | case "globe": |
| 1266 | return <GlobeIcon className={className} aria-hidden />; |
| 1267 | case "hammer": |
| 1268 | return <HammerIcon className={className} aria-hidden />; |
| 1269 | case "message-circle": |
| 1270 | return <MessageCircleIcon className={className} aria-hidden />; |
| 1271 | case "square-pen": |
| 1272 | return <SquarePenIcon className={className} aria-hidden />; |
| 1273 | case "terminal": |
| 1274 | return <TerminalIcon className={className} aria-hidden />; |
| 1275 | case "wrench": |
| 1276 | return <WrenchIcon className={className} aria-hidden />; |
| 1277 | case "x": |
| 1278 | return <XIcon className={className} aria-hidden />; |
| 1279 | case "zap": |
| 1280 | return <ZapIcon className={className} aria-hidden />; |
| 1281 | } |
| 1282 | } |
| 1283 | |
| 1186 | 1284 | function workToneIcon(tone: TimelineWorkEntry["tone"]): { |
| 1187 | | icon: LucideIcon; |
| 1285 | iconName: WorkEntryIconName; |
| 1188 | 1286 | className: string; |
| 1189 | 1287 | } { |
| 1190 | 1288 | if (tone === "error") { |
| 1191 | 1289 | return { |
| 1192 | | icon: CircleAlertIcon, |
| 1290 | iconName: "circle-alert", |
| 1193 | 1291 | className: "text-foreground/92", |
| 1194 | 1292 | }; |
| 1195 | 1293 | } |
| 1196 | 1294 | if (tone === "thinking") { |
| 1197 | 1295 | return { |
| 1198 | | icon: BotIcon, |
| 1296 | iconName: "bot", |
| 1199 | 1297 | className: "text-foreground/92", |
| 1200 | 1298 | }; |
| 1201 | 1299 | } |
| 1202 | 1300 | if (tone === "info") { |
| 1203 | 1301 | return { |
| 1204 | | icon: CheckIcon, |
| 1302 | iconName: "check", |
| 1205 | 1303 | className: "text-muted-foreground", |
| 1206 | 1304 | }; |
| 1207 | 1305 | } |
| 1208 | 1306 | return { |
| 1209 | | icon: ZapIcon, |
| 1307 | iconName: "zap", |
| 1210 | 1308 | className: "text-foreground/92", |
| 1211 | 1309 | }; |
| 1212 | 1310 | } |
| ... | ... | @@ -1236,52 +1334,58 @@ function workEntryRawCommand( |
| 1236 | 1334 | return rawCommand === workEntry.command.trim() ? null : rawCommand; |
| 1237 | 1335 | } |
| 1238 | 1336 | |
| 1239 | | function buildToolCallExpandedBody(workEntry: TimelineWorkEntry): string | null { |
| 1240 | | const blocks: string[] = []; |
| 1241 | | if (workEntry.detail?.trim()) { |
| 1242 | | blocks.push(workEntry.detail.trim()); |
| 1243 | | } |
| 1337 | function buildToolCallExpandedBody( |
| 1338 | workEntry: TimelineWorkEntry, |
| 1339 | workspaceRoot: string | undefined, |
| 1340 | ): string | null { |
| 1244 | 1341 | const raw = workEntryRawCommand(workEntry); |
| 1245 | 1342 | if (raw?.trim()) { |
| 1246 | | blocks.push(`Full command\n${raw.trim()}`); |
| 1247 | | } else if (workEntry.command?.trim()) { |
| 1248 | | blocks.push(`Command\n${workEntry.command.trim()}`); |
| 1343 | return raw.trim(); |
| 1249 | 1344 | } |
| 1250 | | if (blocks.length === 0) { |
| 1251 | | return null; |
| 1345 | if (workEntry.command?.trim()) { |
| 1346 | return workEntry.command.trim(); |
| 1347 | } |
| 1348 | if (workEntry.detail?.trim()) { |
| 1349 | return workEntry.detail.trim(); |
| 1252 | 1350 | } |
| 1253 | | return blocks.join("\n\n"); |
| 1351 | const changedFiles = workEntry.changedFiles ?? []; |
| 1352 | if (changedFiles.length > 0) { |
| 1353 | return changedFiles |
| 1354 | .map((filePath) => formatWorkspaceRelativePath(filePath, workspaceRoot)) |
| 1355 | .join("\n"); |
| 1356 | } |
| 1357 | return null; |
| 1254 | 1358 | } |
| 1255 | 1359 | |
| 1256 | | function workEntryIcon(workEntry: TimelineWorkEntry): LucideIcon { |
| 1360 | function workEntryIconName(workEntry: TimelineWorkEntry): WorkEntryIconName { |
| 1257 | 1361 | if ( |
| 1258 | 1362 | workEntry.sourceActivityKind === "user-input.requested" || |
| 1259 | 1363 | workEntry.sourceActivityKind === "user-input.resolved" |
| 1260 | 1364 | ) { |
| 1261 | | return MessageCircleIcon; |
| 1365 | return "message-circle"; |
| 1262 | 1366 | } |
| 1263 | | if (workEntry.requestKind === "command") return TerminalIcon; |
| 1264 | | if (workEntry.requestKind === "file-read") return EyeIcon; |
| 1265 | | if (workEntry.requestKind === "file-change") return SquarePenIcon; |
| 1367 | if (workEntry.requestKind === "command") return "terminal"; |
| 1368 | if (workEntry.requestKind === "file-read") return "eye"; |
| 1369 | if (workEntry.requestKind === "file-change") return "square-pen"; |
| 1266 | 1370 | |
| 1267 | 1371 | if (workEntry.itemType === "command_execution" || workEntry.command) { |
| 1268 | | return TerminalIcon; |
| 1372 | return "terminal"; |
| 1269 | 1373 | } |
| 1270 | 1374 | if (workEntry.itemType === "file_change" || (workEntry.changedFiles?.length ?? 0) > 0) { |
| 1271 | | return SquarePenIcon; |
| 1375 | return "square-pen"; |
| 1272 | 1376 | } |
| 1273 | | if (workEntry.itemType === "web_search") return GlobeIcon; |
| 1274 | | if (workEntry.itemType === "image_view") return EyeIcon; |
| 1377 | if (workEntry.itemType === "web_search") return "globe"; |
| 1378 | if (workEntry.itemType === "image_view") return "eye"; |
| 1275 | 1379 | |
| 1276 | 1380 | switch (workEntry.itemType) { |
| 1277 | 1381 | case "mcp_tool_call": |
| 1278 | | return WrenchIcon; |
| 1382 | return "wrench"; |
| 1279 | 1383 | case "dynamic_tool_call": |
| 1280 | 1384 | case "collab_agent_tool_call": |
| 1281 | | return HammerIcon; |
| 1385 | return "hammer"; |
| 1282 | 1386 | } |
| 1283 | 1387 | |
| 1284 | | return workToneIcon(workEntry.tone).icon; |
| 1388 | return workToneIcon(workEntry.tone).iconName; |
| 1285 | 1389 | } |
| 1286 | 1390 | |
| 1287 | 1391 | function capitalizePhrase(value: string): string { |
| ... | ... | @@ -1310,7 +1414,7 @@ const SimpleWorkEntryRow = memo(function SimpleWorkEntryRow(props: { |
| 1310 | 1414 | const [expanded, setExpanded] = useState(false); |
| 1311 | 1415 | const iconConfig = workToneIcon(workEntry.tone); |
| 1312 | 1416 | const showWarningIndicator = workEntry.sourceActivityKind === "runtime.warning"; |
| 1313 | | const EntryIcon = showWarningIndicator ? XIcon : workEntryIcon(workEntry); |
| 1417 | const entryIconName = showWarningIndicator ? "x" : workEntryIconName(workEntry); |
| 1314 | 1418 | const heading = toolWorkEntryHeading(workEntry); |
| 1315 | 1419 | const rawPreview = workEntryPreview(workEntry, workspaceRoot); |
| 1316 | 1420 | const preview = |
| ... | ... | @@ -1319,11 +1423,9 @@ const SimpleWorkEntryRow = memo(function SimpleWorkEntryRow(props: { |
| 1319 | 1423 | normalizeCompactToolLabel(heading).toLowerCase() |
| 1320 | 1424 | ? null |
| 1321 | 1425 | : rawPreview; |
| 1322 | | const rawCommand = workEntryRawCommand(workEntry); |
| 1323 | 1426 | const displayText = preview ? `${heading} - ${preview}` : heading; |
| 1324 | | const expandedBody = buildToolCallExpandedBody(workEntry); |
| 1427 | const expandedBody = buildToolCallExpandedBody(workEntry, workspaceRoot); |
| 1325 | 1428 | const canExpand = expandedBody !== null; |
| 1326 | | const hasChangedFiles = (workEntry.changedFiles?.length ?? 0) > 0; |
| 1327 | 1429 | const showFailedIndicator = workEntryIndicatesToolFailure(workEntry); |
| 1328 | 1430 | const showDestructiveRowStyle = |
| 1329 | 1431 | showFailedIndicator && |
| ... | ... | @@ -1335,14 +1437,14 @@ const SimpleWorkEntryRow = memo(function SimpleWorkEntryRow(props: { |
| 1335 | 1437 | : showDestructiveRowStyle |
| 1336 | 1438 | ? "text-destructive" |
| 1337 | 1439 | : workEntry.tone === "tool" || showFailedIndicator |
| 1338 | | ? "text-muted-foreground" |
| 1440 | ? "text-muted-foreground/65" |
| 1339 | 1441 | : iconConfig.className, |
| 1340 | 1442 | ); |
| 1341 | 1443 | const headingClass = showWarningIndicator |
| 1342 | 1444 | ? "font-medium text-warning" |
| 1343 | 1445 | : showDestructiveRowStyle |
| 1344 | 1446 | ? "font-medium text-destructive" |
| 1345 | | : "font-medium text-foreground"; |
| 1447 | : "font-medium text-foreground/82"; |
| 1346 | 1448 | const turnSettled = !activity.activeTurnInProgress; |
| 1347 | 1449 | const showNeutralIndicator = !turnSettled && workEntryIndicatesToolNeutralStatus(workEntry); |
| 1348 | 1450 | const showSuccessIndicator = |
| ... | ... | @@ -1352,6 +1454,7 @@ const SimpleWorkEntryRow = memo(function SimpleWorkEntryRow(props: { |
| 1352 | 1454 | ? { |
| 1353 | 1455 | role: "button" as const, |
| 1354 | 1456 | tabIndex: 0 as const, |
| 1457 | "aria-label": displayText, |
| 1355 | 1458 | onClick: () => setExpanded((v) => !v), |
| 1356 | 1459 | onKeyDown: (e: KeyboardEvent<HTMLDivElement>) => { |
| 1357 | 1460 | if (e.key === "Enter" || e.key === " ") { |
| ... | ... | @@ -1365,114 +1468,66 @@ const SimpleWorkEntryRow = memo(function SimpleWorkEntryRow(props: { |
| 1365 | 1468 | return ( |
| 1366 | 1469 | <div |
| 1367 | 1470 | className={cn( |
| 1368 | | "flex flex-col rounded-xl border border-transparent px-2 py-2 transition-colors", |
| 1471 | "flex flex-col rounded-md px-0.5 py-0.5 transition-colors", |
| 1369 | 1472 | canExpand && |
| 1370 | | "cursor-pointer hover:bg-accent/60 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background", |
| 1473 | "cursor-pointer hover:bg-accent/20 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring/70", |
| 1371 | 1474 | )} |
| 1372 | 1475 | {...rowToggleProps} |
| 1373 | 1476 | > |
| 1374 | | <div className="flex items-center gap-2 transition-[opacity,translate] duration-200"> |
| 1477 | <div className="flex select-none items-center gap-1.5 transition-[opacity,translate] duration-200"> |
| 1375 | 1478 | <span className={iconWrapperClass}> |
| 1376 | | <EntryIcon className="block size-3.5 shrink-0" aria-hidden /> |
| 1479 | <WorkEntryIconSvg |
| 1480 | name={entryIconName} |
| 1481 | className="block size-3.5 shrink-0 stroke-[1.8] opacity-80" |
| 1482 | /> |
| 1377 | 1483 | </span> |
| 1378 | | <div className="flex min-w-0 flex-1 items-center gap-2"> |
| 1484 | <div className="flex min-w-0 flex-1 items-center gap-1.5"> |
| 1379 | 1485 | <div className="min-w-0 flex-1 overflow-hidden"> |
| 1380 | | {rawCommand ? ( |
| 1381 | | <div className="max-w-full"> |
| 1382 | | <Tooltip> |
| 1383 | | <TooltipTrigger |
| 1384 | | closeDelay={0} |
| 1385 | | delay={75} |
| 1386 | | onClick={stopRowToggle} |
| 1387 | | onPointerDown={stopRowToggle} |
| 1388 | | render={ |
| 1389 | | <p |
| 1390 | | className="flex min-w-0 w-full items-center gap-2 text-xs leading-5" |
| 1391 | | aria-label={displayText} |
| 1392 | | /> |
| 1393 | | } |
| 1394 | | > |
| 1395 | | <span className={cn("min-w-0 shrink truncate", headingClass)}>{heading}</span> |
| 1396 | | {preview && ( |
| 1397 | | <span className="min-w-0 flex-1 cursor-default truncate text-muted-foreground transition-colors hover:text-muted-foreground/90 focus-visible:text-muted-foreground/90"> |
| 1398 | | {preview} |
| 1399 | | </span> |
| 1400 | | )} |
| 1401 | | </TooltipTrigger> |
| 1402 | | <TooltipPopup |
| 1403 | | align="start" |
| 1404 | | className="max-w-[min(56rem,calc(100vw-2rem))] px-0 py-0" |
| 1405 | | side="top" |
| 1406 | | > |
| 1407 | | <div className="max-w-[min(56rem,calc(100vw-2rem))] overflow-x-auto px-1.5 py-1 font-mono text-[11px] leading-4 whitespace-nowrap"> |
| 1408 | | {rawCommand} |
| 1409 | | </div> |
| 1410 | | </TooltipPopup> |
| 1411 | | </Tooltip> |
| 1412 | | </div> |
| 1413 | | ) : ( |
| 1414 | | <Tooltip> |
| 1415 | | <TooltipTrigger |
| 1416 | | className="block min-w-0 w-full text-left" |
| 1417 | | aria-label={displayText} |
| 1418 | | onClick={stopRowToggle} |
| 1419 | | onPointerDown={stopRowToggle} |
| 1420 | | > |
| 1421 | | <p className="flex min-w-0 w-full items-center gap-2 text-[11px] leading-5"> |
| 1422 | | <span className={cn("min-w-0 shrink truncate", headingClass)}>{heading}</span> |
| 1423 | | {preview && ( |
| 1424 | | <span className="min-w-0 flex-1 truncate text-muted-foreground"> |
| 1425 | | {preview} |
| 1426 | | </span> |
| 1427 | | )} |
| 1428 | | </p> |
| 1429 | | </TooltipTrigger> |
| 1430 | | <TooltipPopup className="max-w-[min(720px,calc(100vw-2rem))]"> |
| 1431 | | <p className="whitespace-pre-wrap wrap-break-word text-xs leading-5"> |
| 1432 | | {displayText} |
| 1433 | | </p> |
| 1434 | | </TooltipPopup> |
| 1435 | | </Tooltip> |
| 1436 | | )} |
| 1486 | <p className="flex min-w-0 w-full items-baseline gap-1.5 text-[12px] leading-5"> |
| 1487 | <span className={cn("min-w-0 shrink truncate", headingClass)}>{heading}</span> |
| 1488 | {preview && ( |
| 1489 | <span className="min-w-0 flex-1 truncate text-muted-foreground/55">{preview}</span> |
| 1490 | )} |
| 1491 | </p> |
| 1437 | 1492 | </div> |
| 1438 | | <div className="flex shrink-0 items-center gap-0.5"> |
| 1493 | <div className="flex shrink-0 items-center gap-px text-muted-foreground/55"> |
| 1439 | 1494 | <span |
| 1440 | | className="flex size-5 shrink-0 items-center justify-center" |
| 1495 | className="flex size-4 shrink-0 items-center justify-center" |
| 1441 | 1496 | aria-hidden={!canExpand} |
| 1442 | 1497 | > |
| 1443 | 1498 | {canExpand ? ( |
| 1444 | 1499 | <ChevronDownIcon |
| 1445 | 1500 | className={cn( |
| 1446 | | "size-3.5 shrink-0 text-muted-foreground opacity-80 transition-transform duration-200", |
| 1501 | "size-3 shrink-0 opacity-70 transition-transform duration-200", |
| 1447 | 1502 | expanded && "rotate-180", |
| 1448 | 1503 | )} |
| 1449 | 1504 | aria-hidden |
| 1450 | 1505 | /> |
| 1451 | 1506 | ) : null} |
| 1452 | 1507 | </span> |
| 1453 | | <span className="flex size-5 shrink-0 items-center justify-center"> |
| 1508 | <span className="flex size-4 shrink-0 items-center justify-center"> |
| 1454 | 1509 | {showFailedIndicator ? ( |
| 1455 | 1510 | <Tooltip> |
| 1456 | 1511 | <TooltipTrigger |
| 1457 | 1512 | render={ |
| 1458 | 1513 | <span |
| 1459 | | className="flex size-5 items-center justify-center" |
| 1514 | className="flex size-4 items-center justify-center" |
| 1460 | 1515 | aria-label="Tool call failed" |
| 1461 | 1516 | /> |
| 1462 | 1517 | } |
| 1463 | 1518 | > |
| 1464 | | <XIcon className="block size-3.5 shrink-0 text-destructive" aria-hidden /> |
| 1519 | <XIcon className="block size-3 shrink-0 text-destructive" aria-hidden /> |
| 1465 | 1520 | </TooltipTrigger> |
| 1466 | 1521 | <TooltipPopup>Failed</TooltipPopup> |
| 1467 | 1522 | </Tooltip> |
| 1468 | 1523 | ) : showSuccessIndicator ? ( |
| 1469 | 1524 | <Tooltip> |
| 1470 | 1525 | <TooltipTrigger |
| 1471 | | render={<span className="flex size-5 items-center justify-center" />} |
| 1526 | render={<span className="flex size-4 items-center justify-center" />} |
| 1472 | 1527 | > |
| 1473 | | <span className="inline-flex size-5 items-center justify-center text-muted-foreground"> |
| 1528 | <span className="inline-flex size-4 items-center justify-center"> |
| 1474 | 1529 | <CheckIcon |
| 1475 | | className="block size-3.5 shrink-0 stroke-current" |
| 1530 | className="block size-3 shrink-0 stroke-current" |
| 1476 | 1531 | stroke="currentColor" |
| 1477 | 1532 | aria-hidden |
| 1478 | 1533 | /> |
| ... | ... | @@ -1483,12 +1538,9 @@ const SimpleWorkEntryRow = memo(function SimpleWorkEntryRow(props: { |
| 1483 | 1538 | ) : showNeutralIndicator ? ( |
| 1484 | 1539 | <Tooltip> |
| 1485 | 1540 | <TooltipTrigger |
| 1486 | | render={<span className="flex size-5 items-center justify-center" />} |
| 1541 | render={<span className="flex size-4 items-center justify-center" />} |
| 1487 | 1542 | > |
| 1488 | | <MinusIcon |
| 1489 | | className="block size-3.5 shrink-0 text-muted-foreground opacity-80" |
| 1490 | | aria-hidden |
| 1491 | | /> |
| 1543 | <MinusIcon className="block size-3 shrink-0 opacity-70" aria-hidden /> |
| 1492 | 1544 | </TooltipTrigger> |
| 1493 | 1545 | <TooltipPopup>Empty</TooltipPopup> |
| 1494 | 1546 | </Tooltip> |
| ... | ... | @@ -1499,48 +1551,15 @@ const SimpleWorkEntryRow = memo(function SimpleWorkEntryRow(props: { |
| 1499 | 1551 | </div> |
| 1500 | 1552 | {expanded && canExpand && expandedBody ? ( |
| 1501 | 1553 | <div |
| 1502 | | className="mt-2 ms-7 border-s border-border/45 ps-3 pt-0.5" |
| 1554 | className="mt-1 ms-7 cursor-default border-s border-border/45 ps-3 pt-0.5" |
| 1503 | 1555 | onClick={stopRowToggle} |
| 1504 | 1556 | onPointerDown={stopRowToggle} |
| 1505 | 1557 | > |
| 1506 | | <pre className="max-h-64 overflow-auto whitespace-pre-wrap break-words font-mono text-[11px] leading-relaxed text-muted-foreground"> |
| 1558 | <pre className="max-h-64 cursor-text overflow-auto whitespace-pre-wrap break-words font-mono text-[11px] leading-relaxed text-muted-foreground select-text"> |
| 1507 | 1559 | {expandedBody} |
| 1508 | 1560 | </pre> |
| 1509 | 1561 | </div> |
| 1510 | 1562 | ) : null} |
| 1511 | | {hasChangedFiles && ( |
| 1512 | | <div |
| 1513 | | className="mt-1 flex flex-wrap gap-1" |
| 1514 | | onClick={stopRowToggle} |
| 1515 | | onPointerDown={stopRowToggle} |
| 1516 | | > |
| 1517 | | {workEntry.changedFiles?.slice(0, 4).map((filePath) => { |
| 1518 | | const displayPath = formatWorkspaceRelativePath(filePath, workspaceRoot); |
| 1519 | | return ( |
| 1520 | | <Tooltip key={`${workEntry.id}:${filePath}`}> |
| 1521 | | <TooltipTrigger |
| 1522 | | render={ |
| 1523 | | <span |
| 1524 | | className="rounded-md border border-border/55 bg-background/75 px-1.5 py-0.5 font-mono text-[10px] text-muted-foreground/75" |
| 1525 | | aria-label={displayPath} |
| 1526 | | /> |
| 1527 | | } |
| 1528 | | > |
| 1529 | | {displayPath} |
| 1530 | | </TooltipTrigger> |
| 1531 | | <TooltipPopup side="top" className="max-w-[min(40rem,calc(100vw-2rem))]"> |
| 1532 | | <span className="font-mono text-[11px] whitespace-nowrap">{displayPath}</span> |
| 1533 | | </TooltipPopup> |
| 1534 | | </Tooltip> |
| 1535 | | ); |
| 1536 | | })} |
| 1537 | | {(workEntry.changedFiles?.length ?? 0) > 4 && ( |
| 1538 | | <span className="px-1 text-[10px] text-muted-foreground/55"> |
| 1539 | | +{(workEntry.changedFiles?.length ?? 0) - 4} |
| 1540 | | </span> |
| 1541 | | )} |
| 1542 | | </div> |
| 1543 | | )} |
| 1544 | 1563 | </div> |
| 1545 | 1564 | ); |
| 1546 | 1565 | }); |