From 2aeb3146ee302e393d7fb7a772acda93ba50525e Mon Sep 17 00:00:00 2001 From: Catherine Patchell Date: Fri, 24 Jul 2026 16:12:49 -0700 Subject: [PATCH 1/9] feat: swap icon and chevron placement on ResponseStatus --- packages/@react-spectrum/ai/src/ResponseStatus.tsx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/@react-spectrum/ai/src/ResponseStatus.tsx b/packages/@react-spectrum/ai/src/ResponseStatus.tsx index 37d84e4819b..ccd0f71702d 100644 --- a/packages/@react-spectrum/ai/src/ResponseStatus.tsx +++ b/packages/@react-spectrum/ai/src/ResponseStatus.tsx @@ -295,13 +295,7 @@ export const ResponseStatusTitle = forwardRef(function ResponseStatusTitle( aria-label={stringFormatter.format('responsestatus.loading')} /> - ) : isInteractive ? ( - - - - ) : null} - {props.children} - {!isLoading && ( + ) : ( )} + {props.children} + {isInteractive ? ( + + + + ) : null} ); From 5b649410cd65813b9a09b41e4c74a85e9da81efe Mon Sep 17 00:00:00 2001 From: Catherine Patchell Date: Fri, 24 Jul 2026 16:13:31 -0700 Subject: [PATCH 2/9] style: remove hover state on ResponseStatus trigger --- .../@react-spectrum/ai/src/ResponseStatus.tsx | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/packages/@react-spectrum/ai/src/ResponseStatus.tsx b/packages/@react-spectrum/ai/src/ResponseStatus.tsx index ccd0f71702d..7e5b467bd4b 100644 --- a/packages/@react-spectrum/ai/src/ResponseStatus.tsx +++ b/packages/@react-spectrum/ai/src/ResponseStatus.tsx @@ -11,13 +11,7 @@ */ import {AriaLabelingProps, DOMProps, DOMRef, GlobalDOMAttributes} from '@react-types/shared'; -import { - baseColor, - focusRing, - lightDark, - space, - style -} from '@react-spectrum/s2/style' with {type: 'macro'}; +import {baseColor, focusRing, space, style} from '@react-spectrum/s2/style' with {type: 'macro'}; import {Button} from 'react-aria-components/Button'; import {CenterBaseline} from '@react-spectrum/s2/CenterBaseline'; import CheckmarkCircle from '@react-spectrum/s2/icons/CheckmarkCircle'; @@ -217,14 +211,7 @@ const buttonStyles = style({ } }, width: 'full', - backgroundColor: { - default: 'transparent', - isFocusVisible: lightDark('transparent-black-100', 'transparent-white-100'), - isHovered: lightDark('transparent-black-100', 'transparent-white-100'), - isPressed: lightDark('transparent-black-300', 'transparent-white-300'), - isLoading: 'transparent', - isOnlyText: 'transparent' - }, + backgroundColor: 'transparent', transition: 'default', borderWidth: 0, borderRadius: 'default', From 468193b51fe6aedff96a50ee78c2dc36c0347ecd Mon Sep 17 00:00:00 2001 From: Catherine Patchell Date: Fri, 24 Jul 2026 16:14:00 -0700 Subject: [PATCH 3/9] feat: add ExecutionTrace and ExecutionTraceItem components --- .../@react-spectrum/ai/src/ResponseStatus.tsx | 209 +++++++++++++++++- 1 file changed, 208 insertions(+), 1 deletion(-) diff --git a/packages/@react-spectrum/ai/src/ResponseStatus.tsx b/packages/@react-spectrum/ai/src/ResponseStatus.tsx index 7e5b467bd4b..a464f29b125 100644 --- a/packages/@react-spectrum/ai/src/ResponseStatus.tsx +++ b/packages/@react-spectrum/ai/src/ResponseStatus.tsx @@ -11,7 +11,13 @@ */ import {AriaLabelingProps, DOMProps, DOMRef, GlobalDOMAttributes} from '@react-types/shared'; -import {baseColor, focusRing, space, style} from '@react-spectrum/s2/style' with {type: 'macro'}; +import { + baseColor, + focusRing, + iconStyle, + space, + style +} from '@react-spectrum/s2/style' with {type: 'macro'}; import {Button} from 'react-aria-components/Button'; import {CenterBaseline} from '@react-spectrum/s2/CenterBaseline'; import CheckmarkCircle from '@react-spectrum/s2/icons/CheckmarkCircle'; @@ -397,3 +403,204 @@ export const ResponseStatusPanel = forwardRef(function ResponseStatusPanel( ); }); + +export interface ExecutionTraceProps extends DOMProps, AriaLabelingProps { + /** + * The ExecutionTraceItem elements to render as a timeline. Typically placed inside a + * ResponseStatusPanel. + */ + children: ReactNode; + /** + * Spectrum-defined styles, returned by the `style()` macro. + */ + styles?: StyleString; +} + +const executionTraceStyles = style({ + display: 'flex', + flexDirection: 'column', + margin: 0, + padding: 0, + paddingStart: 4, + listStyleType: 'none' +}); + +/** + * An ExecutionTrace displays a timeline of the steps taken while generating a + * response, such as tool calls or searches. + */ +export const ExecutionTrace = forwardRef(function ExecutionTrace( + props: ExecutionTraceProps, + ref: DOMRef +) { + let {styles, children, ...otherProps} = props; + let domRef = useDOMRef(ref); + let domProps = filterDOMProps(otherProps); + + return ( +
    + {children} +
+ ); +}); + +interface DetailTriggerProps { + children: ReactNode; +} + +const detailTriggerStyles = style({ + display: 'block', + paddingY: 4, + marginTop: -2, + textAlign: 'start' +}); + +const detailTriggerChevronStyles = style({ + display: 'inline-flex', + alignItems: 'center', + verticalAlign: 'middle', + marginStart: 4, + rotate: { + isRTL: 180, + isExpanded: 90 + }, + transition: 'default' +}); + +function DetailTrigger(props: DetailTriggerProps) { + let {children} = props; + let {direction} = useLocale(); + let isRTL = direction === 'rtl'; + let {isExpanded} = useContext(DisclosureStateContext)!; + + return ( + + ); +} + +export interface ExecutionTraceItemProps extends DOMProps, AriaLabelingProps { + /** Allows detail content to render but prevents the row from being collapsible. */ + isDetailNotCollapsible?: boolean; + /** + * The label describing the step. + */ + children: ReactNode; + /** + * An icon shown at the leading edge of the row. If omitted, a checkmark is rendered by default. + */ + icon?: ReactNode; + /** + * Additional detail revealed when the step is expanded, such as tool call input or output. + * If omitted, the row is static and cannot be expanded. + */ + detail?: ReactNode; + /** + * Spectrum-defined styles, returned by the `style()` macro. + */ + styles?: StyleString; +} + +const executionTraceItemStyles = style({ + font: 'body', + display: 'flex', + gap: 8, + marginTop: { + isDetailed: -2 + }, + '--divider-display': { + type: 'display', + value: { + default: 'flex', + ':last-child': 'none' + } + } +}); + +const itemIconContainerStyles = style({ + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + flexShrink: 0, + paddingTop: { + isDetailed: 2 + } +}); + +const executionTraceItemDividerStyles = style({ + width: 1, + flexGrow: 1, + marginY: 2, + backgroundColor: 'gray-500', + display: 'var(--divider-display, flex)' +}); + +const executionTraceItemContentStyles = style({ + display: 'flex', + flexDirection: 'column', + paddingBottom: 12, + paddingX: 8 +}); + +const executionTraceItemNoCollapseStyles = style({ + minHeight: 24 +}); + +/** + * An ExecutionTraceItem represents a single step within an ExecutionTrace, such as + * a tool call or search. When a `detail` is provided, the row can be expanded to reveal it. + */ +export const ExecutionTraceItem = forwardRef(function ExecutionTraceItem( + props: ExecutionTraceItemProps, + ref: DOMRef +) { + let { + isDetailNotCollapsible, + detail, + icon =