authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-06-11 18:18:27-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-06-11 18:18:27-07:00
log649f4328eacff110e3a6c11457be3d43be0d1e60
tree3bb7b1ff95b9272516df5438f4be07c17c470df3
parent7db034905b49e6c3448057e484a50e14ebb64d80
signature Signed by PGP key B5690EEEBB952194

[codex] Refine inline tool call timeline UI (#3052)

Co-authored-by: codex <codex@users.noreply.github.com>

4 files changed, 202 insertions(+), 197 deletions(-)

apps/desktop/src/backend/tailscaleEndpointProvider.ts+1-1
......@@ -121,7 +121,7 @@ export const resolveTailscaleAdvertisedEndpoints = Effect.fn("resolveTailscaleAd
121121 input.readMagicDnsName ??
122122 readTailscaleStatus.pipe(
123123 Effect.map((status) => status.magicDnsName),
124 Effect.catch(() => Effect.succeed<string | null>(null)),
124 Effect.orElseSucceed(() => null),
125125 );
126126 const dnsName =
127127 input.statusJson === undefined
apps/mobile/src/app/settings/index.tsx+2-7
......@@ -1,4 +1,4 @@
1import { useAuth, useUser, useUserProfileModal } from "@clerk/expo";
1import { useAuth, useUser } from "@clerk/expo";
22import * as Notifications from "expo-notifications";
33import { Link, Stack, useRouter } from "expo-router";
44import { SymbolView } from "expo-symbols";
......@@ -68,7 +68,6 @@ function ConfiguredSettingsRouteScreen() {
6868 const { push } = useRouter();
6969 const { getToken, isLoaded, isSignedIn } = useAuth({ treatPendingAsSignedOut: false });
7070 const { user } = useUser();
71 const { isAvailable: isUserProfileModalAvailable, presentUserProfile } = useUserProfileModal();
7271 const { savedConnectionsById } = useRemoteEnvironmentState();
7372 const [notificationStatus, setNotificationStatus] = useState<NotificationStatus>("checking");
7473 const [liveActivityStatus, setLiveActivityStatus] = useState<LiveActivityStatus>("checking");
......@@ -266,15 +265,11 @@ function ConfiguredSettingsRouteScreen() {
266265 push("/settings/waitlist");
267266 return;
268267 }
269 if (isUserProfileModalAvailable) {
270 void presentUserProfile();
271 return;
272 }
273268 Alert.alert(
274269 "T3 Cloud unavailable",
275270 "Native T3 Cloud account management is not available in this build.",
276271 );
277 }, [isLoaded, isSignedIn, isUserProfileModalAvailable, presentUserProfile, push]);
272 }, [isLoaded, isSignedIn, push]);
278273
279274 return (
280275 <View collapsable={false} className="flex-1 bg-sheet">
apps/web/src/components/chat/MessagesTimeline.browser.tsx+6-15
......@@ -149,7 +149,7 @@ describe("MessagesTimeline", () => {
149149 }
150150 });
151151
152 it("uses accessible tooltips instead of native titles for work entry details", async () => {
152 it("uses accessible expansion instead of native titles or preview tooltips for work entry details", async () => {
153153 const screen = await render(
154154 <MessagesTimeline
155155 {...buildProps()}
......@@ -181,21 +181,12 @@ describe("MessagesTimeline", () => {
181181 "Command - git diff -- apps/web/src/components/ChatMarkdown.tsx",
182182 );
183183 await commandTrigger.hover();
184 await vi.waitFor(() => {
185 const tooltip = document.querySelector<HTMLElement>('[data-slot="tooltip-popup"]');
186 expect(tooltip?.textContent).toContain(
187 "git diff -- apps/web/src/components/ChatMarkdown.tsx --stat",
188 );
189 });
184 expect(document.querySelector('[data-slot="tooltip-popup"]')).toBeNull();
190185
191 const fileTrigger = page.getByLabelText("repo/apps/web/src/components/ChatMarkdown.tsx", {
192 exact: true,
193 });
194 await fileTrigger.hover();
195 await vi.waitFor(() => {
196 const tooltip = document.querySelector<HTMLElement>('[data-slot="tooltip-popup"]');
197 expect(tooltip?.textContent).toContain("apps/web/src/components/ChatMarkdown.tsx");
198 });
186 await commandTrigger.click();
187 await expect
188 .element(page.getByText("git diff -- apps/web/src/components/ChatMarkdown.tsx --stat"))
189 .toBeVisible();
199190 } finally {
200191 await screen.unmount();
201192 }
apps/web/src/components/chat/MessagesTimeline.tsx+193-174
......@@ -11,6 +11,7 @@ import {
1111 use,
1212 useCallback,
1313 useEffect,
14 useLayoutEffect,
1415 useMemo,
1516 useRef,
1617 useState,
......@@ -44,7 +45,6 @@ import {
4445 EyeIcon,
4546 GlobeIcon,
4647 HammerIcon,
47 type LucideIcon,
4848 MessageCircleIcon,
4949 MinusIcon,
5050 SquarePenIcon,
......@@ -396,7 +396,8 @@ const TimelineRowContent = memo(function TimelineRowContent({ row }: { row: Time
396396 className={cn(
397397 // Commentary (non-terminal assistant) rows carry no metadata row, so
398398 // 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"
400401 ? "pb-2"
401402 : "pb-4",
402403 row.kind === "message" && row.message.role === "assistant" ? "group/assistant" : null,
......@@ -526,7 +527,7 @@ function TurnFoldTimelineRow({ row }: { row: Extract<TimelineRow, { kind: "turn-
526527 aria-expanded={row.expanded}
527528 data-scroll-anchor-ignore
528529 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"
530531 >
531532 <span>{row.label}</span>
532533 <Icon className="size-3.5" />
......@@ -679,6 +680,8 @@ const WorkGroupSection = memo(function WorkGroupSection({
679680}) {
680681 const { workspaceRoot } = use(TimelineRowCtx);
681682 const [isExpanded, setIsExpanded] = useState(false);
683 const sectionRef = useRef<HTMLElement>(null);
684 const anchorBottomBeforeToggleRef = useRef<number | null>(null);
682685 const nonEmptyEntries = useMemo(
683686 () => groupedEntries.filter((entry) => !workEntryIndicatesToolNeutralStatus(entry)),
684687 [groupedEntries],
......@@ -690,39 +693,54 @@ const WorkGroupSection = memo(function WorkGroupSection({
690693 : nonEmptyEntries;
691694 const hiddenCount = nonEmptyEntries.length - visibleEntries.length;
692695 const onlyToolEntries = nonEmptyEntries.every((entry) => workLogEntryIsToolLike(entry));
693 const headerTitle = onlyToolEntries
696 const groupLabel = onlyToolEntries
694697 ? nonEmptyEntries.length === 1
695698 ? "1 tool call"
696699 : `${nonEmptyEntries.length} tool calls`
697700 : "work log";
698701
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
699734 if (nonEmptyEntries.length === 0) return null;
700735
701736 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">
726744 {visibleEntries.map((workEntry) => (
727745 <SimpleWorkEntryRow
728746 key={workEntry.id}
......@@ -731,10 +749,47 @@ const WorkGroupSection = memo(function WorkGroupSection({
731749 />
732750 ))}
733751 </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>
735775 );
736776});
737777
778function 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
738793/** Subscribes directly to the UI state store for expand/collapse state,
739794 * so toggling re-renders only this component — not the entire list. */
740795const AssistantChangedFilesSection = memo(function AssistantChangedFilesSection({
......@@ -1183,30 +1238,73 @@ function formatWorkingTimerNow(startIso: string): string {
11831238 return formatWorkingTimer(startIso, new Date().toISOString()) ?? "0s";
11841239}
11851240
1241type 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
1255function 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
11861284function workToneIcon(tone: TimelineWorkEntry["tone"]): {
1187 icon: LucideIcon;
1285 iconName: WorkEntryIconName;
11881286 className: string;
11891287} {
11901288 if (tone === "error") {
11911289 return {
1192 icon: CircleAlertIcon,
1290 iconName: "circle-alert",
11931291 className: "text-foreground/92",
11941292 };
11951293 }
11961294 if (tone === "thinking") {
11971295 return {
1198 icon: BotIcon,
1296 iconName: "bot",
11991297 className: "text-foreground/92",
12001298 };
12011299 }
12021300 if (tone === "info") {
12031301 return {
1204 icon: CheckIcon,
1302 iconName: "check",
12051303 className: "text-muted-foreground",
12061304 };
12071305 }
12081306 return {
1209 icon: ZapIcon,
1307 iconName: "zap",
12101308 className: "text-foreground/92",
12111309 };
12121310}
......@@ -1236,52 +1334,58 @@ function workEntryRawCommand(
12361334 return rawCommand === workEntry.command.trim() ? null : rawCommand;
12371335}
12381336
1239function buildToolCallExpandedBody(workEntry: TimelineWorkEntry): string | null {
1240 const blocks: string[] = [];
1241 if (workEntry.detail?.trim()) {
1242 blocks.push(workEntry.detail.trim());
1243 }
1337function buildToolCallExpandedBody(
1338 workEntry: TimelineWorkEntry,
1339 workspaceRoot: string | undefined,
1340): string | null {
12441341 const raw = workEntryRawCommand(workEntry);
12451342 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();
12491344 }
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();
12521350 }
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;
12541358}
12551359
1256function workEntryIcon(workEntry: TimelineWorkEntry): LucideIcon {
1360function workEntryIconName(workEntry: TimelineWorkEntry): WorkEntryIconName {
12571361 if (
12581362 workEntry.sourceActivityKind === "user-input.requested" ||
12591363 workEntry.sourceActivityKind === "user-input.resolved"
12601364 ) {
1261 return MessageCircleIcon;
1365 return "message-circle";
12621366 }
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";
12661370
12671371 if (workEntry.itemType === "command_execution" || workEntry.command) {
1268 return TerminalIcon;
1372 return "terminal";
12691373 }
12701374 if (workEntry.itemType === "file_change" || (workEntry.changedFiles?.length ?? 0) > 0) {
1271 return SquarePenIcon;
1375 return "square-pen";
12721376 }
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";
12751379
12761380 switch (workEntry.itemType) {
12771381 case "mcp_tool_call":
1278 return WrenchIcon;
1382 return "wrench";
12791383 case "dynamic_tool_call":
12801384 case "collab_agent_tool_call":
1281 return HammerIcon;
1385 return "hammer";
12821386 }
12831387
1284 return workToneIcon(workEntry.tone).icon;
1388 return workToneIcon(workEntry.tone).iconName;
12851389}
12861390
12871391function capitalizePhrase(value: string): string {
......@@ -1310,7 +1414,7 @@ const SimpleWorkEntryRow = memo(function SimpleWorkEntryRow(props: {
13101414 const [expanded, setExpanded] = useState(false);
13111415 const iconConfig = workToneIcon(workEntry.tone);
13121416 const showWarningIndicator = workEntry.sourceActivityKind === "runtime.warning";
1313 const EntryIcon = showWarningIndicator ? XIcon : workEntryIcon(workEntry);
1417 const entryIconName = showWarningIndicator ? "x" : workEntryIconName(workEntry);
13141418 const heading = toolWorkEntryHeading(workEntry);
13151419 const rawPreview = workEntryPreview(workEntry, workspaceRoot);
13161420 const preview =
......@@ -1319,11 +1423,9 @@ const SimpleWorkEntryRow = memo(function SimpleWorkEntryRow(props: {
13191423 normalizeCompactToolLabel(heading).toLowerCase()
13201424 ? null
13211425 : rawPreview;
1322 const rawCommand = workEntryRawCommand(workEntry);
13231426 const displayText = preview ? `${heading} - ${preview}` : heading;
1324 const expandedBody = buildToolCallExpandedBody(workEntry);
1427 const expandedBody = buildToolCallExpandedBody(workEntry, workspaceRoot);
13251428 const canExpand = expandedBody !== null;
1326 const hasChangedFiles = (workEntry.changedFiles?.length ?? 0) > 0;
13271429 const showFailedIndicator = workEntryIndicatesToolFailure(workEntry);
13281430 const showDestructiveRowStyle =
13291431 showFailedIndicator &&
......@@ -1335,14 +1437,14 @@ const SimpleWorkEntryRow = memo(function SimpleWorkEntryRow(props: {
13351437 : showDestructiveRowStyle
13361438 ? "text-destructive"
13371439 : workEntry.tone === "tool" || showFailedIndicator
1338 ? "text-muted-foreground"
1440 ? "text-muted-foreground/65"
13391441 : iconConfig.className,
13401442 );
13411443 const headingClass = showWarningIndicator
13421444 ? "font-medium text-warning"
13431445 : showDestructiveRowStyle
13441446 ? "font-medium text-destructive"
1345 : "font-medium text-foreground";
1447 : "font-medium text-foreground/82";
13461448 const turnSettled = !activity.activeTurnInProgress;
13471449 const showNeutralIndicator = !turnSettled && workEntryIndicatesToolNeutralStatus(workEntry);
13481450 const showSuccessIndicator =
......@@ -1352,6 +1454,7 @@ const SimpleWorkEntryRow = memo(function SimpleWorkEntryRow(props: {
13521454 ? {
13531455 role: "button" as const,
13541456 tabIndex: 0 as const,
1457 "aria-label": displayText,
13551458 onClick: () => setExpanded((v) => !v),
13561459 onKeyDown: (e: KeyboardEvent<HTMLDivElement>) => {
13571460 if (e.key === "Enter" || e.key === " ") {
......@@ -1365,114 +1468,66 @@ const SimpleWorkEntryRow = memo(function SimpleWorkEntryRow(props: {
13651468 return (
13661469 <div
13671470 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",
13691472 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",
13711474 )}
13721475 {...rowToggleProps}
13731476 >
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">
13751478 <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 />
13771483 </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">
13791485 <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>
14371492 </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">
14391494 <span
1440 className="flex size-5 shrink-0 items-center justify-center"
1495 className="flex size-4 shrink-0 items-center justify-center"
14411496 aria-hidden={!canExpand}
14421497 >
14431498 {canExpand ? (
14441499 <ChevronDownIcon
14451500 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",
14471502 expanded && "rotate-180",
14481503 )}
14491504 aria-hidden
14501505 />
14511506 ) : null}
14521507 </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">
14541509 {showFailedIndicator ? (
14551510 <Tooltip>
14561511 <TooltipTrigger
14571512 render={
14581513 <span
1459 className="flex size-5 items-center justify-center"
1514 className="flex size-4 items-center justify-center"
14601515 aria-label="Tool call failed"
14611516 />
14621517 }
14631518 >
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 />
14651520 </TooltipTrigger>
14661521 <TooltipPopup>Failed</TooltipPopup>
14671522 </Tooltip>
14681523 ) : showSuccessIndicator ? (
14691524 <Tooltip>
14701525 <TooltipTrigger
1471 render={<span className="flex size-5 items-center justify-center" />}
1526 render={<span className="flex size-4 items-center justify-center" />}
14721527 >
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">
14741529 <CheckIcon
1475 className="block size-3.5 shrink-0 stroke-current"
1530 className="block size-3 shrink-0 stroke-current"
14761531 stroke="currentColor"
14771532 aria-hidden
14781533 />
......@@ -1483,12 +1538,9 @@ const SimpleWorkEntryRow = memo(function SimpleWorkEntryRow(props: {
14831538 ) : showNeutralIndicator ? (
14841539 <Tooltip>
14851540 <TooltipTrigger
1486 render={<span className="flex size-5 items-center justify-center" />}
1541 render={<span className="flex size-4 items-center justify-center" />}
14871542 >
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 />
14921544 </TooltipTrigger>
14931545 <TooltipPopup>Empty</TooltipPopup>
14941546 </Tooltip>
......@@ -1499,48 +1551,15 @@ const SimpleWorkEntryRow = memo(function SimpleWorkEntryRow(props: {
14991551 </div>
15001552 {expanded && canExpand && expandedBody ? (
15011553 <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"
15031555 onClick={stopRowToggle}
15041556 onPointerDown={stopRowToggle}
15051557 >
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">
15071559 {expandedBody}
15081560 </pre>
15091561 </div>
15101562 ) : 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 )}
15441563 </div>
15451564 );
15461565});