Sticky headers where the top rounded border is sticky. (#12971)

This commit is contained in:
Jacob Richman
2025-11-12 17:01:16 -08:00
committed by GitHub
parent d26b828ab3
commit ee7065f665
13 changed files with 346 additions and 209 deletions

View File

@@ -19,6 +19,7 @@ import { calculateMainAreaWidth } from '../ui/utils/ui-sizing.js';
import { VimModeProvider } from '../ui/contexts/VimModeContext.js';
import { MouseProvider } from '../ui/contexts/MouseContext.js';
import { ScrollProvider } from '../ui/contexts/ScrollProvider.js';
import { StreamingContext } from '../ui/contexts/StreamingContext.js';
import { type Config } from '@google/gemini-cli-core';
@@ -69,6 +70,9 @@ const mockConfig = {
getTargetDir: () =>
'/Users/test/project/foo/bar/and/some/more/directories/to/make/it/long',
getDebugMode: () => false,
isTrustedFolder: () => true,
getIdeMode: () => false,
getEnableInteractiveShell: () => true,
};
const configProxy = new Proxy(mockConfig, {
@@ -177,20 +181,22 @@ export const renderWithProviders = (
<UIStateContext.Provider value={finalUiState}>
<VimModeProvider settings={finalSettings}>
<ShellFocusContext.Provider value={shellFocus}>
<KeypressProvider>
<MouseProvider mouseEventsEnabled={mouseEventsEnabled}>
<ScrollProvider>
<Box
width={terminalWidth}
flexShrink={0}
flexGrow={0}
flexDirection="column"
>
{component}
</Box>
</ScrollProvider>
</MouseProvider>
</KeypressProvider>
<StreamingContext.Provider value={finalUiState.streamingState}>
<KeypressProvider>
<MouseProvider mouseEventsEnabled={mouseEventsEnabled}>
<ScrollProvider>
<Box
width={terminalWidth}
flexShrink={0}
flexGrow={0}
flexDirection="column"
>
{component}
</Box>
</ScrollProvider>
</MouseProvider>
</KeypressProvider>
</StreamingContext.Provider>
</ShellFocusContext.Provider>
</VimModeProvider>
</UIStateContext.Provider>

View File

@@ -11,7 +11,6 @@ import type { HistoryItem, HistoryItemWithoutId } from '../types.js';
import { Text } from 'ink';
import { renderWithProviders } from '../../test-utils/render.js';
import type { Config } from '@google/gemini-cli-core';
import type { ToolMessageProps } from './messages/ToolMessage.js';
vi.mock('../contexts/AppContext.js', () => ({
useAppContext: () => ({
@@ -32,14 +31,6 @@ vi.mock('../GeminiRespondingSpinner.js', () => ({
GeminiRespondingSpinner: () => <Text>Spinner</Text>,
}));
vi.mock('./messages/ToolMessage.js', () => ({
ToolMessage: (props: ToolMessageProps) => (
<Text>
ToolMessage: {props.name} - {props.status}
</Text>
),
}));
const mockHistory: HistoryItem[] = [
{
id: 1,

View File

@@ -10,9 +10,14 @@ import { StickyHeader } from './StickyHeader.js';
import { renderWithProviders } from '../../test-utils/render.js';
describe('StickyHeader', () => {
it('renders children', () => {
it.each([true, false])('renders children with isFirst=%s', (isFirst) => {
const { lastFrame } = renderWithProviders(
<StickyHeader width={80}>
<StickyHeader
isFirst={isFirst}
width={80}
borderColor="green"
borderDimColor={false}
>
<Text>Hello Sticky</Text>
</StickyHeader>,
);

View File

@@ -11,11 +11,17 @@ import { theme } from '../semantic-colors.js';
export interface StickyHeaderProps {
children: React.ReactNode;
width: number;
isFirst: boolean;
borderColor: string;
borderDimColor: boolean;
}
export const StickyHeader: React.FC<StickyHeaderProps> = ({
children,
width,
isFirst,
borderColor,
borderDimColor,
}) => (
<Box
sticky
@@ -24,20 +30,43 @@ export const StickyHeader: React.FC<StickyHeaderProps> = ({
width={width}
stickyChildren={
<Box
borderStyle="single"
borderStyle="round"
flexDirection="column"
width={width}
opaque
borderColor={theme.ui.dark}
borderTop={false}
borderLeft={false}
borderRight={false}
paddingX={1}
borderColor={borderColor}
borderDimColor={borderDimColor}
borderBottom={false}
borderTop={isFirst}
paddingTop={isFirst ? 0 : 1}
>
{children}
<Box paddingX={1}>{children}</Box>
{/* Dark border to separate header from content. */}
<Box
width={width - 2}
borderColor={theme.ui.dark}
borderStyle="single"
borderTop={false}
borderBottom={true}
borderLeft={false}
borderRight={false}
></Box>
</Box>
}
>
<Box paddingX={1} width={width}>
<Box
borderStyle="round"
width={width}
borderColor={borderColor}
borderDimColor={borderDimColor}
borderBottom={false}
borderTop={isFirst}
borderLeft={true}
borderRight={true}
paddingX={1}
paddingBottom={1}
paddingTop={isFirst ? 0 : 1}
>
{children}
</Box>
</Box>

View File

@@ -17,12 +17,15 @@ Tips for getting started:
3. Create GEMINI.md files to customize your interactions with Gemini.
4. /help for more information.
╭──────────────────────────────────────────────────────────────────────────────╮
ToolMessage: tool1 - Success
✓ tool1 Description for tool 1
│ │
╰──────────────────────────────────────────────────────────────────────────────╯
╭──────────────────────────────────────────────────────────────────────────────╮
ToolMessage: tool2 - Success
✓ tool2 Description for tool 2
│ │
╰──────────────────────────────────────────────────────────────────────────────╯
╭──────────────────────────────────────────────────────────────────────────────╮
ToolMessage: tool3 - Pending
o tool3 Description for tool 3
│ │
╰──────────────────────────────────────────────────────────────────────────────╯"
`;

View File

@@ -41,7 +41,6 @@ export const ToolConfirmationMessage: React.FC<
terminalWidth,
}) => {
const { onConfirm } = confirmationDetails;
const childWidth = terminalWidth - 2; // 2 for padding
const isAlternateBuffer = useAlternateBuffer();
@@ -249,21 +248,15 @@ export const ToolConfirmationMessage: React.FC<
</Box>
);
bodyContent = (
<Box flexDirection="column">
<Box paddingX={1}>
{isAlternateBuffer ? (
commandBox
) : (
<MaxSizedBox
maxHeight={bodyContentHeight}
maxWidth={Math.max(childWidth, 1)}
>
{commandBox}
</MaxSizedBox>
)}
</Box>
</Box>
bodyContent = isAlternateBuffer ? (
commandBox
) : (
<MaxSizedBox
maxHeight={bodyContentHeight}
maxWidth={Math.max(terminalWidth, 1)}
>
{commandBox}
</MaxSizedBox>
);
} else if (confirmationDetails.type === 'info') {
const infoProps = confirmationDetails;
@@ -274,7 +267,7 @@ export const ToolConfirmationMessage: React.FC<
);
bodyContent = (
<Box flexDirection="column" paddingX={1}>
<Box flexDirection="column">
<Text color={theme.text.link}>
<RenderInline
text={infoProps.prompt}
@@ -299,7 +292,7 @@ export const ToolConfirmationMessage: React.FC<
const mcpProps = confirmationDetails as ToolMcpConfirmationDetails;
bodyContent = (
<Box flexDirection="column" paddingX={1}>
<Box flexDirection="column">
<Text color={theme.text.link}>MCP Server: {mcpProps.serverName}</Text>
<Text color={theme.text.link}>Tool: {mcpProps.toolName}</Text>
</Box>
@@ -315,7 +308,6 @@ export const ToolConfirmationMessage: React.FC<
availableTerminalHeight,
terminalWidth,
isAlternateBuffer,
childWidth,
]);
if (confirmationDetails.type === 'edit') {
@@ -326,7 +318,8 @@ export const ToolConfirmationMessage: React.FC<
borderStyle="round"
borderColor={theme.border.default}
justifyContent="space-around"
padding={1}
paddingTop={1}
paddingBottom={1}
overflow="hidden"
>
<Text color={theme.text.primary}>Modify in progress: </Text>
@@ -342,23 +335,17 @@ export const ToolConfirmationMessage: React.FC<
<Box flexDirection="column" paddingTop={0} paddingBottom={1}>
{/* Body Content (Diff Renderer or Command Info) */}
{/* No separate context display here anymore for edits */}
<Box
flexGrow={1}
flexShrink={1}
overflow="hidden"
marginBottom={1}
paddingLeft={1}
>
<Box flexGrow={1} flexShrink={1} overflow="hidden" marginBottom={1}>
{bodyContent}
</Box>
{/* Confirmation Question */}
<Box marginBottom={1} flexShrink={0} paddingX={1}>
<Box marginBottom={1} flexShrink={0}>
<Text color={theme.text.primary}>{question}</Text>
</Box>
{/* Select Input for Options */}
<Box flexShrink={0} paddingX={1}>
<Box flexShrink={0}>
<RadioButtonSelect
items={options}
onSelect={handleSelect}

View File

@@ -6,59 +6,10 @@
import { renderWithProviders } from '../../../test-utils/render.js';
import { describe, it, expect, vi } from 'vitest';
import { Text } from 'ink';
import { ToolGroupMessage } from './ToolGroupMessage.js';
import type { IndividualToolCallDisplay } from '../../types.js';
import { ToolCallStatus } from '../../types.js';
import type { ToolCallConfirmationDetails } from '@google/gemini-cli-core';
import { TOOL_STATUS } from '../../constants.js';
// Mock child components to isolate ToolGroupMessage behavior
vi.mock('./ToolMessage.js', () => ({
ToolMessage: function MockToolMessage({
callId,
name,
description,
status,
emphasis,
}: {
callId: string;
name: string;
description: string;
status: ToolCallStatus;
emphasis: string;
}) {
// Use the same constants as the real component
const statusSymbolMap: Record<ToolCallStatus, string> = {
[ToolCallStatus.Success]: TOOL_STATUS.SUCCESS,
[ToolCallStatus.Pending]: TOOL_STATUS.PENDING,
[ToolCallStatus.Executing]: TOOL_STATUS.EXECUTING,
[ToolCallStatus.Confirming]: TOOL_STATUS.CONFIRMING,
[ToolCallStatus.Canceled]: TOOL_STATUS.CANCELED,
[ToolCallStatus.Error]: TOOL_STATUS.ERROR,
};
const statusSymbol = statusSymbolMap[status] || '?';
return (
<Text>
MockTool[{callId}]: {statusSymbol} {name} - {description} ({emphasis})
</Text>
);
},
}));
vi.mock('./ToolConfirmationMessage.js', () => ({
ToolConfirmationMessage: function MockToolConfirmationMessage({
confirmationDetails,
}: {
confirmationDetails: ToolCallConfirmationDetails;
}) {
const displayText =
confirmationDetails?.type === 'info'
? (confirmationDetails as { prompt: string }).prompt
: confirmationDetails?.title || 'confirm';
return <Text>MockConfirmation: {displayText}</Text>;
},
}));
import { Scrollable } from '../shared/Scrollable.js';
describe('<ToolGroupMessage />', () => {
const createToolCall = (
@@ -250,6 +201,45 @@ describe('<ToolGroupMessage />', () => {
expect(lastFrame()).toMatchSnapshot();
unmount();
});
it('renders sticky header when scrolled', () => {
const toolCalls = [
createToolCall({
callId: '1',
name: 'tool-1',
description: 'Description 1\n'.repeat(5),
}),
createToolCall({
callId: '2',
name: 'tool-2',
description: 'Description 2\n'.repeat(5),
}),
];
const { lastFrame, unmount } = renderWithProviders(
<Scrollable height={10} hasFocus={true} scrollToBottom={true}>
<ToolGroupMessage {...baseProps} toolCalls={toolCalls} />
</Scrollable>,
);
expect(lastFrame()).toMatchSnapshot();
unmount();
});
it('renders tool call with outputFile', () => {
const toolCalls = [
createToolCall({
callId: 'tool-output-file',
name: 'tool-with-file',
description: 'Tool that saved output to file',
status: ToolCallStatus.Success,
outputFile: '/path/to/output.txt',
}),
];
const { lastFrame, unmount } = renderWithProviders(
<ToolGroupMessage {...baseProps} toolCalls={toolCalls} />,
);
expect(lastFrame()).toMatchSnapshot();
unmount();
});
});
describe('Border Color Logic', () => {

View File

@@ -14,7 +14,6 @@ import { ToolConfirmationMessage } from './ToolConfirmationMessage.js';
import { theme } from '../../semantic-colors.js';
import { SHELL_COMMAND_NAME, SHELL_NAME } from '../../constants.js';
import { useConfig } from '../../contexts/ConfigContext.js';
import { useAlternateBuffer } from '../../hooks/useAlternateBuffer.js';
interface ToolGroupMessageProps {
groupId: number;
@@ -48,7 +47,6 @@ export const ToolGroupMessage: React.FC<ToolGroupMessageProps> = ({
);
const config = useConfig();
const isAlternateBuffer = useAlternateBuffer();
const isShellCommand = toolCalls.some(
(t) => t.name === SHELL_COMMAND_NAME || t.name === SHELL_NAME,
);
@@ -59,10 +57,10 @@ export const ToolGroupMessage: React.FC<ToolGroupMessageProps> = ({
? theme.status.warning
: theme.border.default;
const borderDimColor =
hasPending && (!isShellCommand || !isEmbeddedShellFocused);
const staticHeight = /* border */ 2 + /* marginBottom */ 1;
// This is a bit of a magic number, but it accounts for the border and
// marginLeft in regular mode and just the border in alternate buffer mode.
const innerWidth = isAlternateBuffer ? terminalWidth - 3 : terminalWidth - 4;
// only prompt for tool approval on the first 'confirming' tool in the list
// note, after the CTA, this automatically moves over to the next 'confirming' tool
@@ -89,9 +87,11 @@ export const ToolGroupMessage: React.FC<ToolGroupMessageProps> = ({
: undefined;
return (
// This box doesn't have a border even though it conceptually does because
// we need to allow the sticky headers to render the borders themselves so
// that the top border can be sticky.
<Box
flexDirection="column"
borderStyle="round"
/*
This width constraint is highly important and protects us from an Ink rendering bug.
Since the ToolGroup can typically change rendering states frequently, it can cause
@@ -99,52 +99,65 @@ export const ToolGroupMessage: React.FC<ToolGroupMessageProps> = ({
cause tearing.
*/
width={terminalWidth}
borderDimColor={
hasPending && (!isShellCommand || !isEmbeddedShellFocused)
}
borderColor={borderColor}
gap={1}
>
{toolCalls.map((tool) => {
{toolCalls.map((tool, index) => {
const isConfirming = toolAwaitingApproval?.callId === tool.callId;
const isFirst = index === 0;
const isLast = index === toolCalls.length - 1;
return (
<Box
key={tool.callId}
flexDirection="column"
minHeight={1}
width={innerWidth}
width={terminalWidth}
>
<ToolMessage
{...tool}
availableTerminalHeight={availableTerminalHeightPerToolMessage}
terminalWidth={innerWidth}
terminalWidth={terminalWidth}
emphasis={
isConfirming ? 'high' : toolAwaitingApproval ? 'low' : 'medium'
}
activeShellPtyId={activeShellPtyId}
embeddedShellFocused={embeddedShellFocused}
config={config}
isFirst={isFirst}
borderColor={borderColor}
borderDimColor={borderDimColor}
/>
{tool.status === ToolCallStatus.Confirming &&
isConfirming &&
tool.confirmationDetails && (
<ToolConfirmationMessage
confirmationDetails={tool.confirmationDetails}
config={config}
isFocused={isFocused}
availableTerminalHeight={
availableTerminalHeightPerToolMessage
}
terminalWidth={innerWidth}
/>
<Box
borderLeft={true}
borderRight={true}
borderTop={false}
borderBottom={isLast}
borderColor={borderColor}
borderDimColor={borderDimColor}
flexDirection="column"
borderStyle="round"
paddingLeft={1}
paddingRight={1}
>
{tool.status === ToolCallStatus.Confirming &&
isConfirming &&
tool.confirmationDetails && (
<ToolConfirmationMessage
confirmationDetails={tool.confirmationDetails}
config={config}
isFocused={isFocused}
availableTerminalHeight={
availableTerminalHeightPerToolMessage
}
terminalWidth={terminalWidth - 4}
/>
)}
{tool.outputFile && (
<Box>
<Text color={theme.text.primary}>
Output too long and was saved to: {tool.outputFile}
</Text>
</Box>
)}
{tool.outputFile && (
<Box marginX={1}>
<Text color={theme.text.primary}>
Output too long and was saved to: {tool.outputFile}
</Text>
</Box>
)}
</Box>
</Box>
);
})}

View File

@@ -89,6 +89,9 @@ describe('<ToolMessage />', () => {
terminalWidth: 80,
confirmationDetails: undefined,
emphasis: 'medium',
isFirst: true,
borderColor: 'green',
borderDimColor: false,
};
it('renders basic tool information', () => {

View File

@@ -42,6 +42,9 @@ export interface ToolMessageProps extends IndividualToolCallDisplay {
renderOutputAsMarkdown?: boolean;
activeShellPtyId?: number | null;
embeddedShellFocused?: boolean;
isFirst: boolean;
borderColor: string;
borderDimColor: boolean;
config?: Config;
}
@@ -58,6 +61,9 @@ export const ToolMessage: React.FC<ToolMessageProps> = ({
embeddedShellFocused,
ptyId,
config,
isFirst,
borderColor,
borderDimColor,
}) => {
const { renderMarkdown } = useUIState();
const isAlternateBuffer = useAlternateBuffer();
@@ -116,7 +122,8 @@ export const ToolMessage: React.FC<ToolMessageProps> = ({
if (availableHeight && !isAlternateBuffer) {
renderOutputAsMarkdown = false;
}
const childWidth = terminalWidth;
const combinedPaddingAndBorderWidth = 4;
const childWidth = terminalWidth - combinedPaddingAndBorderWidth;
const truncatedResultDisplay = React.useMemo(() => {
if (typeof resultDisplay === 'string') {
@@ -131,7 +138,7 @@ export const ToolMessage: React.FC<ToolMessageProps> = ({
if (!truncatedResultDisplay) return null;
return (
<Box width={terminalWidth} flexDirection="column" paddingLeft={1}>
<Box width={childWidth} flexDirection="column">
<Box flexDirection="column">
{typeof truncatedResultDisplay === 'string' &&
renderOutputAsMarkdown ? (
@@ -189,15 +196,16 @@ export const ToolMessage: React.FC<ToolMessageProps> = ({
renderMarkdown,
isAlternateBuffer,
availableHeight,
terminalWidth,
]);
return (
// We have the StickyHeader intentionally exceedsthe allowed width for this
// component by 1 so tne horizontal line it renders can extend into the 1
// pixel of padding of the box drawn by the parent of the ToolMessage.
<>
<StickyHeader width={terminalWidth + 1}>
<StickyHeader
width={terminalWidth}
isFirst={isFirst}
borderColor={borderColor}
borderDimColor={borderDimColor}
>
<ToolStatusIndicator status={status} name={name} />
<ToolInfo
name={name}
@@ -214,15 +222,28 @@ export const ToolMessage: React.FC<ToolMessageProps> = ({
)}
{emphasis === 'high' && <TrailingIndicator />}
</StickyHeader>
{renderedResult}
{isThisShellFocused && config && (
<Box paddingLeft={STATUS_INDICATOR_WIDTH} marginTop={1}>
<ShellInputPrompt
activeShellPtyId={activeShellPtyId ?? null}
focus={embeddedShellFocused}
/>
</Box>
)}
<Box
width={terminalWidth}
borderStyle="round"
borderColor={borderColor}
borderDimColor={borderDimColor}
borderTop={false}
borderBottom={false}
borderLeft={true}
borderRight={true}
paddingX={1}
flexDirection="column"
>
{renderedResult}
{isThisShellFocused && config && (
<Box paddingLeft={STATUS_INDICATOR_WIDTH} marginTop={1}>
<ShellInputPrompt
activeShellPtyId={activeShellPtyId ?? null}
focus={embeddedShellFocused}
/>
</Box>
)}
</Box>
</>
);
};

View File

@@ -20,6 +20,9 @@ describe('<ToolMessage /> - Raw Markdown Display Snapshots', () => {
terminalWidth: 80,
confirmationDetails: undefined,
emphasis: 'medium',
isFirst: true,
borderColor: 'green',
borderDimColor: false,
};
it.each([

View File

@@ -2,108 +2,182 @@
exports[`<ToolGroupMessage /> > Border Color Logic > uses gray border when all tools are successful and no shell commands 1`] = `
"╭──────────────────────────────────────────────────────────────────────────────╮
MockTool[tool-123]: ✓ test-tool - A tool for testing (medium)
│ ✓ test-tool A tool for testing
│ │
MockTool[tool-2]: ✓ another-tool - A tool for testing (medium)
Test result
│ │
│ ✓ another-tool A tool for testing │
│ │
│ Test result │
╰──────────────────────────────────────────────────────────────────────────────╯"
`;
exports[`<ToolGroupMessage /> > Border Color Logic > uses yellow border for shell commands even when successful 1`] = `
"╭──────────────────────────────────────────────────────────────────────────────╮
MockTool[tool-123]: ✓ run_shell_command - A tool for testing (medium)
│ ✓ run_shell_command A tool for testing
│ │
│ Test result │
╰──────────────────────────────────────────────────────────────────────────────╯"
`;
exports[`<ToolGroupMessage /> > Border Color Logic > uses yellow border when tools are pending 1`] = `
"╭──────────────────────────────────────────────────────────────────────────────╮
MockTool[tool-123]: o test-tool - A tool for testing (medium)
│ o test-tool A tool for testing
│ │
│ Test result │
╰──────────────────────────────────────────────────────────────────────────────╯"
`;
exports[`<ToolGroupMessage /> > Confirmation Handling > shows confirmation dialog for first confirming tool only 1`] = `
"╭──────────────────────────────────────────────────────────────────────────────╮
MockTool[tool-1]: ? first-confirm - A tool for testing (high)
│MockConfirmation: Confirm first tool │
│ ? first-confirm A tool for testing
│ │
MockTool[tool-2]: ? second-confirm - A tool for testing (low)
Test result
│ Confirm first tool │
│ │
│ Do you want to proceed? │
│ │
│ ● 1. Yes, allow once │
│ 2. Yes, allow always │
│ 3. No, suggest changes (esc) │
│ │
│ │
│ ? second-confirm A tool for testing │
│ │
│ Test result │
╰──────────────────────────────────────────────────────────────────────────────╯"
`;
exports[`<ToolGroupMessage /> > Golden Snapshots > renders empty tool calls array 1`] = `
"╭──────────────────────────────────────────────────────────────────────────────╮
╰──────────────────────────────────────────────────────────────────────────────╯"
`;
exports[`<ToolGroupMessage /> > Golden Snapshots > renders empty tool calls array 1`] = `""`;
exports[`<ToolGroupMessage /> > Golden Snapshots > renders mixed tool calls including shell command 1`] = `
"╭──────────────────────────────────────────────────────────────────────────────╮
MockTool[tool-1]: ✓ read_file - Read a file (medium)
│ ✓ read_file Read a file
│ │
MockTool[tool-2]: ⊷ run_shell_command - Run command (medium)
Test result
│ │
MockTool[tool-3]: o write_file - Write to file (medium)
⊷ run_shell_command Run command
│ │
│ Test result │
│ │
│ o write_file Write to file │
│ │
│ Test result │
╰──────────────────────────────────────────────────────────────────────────────╯"
`;
exports[`<ToolGroupMessage /> > Golden Snapshots > renders multiple tool calls with different statuses 1`] = `
"╭──────────────────────────────────────────────────────────────────────────────╮
MockTool[tool-1]: ✓ successful-tool - This tool succeeded (medium)
│ ✓ successful-tool This tool succeeded
│ │
MockTool[tool-2]: o pending-tool - This tool is pending (medium)
Test result
│ │
MockTool[tool-3]: x error-tool - This tool failed (medium)
o pending-tool This tool is pending
│ │
│ Test result │
│ │
│ x error-tool This tool failed │
│ │
│ Test result │
╰──────────────────────────────────────────────────────────────────────────────╯"
`;
exports[`<ToolGroupMessage /> > Golden Snapshots > renders shell command with yellow border 1`] = `
"╭──────────────────────────────────────────────────────────────────────────────╮
MockTool[shell-1]: ✓ run_shell_command - Execute shell command (medium)
│ ✓ run_shell_command Execute shell command
│ │
│ Test result │
╰──────────────────────────────────────────────────────────────────────────────╯"
`;
exports[`<ToolGroupMessage /> > Golden Snapshots > renders single successful tool call 1`] = `
"╭──────────────────────────────────────────────────────────────────────────────╮
MockTool[tool-123]: ✓ test-tool - A tool for testing (medium)
│ ✓ test-tool A tool for testing
│ │
│ Test result │
╰──────────────────────────────────────────────────────────────────────────────╯"
`;
exports[`<ToolGroupMessage /> > Golden Snapshots > renders sticky header when scrolled 1`] = `
"│ │
│ ✓ tool-2 Description 2 │
│ Description 2 │
│ Description 2 │
│ Description 2 │ ▄
│ Description 2 │ █
│ │ █
│ │ █
│ Test result │ █
╰──────────────────────────────────────────────────────────────────────────────╯ █"
`;
exports[`<ToolGroupMessage /> > Golden Snapshots > renders tool call awaiting confirmation 1`] = `
"╭──────────────────────────────────────────────────────────────────────────────╮
MockTool[tool-confirm]: ? confirmation-tool - This tool needs confirmation │
(high)
MockConfirmation: Are you sure you want to proceed?
│ ? confirmation-tool This tool needs confirmation
Test result
│ Are you sure you want to proceed? │
│ │
│ Do you want to proceed? │
│ │
│ ● 1. Yes, allow once │
│ 2. Yes, allow always │
│ 3. No, suggest changes (esc) │
│ │
╰──────────────────────────────────────────────────────────────────────────────╯"
`;
exports[`<ToolGroupMessage /> > Golden Snapshots > renders tool call with outputFile 1`] = `
"╭──────────────────────────────────────────────────────────────────────────────╮
│ ✓ tool-with-file Tool that saved output to file │
│ │
│ Test result │
│ Output too long and was saved to: /path/to/output.txt │
╰──────────────────────────────────────────────────────────────────────────────╯"
`;
exports[`<ToolGroupMessage /> > Golden Snapshots > renders when not focused 1`] = `
"╭──────────────────────────────────────────────────────────────────────────────╮
MockTool[tool-123]: ✓ test-tool - A tool for testing (medium)
│ ✓ test-tool A tool for testing
│ │
│ Test result │
╰──────────────────────────────────────────────────────────────────────────────╯"
`;
exports[`<ToolGroupMessage /> > Golden Snapshots > renders with limited terminal height 1`] = `
"╭──────────────────────────────────────────────────────────────────────────────╮
MockTool[tool-1]: ✓ tool-with-result - Tool with output (medium)
│ ✓ tool-with-result Tool with output
│ │
MockTool[tool-2]: ✓ another-tool - Another tool (medium)
This is a long result that might need height constraints
│ │
│ ✓ another-tool Another tool │
│ │
│ More output here │
╰──────────────────────────────────────────────────────────────────────────────╯"
`;
exports[`<ToolGroupMessage /> > Golden Snapshots > renders with narrow terminal width 1`] = `
"╭──────────────────────────────────────╮
MockTool[tool-123]: ✓
│very-long-tool-name-that-might-wrap
- This is a very long description
that might cause wrapping issues │
(medium)
✓ very-long-tool-name-that-might-wr
ap This is a very long
description that might cause
wrapping issues
│ Test result │
╰──────────────────────────────────────╯"
`;
exports[`<ToolGroupMessage /> > Height Calculation > calculates available height correctly with multiple tools with results 1`] = `
"╭──────────────────────────────────────────────────────────────────────────────╮
MockTool[tool-1]: ✓ test-tool - A tool for testing (medium)
│ ✓ test-tool A tool for testing
│ │
MockTool[tool-2]: ✓ test-tool - A tool for testing (medium)
Result 1
│ │
│ ✓ test-tool A tool for testing │
│ │
│ Result 2 │
│ │
│ ✓ test-tool A tool for testing │
│ │
│MockTool[tool-3]: ✓ test-tool - A tool for testing (medium) │
╰──────────────────────────────────────────────────────────────────────────────╯"
`;

View File

@@ -1,31 +1,43 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`<ToolMessage /> - Raw Markdown Display Snapshots > renders with renderMarkdown=false, useAlternateBuffer=false '(raw markdown, regular buffer)' 1`] = `
" ✓ test-tool A tool for testing
Test **bold** and \`code\` markdown"
"╭──────────────────────────────────────────────────────────────────────────────╮
│ ✓ test-tool A tool for testing │
│ │
│ Test **bold** and \`code\` markdown │"
`;
exports[`<ToolMessage /> - Raw Markdown Display Snapshots > renders with renderMarkdown=false, useAlternateBuffer=true '(raw markdown, alternate buffer)' 1`] = `
" ✓ test-tool A tool for testing
Test **bold** and \`code\` markdown"
"╭──────────────────────────────────────────────────────────────────────────────╮
│ ✓ test-tool A tool for testing │
│ │
│ Test **bold** and \`code\` markdown │"
`;
exports[`<ToolMessage /> - Raw Markdown Display Snapshots > renders with renderMarkdown=true, useAlternateBuffer=false '(constrained height, regular buffer -…' 1`] = `
" ✓ test-tool A tool for testing
Test **bold** and \`code\` markdown"
"╭──────────────────────────────────────────────────────────────────────────────╮
│ ✓ test-tool A tool for testing │
│ │
│ Test **bold** and \`code\` markdown │"
`;
exports[`<ToolMessage /> - Raw Markdown Display Snapshots > renders with renderMarkdown=true, useAlternateBuffer=false '(default, regular buffer)' 1`] = `
" ✓ test-tool A tool for testing
Test bold and code markdown"
"╭──────────────────────────────────────────────────────────────────────────────╮
│ ✓ test-tool A tool for testing │
│ │
│ Test bold and code markdown │"
`;
exports[`<ToolMessage /> - Raw Markdown Display Snapshots > renders with renderMarkdown=true, useAlternateBuffer=true '(constrained height, alternate buffer…' 1`] = `
" ✓ test-tool A tool for testing
Test bold and code markdown"
"╭──────────────────────────────────────────────────────────────────────────────╮
│ ✓ test-tool A tool for testing │
│ │
│ Test bold and code markdown │"
`;
exports[`<ToolMessage /> - Raw Markdown Display Snapshots > renders with renderMarkdown=true, useAlternateBuffer=true '(default, alternate buffer)' 1`] = `
" ✓ test-tool A tool for testing
Test bold and code markdown"
"╭──────────────────────────────────────────────────────────────────────────────╮
│ ✓ test-tool A tool for testing │
│ │
│ Test bold and code markdown │"
`;