diff --git a/packages/cli/src/ui/components/MainContent.test.tsx b/packages/cli/src/ui/components/MainContent.test.tsx
index 989a7cb866..7228e608f1 100644
--- a/packages/cli/src/ui/components/MainContent.test.tsx
+++ b/packages/cli/src/ui/components/MainContent.test.tsx
@@ -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(, {
+ uiState: uiStateWithBtw as Partial,
+ });
+ 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);
diff --git a/packages/cli/src/ui/components/__snapshots__/MainContent.test.tsx.snap b/packages/cli/src/ui/components/__snapshots__/MainContent.test.tsx.snap
index 7dab229ecd..097b3fc177 100644
--- a/packages/cli/src/ui/components/__snapshots__/MainContent.test.tsx.snap
+++ b/packages/cli/src/ui/components/__snapshots__/MainContent.test.tsx.snap
@@ -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)
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
diff --git a/packages/cli/src/ui/hooks/useBtw.test.ts b/packages/cli/src/ui/hooks/useBtw.test.ts
index 4ca69a0675..4af8495d51 100644
--- a/packages/cli/src/ui/hooks/useBtw.test.ts
+++ b/packages/cli/src/ui/hooks/useBtw.test.ts
@@ -83,8 +83,10 @@ describe('useBtw', () => {
useBtw(mockGeminiClient as unknown as GeminiClient),
);
+ let submitPromise!: Promise;
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;
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;
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;
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((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;
+ 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);
diff --git a/packages/cli/src/ui/hooks/useBtw.ts b/packages/cli/src/ui/hooks/useBtw.ts
index 4654ef86ca..a1d5ecb130 100644
--- a/packages/cli/src/ui/hooks/useBtw.ts
+++ b/packages/cli/src/ui/hooks/useBtw.ts
@@ -87,6 +87,7 @@ export const useBtw = (
abortControllerRef.current.abort();
abortControllerRef.current = null;
}
+ requestIdRef.current++;
dispatch({ type: 'DISMISS' });
}, []);