Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
f9573d6
feat(cli): truncate shell command output in history
jwhelangoog Jan 23, 2026
bb4df34
feat(cli): refine shell output with scrollable viewport and optimized…
jwhelangoog Jan 24, 2026
9ebd7d0
test(cli): add comprehensive test coverage for output truncation and …
jwhelangoog Jan 24, 2026
298f990
fix(cli): optimize pruneShellOutput performance and trailing newline …
jwhelangoog Jan 24, 2026
fcbe4ee
test(cli): fix type errors in toolMapping and textUtils mocks
jwhelangoog Jan 24, 2026
7392cf0
Merge branch 'main' into feature/ux-dev/shell-output-truncation-from-…
jwhelangoog Jan 28, 2026
67ab5e0
Merge branch 'main' into feature/ux-dev/shell-output-truncation-from-…
jwhelangoog Jan 28, 2026
c4988a2
Merge branch 'upstream/main' into feature/ux-dev/shell-output-truncat…
jwhelangoog Feb 2, 2026
fabd425
feat(cli): increase shell history output to 25 lines
jwhelangoog Feb 3, 2026
b4df51d
refactor(cli): persist full shell output in history
jwhelangoog Feb 3, 2026
ef0f934
feat(cli): implement scrollable shell panes in alternate buffer
jwhelangoog Feb 3, 2026
a91ca89
fix(cli): enable Shift+Tab focus navigation from shell
jwhelangoog Feb 3, 2026
207cc12
feat(cli): centralize shell input keybindings configuration
jwhelangoog Feb 3, 2026
dc2add4
refactor(cli): implement centralized keybindings in ShellInputPrompt
jwhelangoog Feb 3, 2026
aa6a7bf
test(cli): improve coverage for toolMapping
jwhelangoog Feb 3, 2026
46c72bc
Merge remote-tracking branch 'upstream/main' into feature/ux-dev/shel…
jwhelangoog Feb 3, 2026
298076a
fix(cli): restore ctrl+o expansion for shell output
jwhelangoog Feb 3, 2026
7d99570
test(cli): update snapshots for tool group and sticky headers
jwhelangoog Feb 3, 2026
1dcb695
fix(cli): prevent extra blank lines in tool results by using maxHeight
jwhelangoog Feb 3, 2026
9e8581a
Merge remote-tracking branch 'upstream/main' into feature/ux-dev/shel…
jwhelangoog Feb 3, 2026
eaa1424
perf(cli): virtualize shell output in alternate buffer mode using Scr…
jwhelangoog Feb 3, 2026
14fb4a2
ui(cli): use overflow:hidden instead of ellipses for shell output tru…
jwhelangoog Feb 3, 2026
26f6131
refactor(cli): dynamically propagate focus to scrollable tool result …
jwhelangoog Feb 4, 2026
649025d
fix(cli): prevent shell output rendering loops and simplify ToolResul…
jwhelangoog Feb 4, 2026
58f0128
test(cli): use real Scrollable/AnsiOutput in ToolResultDisplay tests
jwhelangoog Feb 4, 2026
c36eff1
perf(cli): optimize ToolResultDisplay virtualization callbacks
jwhelangoog Feb 4, 2026
a83ac7a
fix(cli): use separate constants for active shell and history truncation
jwhelangoog Feb 4, 2026
6c70e39
Merge remote-tracking branch 'upstream/main' into feature/ux-dev/shel…
jwhelangoog Feb 4, 2026
0f508c3
feat(cli): constrain completed shell output to 25 lines in history
jwhelangoog Feb 5, 2026
2ba4dd9
fix(cli): when applicable, ensure tool result output can be scrolled …
jwhelangoog Feb 5, 2026
89c060c
style(cli): add horizontal margins to tool message boxes for improved…
jwhelangoog Feb 6, 2026
e356608
feat(cli): reduce shell output height and refine truncation
jwhelangoog Feb 6, 2026
ae8b93e
chore(test): clean up test infrastructure and remove excessive mocks
jwhelangoog Feb 6, 2026
77277f7
fix(cli): prevent shell output overflow by constraining height to ava…
jwhelangoog Feb 6, 2026
405b4f0
refactor(cli): enhance scrollable components and focus scoping
jwhelangoog Feb 6, 2026
c9fdaf8
refactor(cli): unify scrolling keybindings and sync documentation
jwhelangoog Feb 6, 2026
f662c7a
style(cli): adjust tool output margin to right-side only
jwhelangoog Feb 6, 2026
3d06430
refactor(cli): rename SHELL_LEAVE_FOCUS to UNFOCUS_SHELL
jwhelangoog Feb 7, 2026
57ce58c
refactor(cli): address PR feedback
jwhelangoog Feb 7, 2026
320c8fb
test(cli): simplify ShellToolMessage tests using renderShell helper a…
jwhelangoog Feb 7, 2026
6f15db5
Enable shell tool output expansion in Alternate Buffer mode
jwhelangoog Feb 8, 2026
d87fd62
Propagate available height to history items in Alternate Buffer mode
jwhelangoog Feb 8, 2026
70ae6fa
refactor(cli): handle scroll keypress boundaries in Scrollable
jwhelangoog Feb 8, 2026
8c62743
refactor(cli): unify shell unfocus commands into UNFOCUS_SHELL_INPUT
jwhelangoog Feb 8, 2026
5553e86
Merge remote-tracking branch 'upstream/main' into feature/ux-dev/shel…
jwhelangoog Feb 8, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 1 addition & 24 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

