feat(ux): Surface internal errors via unified event system (#11803)

This commit is contained in:
Abhi
2025-10-23 14:14:14 -04:00
committed by GitHub
parent 7787a31f81
commit 3a501196f0
9 changed files with 676 additions and 3 deletions
+92 -1
View File
@@ -15,7 +15,29 @@ import {
} from 'vitest';
import { render, cleanup } from 'ink-testing-library';
import { AppContainer } from './AppContainer.js';
import { type Config, makeFakeConfig } from '@google/gemini-cli-core';
import {
type Config,
makeFakeConfig,
CoreEvent,
type UserFeedbackPayload,
} from '@google/gemini-cli-core';
// Mock coreEvents
const mockCoreEvents = vi.hoisted(() => ({
on: vi.fn(),
off: vi.fn(),
drainFeedbackBacklog: vi.fn(),
emit: vi.fn(),
}));
vi.mock('@google/gemini-cli-core', async (importOriginal) => {
const actual =
await importOriginal<typeof import('@google/gemini-cli-core')>();
return {
...actual,
coreEvents: mockCoreEvents,
};
});
import type { LoadedSettings } from '../config/settings.js';
import type { InitializationResult } from '../core/initializer.js';
import { useQuotaAndFallback } from './hooks/useQuotaAndFallback.js';
@@ -1293,4 +1315,73 @@ describe('AppContainer State Management', () => {
expect(mockCloseModelDialog).toHaveBeenCalled();
});
});
describe('CoreEvents Integration', () => {
it('subscribes to UserFeedback and drains backlog on mount', () => {
render(
<AppContainer
config={mockConfig}
settings={mockSettings}
version="1.0.0"
initializationResult={mockInitResult}
/>,
);
expect(mockCoreEvents.on).toHaveBeenCalledWith(
CoreEvent.UserFeedback,
expect.any(Function),
);
expect(mockCoreEvents.drainFeedbackBacklog).toHaveBeenCalledTimes(1);
});
it('unsubscribes from UserFeedback on unmount', () => {
const { unmount } = render(
<AppContainer
config={mockConfig}
settings={mockSettings}
version="1.0.0"
initializationResult={mockInitResult}
/>,
);
unmount();
expect(mockCoreEvents.off).toHaveBeenCalledWith(
CoreEvent.UserFeedback,
expect.any(Function),
);
});
it('adds history item when UserFeedback event is received', () => {
render(
<AppContainer
config={mockConfig}
settings={mockSettings}
version="1.0.0"
initializationResult={mockInitResult}
/>,
);
// Get the registered handler
const handler = mockCoreEvents.on.mock.calls.find(
(call: unknown[]) => call[0] === CoreEvent.UserFeedback,
)?.[1];
expect(handler).toBeDefined();
// Simulate an event
const payload: UserFeedbackPayload = {
severity: 'error',
message: 'Test error message',
};
handler(payload);
expect(mockedUseHistory().addItem).toHaveBeenCalledWith(
expect.objectContaining({
type: 'error',
text: 'Test error message',
}),
expect.any(Number),
);
});
});
});
+50
View File
@@ -34,6 +34,7 @@ import {
type IdeInfo,
type IdeContext,
type UserTierId,
type UserFeedbackPayload,
DEFAULT_GEMINI_FLASH_MODEL,
IdeClient,
ideContextStore,
@@ -44,6 +45,8 @@ import {
recordExitFail,
ShellExecutionService,
debugLogger,
coreEvents,
CoreEvent,
} from '@google/gemini-cli-core';
import { validateAuthMethod } from '../config/auth.js';
import { loadHierarchicalGeminiMemory } from '../config/config.js';
@@ -1066,6 +1069,53 @@ Logging in with Google... Please restart Gemini CLI to continue.
stdout,
]);
useEffect(() => {
const handleUserFeedback = (payload: UserFeedbackPayload) => {
let type: MessageType;
switch (payload.severity) {
case 'error':
type = MessageType.ERROR;
break;
case 'warning':
type = MessageType.WARNING;
break;
case 'info':
type = MessageType.INFO;
break;
default:
throw new Error(
`Unexpected severity for user feedback: ${payload.severity}`,
);
}
historyManager.addItem(
{
type,
text: payload.message,
},
Date.now(),
);
// If there is an attached error object, log it to the debug drawer.
if (payload.error) {
debugLogger.warn(
`[Feedback Details for "${payload.message}"]`,
payload.error,
);
}
};
coreEvents.on(CoreEvent.UserFeedback, handleUserFeedback);
// Flush any messages that happened during startup before this component
// mounted.
coreEvents.drainFeedbackBacklog();
return () => {
coreEvents.off(CoreEvent.UserFeedback, handleUserFeedback);
};
}, [historyManager]);
const filteredConsoleMessages = useMemo(() => {
if (config.getDebugMode()) {
return consoleMessages;