From 6ebe28453d8b9abb11f1b61689020caafae3014e Mon Sep 17 00:00:00 2001 From: Sharvil Saxena <97365286+sharziki@users.noreply.github.com> Date: Sun, 19 Apr 2026 02:59:20 -0400 Subject: [PATCH] fix(cli): /clear dismisses active /btw side-question dialog (#3431) The /clear command cleared the history log but left an active /btw side-question dialog visible in the fixed bottom area, because /btw stores state in dedicated btwItem state (via setBtwItem) rather than in history items. The ui.clear callback only called clearItems() and clearScreen(), never cancelBtw(), so the pending-btw dialog survived. Call cancelBtw() from ui.clear so /clear (and /reset, /new) abort any in-flight btw request and null out the btwItem state. Fixes #3334 Co-authored-by: Claude Opus 4.7 --- .../ui/hooks/slashCommandProcessor.test.ts | 50 +++++++++++++++++++ .../cli/src/ui/hooks/slashCommandProcessor.ts | 1 + 2 files changed, 51 insertions(+) diff --git a/packages/cli/src/ui/hooks/slashCommandProcessor.test.ts b/packages/cli/src/ui/hooks/slashCommandProcessor.test.ts index f9354c011..4b4d61d17 100644 --- a/packages/cli/src/ui/hooks/slashCommandProcessor.test.ts +++ b/packages/cli/src/ui/hooks/slashCommandProcessor.test.ts @@ -14,6 +14,7 @@ import type { } from '../commands/types.js'; import { CommandKind } from '../commands/types.js'; import type { LoadedSettings } from '../../config/settings.js'; +import type { HistoryItemBtw } from '../types.js'; import { MessageType } from '../types.js'; import { BuiltinCommandLoader } from '../../services/BuiltinCommandLoader.js'; import { FileCommandLoader } from '../../services/FileCommandLoader.js'; @@ -1124,4 +1125,53 @@ describe('useSlashCommandProcessor', () => { expect(logSlashCommand).not.toHaveBeenCalled(); }); }); + + describe('ui.clear and /btw dialog', () => { + it('should dismiss an active btw dialog when ui.clear is called', async () => { + const result = setupProcessorHook(); + await waitFor(() => expect(result.current.commandContext).toBeDefined()); + + const btwItem: HistoryItemBtw = { + type: MessageType.BTW, + btw: { question: 'why?', answer: '', isPending: true }, + }; + + act(() => { + result.current.commandContext.ui.setBtwItem(btwItem); + }); + await waitFor(() => { + expect(result.current.commandContext.ui.btwItem).toEqual(btwItem); + }); + + act(() => { + result.current.commandContext.ui.clear(); + }); + + await waitFor(() => { + expect(result.current.commandContext.ui.btwItem).toBeNull(); + }); + }); + + it('should abort the in-flight btw request when ui.clear is called', async () => { + const result = setupProcessorHook(); + await waitFor(() => expect(result.current.commandContext).toBeDefined()); + + const abortController = new AbortController(); + const abortSpy = vi.spyOn(abortController, 'abort'); + + act(() => { + result.current.commandContext.ui.btwAbortControllerRef.current = + abortController; + }); + + act(() => { + result.current.commandContext.ui.clear(); + }); + + expect(abortSpy).toHaveBeenCalledTimes(1); + expect( + result.current.commandContext.ui.btwAbortControllerRef.current, + ).toBeNull(); + }); + }); }); diff --git a/packages/cli/src/ui/hooks/slashCommandProcessor.ts b/packages/cli/src/ui/hooks/slashCommandProcessor.ts index c8dd9e580..d9f908295 100644 --- a/packages/cli/src/ui/hooks/slashCommandProcessor.ts +++ b/packages/cli/src/ui/hooks/slashCommandProcessor.ts @@ -259,6 +259,7 @@ export const useSlashCommandProcessor = ( ui: { addItem, clear: () => { + cancelBtw(); clearItems(); clearScreen(); refreshStatic();