44 changes: 42 additions & 2 deletions packages/cli/src/ui/components/AnsiOutput.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,9 @@ describe('<AnsiOutputText />', () => {
const output = lastFrame();
expect(output).toBeDefined();
const lines = output!.split('\n');
expect(lines[0]).toBe('First line');
expect(lines[1]).toBe('Third line');
expect(lines[0].trim()).toBe('First line');
expect(lines[1].trim()).toBe('');
expect(lines[2].trim()).toBe('Third line');
});

it('respects the availableTerminalHeight prop and slices the lines correctly', () => {
Expand All @@ -89,6 +90,45 @@ describe('<AnsiOutputText />', () => {
expect(output).toContain('Line 4');
});

it('respects the maxLines prop and slices the lines correctly', () => {
const data: AnsiOutput = [
[createAnsiToken({ text: 'Line 1' })],
[createAnsiToken({ text: 'Line 2' })],
[createAnsiToken({ text: 'Line 3' })],
[createAnsiToken({ text: 'Line 4' })],
];
const { lastFrame } = render(
<AnsiOutputText data={data} maxLines={2} width={80} />,
);
const output = lastFrame();
expect(output).not.toContain('Line 1');
expect(output).not.toContain('Line 2');
expect(output).toContain('Line 3');
expect(output).toContain('Line 4');
});

it('prioritizes maxLines over availableTerminalHeight if maxLines is smaller', () => {
const data: AnsiOutput = [
[createAnsiToken({ text: 'Line 1' })],
[createAnsiToken({ text: 'Line 2' })],
[createAnsiToken({ text: 'Line 3' })],
[createAnsiToken({ text: 'Line 4' })],
];
// availableTerminalHeight=3, maxLines=2 => show 2 lines
const { lastFrame } = render(
<AnsiOutputText
data={data}
availableTerminalHeight={3}
maxLines={2}
width={80}
/>,
);
const output = lastFrame();
expect(output).not.toContain('Line 2');
expect(output).toContain('Line 3');
expect(output).toContain('Line 4');
});

it('renders a large AnsiOutput object without crashing', () => {
const largeData: AnsiOutput = [];
for (let i = 0; i < 1000; i++) {
Expand Down
62 changes: 39 additions & 23 deletions packages/cli/src/ui/components/AnsiOutput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,40 +14,56 @@ interface AnsiOutputProps {
data: AnsiOutput;
availableTerminalHeight?: number;
width: number;
maxLines?: number;
disableTruncation?: boolean;
}

export const AnsiOutputText: React.FC<AnsiOutputProps> = ({
data,
availableTerminalHeight,
width,
maxLines,
disableTruncation,
}) => {
const lastLines = data.slice(
-(availableTerminalHeight && availableTerminalHeight > 0
const availableHeightLimit =
availableTerminalHeight && availableTerminalHeight > 0
? availableTerminalHeight
: DEFAULT_HEIGHT),
);
: undefined;

const numLinesRetained =
availableHeightLimit !== undefined && maxLines !== undefined
? Math.min(availableHeightLimit, maxLines)
: (availableHeightLimit ?? maxLines ?? DEFAULT_HEIGHT);

const lastLines = disableTruncation ? data : data.slice(-numLinesRetained);
return (
<Box flexDirection="column" width={width} flexShrink={0}>
<Box flexDirection="column" width={width} flexShrink={0} overflow="hidden">
{lastLines.map((line: AnsiLine, lineIndex: number) => (
<Text key={lineIndex} wrap="truncate">
{line.length > 0
? line.map((token: AnsiToken, tokenIndex: number) => (
<Text
key={tokenIndex}
color={token.fg}
backgroundColor={token.bg}
inverse={token.inverse}
dimColor={token.dim}
bold={token.bold}
italic={token.italic}
underline={token.underline}
>
{token.text}
</Text>
))
: null}
</Text>
<Box key={lineIndex} height={1} overflow="hidden">
<AnsiLineText line={line} />
</Box>
))}
</Box>
);
};

export const AnsiLineText: React.FC<{ line: AnsiLine }> = ({ line }) => (
<Text>
{line.length > 0
? line.map((token: AnsiToken, tokenIndex: number) => (
<Text
key={tokenIndex}
color={token.fg}
backgroundColor={token.bg}
inverse={token.inverse}
dimColor={token.dim}
bold={token.bold}
italic={token.italic}
underline={token.underline}
>
{token.text}
</Text>
))
: null}
</Text>
);
Loading
Loading