authorgravatar for maria@kuuro.netmaria <maria@kuuro.net> 2026-09-03 22:35:24-04:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-09-03 22:35:24-04:00
log710f6dc417ebf303eede3df3605a6938482d83ab
tree0f82a138475a2e8c9ad88d99d45775522bdb547b
parentf96a220b5b154ea44c94bf43929c6362cd511699
signature Signed by PGP key B5690EEEBB952194

fix(web): simplify expanded tool details (#9549)

Co-authored-by: maria-rcks <254055478+maria-rcks@users.noreply.github.com>

1 files changed, 65 insertions(+), 27 deletions(-)

apps/web/src/components/chat/MessagesTimeline.tsx+65-27
......@@ -2886,27 +2886,47 @@ function workEntryRawCommand(
28862886function buildToolCallExpandedBody(
28872887 workEntry: TimelineWorkEntry,
28882888 workspaceRoot: string | undefined,
2889 visibleLabel: string,
2890 viewedImagePath: string | null,
28892891): string | null {
28902892 const blocks: string[] = [];
2893 const seen = new Set<string>();
2894 const addBlock = (value: string | null | undefined) => {
2895 const text = value?.trim();
2896 if (!text || seen.has(text)) return;
2897 seen.add(text);
2898 blocks.push(text);
2899 };
28912900 if (workEntry.itemType === "mcp_tool_call" && workEntry.toolData !== undefined) {
2892 blocks.push(`MCP call\n${JSON.stringify(workEntry.toolData, null, 2)}`);
2901 addBlock(`MCP call\n${JSON.stringify(workEntry.toolData, null, 2)}`);
28932902 }
2903 const command = workEntry.command?.trim();
28942904 const raw = workEntryRawCommand(workEntry);
2895 if (raw?.trim()) {
2896 blocks.push(raw.trim());
2897 } else if (workEntry.command?.trim()) {
2898 blocks.push(workEntry.command.trim());
2905 if (command === visibleLabel.trim()) {
2906 seen.add(command);
2907 } else {
2908 addBlock(raw ?? command);
28992909 }
2900 if (workEntry.detail?.trim()) {
2901 blocks.push(workEntry.detail.trim());
2910 const detail = workEntry.detail?.trim();
2911 if (detail !== viewedImagePath?.trim()) {
2912 addBlock(detail);
29022913 }
2903 const changedFiles = workEntry.changedFiles ?? [];
2914 const viewedImagePaths = new Set(
2915 viewedImagePath
2916 ? [viewedImagePath.trim(), formatWorkspaceRelativePath(viewedImagePath, workspaceRoot)]
2917 : [],
2918 );
2919 const changedFiles = (workEntry.changedFiles ?? []).flatMap((filePath) => {
2920 const formattedPath = formatWorkspaceRelativePath(filePath, workspaceRoot);
2921 return viewedImagePaths.has(filePath) ||
2922 viewedImagePaths.has(formattedPath) ||
2923 filePath.trim() === detail ||
2924 formattedPath === detail
2925 ? []
2926 : [formattedPath];
2927 });
29042928 if (changedFiles.length > 0) {
2905 blocks.push(
2906 changedFiles
2907 .map((filePath) => formatWorkspaceRelativePath(filePath, workspaceRoot))
2908 .join("\n"),
2909 );
2929 addBlock([...new Set(changedFiles)].join("\n"));
29102930 }
29112931 return blocks.length > 0 ? blocks.join("\n\n") : null;
29122932}
......@@ -3092,19 +3112,7 @@ const PlainWorkEntryRow = memo(function PlainWorkEntryRow(props: {
30923112 ? "circle-alert"
30933113 : workEntryIconName(workEntry);
30943114 const previewText = displayLabel ?? workEntryDisplayLabel(workEntry, workspaceRoot);
3095 const displayText =
3096 !toolPresentation && expanded && workEntry.command?.trim() ? "Command" : previewText;
30973115 const viewedImagePath = workEntryViewedImagePath(workEntry);
3098 const canExpand =
3099 (workEntry.itemType === "mcp_tool_call" && workEntry.toolData !== undefined) ||
3100 Boolean(
3101 workEntryRawCommand(workEntry) ||
3102 workEntry.command?.trim() ||
3103 workEntry.detail?.trim() ||
3104 workEntry.changedFiles?.length ||
3105 viewedImagePath,
3106 );
3107 const expandedBody = expanded ? buildToolCallExpandedBody(workEntry, workspaceRoot) : null;
31083116 const viewedImage =
31093117 viewedImagePath && threadRef
31103118 ? resolveViewedImageAsset(viewedImagePath, {
......@@ -3112,6 +3120,24 @@ const PlainWorkEntryRow = memo(function PlainWorkEntryRow(props: {
31123120 workspaceRoot,
31133121 })
31143122 : null;
3123 const commandMatchesVisibleLabel = workEntry.command?.trim() === previewText.trim();
3124 const canExpand =
3125 (workEntry.itemType === "mcp_tool_call" && workEntry.toolData !== undefined) ||
3126 Boolean(
3127 (!commandMatchesVisibleLabel &&
3128 (workEntryRawCommand(workEntry) || workEntry.command?.trim())) ||
3129 workEntry.detail?.trim() ||
3130 workEntry.changedFiles?.length ||
3131 viewedImage,
3132 );
3133 const expandedBody = expanded
3134 ? buildToolCallExpandedBody(
3135 workEntry,
3136 workspaceRoot,
3137 previewText,
3138 viewedImage ? viewedImagePath : null,
3139 )
3140 : null;
31153141 const showDestructiveRowStyle =
31163142 showFailedIndicator &&
31173143 (workEntrySignalsSevereFailure(workEntry) || !workLogEntryIsToolLike(workEntry));
......@@ -3183,7 +3209,19 @@ const PlainWorkEntryRow = memo(function PlainWorkEntryRow(props: {
31833209 <div className="flex min-w-0 flex-1 items-center gap-1.5">
31843210 <div className="min-w-0 flex-1 overflow-hidden">
31853211 <p className="flex min-w-0 w-full items-baseline gap-1.5 text-sm leading-relaxed">
3186 <span className={cn("min-w-0 flex-1 truncate", headingClass)}>{displayText}</span>
3212 <span
3213 className={cn(
3214 "min-w-0 flex-1",
3215 expanded || (commandMatchesVisibleLabel && !canExpand)
3216 ? "whitespace-pre-wrap break-words select-text"
3217 : "truncate",
3218 headingClass,
3219 )}
3220 onClick={expanded ? stopRowToggle : undefined}
3221 onPointerDown={expanded ? stopRowToggle : undefined}
3222 >
3223 {previewText}
3224 </span>
31873225 </p>
31883226 </div>
31893227 {showFailedIndicator && hasSpecialToolIcon ? (
......@@ -3224,7 +3262,7 @@ const PlainWorkEntryRow = memo(function PlainWorkEntryRow(props: {
32243262 ) : null}
32253263 {expanded && canExpand && expandedBody ? (
32263264 <div
3227 className="mt-1 ms-7 cursor-default border-s border-border/45 ps-3 pt-0.5"
3265 className="mt-1 ms-7 cursor-default rounded-md bg-muted/40 px-3 py-2"
32283266 onClick={stopRowToggle}
32293267 onPointerDown={stopRowToggle}
32303268 >