fix(ui): prevent empty tool-group border stubs after filtering (#21852)

Co-authored-by: jacob314 <jacob314@gmail.com>
This commit is contained in:
Aashir Javed
2026-03-10 12:09:07 -07:00
committed by GitHub
parent 2dd037682c
commit b00d7c88ad
2 changed files with 253 additions and 5 deletions

View File

@@ -69,6 +69,11 @@ describe('<ToolGroupMessage />', () => {
ui: { errorVerbosity: 'full' },
},
});
const lowVerbositySettings = createMockSettings({
merged: {
ui: { errorVerbosity: 'low' },
},
});
describe('Golden Snapshots', () => {
it('renders single successful tool call', async () => {
@@ -721,6 +726,245 @@ describe('<ToolGroupMessage />', () => {
expect(lastFrame({ allowEmpty: true })).toBe('');
unmount();
});
it('does not render a bottom-border fragment when all tools are filtered out', async () => {
const toolCalls = [
createToolCall({
callId: 'hidden-error-tool',
name: 'error-tool',
status: CoreToolCallStatus.Error,
resultDisplay: 'Hidden in low verbosity',
isClientInitiated: false,
}),
];
const item = createItem(toolCalls);
const { lastFrame, unmount, waitUntilReady } = renderWithProviders(
<ToolGroupMessage
{...baseProps}
item={item}
toolCalls={toolCalls}
borderTop={false}
borderBottom={true}
/>,
{
config: baseMockConfig,
settings: lowVerbositySettings,
},
);
await waitUntilReady();
expect(lastFrame({ allowEmpty: true })).toBe('');
unmount();
});
it('still renders explicit closing slices for split static/pending groups', async () => {
const toolCalls: IndividualToolCallDisplay[] = [];
const item = createItem(toolCalls);
const { lastFrame, unmount, waitUntilReady } = renderWithProviders(
<ToolGroupMessage
{...baseProps}
item={item}
toolCalls={toolCalls}
borderTop={false}
borderBottom={true}
/>,
{
config: baseMockConfig,
settings: fullVerbositySettings,
},
);
await waitUntilReady();
expect(lastFrame({ allowEmpty: true })).not.toBe('');
unmount();
});
it('does not render a border fragment when plan-mode tools are filtered out', async () => {
const toolCalls = [
createToolCall({
callId: 'plan-write',
name: WRITE_FILE_DISPLAY_NAME,
approvalMode: ApprovalMode.PLAN,
status: CoreToolCallStatus.Success,
resultDisplay: 'Plan file written',
}),
];
const item = createItem(toolCalls);
const { lastFrame, unmount, waitUntilReady } = renderWithProviders(
<ToolGroupMessage
{...baseProps}
item={item}
toolCalls={toolCalls}
borderTop={false}
borderBottom={true}
/>,
{
config: baseMockConfig,
settings: fullVerbositySettings,
},
);
await waitUntilReady();
expect(lastFrame({ allowEmpty: true })).toBe('');
unmount();
});
it('does not render a border fragment when only confirming tools are present', async () => {
const toolCalls = [
createToolCall({
callId: 'confirm-only',
status: CoreToolCallStatus.AwaitingApproval,
confirmationDetails: {
type: 'info',
title: 'Confirm',
prompt: 'Proceed?',
},
}),
];
const item = createItem(toolCalls);
const { lastFrame, unmount, waitUntilReady } = renderWithProviders(
<ToolGroupMessage
{...baseProps}
item={item}
toolCalls={toolCalls}
borderTop={false}
borderBottom={true}
/>,
{
config: baseMockConfig,
settings: fullVerbositySettings,
},
);
await waitUntilReady();
expect(lastFrame({ allowEmpty: true })).toBe('');
unmount();
});
it('does not leave a border stub when transitioning from visible to fully filtered tools', async () => {
const visibleTools = [
createToolCall({
callId: 'visible-success',
name: 'visible-tool',
status: CoreToolCallStatus.Success,
resultDisplay: 'visible output',
}),
];
const hiddenTools = [
createToolCall({
callId: 'hidden-error',
name: 'hidden-error-tool',
status: CoreToolCallStatus.Error,
resultDisplay: 'hidden output',
isClientInitiated: false,
}),
];
const initialItem = createItem(visibleTools);
const hiddenItem = createItem(hiddenTools);
const firstRender = renderWithProviders(
<ToolGroupMessage
{...baseProps}
item={initialItem}
toolCalls={visibleTools}
borderTop={false}
borderBottom={true}
/>,
{
config: baseMockConfig,
settings: lowVerbositySettings,
},
);
await firstRender.waitUntilReady();
expect(firstRender.lastFrame()).toContain('visible-tool');
firstRender.unmount();
const secondRender = renderWithProviders(
<ToolGroupMessage
{...baseProps}
item={hiddenItem}
toolCalls={hiddenTools}
borderTop={false}
borderBottom={true}
/>,
{
config: baseMockConfig,
settings: lowVerbositySettings,
},
);
await secondRender.waitUntilReady();
expect(secondRender.lastFrame({ allowEmpty: true })).toBe('');
secondRender.unmount();
});
it('keeps visible tools rendered with many filtered tools (stress case)', async () => {
const visibleTool = createToolCall({
callId: 'visible-tool',
name: 'visible-tool',
status: CoreToolCallStatus.Success,
resultDisplay: 'visible output',
});
const hiddenTools = Array.from({ length: 50 }, (_, index) =>
createToolCall({
callId: `hidden-${index}`,
name: `hidden-error-${index}`,
status: CoreToolCallStatus.Error,
resultDisplay: `hidden output ${index}`,
isClientInitiated: false,
}),
);
const toolCalls = [visibleTool, ...hiddenTools];
const item = createItem(toolCalls);
const { lastFrame, unmount, waitUntilReady } = renderWithProviders(
<ToolGroupMessage
{...baseProps}
item={item}
toolCalls={toolCalls}
borderTop={false}
borderBottom={true}
/>,
{
config: baseMockConfig,
settings: lowVerbositySettings,
},
);
await waitUntilReady();
const output = lastFrame();
expect(output).toContain('visible-tool');
expect(output).not.toContain('hidden-error-0');
expect(output).not.toContain('hidden-error-49');
unmount();
});
it('renders explicit closing slice even at very narrow terminal width', async () => {
const toolCalls: IndividualToolCallDisplay[] = [];
const item = createItem(toolCalls);
const { lastFrame, unmount, waitUntilReady } = renderWithProviders(
<ToolGroupMessage
item={item}
toolCalls={toolCalls}
terminalWidth={8}
borderTop={false}
borderBottom={true}
/>,
{
config: baseMockConfig,
settings: fullVerbositySettings,
},
);
await waitUntilReady();
expect(lastFrame({ allowEmpty: true })).not.toBe('');
unmount();
});
});
describe('Plan Mode Filtering', () => {

View File

@@ -141,11 +141,15 @@ export const ToolGroupMessage: React.FC<ToolGroupMessageProps> = ({
const contentWidth = terminalWidth - TOOL_MESSAGE_HORIZONTAL_MARGIN;
// If all tools are filtered out (e.g., in-progress AskUser tools, confirming tools),
// only render if we need to close a border from previous
// tool groups. borderBottomOverride=true means we must render the closing border;
// undefined or false means there's nothing to display.
if (visibleToolCalls.length === 0 && borderBottomOverride !== true) {
// If all tools are filtered out (e.g., in-progress AskUser tools, low-verbosity
// internal errors, plan-mode hidden write/edit), we should not emit standalone
// border fragments. The only case where an empty group should render is the
// explicit "closing slice" (tools: []) used to bridge static/pending sections.
const isExplicitClosingSlice = allToolCalls.length === 0;
if (
visibleToolCalls.length === 0 &&
(!isExplicitClosingSlice || borderBottomOverride !== true)
) {
return null;
}