| author | |
| committer | |
| log | c3b8825bf476cbce5e061c0f99570cf1f6723b89 |
| tree | 697e8b810c9a9824720ae12982ba314f164beb2e |
| parent | 61a91b6ef1bd45424169c6650362b358d49bbe34 |
| signature | Signed by PGP key B5690EEEBB952194 |
5 files changed, 72 insertions(+), 50 deletions(-)
apps/mobile/src/features/threads/thread-work-log.tsx+20-20| ... | @@ -76,14 +76,13 @@ type WorkContentIcon = AppSymbolName | "browser" | "t3-code"; | ... | @@ -76,14 +76,13 @@ type WorkContentIcon = AppSymbolName | "browser" | "t3-code"; |
| 76 | function WorkLogIcon(props: { | 76 | function WorkLogIcon(props: { |
| 77 | readonly icon: WorkContentIcon; | 77 | readonly icon: WorkContentIcon; |
| 78 | readonly color: ColorValue; | 78 | readonly color: ColorValue; |
| 79 | readonly colorClassName?: string; | ||
| 79 | readonly highlighted?: boolean; | 80 | readonly highlighted?: boolean; |
| 80 | }) { | 81 | }) { |
| 82 | const colorClassName = props.highlighted ? "accent-foreground" : props.colorClassName; | ||
| 81 | if (props.icon === "t3-code") { | 83 | if (props.icon === "t3-code") { |
| 82 | return ( | 84 | return ( |
| 83 | <T3Wordmark | 85 | <T3Wordmark height={10} {...(colorClassName ? { colorClassName } : { color: props.color })} /> |
| 84 | height={10} | ||
| 85 | {...(props.highlighted ? { colorClassName: "accent-foreground" } : { color: props.color })} | ||
| 86 | /> | ||
| 87 | ); | 86 | ); |
| 88 | } | 87 | } |
| 89 | return ( | 88 | return ( |
| ... | @@ -91,9 +90,7 @@ function WorkLogIcon(props: { | ... | @@ -91,9 +90,7 @@ function WorkLogIcon(props: { |
| 91 | name={props.icon === "browser" ? { ios: "globe", android: "public" } : props.icon} | 90 | name={props.icon === "browser" ? { ios: "globe", android: "public" } : props.icon} |
| 92 | size={14} | 91 | size={14} |
| 93 | weight="medium" | 92 | weight="medium" |
| 94 | {...(props.highlighted | 93 | {...(colorClassName ? { tintColorClassName: colorClassName } : { tintColor: props.color })} |
| 95 | ? { tintColorClassName: "accent-foreground" } | ||
| 96 | : { tintColor: props.color })} | ||
| 97 | type="monochrome" | 94 | type="monochrome" |
| 98 | /> | 95 | /> |
| 99 | ); | 96 | ); |
| ... | @@ -707,11 +704,7 @@ const ThreadWorkLogRow = memo(function ThreadWorkLogRow( | ... | @@ -707,11 +704,7 @@ const ThreadWorkLogRow = memo(function ThreadWorkLogRow( |
| 707 | const iconIsDestructive = row.icon === "alert" || row.icon === "warning"; | 704 | const iconIsDestructive = row.icon === "alert" || row.icon === "warning"; |
| 708 | const failed = row.status === "failure"; | 705 | const failed = row.status === "failure"; |
| 709 | const toolIcon = row.workEntry.toolIcon ?? row.workEntry.toolSource?.icon; | 706 | const toolIcon = row.workEntry.toolIcon ?? row.workEntry.toolSource?.icon; |
| 710 | const hasSpecialToolIcon = | 707 | const icon = toolPresentation?.icon ?? workRowSymbolName(row.icon); |
| 711 | toolPresentation !== null || row.workEntry.toolSurface !== undefined || toolIcon !== undefined; | ||
| 712 | const icon = | ||
| 713 | toolPresentation?.icon ?? | ||
| 714 | (failed && !hasSpecialToolIcon ? "xmark" : workRowSymbolName(row.icon)); | ||
| 715 | 708 | ||
| 716 | return ( | 709 | return ( |
| 717 | <Animated.View | 710 | <Animated.View |
| ... | @@ -750,12 +743,7 @@ const ThreadWorkLogRow = memo(function ThreadWorkLogRow( | ... | @@ -750,12 +743,7 @@ const ThreadWorkLogRow = memo(function ThreadWorkLogRow( |
| 750 | ) : ( | 743 | ) : ( |
| 751 | <> | 744 | <> |
| 752 | <View className="h-6 w-6 shrink-0 items-center justify-center"> | 745 | <View className="h-6 w-6 shrink-0 items-center justify-center"> |
| 753 | {failed && !hasSpecialToolIcon ? ( | 746 | {toolIcon ? ( |
| 754 | <WorkLogIcon | ||
| 755 | icon={icon} | ||
| 756 | color={iconIsDestructive ? "#e11d48" : props.iconSubtleColor} | ||
| 757 | /> | ||
| 758 | ) : ( | ||
| 759 | <ToolActivityIconView | 747 | <ToolActivityIconView |
| 760 | environmentId={props.environmentId} | 748 | environmentId={props.environmentId} |
| 761 | icon={toolIcon} | 749 | icon={toolIcon} |
| ... | @@ -763,6 +751,18 @@ const ThreadWorkLogRow = memo(function ThreadWorkLogRow( | ... | @@ -763,6 +751,18 @@ const ThreadWorkLogRow = memo(function ThreadWorkLogRow( |
| 763 | fallbackColor={props.iconSubtleColor} | 751 | fallbackColor={props.iconSubtleColor} |
| 764 | themeAppearance={props.themeAppearance} | 752 | themeAppearance={props.themeAppearance} |
| 765 | /> | 753 | /> |
| 754 | ) : ( | ||
| 755 | <WorkLogIcon | ||
| 756 | icon={icon} | ||
| 757 | color={props.iconSubtleColor} | ||
| 758 | colorClassName={ | ||
| 759 | iconIsDestructive | ||
| 760 | ? "accent-adaptive-rose-600-400" | ||
| 761 | : failed | ||
| 762 | ? "accent-danger-foreground/40" | ||
| 763 | : undefined | ||
| 764 | } | ||
| 765 | /> | ||
| 766 | )} | 766 | )} |
| 767 | </View> | 767 | </View> |
| 768 | <Text | 768 | <Text |
| ... | @@ -783,7 +783,7 @@ const ThreadWorkLogRow = memo(function ThreadWorkLogRow( | ... | @@ -783,7 +783,7 @@ const ThreadWorkLogRow = memo(function ThreadWorkLogRow( |
| 783 | Copied | 783 | Copied |
| 784 | </Text> | 784 | </Text> |
| 785 | ) : null} | 785 | ) : null} |
| 786 | {failed && hasSpecialToolIcon ? ( | 786 | {failed && toolIcon !== undefined ? ( |
| 787 | <View | 787 | <View |
| 788 | className="h-4 w-4 items-center justify-center" | 788 | className="h-4 w-4 items-center justify-center" |
| 789 | accessibilityElementsHidden | 789 | accessibilityElementsHidden |
| ... | @@ -792,7 +792,7 @@ const ThreadWorkLogRow = memo(function ThreadWorkLogRow( | ... | @@ -792,7 +792,7 @@ const ThreadWorkLogRow = memo(function ThreadWorkLogRow( |
| 792 | <SymbolView | 792 | <SymbolView |
| 793 | name="xmark" | 793 | name="xmark" |
| 794 | size={11} | 794 | size={11} |
| 795 | tintColorClassName="accent-adaptive-rose-600-400" | 795 | tintColorClassName="accent-danger-foreground/40" |
| 796 | type="monochrome" | 796 | type="monochrome" |
| 797 | /> | 797 | /> |
| 798 | </View> | 798 | </View> |
apps/web/src/components/chat/MessagesTimeline.test.tsx+1-1| ... | @@ -1471,7 +1471,7 @@ describe("MessagesTimeline", () => { | ... | @@ -1471,7 +1471,7 @@ describe("MessagesTimeline", () => { |
| 1471 | ); | 1471 | ); |
| 1472 | 1472 | ||
| 1473 | expect(markup).toContain('aria-label="Received 1 update and used 1 tool, tool call failed"'); | 1473 | expect(markup).toContain('aria-label="Received 1 update and used 1 tool, tool call failed"'); |
| 1474 | // Ordinary tool failures render muted, not red. | 1474 | // Ordinary tool failures do not use destructive row styling. |
| 1475 | expect(markup).not.toContain("text-destructive"); | 1475 | expect(markup).not.toContain("text-destructive"); |
| 1476 | }); | 1476 | }); |
| 1477 | 1477 |
apps/web/src/components/chat/MessagesTimeline.tsx+43-29| ... | @@ -1893,6 +1893,17 @@ function ActivityShimmerOverlay({ children }: { children: ReactNode }) { | ... | @@ -1893,6 +1893,17 @@ function ActivityShimmerOverlay({ children }: { children: ReactNode }) { |
| 1893 | ); | 1893 | ); |
| 1894 | } | 1894 | } |
| 1895 | 1895 | ||
| 1896 | const failedToolIconClassName = "text-tool-error-icon/40"; | ||
| 1897 | |||
| 1898 | /** Image icons and the gradient computer-use mark cannot take a currentColor | ||
| 1899 | * tint, so failed rows using them get a trailing x instead. */ | ||
| 1900 | function toolIconAcceptsTint( | ||
| 1901 | iconName: WorkEntryIconName, | ||
| 1902 | toolIcon: ToolActivityIcon | undefined, | ||
| 1903 | ): boolean { | ||
| 1904 | return toolIcon === undefined && iconName !== "computer"; | ||
| 1905 | } | ||
| 1906 | |||
| 1896 | function LiveActivityRow({ | 1907 | function LiveActivityRow({ |
| 1897 | label, | 1908 | label, |
| 1898 | iconName, | 1909 | iconName, |
| ... | @@ -1941,37 +1952,41 @@ function LiveActivityContent({ | ... | @@ -1941,37 +1952,41 @@ function LiveActivityContent({ |
| 1941 | announceFailure?: boolean; | 1952 | announceFailure?: boolean; |
| 1942 | highlighted?: boolean; | 1953 | highlighted?: boolean; |
| 1943 | }) { | 1954 | }) { |
| 1944 | const isSpecialToolIcon = | 1955 | const showTrailingFailureMark = |
| 1945 | iconName === "browser" || iconName === "computer" || iconName === "t3-code"; | 1956 | failed && iconName !== undefined && !toolIconAcceptsTint(iconName, toolIcon); |
| 1946 | const resolvedIconName = failed && !isSpecialToolIcon ? "circle-alert" : iconName; | ||
| 1947 | 1957 | ||
| 1948 | return ( | 1958 | return ( |
| 1949 | <span | 1959 | <span |
| 1950 | className={cn( | 1960 | className={cn( |
| 1951 | "flex min-h-6 min-w-0 items-center gap-1.5 py-0.5", | 1961 | "flex min-h-6 min-w-0 items-center gap-1.5 py-0.5", |
| 1952 | resolvedIconName ? "px-0.5" : "px-1", | 1962 | iconName ? "px-0.5" : "px-1", |
| 1953 | highlighted ? "text-foreground" : "text-secondary-label", | 1963 | highlighted ? "text-foreground" : "text-secondary-label", |
| 1954 | )} | 1964 | )} |
| 1955 | > | 1965 | > |
| 1956 | {resolvedIconName ? ( | 1966 | {iconName ? ( |
| 1957 | <span | 1967 | <span |
| 1958 | className={cn( | 1968 | className={cn( |
| 1959 | "flex size-6 shrink-0 items-center justify-center", | 1969 | "flex size-6 shrink-0 items-center justify-center", |
| 1960 | highlighted ? "text-foreground" : "text-icon-muted", | 1970 | highlighted ? "text-foreground" : failed ? failedToolIconClassName : "text-icon-muted", |
| 1961 | )} | 1971 | )} |
| 1962 | role={announceFailure ? "img" : undefined} | 1972 | role={announceFailure ? "img" : undefined} |
| 1963 | aria-label={announceFailure ? "Tool call failed" : undefined} | 1973 | aria-label={announceFailure ? "Tool call failed" : undefined} |
| 1964 | > | 1974 | > |
| 1965 | <ToolActivityIconView | 1975 | <ToolActivityIconView |
| 1966 | icon={failed && !isSpecialToolIcon ? undefined : toolIcon} | 1976 | icon={toolIcon} |
| 1967 | fallbackName={resolvedIconName} | 1977 | fallbackName={iconName} |
| 1968 | className="block size-4 shrink-0 stroke-[1.8]" | 1978 | className="block size-4 shrink-0 stroke-[1.8]" |
| 1969 | muted={!highlighted} | 1979 | muted={!highlighted} |
| 1970 | /> | 1980 | /> |
| 1971 | </span> | 1981 | </span> |
| 1972 | ) : null} | 1982 | ) : null} |
| 1973 | <span className="min-w-0 flex-1 truncate">{label}</span> | 1983 | <span className="min-w-0 flex-1 truncate">{label}</span> |
| 1974 | {failed && isSpecialToolIcon ? <XIcon aria-hidden className="size-3 shrink-0" /> : null} | 1984 | {showTrailingFailureMark ? ( |
| 1985 | <XIcon | ||
| 1986 | aria-hidden | ||
| 1987 | className={cn("size-3 shrink-0", !highlighted && failedToolIconClassName)} | ||
| 1988 | /> | ||
| 1989 | ) : null} | ||
| 1975 | </span> | 1990 | </span> |
| 1976 | ); | 1991 | ); |
| 1977 | } | 1992 | } |
| ... | @@ -3088,12 +3103,15 @@ const PlainWorkEntryRow = memo(function PlainWorkEntryRow(props: { | ... | @@ -3088,12 +3103,15 @@ const PlainWorkEntryRow = memo(function PlainWorkEntryRow(props: { |
| 3088 | const iconConfig = workToneIcon(workEntry.tone); | 3103 | const iconConfig = workToneIcon(workEntry.tone); |
| 3089 | const showWarningIndicator = workEntry.sourceActivityKind === "runtime.warning"; | 3104 | const showWarningIndicator = workEntry.sourceActivityKind === "runtime.warning"; |
| 3090 | const showFailedIndicator = workEntryDisplayIndicatesToolFailure(workEntry); | 3105 | const showFailedIndicator = workEntryDisplayIndicatesToolFailure(workEntry); |
| 3091 | const toolPresentation = resolveWorkEntryToolPresentation(workEntry); | 3106 | const showDestructiveRowStyle = |
| 3092 | const hasSpecialToolIcon = toolPresentation !== null || workEntry.toolSurface !== undefined; | 3107 | showFailedIndicator && |
| 3108 | (workEntrySignalsSevereFailure(workEntry) || !workLogEntryIsToolLike(workEntry)); | ||
| 3093 | const entryIconName = | 3109 | const entryIconName = |
| 3094 | showWarningIndicator || (showFailedIndicator && !hasSpecialToolIcon) | 3110 | showWarningIndicator || showDestructiveRowStyle ? "circle-alert" : workEntryIconName(workEntry); |
| 3095 | ? "circle-alert" | 3111 | const entryToolIcon = |
| 3096 | : workEntryIconName(workEntry); | 3112 | showWarningIndicator || showDestructiveRowStyle |
| 3113 | ? undefined | ||
| 3114 | : (workEntry.toolIcon ?? workEntry.toolSource?.icon); | ||
| 3097 | const previewText = displayLabel ?? workEntryDisplayLabel(workEntry, workspaceRoot); | 3115 | const previewText = displayLabel ?? workEntryDisplayLabel(workEntry, workspaceRoot); |
| 3098 | const viewedImagePath = workEntryViewedImagePath(workEntry); | 3116 | const viewedImagePath = workEntryViewedImagePath(workEntry); |
| 3099 | const viewedImage = | 3117 | const viewedImage = |
| ... | @@ -3121,20 +3139,18 @@ const PlainWorkEntryRow = memo(function PlainWorkEntryRow(props: { | ... | @@ -3121,20 +3139,18 @@ const PlainWorkEntryRow = memo(function PlainWorkEntryRow(props: { |
| 3121 | viewedImage ? viewedImagePath : null, | 3139 | viewedImage ? viewedImagePath : null, |
| 3122 | ) | 3140 | ) |
| 3123 | : null; | 3141 | : null; |
| 3124 | const showDestructiveRowStyle = | 3142 | // Reserve destructive row styling for severe failures, not routine tool errors. |
| 3125 | showFailedIndicator && | ||
| 3126 | (workEntrySignalsSevereFailure(workEntry) || !workLogEntryIsToolLike(workEntry)); | ||
| 3127 | // Ordinary tool failures stay muted; only runtime errors and warnings get | ||
| 3128 | // color. The red treatment is reserved for severe failures. | ||
| 3129 | const iconWrapperClass = cn( | 3143 | const iconWrapperClass = cn( |
| 3130 | "flex size-6 shrink-0 items-center justify-center", | 3144 | "flex size-6 shrink-0 items-center justify-center", |
| 3131 | showWarningIndicator | 3145 | showWarningIndicator |
| 3132 | ? "text-warning" | 3146 | ? "text-warning" |
| 3133 | : showDestructiveRowStyle | 3147 | : showDestructiveRowStyle |
| 3134 | ? "text-destructive" | 3148 | ? "text-destructive" |
| 3135 | : workEntry.tone === "tool" || showFailedIndicator | 3149 | : showFailedIndicator |
| 3136 | ? "text-icon-muted" | 3150 | ? failedToolIconClassName |
| 3137 | : iconConfig.className, | 3151 | : workEntry.tone === "tool" |
| 3152 | ? "text-icon-muted" | ||
| 3153 | : iconConfig.className, | ||
| 3138 | ); | 3154 | ); |
| 3139 | const headingClass = showWarningIndicator | 3155 | const headingClass = showWarningIndicator |
| 3140 | ? "font-medium text-warning" | 3156 | ? "font-medium text-warning" |
| ... | @@ -3179,11 +3195,7 @@ const PlainWorkEntryRow = memo(function PlainWorkEntryRow(props: { | ... | @@ -3179,11 +3195,7 @@ const PlainWorkEntryRow = memo(function PlainWorkEntryRow(props: { |
| 3179 | aria-label={showFailedIndicator ? "Tool call failed" : undefined} | 3195 | aria-label={showFailedIndicator ? "Tool call failed" : undefined} |
| 3180 | > | 3196 | > |
| 3181 | <ToolActivityIconView | 3197 | <ToolActivityIconView |
| 3182 | icon={ | 3198 | icon={entryToolIcon} |
| 3183 | showWarningIndicator || (showFailedIndicator && !hasSpecialToolIcon) | ||
| 3184 | ? undefined | ||
| 3185 | : (workEntry.toolIcon ?? workEntry.toolSource?.icon) | ||
| 3186 | } | ||
| 3187 | fallbackName={entryIconName} | 3199 | fallbackName={entryIconName} |
| 3188 | className="block size-4 shrink-0 stroke-[1.8]" | 3200 | className="block size-4 shrink-0 stroke-[1.8]" |
| 3189 | muted | 3201 | muted |
| ... | @@ -3207,8 +3219,10 @@ const PlainWorkEntryRow = memo(function PlainWorkEntryRow(props: { | ... | @@ -3207,8 +3219,10 @@ const PlainWorkEntryRow = memo(function PlainWorkEntryRow(props: { |
| 3207 | </span> | 3219 | </span> |
| 3208 | </p> | 3220 | </p> |
| 3209 | </div> | 3221 | </div> |
| 3210 | {showFailedIndicator && hasSpecialToolIcon ? ( | 3222 | {showFailedIndicator && |
| 3211 | <XIcon aria-hidden className="size-3 shrink-0 text-icon-muted" /> | 3223 | !showDestructiveRowStyle && |
| 3224 | !toolIconAcceptsTint(entryIconName, entryToolIcon) ? ( | ||
| 3225 | <XIcon aria-hidden className={cn("size-3 shrink-0", failedToolIconClassName)} /> | ||
| 3212 | ) : null} | 3226 | ) : null} |
| 3213 | <span | 3227 | <span |
| 3214 | className={cn( | 3228 | className={cn( |
apps/web/src/index.css+4| ... | @@ -154,6 +154,7 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil | ... | @@ -154,6 +154,7 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil |
| 154 | --color-warning: var(--warning); | 154 | --color-warning: var(--warning); |
| 155 | --color-warning-surface: var(--warning-surface); | 155 | --color-warning-surface: var(--warning-surface); |
| 156 | --color-error-foreground: var(--error-foreground); | 156 | --color-error-foreground: var(--error-foreground); |
| 157 | --color-tool-error-icon: var(--tool-error-icon); | ||
| 157 | --color-error: var(--error); | 158 | --color-error: var(--error); |
| 158 | --color-error-surface: var(--error-surface); | 159 | --color-error-surface: var(--error-surface); |
| 159 | --color-update-foreground: var(--update-foreground); | 160 | --color-update-foreground: var(--update-foreground); |
| ... | @@ -948,6 +949,7 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil | ... | @@ -948,6 +949,7 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil |
| 948 | --accent-foreground: var(--color-zinc-900); | 949 | --accent-foreground: var(--color-zinc-900); |
| 949 | --error: var(--color-red-500); | 950 | --error: var(--color-red-500); |
| 950 | --error-foreground: var(--color-red-700); | 951 | --error-foreground: var(--color-red-700); |
| 952 | --tool-error-icon: var(--error); | ||
| 951 | --error-surface: color-mix(in srgb, var(--error) 8%, transparent); | 953 | --error-surface: color-mix(in srgb, var(--error) 8%, transparent); |
| 952 | --destructive: var(--error); | 954 | --destructive: var(--error); |
| 953 | --border: var(--color-zinc-200); | 955 | --border: var(--color-zinc-200); |
| ... | @@ -1002,6 +1004,7 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil | ... | @@ -1002,6 +1004,7 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil |
| 1002 | --accent-foreground: var(--color-neutral-100); | 1004 | --accent-foreground: var(--color-neutral-100); |
| 1003 | --error: color-mix(in srgb, var(--color-red-500) 90%, var(--color-white)); | 1005 | --error: color-mix(in srgb, var(--color-red-500) 90%, var(--color-white)); |
| 1004 | --error-foreground: var(--color-red-400); | 1006 | --error-foreground: var(--color-red-400); |
| 1007 | --tool-error-icon: #fca5a5; | ||
| 1005 | --error-surface: color-mix(in srgb, var(--error) 16%, transparent); | 1008 | --error-surface: color-mix(in srgb, var(--error) 16%, transparent); |
| 1006 | --border: --alpha(var(--color-white) / 6%); | 1009 | --border: --alpha(var(--color-white) / 6%); |
| 1007 | --input: --alpha(var(--color-white) / 8%); | 1010 | --input: --alpha(var(--color-white) / 8%); |
| ... | @@ -1114,6 +1117,7 @@ html[data-theme-id]:not([data-theme-id=""]) { | ... | @@ -1114,6 +1117,7 @@ html[data-theme-id]:not([data-theme-id=""]) { |
| 1114 | --message-action-hover: var(--app-theme-message-action-hover); | 1117 | --message-action-hover: var(--app-theme-message-action-hover); |
| 1115 | --error: var(--app-theme-error); | 1118 | --error: var(--app-theme-error); |
| 1116 | --error-foreground: var(--app-theme-error-foreground); | 1119 | --error-foreground: var(--app-theme-error-foreground); |
| 1120 | --tool-error-icon: var(--error-foreground); | ||
| 1117 | --error-surface: var(--app-theme-error-surface); | 1121 | --error-surface: var(--app-theme-error-surface); |
| 1118 | --destructive: var(--error); | 1122 | --destructive: var(--error); |
| 1119 | --destructive-foreground: var(--error-foreground); | 1123 | --destructive-foreground: var(--error-foreground); |
docs/user/tool-activity.md+4| ... | @@ -13,6 +13,10 @@ The latest live activity stays in the present tense while the turn continues, su | ... | @@ -13,6 +13,10 @@ The latest live activity stays in the present tense while the turn continues, su |
| 13 | Expanded rows follow the call's own state, such as "Clicked" after success. | 13 | Expanded rows follow the call's own state, such as "Clicked" after success. |
| 14 | When a call has not reported a state yet, the label stays in the present tense. | 14 | When a call has not reported a state yet, the label stays in the present tense. |
| 15 | Failed, declined, and stopped calls say what happened without implying success. | 15 | Failed, declined, and stopped calls say what happened without implying success. |
| 16 | Failed calls keep their tool icon with a muted red tint, so a failed command still | ||
| 17 | looks like a command. App logos and other multicolor icons keep their artwork and | ||
| 18 | show a small muted red failure mark beside the row. Expand the call to inspect its | ||
| 19 | output. Runtime errors and warnings keep their stronger styling. | ||
| 16 | Preview browser actions use a globe icon. Other T3 tools keep the T3 mark. | 20 | Preview browser actions use a globe icon. Other T3 tools keep the T3 mark. |
| 17 | Group summaries count browser actions separately, such as "Used browser 18 times" or | 21 | Group summaries count browser actions separately, such as "Used browser 18 times" or |
| 18 | "Ran 4 commands and used browser 15 times". Browser-only groups also use a globe icon. | 22 | "Ran 4 commands and used browser 15 times". Browser-only groups also use a globe icon. |