refactor: Update Card component to use a status prop and integrate ToolStatusIndicator.

This commit is contained in:
Mark McLaughlin
2026-02-05 15:37:24 -08:00
parent cc78073add
commit 1ecfcc963e
3 changed files with 52 additions and 69 deletions

View File

@@ -8,55 +8,55 @@ import { render } from '../../../test-utils/render.js';
import { Card } from './Card.js';
import { Text } from 'ink';
import { describe, it, expect } from 'vitest';
import { ToolCallStatus } from '../../types.js';
describe('Card', () => {
it.each([
{
variant: 'warning',
status: ToolCallStatus.Pending,
title: 'Gemini CLI update available',
suffix: '0.26.0 → 0.27.0',
prefix: true,
body: 'Installed via Homebrew. Please update with "brew upgrade gemini-cli".',
},
{
variant: 'information',
status: ToolCallStatus.Canceled,
title: 'Delegate to agent',
suffix: "Delegating to agent 'cli_help'",
prefix: true,
body: '🤖💭 Execution limit reached (ERROR_NO_COMPLETE_TASK_CALL). Attempting one final recovery turn with a grace period.',
},
{
variant: 'error',
status: ToolCallStatus.Error,
title: 'Error',
suffix: '429 You exceeded your current quota',
prefix: true,
body: 'Go to https://aistudio.google.com/apikey to upgrade your quota tier, or submit a quota increase request in https://ai.google.dev/gemini-api/docs/rate-limits',
},
{
variant: 'confirmation',
status: ToolCallStatus.Confirming,
title: 'Shell',
suffix: 'node -v && which gemini',
prefix: true,
body: "ls /usr/local/bin | grep 'xattr'",
},
{
variant: 'success',
status: ToolCallStatus.Success,
title: 'ReadFolder',
suffix: '/usr/local/bin',
prefix: true,
body: 'Listed 39 item(s).',
},
] as const)(
'renders a $variant card with prefix=$prefix',
({ variant, title, suffix, prefix, body }) => {
'renders a $status card with prefix=$prefix',
({ status, title, suffix, prefix, body }) => {
const { lastFrame } = render(
<Card variant={variant} title={title} suffix={suffix} prefix={prefix}>
<Card status={status} title={title} suffix={suffix} prefix={prefix}>
<Text>{body}</Text>
</Card>,
);
const output = lastFrame();
expect(output).toMatchSnapshot();
},
);

View File

@@ -7,7 +7,8 @@
import type React from 'react';
import { Box, Text } from 'ink';
import { theme } from '../../semantic-colors.js';
import { TOOL_STATUS } from '../../constants.js';
import { ToolStatusIndicator } from '../messages/ToolShared.js';
import { ToolCallStatus } from '../../types.js';
/**
* Props for the Card component.
@@ -22,55 +23,39 @@ export interface CardProps {
/** The content to be displayed inside the card. */
children?: React.ReactNode;
/** The styling and intent of the card. */
variant?: 'information' | 'success' | 'warning' | 'error' | 'confirmation';
status?: ToolCallStatus;
}
export const Card: React.FC<CardProps> = ({
variant = 'information',
status = ToolCallStatus.Pending,
title,
prefix = true,
suffix,
children,
}) => {
const getColors = () => {
switch (variant) {
case 'error':
return { border: theme.status.error, text: theme.status.error };
case 'warning':
return { border: theme.status.warning, text: theme.status.warning };
case 'success':
return { border: theme.status.success, text: theme.status.success };
case 'confirmation':
switch (status) {
case ToolCallStatus.Pending:
return { border: theme.border.default, text: theme.text.accent };
case ToolCallStatus.Confirming:
return { border: theme.border.focused, text: theme.text.link };
case 'information':
return { border: theme.border.default, text: theme.text.primary };
case ToolCallStatus.Error:
return { border: theme.status.error, text: theme.status.error };
case ToolCallStatus.Success:
return { border: theme.border.default, text: theme.status.success };
case ToolCallStatus.Canceled:
return { border: theme.status.warning, text: theme.status.warning };
case ToolCallStatus.Executing:
return { border: theme.border.default, text: theme.status.success };
default:
return { border: theme.border.default, text: theme.text.primary };
}
};
const getGlyph = () => {
switch (variant) {
case 'error':
return TOOL_STATUS.ERROR;
case 'success':
return TOOL_STATUS.SUCCESS;
case 'warning':
return TOOL_STATUS.WARNING;
case 'confirmation':
return TOOL_STATUS.CONFIRMING;
case 'information':
return TOOL_STATUS.INFORMATION;
default:
return TOOL_STATUS.INFORMATION;
}
};
const colors = getColors();
const glyph = getGlyph();
return (
<Box flexDirection="column" marginBottom={1}>
<Box flexDirection="column">
<Box width="100%" flexDirection="row">
{/* Top border section */}
<Box
@@ -88,7 +73,10 @@ export const Card: React.FC<CardProps> = ({
gap={1}
justifyContent="flex-start"
>
<Box>{prefix && <Text color={colors.text}>{glyph}</Text>}</Box>
{/* TODO: Use shared ToolStatusIndicator component */}
<Box marginRight={-2}>
{prefix && <ToolStatusIndicator status={status} name={title} />}
</Box>
<Text bold color={colors.text}>
{title}
</Text>

View File

@@ -1,38 +1,33 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`Card > renders a 'confirmation' card with prefix=true 1`] = `
"╭ ? Shell node -v && which gemini ─────────────────────────────────────────────────────────────────╮
│ ls /usr/local/bin | grep 'xattr' │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
"
exports[`Card > renders a 'Canceled' card with prefix=true 1`] = `
"╭ - Delegate to agent Delegating to agent 'cli_help' ──────────────────────────────────────────────╮
│ 🤖💭 Execution limit reached (ERROR_NO_COMPLETE_TASK_CALL). Attempting one final recovery turn │
│ with a grace period. │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯"
`;
exports[`Card > renders a 'error' card with prefix=true 1`] = `
"╭ x Error 429 You exceeded your current quota ─────────────────────────────────────────────────────╮
│ Go to https://aistudio.google.com/apikey to upgrade your quota tier, or submit a quota increase │
│ request in https://ai.google.dev/gemini-api/docs/rate-limits │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
"
exports[`Card > renders a 'Confirming' card with prefix=true 1`] = `
"╭ ? Shell node -v && which gemini ─────────────────────────────────────────────────────────────────╮
│ ls /usr/local/bin | grep 'xattr' │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯"
`;
exports[`Card > renders a 'information' card with prefix=true 1`] = `
" Delegate to agent Delegating to agent 'cli_help' ──────────────────────────────────────────────╮
│ 🤖💭 Execution limit reached (ERROR_NO_COMPLETE_TASK_CALL). Attempting one final recovery turn │
│ with a grace period.
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
"
exports[`Card > renders a 'Error' card with prefix=true 1`] = `
"╭ x Error 429 You exceeded your current quota ─────────────────────────────────────────────────────╮
│ Go to https://aistudio.google.com/apikey to upgrade your quota tier, or submit a quota increase │
│ request in https://ai.google.dev/gemini-api/docs/rate-limits │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯"
`;
exports[`Card > renders a 'success' card with prefix=true 1`] = `
"╭ ✓ ReadFolder /usr/local/bin ─────────────────────────────────────────────────────────────────────╮
│ Listed 39 item(s).
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
"
exports[`Card > renders a 'Pending' card with prefix=true 1`] = `
"╭ o Gemini CLI update available 0.26.0 → 0.27.0 ───────────────────────────────────────────────────╮
│ Installed via Homebrew. Please update with "brew upgrade gemini-cli". │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯"
`;
exports[`Card > renders a 'warning' card with prefix=true 1`] = `
"╭ ⚠ Gemini CLI update available 0.26.0 → 0.27.0 ───────────────────────────────────────────────────╮
│ Installed via Homebrew. Please update with "brew upgrade gemini-cli".
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
"
exports[`Card > renders a 'Success' card with prefix=true 1`] = `
"╭ ✓ ReadFolder /usr/local/bin ─────────────────────────────────────────────────────────────────────╮
│ Listed 39 item(s). │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯"
`;