fix(cli): resolve btw command truncation in alternate buffer mode

The btw command's response was previously being rendered outside the `ScrollableList` component in `MainContent.tsx`. This caused its output to be severely truncated when the response was lengthy and the user had alternate buffer mode enabled, as the root container restricts height strictly to the terminal lines.

This commit incorporates the btw output as a dynamic item inside the `virtualizedData` fed to `ScrollableList` when the alternate buffer is active. This ensures the output is scrollable and not arbitrarily cut off. It also patches `useBtw` to fix a React testing warning regarding `act(...)` updates and a bug where a dismissed btw query could overwrite state if a delayed API callback arrived after dismissal.
This commit is contained in:
Mahima Shanware
2026-03-31 17:21:30 +00:00
committed by Mahima Shanware
parent 19698ca4ac
commit ed4c17e03e
4 changed files with 75 additions and 8 deletions
@@ -393,6 +393,33 @@ describe('MainContent', () => {
unmount();
});
it('renders BtwDisplay within ScrollableList in alternate buffer mode when btw is active', async () => {
vi.mocked(useAlternateBuffer).mockReturnValue(true);
const uiStateWithBtw = {
...defaultMockUiState,
btwState: {
isActive: true,
query: 'test query',
response: 'test response',
isStreaming: false,
error: null,
},
};
const { lastFrame, unmount } = await renderWithProviders(<MainContent />, {
uiState: uiStateWithBtw as Partial<UIState>,
});
const output = lastFrame();
// Verify ScrollableList is rendered (from our mock)
expect(output).toContain('ScrollableList');
// Verify btw response is rendered
expect(output).toContain('test query');
expect(output).toContain('test response');
expect(output).toMatchSnapshot();
// Verify it rendered inside ScrollableList's items in the mock
unmount();
});
it('renders minimal header in minimal mode (alternate buffer)', async () => {
vi.mocked(useAlternateBuffer).mockReturnValue(true);
@@ -92,6 +92,23 @@ exports[`MainContent > MainContent Tool Output Height Logic > 'Normal mode - Unc
"
`;
exports[`MainContent > renders BtwDisplay within ScrollableList in alternate buffer mode when btw is active 1`] = `
"ScrollableList
AppHeader(full)
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
> Hello
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
✦ Hi there
╭──────────────────────────────────────────────────────────────────────────────────────────────────╮
│ BY THE WAY Press Esc, Enter or Space to dismiss │
│ │
│ Q: test query │
│ │
│ test response │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
"
`;
exports[`MainContent > renders a ToolConfirmationQueue without an extra line when preceded by hidden tools 1`] = `
"AppHeader(full)
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
+30 -8
View File
@@ -83,8 +83,10 @@ describe('useBtw', () => {
useBtw(mockGeminiClient as unknown as GeminiClient),
);
let submitPromise!: Promise<void>;
await act(async () => {
await result.current.submitBtw('test query');
submitPromise = result.current.submitBtw('test query');
await submitPromise;
});
expect(result.current.error).toBe('API Error');
@@ -104,8 +106,10 @@ describe('useBtw', () => {
useBtw(mockGeminiClient as unknown as GeminiClient),
);
let submitPromise!: Promise<void>;
await act(async () => {
await result.current.submitBtw('test query');
submitPromise = result.current.submitBtw('test query');
await submitPromise;
});
expect(result.current.error).toBe('Direct string error');
@@ -124,8 +128,10 @@ describe('useBtw', () => {
useBtw(mockGeminiClient as unknown as GeminiClient),
);
let submitPromise!: Promise<void>;
await act(async () => {
await result.current.submitBtw('test query');
submitPromise = result.current.submitBtw('test query');
await submitPromise;
});
expect(result.current.error).toBe('Unknown error');
@@ -144,18 +150,25 @@ describe('useBtw', () => {
useBtw(mockGeminiClient as unknown as GeminiClient),
);
let submitPromise!: Promise<void>;
await act(async () => {
await result.current.submitBtw('test query');
submitPromise = result.current.submitBtw('test query');
await submitPromise;
});
expect(result.current.error).toBe('Just some raw string value');
});
it('should reset state on dismiss', async () => {
let resolveStream: (value: void) => void;
const streamGate = new Promise<void>((resolve) => {
resolveStream = resolve;
});
const mockStream = (async function* () {
yield { type: GeminiEventType.Content, value: 'partial' };
// Hang
await new Promise(() => {});
await streamGate;
})();
mockGeminiClient.sendBtwStream.mockReturnValue(mockStream);
@@ -163,14 +176,23 @@ describe('useBtw', () => {
useBtw(mockGeminiClient as unknown as GeminiClient),
);
act(() => {
void result.current.submitBtw('test query');
let submitPromise!: Promise<void>;
await act(async () => {
submitPromise = result.current.submitBtw('test query');
});
expect(result.current.isActive).toBe(true);
expect(result.current.query).toBe('test query');
act(() => {
await act(async () => {
result.current.dismissBtw();
resolveStream();
// wait for the catch/finally blocks inside submitBtw to finish
try {
await submitPromise;
} catch (_e) {
// ignore AbortError
}
});
expect(result.current.isActive).toBe(false);
+1
View File
@@ -87,6 +87,7 @@ export const useBtw = (
abortControllerRef.current.abort();
abortControllerRef.current = null;
}
requestIdRef.current++;
dispatch({ type: 'DISMISS' });
}, []);