diff --git a/packages/cli/src/ui/AppContainer.test.tsx b/packages/cli/src/ui/AppContainer.test.tsx
index ca2c44979..5003edb8b 100644
--- a/packages/cli/src/ui/AppContainer.test.tsx
+++ b/packages/cli/src/ui/AppContainer.test.tsx
@@ -3077,6 +3077,85 @@ describe('AppContainer State Management', () => {
expect(trigger.activeCount()).toBe(0);
});
});
+
+ describe('IDE mode rewind guard', () => {
+ it('shows info message instead of opening rewind selector when IDE mode is enabled', () => {
+ const mockAddItem = vi.fn();
+ mockedUseHistory.mockReturnValue({
+ history: [{ id: 1, type: 'user', text: 'hello' }],
+ addItem: mockAddItem,
+ updateItem: vi.fn(),
+ clearItems: vi.fn(),
+ loadHistory: vi.fn(),
+ truncateToItem: vi.fn(),
+ });
+ mockedUseGeminiStream.mockReturnValue({
+ streamingState: 'idle',
+ submitQuery: vi.fn(),
+ initError: null,
+ pendingHistoryItems: [],
+ thought: null,
+ cancelOngoingRequest: vi.fn(),
+ retryLastPrompt: vi.fn(),
+ });
+ vi.spyOn(mockConfig, 'getIdeMode').mockReturnValue(true);
+
+ render(
+ ,
+ );
+
+ capturedUIActions.openRewindSelector();
+
+ expect(mockAddItem).toHaveBeenCalledWith(
+ expect.objectContaining({
+ type: 'info',
+ text: expect.stringMatching(/rewind.*disabled.*IDE/i),
+ }),
+ expect.any(Number),
+ );
+ expect(capturedUIState.isRewindSelectorOpen).toBeFalsy();
+ });
+
+ it('opens rewind selector normally when IDE mode is disabled', () => {
+ const mockAddItemDisabled = vi.fn();
+ mockedUseHistory.mockReturnValue({
+ history: [{ id: 1, type: 'user', text: 'hello' }],
+ addItem: mockAddItemDisabled,
+ updateItem: vi.fn(),
+ clearItems: vi.fn(),
+ loadHistory: vi.fn(),
+ truncateToItem: vi.fn(),
+ });
+ mockedUseGeminiStream.mockReturnValue({
+ streamingState: 'idle',
+ submitQuery: vi.fn(),
+ initError: null,
+ pendingHistoryItems: [],
+ thought: null,
+ cancelOngoingRequest: vi.fn(),
+ retryLastPrompt: vi.fn(),
+ });
+ vi.spyOn(mockConfig, 'getIdeMode').mockReturnValue(false);
+
+ render(
+ ,
+ );
+
+ capturedUIActions.openRewindSelector();
+
+ expect(mockAddItemDisabled).not.toHaveBeenCalled();
+ });
+ });
});
describe('dedupeNewestFirst', () => {
diff --git a/packages/cli/src/ui/AppContainer.tsx b/packages/cli/src/ui/AppContainer.tsx
index 4e8a39120..0e87599d7 100644
--- a/packages/cli/src/ui/AppContainer.tsx
+++ b/packages/cli/src/ui/AppContainer.tsx
@@ -2160,14 +2160,25 @@ export const AppContainer = (props: AppContainerProps) => {
}, []);
// --- Rewind selector callbacks ---
+ // IDE guard here is NOT redundant with the keyboard handler guard (line ~2375):
+ // /rewind calls openRewindSelector directly, bypassing the keyboard handler.
const openRewindSelector = useCallback(() => {
if (streamingState !== StreamingState.Idle) return;
- if (config.getIdeMode()) return;
if (dialogsVisibleRef.current) return;
+ if (config.getIdeMode()) {
+ historyManager.addItem(
+ {
+ type: 'info',
+ text: 'Rewind is disabled in IDE mode.',
+ },
+ Date.now(),
+ );
+ return;
+ }
const hasUserTurns = historyManager.history.some((h) => h.type === 'user');
if (!hasUserTurns) return;
setIsRewindSelectorOpen(true);
- }, [streamingState, config, historyManager.history]);
+ }, [streamingState, config, historyManager]);
openRewindSelectorRef.current = openRewindSelector;
const closeRewindSelector = useCallback(() => {
@@ -2559,7 +2570,8 @@ export const AppContainer = (props: AppContainerProps) => {
// Input is empty and idle — double-ESC opens rewind selector
if (
streamingState === StreamingState.Idle &&
- !dialogsVisibleRef.current
+ !dialogsVisibleRef.current &&
+ !config.getIdeMode()
) {
if (escapeTimerRef.current) {
clearTimeout(escapeTimerRef.current);