✂️ fix: Truncate Overflowing Activity and Intent Labels in Chat UI (#14607)

* ✂️ fix: Truncate Overflowing Activity and Intent Labels in Chat UI

* 🖍️ style: Use Middle Dot Separator in Tool Label Chrome

* ✂️ fix: Truncate Completed Web Search Label
This commit is contained in:
Danny Avila 2026-08-03 13:20:39 -04:00 • committed by GitHub
parent f738810c11
commit 5029dd467e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 34 additions and 27 deletions

View file

@ -164,7 +164,7 @@ const Part = memo(function Part({
const failed = part.status === 'failed' || part.status === 'partial';
return (
<div
className={`my-1 pl-1 text-sm italic ${failed ? 'text-amber-600 dark:text-amber-400' : 'text-text-secondary'}`}
className={`my-1 break-words pl-1 text-sm italic ${failed ? 'text-amber-600 dark:text-amber-400' : 'text-text-secondary'}`}
>
{display}
</div>

View file

@ -96,7 +96,7 @@ export default function BashCall({
return (
<>
<div className="relative my-1.5 flex size-5 shrink-0 items-center gap-2.5">
<div className="relative my-1.5 flex h-5 shrink-0 items-center gap-2.5">
<ProgressText
progress={progress}
onClick={toggleCode}

View file

@ -105,7 +105,7 @@ export default function ExecuteCode({
return (
<>
<div className="relative my-1.5 flex size-5 shrink-0 items-center gap-2.5">
<div className="relative my-1.5 flex h-5 shrink-0 items-center gap-2.5">
<ProgressText
progress={progress}
onClick={toggleCode}

View file

@ -160,7 +160,7 @@ export default function FileAuthoringCall({
return (
<>
<div className="relative my-1.5 flex size-5 shrink-0 items-center gap-2.5">
<div className="relative my-1.5 flex h-5 shrink-0 items-center gap-2.5">
<ProgressText
progress={progress}
onClick={toggleCode}

View file

@ -83,7 +83,7 @@ export default function ProgressText({
return (
<span
className={cn(
'progress-text-content tool-status-text whitespace-nowrap font-medium',
'progress-text-content tool-status-text min-w-0 truncate font-medium',
progress < 1 && 'shimmer',
)}
>

View file

@ -33,10 +33,10 @@ export default function PendingSkillCall({
: localize('com_ui_skill_running', { 0: skillName });
return (
<div className="relative my-1.5 flex size-5 shrink-0 items-center gap-2.5">
<div className="relative my-1.5 flex h-5 shrink-0 items-center gap-2.5">
<div className="progress-text-wrapper text-token-text-secondary relative -mt-[0.75px] h-5 w-full leading-5">
<div
className="progress-text-content absolute left-0 top-0 overflow-visible whitespace-nowrap"
className="progress-text-content absolute left-0 top-0 max-w-full overflow-visible whitespace-nowrap"
style={{ opacity: 1, transform: 'none' }}
>
<div className="inline-flex w-full items-center gap-2">
@ -44,7 +44,7 @@ export default function PendingSkillCall({
className={cn('size-4 shrink-0 text-text-secondary', !loaded && 'animate-pulse')}
aria-hidden="true"
/>
<span className={cn(!loaded && 'shimmer', 'font-medium')}>{text}</span>
<span className={cn(!loaded && 'shimmer', 'min-w-0 truncate font-medium')}>{text}</span>
</div>
</div>
</div>

View file

@ -92,7 +92,7 @@ export default function ReadFileCall({
return (
<>
<div className="relative my-1.5 flex size-5 shrink-0 items-center gap-2.5">
<div className="relative my-1.5 flex h-5 shrink-0 items-center gap-2.5">
<ProgressText
progress={progress}
onClick={toggleCode}

View file

@ -36,7 +36,7 @@ export default function SkillCall({
return (
<>
<div className="relative my-1.5 flex size-5 shrink-0 items-center gap-2.5">
<div className="relative my-1.5 flex h-5 shrink-0 items-center gap-2.5">
<ProgressText
progress={progress}
onClick={toggleCode}

View file

@ -489,7 +489,9 @@ export default function SubagentCall({
<Users size={14} />
)}
</div>
<span className="shrink-0">{headerText}</span>
<span className="min-w-0 truncate" title={headerText}>
{headerText}
</span>
{subagentNameLabel ? (
<span
className="min-w-0 flex-1 truncate font-normal text-text-secondary"

View file

@ -6,15 +6,18 @@ import { cn } from '~/utils';
const wrapperClass =
'progress-text-wrapper text-token-text-secondary relative -mt-[0.75px] h-5 w-full leading-5';
/** `max-w-full` caps the absolutely-positioned line at the message column;
* the label span truncates itself, so overflow stays visible for the
* button's focus ring. */
const contentClass =
'progress-text-content absolute left-0 top-0 max-w-full overflow-visible whitespace-nowrap';
const Wrapper = ({ popover, children }: { popover: boolean; children: React.ReactNode }) => {
if (popover) {
return (
<div className={wrapperClass}>
<Popover.Trigger asChild>
<div
className="progress-text-content absolute left-0 top-0 overflow-visible whitespace-nowrap"
style={{ opacity: 1, transform: 'none' }}
>
<div className={contentClass} style={{ opacity: 1, transform: 'none' }}>
{children}
</div>
</Popover.Trigger>
@ -24,10 +27,7 @@ const Wrapper = ({ popover, children }: { popover: boolean; children: React.Reac
return (
<div className={wrapperClass}>
<div
className="progress-text-content absolute left-0 top-0 overflow-visible whitespace-nowrap"
style={{ opacity: 1, transform: 'none' }}
>
<div className={contentClass} style={{ opacity: 1, transform: 'none' }}>
{children}
</div>
</div>
@ -98,10 +98,12 @@ export default function ProgressText({
aria-expanded={hasInput ? isExpanded : undefined}
>
{icon}
<span className={cn(showShimmer ? 'shimmer' : '', 'font-medium')}>{text}</span>
<span className={cn(showShimmer ? 'shimmer' : '', 'min-w-0 truncate font-medium')}>
{text}
</span>
{subtitle && <span className="font-normal text-text-secondary">{subtitle}</span>}
{errorSuffix && (
<span className="font-normal text-red-600 dark:text-red-400">— {errorSuffix}</span>
<span className="font-normal text-red-600 dark:text-red-400">· {errorSuffix}</span>
)}
{hasInput && (
<ChevronDown

View file

@ -376,10 +376,11 @@ export default function ToolCallGroup({
)}
<span
className={cn(
'tool-status-text font-medium',
'tool-status-text min-w-0 truncate font-medium',
activityFailed && 'text-amber-600 dark:text-amber-400',
)}
role="status"
title={groupLabel}
>
{groupLabel}
</span>
@ -387,7 +388,9 @@ export default function ToolCallGroup({
* questions) — every entry deduplicates to the same token, which
* adds noise without info. Mixed groups keep the summary. */}
{toolNameSummary && !allSubagents && !allAskQuestions && (
<span className="text-xs font-normal text-text-secondary">— {toolNameSummary}</span>
<span className="min-w-0 max-w-[40%] truncate text-xs font-normal text-text-secondary">
· {toolNameSummary}
</span>
)}
<ChevronDown
className={cn(

View file

@ -238,7 +238,7 @@ export default function WebSearch({
) : (
<Globe className="size-4 shrink-0 text-text-secondary" aria-hidden="true" />
)}
<span className="font-medium">{completedText}</span>
<span className="min-w-0 truncate font-medium">{completedText}</span>
{hasSourceData && (
<ChevronDown
className={cn(
@ -289,7 +289,7 @@ export default function WebSearch({
</span>
{showSources && <StackedFavicons sources={streamingSources} start={-5} />}
<Globe className="size-4 shrink-0 text-text-secondary" aria-hidden="true" />
<span className="tool-status-text shimmer font-medium text-text-secondary">
<span className="tool-status-text shimmer min-w-0 truncate font-medium text-text-secondary">
{progressText}
</span>
</div>

View file

@ -507,7 +507,7 @@ describe('ToolCallGroup image hoisting', () => {
],
});
expect(screen.getByText('— Code')).toBeInTheDocument();
expect(screen.getByText('· Code')).toBeInTheDocument();
expect(screen.queryByText(/Code, bash_tool/)).not.toBeInTheDocument();
expect(screen.getByTestId('stacked-icons')).toHaveAttribute(
'data-tool-names',
@ -530,7 +530,7 @@ describe('ToolCallGroup image hoisting', () => {
expect(screen.queryByText('Used 2 tools')).not.toBeInTheDocument();
expect(screen.getByTestId('question-icon')).toBeInTheDocument();
expect(screen.queryByTestId('stacked-icons')).not.toBeInTheDocument();
expect(screen.queryByText(/— ask_user_question/)).not.toBeInTheDocument();
expect(screen.queryByText(/· ask_user_question/)).not.toBeInTheDocument();
});
it('uses the present tense while a multi-question turn is still streaming', () => {