feat(ui): improve startup warnings UX with dismissal and show-count limits (#19584)

This commit is contained in:
Spencer
2026-02-20 13:22:45 -05:00
committed by GitHub
parent d24f10b087
commit fe428936d5
12 changed files with 503 additions and 109 deletions

View File

@@ -5,15 +5,22 @@
*/
import { Box, Text, useIsScreenReaderEnabled } from 'ink';
import { useEffect, useState } from 'react';
import { useEffect, useState, useMemo, useRef, useCallback } from 'react';
import { useAppContext } from '../contexts/AppContext.js';
import { useUIState } from '../contexts/UIStateContext.js';
import { theme } from '../semantic-colors.js';
import { StreamingState } from '../types.js';
import { UpdateNotification } from './UpdateNotification.js';
import { persistentState } from '../../utils/persistentState.js';
import { useKeypress } from '../hooks/useKeypress.js';
import { KeypressPriority } from '../contexts/KeypressContext.js';
import { GEMINI_DIR, Storage, homedir } from '@google/gemini-cli-core';
import {
GEMINI_DIR,
Storage,
homedir,
WarningPriority,
} from '@google/gemini-cli-core';
import * as fs from 'node:fs/promises';
import path from 'node:path';
@@ -25,12 +32,13 @@ const screenReaderNudgeFilePath = path.join(
'seen_screen_reader_nudge.json',
);
const MAX_STARTUP_WARNING_SHOW_COUNT = 3;
export const Notifications = () => {
const { startupWarnings } = useAppContext();
const { initError, streamingState, updateInfo } = useUIState();
const isScreenReaderEnabled = useIsScreenReaderEnabled();
const showStartupWarnings = startupWarnings.length > 0;
const showInitError =
initError && streamingState !== StreamingState.Responding;
@@ -38,6 +46,57 @@ export const Notifications = () => {
persistentState.get('hasSeenScreenReaderNudge'),
);
const [dismissed, setDismissed] = useState(false);
// Track if we have already incremented the show count in this session
const hasIncrementedRef = useRef(false);
// Filter warnings based on persistent state count if low priority
const visibleWarnings = useMemo(() => {
if (dismissed) return [];
const counts = persistentState.get('startupWarningCounts') || {};
return startupWarnings.filter((w) => {
if (w.priority === WarningPriority.Low) {
const count = counts[w.id] || 0;
return count < MAX_STARTUP_WARNING_SHOW_COUNT;
}
return true;
});
}, [startupWarnings, dismissed]);
const showStartupWarnings = visibleWarnings.length > 0;
// Increment counts for low priority warnings when shown
useEffect(() => {
if (visibleWarnings.length > 0 && !hasIncrementedRef.current) {
const counts = { ...(persistentState.get('startupWarningCounts') || {}) };
let changed = false;
visibleWarnings.forEach((w) => {
if (w.priority === WarningPriority.Low) {
counts[w.id] = (counts[w.id] || 0) + 1;
changed = true;
}
});
if (changed) {
persistentState.set('startupWarningCounts', counts);
}
hasIncrementedRef.current = true;
}
}, [visibleWarnings]);
const handleKeyPress = useCallback(() => {
if (showStartupWarnings) {
setDismissed(true);
}
return false;
}, [showStartupWarnings]);
useKeypress(handleKeyPress, {
isActive: showStartupWarnings,
priority: KeypressPriority.Critical,
});
useEffect(() => {
const checkLegacyScreenReaderNudge = async () => {
if (hasSeenScreenReaderNudge !== undefined) return;
@@ -89,13 +148,13 @@ export const Notifications = () => {
{updateInfo && <UpdateNotification message={updateInfo.message} />}
{showStartupWarnings && (
<Box marginY={1} flexDirection="column">
{startupWarnings.map((warning, index) => (
{visibleWarnings.map((warning, index) => (
<Box key={index} flexDirection="row">
<Box width={3}>
<Text color={theme.status.warning}> </Text>
</Box>
<Box flexGrow={1}>
<Text color={theme.status.warning}>{warning}</Text>
<Text color={theme.status.warning}>{warning.message}</Text>
</Box>
</Box>
))}