Revert "feat(accessibility): implement centralized screen reader layo… (#9255)

This commit is contained in:
christine betts
2025-09-23 15:44:33 -04:00
committed by GitHub
parent 16278fd849
commit 39b0948417
10 changed files with 64 additions and 348 deletions

View File

@@ -32,46 +32,6 @@ vi.mock('./components/QuittingDisplay.js', () => ({
QuittingDisplay: () => <Text>Quitting...</Text>,
}));
vi.mock('./components/Footer.js', () => ({
Footer: () => <Text>Footer</Text>,
}));
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<UIState> = {
streamingState: StreamingState.Idle,
@@ -95,6 +55,7 @@ describe('App', () => {
);
expect(lastFrame()).toContain('MainContent');
expect(lastFrame()).toContain('Notifications');
expect(lastFrame()).toContain('Composer');
});
@@ -126,6 +87,7 @@ describe('App', () => {
);
expect(lastFrame()).toContain('MainContent');
expect(lastFrame()).toContain('Notifications');
expect(lastFrame()).toContain('DialogManager');
});

View File

@@ -4,16 +4,18 @@
* SPDX-License-Identifier: Apache-2.0
*/
import { useUIState } from './contexts/UIStateContext.js';
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 { QuittingDisplay } from './components/QuittingDisplay.js';
import { useLayoutConfig } from './hooks/useLayoutConfig.js';
import { ScreenReaderAppLayout } from './layouts/ScreenReaderAppLayout.js';
import { DefaultAppLayout } from './layouts/DefaultAppLayout.js';
import { theme } from './semantic-colors.js';
export const App = () => {
const uiState = useUIState();
const layout = useLayoutConfig();
if (uiState.quittingMessages) {
return <QuittingDisplay />;
@@ -21,11 +23,35 @@ export const App = () => {
return (
<StreamingContext.Provider value={uiState.streamingState}>
{layout.mode === 'screenReader' ? (
<ScreenReaderAppLayout />
) : (
<DefaultAppLayout />
)}
<Box flexDirection="column" width="90%">
<MainContent />
<Box flexDirection="column" ref={uiState.mainControlsRef}>
<Notifications />
{uiState.dialogsVisible ? (
<DialogManager addItem={uiState.historyManager.addItem} />
) : (
<Composer />
)}
{uiState.dialogsVisible && uiState.ctrlCPressedOnce && (
<Box marginTop={1}>
<Text color={theme.status.warning}>
Press Ctrl+C again to exit.
</Text>
</Box>
)}
{uiState.dialogsVisible && uiState.ctrlDPressedOnce && (
<Box marginTop={1}>
<Text color={theme.status.warning}>
Press Ctrl+D again to exit.
</Text>
</Box>
)}
</Box>
</Box>
</StreamingContext.Provider>
);
};

View File

@@ -26,12 +26,10 @@ 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();
@@ -178,7 +176,7 @@ export const Composer = () => {
/>
)}
{!settings.merged.ui?.hideFooter && layout.shouldShowFooterInComposer && (
{!settings.merged.ui?.hideFooter && (
<Footer {...footerProps} vimMode={vimEnabled ? vimMode : undefined} />
)}
</Box>

View File

@@ -1,36 +0,0 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { HistoryItemDisplay } from './HistoryItemDisplay.js';
import type { HistoryItem } from '../types.js';
import type { SlashCommand } from '../commands/types.js';
interface HistoryListProps {
history: HistoryItem[];
terminalWidth: number;
staticAreaMaxItemHeight: number;
slashCommands: readonly SlashCommand[];
}
export const HistoryList = ({
history,
terminalWidth,
staticAreaMaxItemHeight,
slashCommands,
}: HistoryListProps) => (
<>
{history.map((h) => (
<HistoryItemDisplay
terminalWidth={terminalWidth}
availableTerminalHeight={staticAreaMaxItemHeight}
key={h.id}
item={h}
isPending={false}
commands={slashCommands}
/>
))}
</>
);

View File

@@ -5,19 +5,16 @@
*/
import { Box, Static } from 'ink';
import { HistoryList } from './HistoryList.js';
import { PendingHistoryList } from './PendingHistoryList.js';
import { HistoryItemDisplay } from './HistoryItemDisplay.js';
import { ShowMoreLines } from './ShowMoreLines.js';
import { OverflowProvider } from '../contexts/OverflowContext.js';
import { useUIState } from '../contexts/UIStateContext.js';
import { useAppContext } from '../contexts/AppContext.js';
import { AppHeader } from './AppHeader.js';
import { useLayoutConfig } from '../hooks/useLayoutConfig.js';
export const MainContent = () => {
const { version } = useAppContext();
const uiState = useUIState();
const layout = useLayoutConfig();
const {
pendingHistoryItems,
mainAreaWidth,
@@ -25,60 +22,42 @@ export const MainContent = () => {
availableTerminalHeight,
} = uiState;
// In screen reader mode, use regular layout without Static component
if (!layout.shouldUseStatic) {
return (
<OverflowProvider>
<Box flexDirection="column">
<AppHeader version={version} />
<HistoryList
history={uiState.history}
terminalWidth={mainAreaWidth}
staticAreaMaxItemHeight={staticAreaMaxItemHeight}
slashCommands={uiState.slashCommands}
/>
<PendingHistoryList
pendingHistoryItems={pendingHistoryItems}
terminalWidth={mainAreaWidth}
availableTerminalHeight={availableTerminalHeight}
constrainHeight={uiState.constrainHeight}
isEditorDialogOpen={uiState.isEditorDialogOpen}
/>
<ShowMoreLines constrainHeight={uiState.constrainHeight} />
</Box>
</OverflowProvider>
);
}
// Default mode with Static component
return (
<>
<Static
key={uiState.historyRemountKey}
items={[
<AppHeader key="app-header" version={version} />,
<HistoryList
key="history-list"
history={uiState.history}
terminalWidth={mainAreaWidth}
staticAreaMaxItemHeight={staticAreaMaxItemHeight}
slashCommands={uiState.slashCommands}
/>,
...uiState.history.map((h) => (
<HistoryItemDisplay
terminalWidth={mainAreaWidth}
availableTerminalHeight={staticAreaMaxItemHeight}
key={h.id}
item={h}
isPending={false}
commands={uiState.slashCommands}
/>
)),
]}
>
{(item) => item}
</Static>
<OverflowProvider>
<Box flexDirection="column">
<PendingHistoryList
pendingHistoryItems={pendingHistoryItems}
terminalWidth={mainAreaWidth}
availableTerminalHeight={availableTerminalHeight}
constrainHeight={uiState.constrainHeight}
isEditorDialogOpen={uiState.isEditorDialogOpen}
activePtyId={uiState.activePtyId?.toString()}
embeddedShellFocused={uiState.embeddedShellFocused}
/>
{pendingHistoryItems.map((item, i) => (
<HistoryItemDisplay
key={i}
availableTerminalHeight={
uiState.constrainHeight ? availableTerminalHeight : undefined
}
terminalWidth={mainAreaWidth}
item={{ ...item, id: 0 }}
isPending={true}
isFocused={!uiState.isEditorDialogOpen}
activeShellPtyId={uiState.activePtyId}
embeddedShellFocused={uiState.embeddedShellFocused}
/>
))}
<ShowMoreLines constrainHeight={uiState.constrainHeight} />
</Box>
</OverflowProvider>

View File

@@ -1,45 +0,0 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { HistoryItemDisplay } from './HistoryItemDisplay.js';
import type { HistoryItemWithoutId } from '../types.js';
interface PendingHistoryListProps {
pendingHistoryItems: HistoryItemWithoutId[];
terminalWidth: number;
availableTerminalHeight?: number;
constrainHeight?: boolean;
isEditorDialogOpen: boolean;
activePtyId?: string;
embeddedShellFocused?: boolean;
}
export const PendingHistoryList = ({
pendingHistoryItems,
terminalWidth,
availableTerminalHeight,
constrainHeight,
isEditorDialogOpen,
activePtyId,
embeddedShellFocused,
}: PendingHistoryListProps) => (
<>
{pendingHistoryItems.map((item, i) => (
<HistoryItemDisplay
key={i}
availableTerminalHeight={
constrainHeight ? availableTerminalHeight : undefined
}
terminalWidth={terminalWidth}
item={{ ...item, id: 0 }}
isPending={true}
isFocused={!isEditorDialogOpen}
activeShellPtyId={activePtyId ? parseInt(activePtyId, 10) : null}
embeddedShellFocused={embeddedShellFocused}
/>
))}
</>
);

View File

@@ -1,32 +0,0 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { useUIState } from '../contexts/UIStateContext.js';
import { useConfig } from '../contexts/ConfigContext.js';
import { useSettings } from '../contexts/SettingsContext.js';
export const useFooterProps = () => {
const uiState = useUIState();
const config = useConfig();
const settings = useSettings();
return {
model: config.getModel(),
targetDir: config.getTargetDir(),
debugMode: config.getDebugMode(),
branchName: uiState.branchName,
debugMessage: uiState.debugMessage,
corgiMode: uiState.corgiMode,
errorCount: uiState.errorCount,
showErrorDetails: uiState.showErrorDetails,
showMemoryUsage:
config.getDebugMode() || settings.merged.ui?.showMemoryUsage || false,
promptTokenCount: uiState.sessionStats.lastPromptTokenCount,
nightly: uiState.nightly,
isTrustedFolder: uiState.isTrustedFolder,
vimMode: undefined,
};
};

View File

@@ -1,38 +0,0 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { useIsScreenReaderEnabled } from 'ink';
export interface LayoutConfig {
shouldUseStatic: boolean;
shouldShowFooterInComposer: boolean;
mode: 'default' | 'screenReader';
allowStaticToggle?: boolean;
}
export interface LayoutConfigOptions {
forceStaticMode?: boolean;
allowToggle?: boolean;
}
export const useLayoutConfig = (
options?: LayoutConfigOptions,
): LayoutConfig => {
const isScreenReader = useIsScreenReaderEnabled();
// Allow overriding static behavior when toggle is enabled
const shouldUseStatic =
options?.forceStaticMode !== undefined
? options.forceStaticMode
: !isScreenReader;
return {
shouldUseStatic,
shouldShowFooterInComposer: !isScreenReader,
mode: isScreenReader ? 'screenReader' : 'default',
allowStaticToggle: options?.allowToggle ?? false,
};
};

View File

@@ -1,50 +0,0 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import type React from 'react';
import { Box, Text } from 'ink';
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 { theme } from '../semantic-colors.js';
export const DefaultAppLayout: React.FC = () => {
const uiState = useUIState();
return (
<Box flexDirection="column" width="90%">
<MainContent />
<Box flexDirection="column" ref={uiState.mainControlsRef}>
<Notifications />
{uiState.dialogsVisible ? (
<DialogManager addItem={uiState.historyManager.addItem} />
) : (
<Composer />
)}
{uiState.dialogsVisible && uiState.ctrlCPressedOnce && (
<Box marginTop={1}>
<Text color={theme.status.warning}>
Press Ctrl+C again to exit.
</Text>
</Box>
)}
{uiState.dialogsVisible && uiState.ctrlDPressedOnce && (
<Box marginTop={1}>
<Text color={theme.status.warning}>
Press Ctrl+D again to exit.
</Text>
</Box>
)}
</Box>
</Box>
);
};

View File

@@ -1,48 +0,0 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import type React from 'react';
import { Box, Text } from 'ink';
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 { Footer } from '../components/Footer.js';
import { useUIState } from '../contexts/UIStateContext.js';
import { useFooterProps } from '../hooks/useFooterProps.js';
import { theme } from '../semantic-colors.js';
export const ScreenReaderAppLayout: React.FC = () => {
const uiState = useUIState();
const footerProps = useFooterProps();
return (
<Box flexDirection="column" width="90%" height="100%">
<Notifications />
<Footer {...footerProps} />
<Box flexGrow={1} overflow="hidden">
<MainContent />
</Box>
{uiState.dialogsVisible ? (
<DialogManager addItem={uiState.historyManager.addItem} />
) : (
<Composer />
)}
{uiState.dialogsVisible && uiState.ctrlDPressedOnce && (
<Box marginTop={1}>
<Text color={theme.status.warning}>Press Ctrl+C again to exit.</Text>
</Box>
)}
{uiState.dialogsVisible && uiState.ctrlDPressedOnce && (
<Box marginTop={1}>
<Text color={theme.status.warning}>Press Ctrl+D again to exit.</Text>
</Box>
)}
</Box>
);
};