Skip to content

Commit 2dfb813

Browse files
authored
(fix): appcontainer should not poll and footer should use currentModel from ui state (#11923)
1 parent cb0947c commit 2dfb813

File tree

6 files changed

+72
-12
lines changed

6 files changed

+72
-12
lines changed

packages/cli/src/test-utils/render.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ const baseMockUiState = {
6464
streamingState: StreamingState.Idle,
6565
mainAreaWidth: 100,
6666
terminalWidth: 120,
67+
currentModel: 'gemini-pro',
6768
};
6869

6970
export const renderWithProviders = (

packages/cli/src/ui/AppContainer.tsx

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -261,20 +261,18 @@ export const AppContainer = (props: AppContainerProps) => {
261261
[historyManager.addItem],
262262
);
263263

264-
// Watch for model changes (e.g., from Flash fallback)
264+
// Subscribe to fallback mode changes from core
265265
useEffect(() => {
266-
const checkModelChange = () => {
266+
const handleFallbackModeChanged = () => {
267267
const effectiveModel = getEffectiveModel();
268-
if (effectiveModel !== currentModel) {
269-
setCurrentModel(effectiveModel);
270-
}
268+
setCurrentModel(effectiveModel);
271269
};
272270

273-
checkModelChange();
274-
const interval = setInterval(checkModelChange, 1000); // Check every second
275-
276-
return () => clearInterval(interval);
277-
}, [config, currentModel, getEffectiveModel]);
271+
coreEvents.on(CoreEvent.FallbackModeChanged, handleFallbackModeChanged);
272+
return () => {
273+
coreEvents.off(CoreEvent.FallbackModeChanged, handleFallbackModeChanged);
274+
};
275+
}, [getEffectiveModel]);
278276

279277
const {
280278
consoleMessages,

packages/cli/src/ui/components/Footer.test.tsx

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,3 +256,31 @@ describe('<Footer />', () => {
256256
});
257257
});
258258
});
259+
260+
describe('fallback mode display', () => {
261+
it('should display Flash model when in fallback mode, not the configured Pro model', () => {
262+
const { lastFrame } = renderWithProviders(<Footer />, {
263+
width: 120,
264+
uiState: {
265+
sessionStats: mockSessionStats,
266+
currentModel: 'gemini-2.5-flash', // Fallback active, showing Flash
267+
},
268+
});
269+
270+
// Footer should show the effective model (Flash), not the config model (Pro)
271+
expect(lastFrame()).toContain('gemini-2.5-flash');
272+
expect(lastFrame()).not.toContain('gemini-2.5-pro');
273+
});
274+
275+
it('should display Pro model when NOT in fallback mode', () => {
276+
const { lastFrame } = renderWithProviders(<Footer />, {
277+
width: 120,
278+
uiState: {
279+
sessionStats: mockSessionStats,
280+
currentModel: 'gemini-2.5-pro', // Normal mode, showing Pro
281+
},
282+
});
283+
284+
expect(lastFrame()).toContain('gemini-2.5-pro');
285+
});
286+
});

packages/cli/src/ui/components/Footer.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ import { MemoryUsageDisplay } from './MemoryUsageDisplay.js';
1515
import { ContextUsageDisplay } from './ContextUsageDisplay.js';
1616
import { DebugProfiler } from './DebugProfiler.js';
1717
import { isDevelopment } from '../../utils/installationInfo.js';
18-
1918
import { useUIState } from '../contexts/UIStateContext.js';
2019
import { useConfig } from '../contexts/ConfigContext.js';
2120
import { useSettings } from '../contexts/SettingsContext.js';
@@ -41,7 +40,7 @@ export const Footer: React.FC = () => {
4140
isTrustedFolder,
4241
mainAreaWidth,
4342
} = {
44-
model: config.getModel(),
43+
model: uiState.currentModel,
4544
targetDir: config.getTargetDir(),
4645
debugMode: config.getDebugMode(),
4746
branchName: uiState.branchName,

packages/core/src/fallback/handler.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import type { Config } from '../config/config.js';
88
import { AuthType } from '../core/contentGenerator.js';
99
import { DEFAULT_GEMINI_FLASH_MODEL } from '../config/models.js';
1010
import { logFlashFallback, FlashFallbackEvent } from '../telemetry/index.js';
11+
import { coreEvents } from '../utils/events.js';
1112

1213
export async function handleFallback(
1314
config: Config,
@@ -62,6 +63,7 @@ export async function handleFallback(
6263
function activateFallbackMode(config: Config, authType: string | undefined) {
6364
if (!config.isInFallbackMode()) {
6465
config.setFallbackMode(true);
66+
coreEvents.emitFallbackModeChanged(true);
6567
if (authType) {
6668
logFlashFallback(config, new FlashFallbackEvent(authType));
6769
}

packages/core/src/utils/events.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,19 @@ export interface UserFeedbackPayload {
3333
error?: unknown;
3434
}
3535

36+
/**
37+
* Payload for the 'fallback-mode-changed' event.
38+
*/
39+
export interface FallbackModeChangedPayload {
40+
/**
41+
* Whether fallback mode is now active.
42+
*/
43+
isInFallbackMode: boolean;
44+
}
45+
3646
export enum CoreEvent {
3747
UserFeedback = 'user-feedback',
48+
FallbackModeChanged = 'fallback-mode-changed',
3849
}
3950

4051
export class CoreEventEmitter extends EventEmitter {
@@ -66,6 +77,15 @@ export class CoreEventEmitter extends EventEmitter {
6677
}
6778
}
6879

80+
/**
81+
* Notifies subscribers that fallback mode has changed.
82+
* This is synchronous and doesn't use backlog (UI should already be initialized).
83+
*/
84+
emitFallbackModeChanged(isInFallbackMode: boolean): void {
85+
const payload: FallbackModeChangedPayload = { isInFallbackMode };
86+
this.emit(CoreEvent.FallbackModeChanged, payload);
87+
}
88+
6989
/**
7090
* Flushes buffered messages. Call this immediately after primary UI listener
7191
* subscribes.
@@ -82,6 +102,10 @@ export class CoreEventEmitter extends EventEmitter {
82102
event: CoreEvent.UserFeedback,
83103
listener: (payload: UserFeedbackPayload) => void,
84104
): this;
105+
override on(
106+
event: CoreEvent.FallbackModeChanged,
107+
listener: (payload: FallbackModeChangedPayload) => void,
108+
): this;
85109
override on(
86110
event: string | symbol,
87111
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -94,6 +118,10 @@ export class CoreEventEmitter extends EventEmitter {
94118
event: CoreEvent.UserFeedback,
95119
listener: (payload: UserFeedbackPayload) => void,
96120
): this;
121+
override off(
122+
event: CoreEvent.FallbackModeChanged,
123+
listener: (payload: FallbackModeChangedPayload) => void,
124+
): this;
97125
override off(
98126
event: string | symbol,
99127
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -106,6 +134,10 @@ export class CoreEventEmitter extends EventEmitter {
106134
event: CoreEvent.UserFeedback,
107135
payload: UserFeedbackPayload,
108136
): boolean;
137+
override emit(
138+
event: CoreEvent.FallbackModeChanged,
139+
payload: FallbackModeChangedPayload,
140+
): boolean;
109141
// eslint-disable-next-line @typescript-eslint/no-explicit-any
110142
override emit(event: string | symbol, ...args: any[]): boolean {
111143
return super.emit(event, ...args);

0 commit comments

Comments
 (0)