From bfd904bfc827a30d1807ee5d0b1e39cfc4b1a31b Mon Sep 17 00:00:00 2001 From: Victor Tsaran Date: Tue, 23 Sep 2025 11:04:49 -0700 Subject: [PATCH] feat(accessibility): implement centralized screen reader layout system (#8155) --- packages/cli/src/ui/App.test.tsx | 42 ++++++++++- packages/cli/src/ui/App.tsx | 46 +++--------- packages/cli/src/ui/components/Composer.tsx | 4 +- .../cli/src/ui/components/HistoryList.tsx | 36 ++++++++++ .../cli/src/ui/components/MainContent.tsx | 71 ++++++++++++------- .../src/ui/components/PendingHistoryList.tsx | 45 ++++++++++++ packages/cli/src/ui/hooks/useFooterProps.ts | 32 +++++++++ packages/cli/src/ui/hooks/useLayoutConfig.ts | 38 ++++++++++ .../cli/src/ui/layouts/DefaultAppLayout.tsx | 50 +++++++++++++ .../src/ui/layouts/ScreenReaderAppLayout.tsx | 48 +++++++++++++ 10 files changed, 348 insertions(+), 64 deletions(-) create mode 100644 packages/cli/src/ui/components/HistoryList.tsx create mode 100644 packages/cli/src/ui/components/PendingHistoryList.tsx create mode 100644 packages/cli/src/ui/hooks/useFooterProps.ts create mode 100644 packages/cli/src/ui/hooks/useLayoutConfig.ts create mode 100644 packages/cli/src/ui/layouts/DefaultAppLayout.tsx create mode 100644 packages/cli/src/ui/layouts/ScreenReaderAppLayout.tsx diff --git a/packages/cli/src/ui/App.test.tsx b/packages/cli/src/ui/App.test.tsx index aad5e51211..a8232c7c24 100644 --- a/packages/cli/src/ui/App.test.tsx +++ b/packages/cli/src/ui/App.test.tsx @@ -32,6 +32,46 @@ vi.mock('./components/QuittingDisplay.js', () => ({ QuittingDisplay: () => Quitting..., })); +vi.mock('./components/Footer.js', () => ({ + Footer: () => Footer, +})); + +vi.mock('./semantic-colors.js', () => ({ + theme: { + status: { + warning: 'yellow', + }, + }, +})); + +// Don't mock the layout components - let them render normally so tests can see the Ctrl messages + +vi.mock('./hooks/useLayoutConfig.js', () => ({ + useLayoutConfig: () => ({ + mode: 'default', + shouldUseStatic: true, + shouldShowFooterInComposer: true, + }), +})); + +vi.mock('./hooks/useFooterProps.js', () => ({ + useFooterProps: () => ({ + model: 'test-model', + targetDir: '/test', + debugMode: false, + branchName: 'test-branch', + debugMessage: '', + corgiMode: false, + errorCount: 0, + showErrorDetails: false, + showMemoryUsage: false, + promptTokenCount: 0, + nightly: false, + isTrustedFolder: true, + vimMode: undefined, + }), +})); + describe('App', () => { const mockUIState: Partial = { streamingState: StreamingState.Idle, @@ -55,7 +95,6 @@ describe('App', () => { ); expect(lastFrame()).toContain('MainContent'); - expect(lastFrame()).toContain('Notifications'); expect(lastFrame()).toContain('Composer'); }); @@ -87,7 +126,6 @@ describe('App', () => { ); expect(lastFrame()).toContain('MainContent'); - expect(lastFrame()).toContain('Notifications'); expect(lastFrame()).toContain('DialogManager'); }); diff --git a/packages/cli/src/ui/App.tsx b/packages/cli/src/ui/App.tsx index 8a582be7f7..50d1b84afe 100644 --- a/packages/cli/src/ui/App.tsx +++ b/packages/cli/src/ui/App.tsx @@ -4,18 +4,16 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { Box, Text } from 'ink'; -import { StreamingContext } from './contexts/StreamingContext.js'; -import { Notifications } from './components/Notifications.js'; -import { MainContent } from './components/MainContent.js'; -import { DialogManager } from './components/DialogManager.js'; -import { Composer } from './components/Composer.js'; import { useUIState } from './contexts/UIStateContext.js'; +import { StreamingContext } from './contexts/StreamingContext.js'; import { QuittingDisplay } from './components/QuittingDisplay.js'; -import { theme } from './semantic-colors.js'; +import { useLayoutConfig } from './hooks/useLayoutConfig.js'; +import { ScreenReaderAppLayout } from './layouts/ScreenReaderAppLayout.js'; +import { DefaultAppLayout } from './layouts/DefaultAppLayout.js'; export const App = () => { const uiState = useUIState(); + const layout = useLayoutConfig(); if (uiState.quittingMessages) { return ; @@ -23,35 +21,11 @@ export const App = () => { return ( - - - - - - - {uiState.dialogsVisible ? ( - - ) : ( - - )} - - {uiState.dialogsVisible && uiState.ctrlCPressedOnce && ( - - - Press Ctrl+C again to exit. - - - )} - - {uiState.dialogsVisible && uiState.ctrlDPressedOnce && ( - - - Press Ctrl+D again to exit. - - - )} - - + {layout.mode === 'screenReader' ? ( + + ) : ( + + )} ); }; diff --git a/packages/cli/src/ui/components/Composer.tsx b/packages/cli/src/ui/components/Composer.tsx index 9acc49a44d..2e826d97f2 100644 --- a/packages/cli/src/ui/components/Composer.tsx +++ b/packages/cli/src/ui/components/Composer.tsx @@ -26,10 +26,12 @@ import { useSettings } from '../contexts/SettingsContext.js'; import { ApprovalMode } from '@google/gemini-cli-core'; import { StreamingState } from '../types.js'; import { ConfigInitDisplay } from '../components/ConfigInitDisplay.js'; +import { useLayoutConfig } from '../hooks/useLayoutConfig.js'; export const Composer = () => { const config = useConfig(); const settings = useSettings(); + const layout = useLayoutConfig(); const uiState = useUIState(); const uiActions = useUIActions(); const { vimEnabled, vimMode } = useVimMode(); @@ -176,7 +178,7 @@ export const Composer = () => { /> )} - {!settings.merged.ui?.hideFooter && ( + {!settings.merged.ui?.hideFooter && layout.shouldShowFooterInComposer && (