diff --git a/Composer.tsx.conflicted b/Composer.tsx.conflicted
deleted file mode 100644
index ddcb389344..0000000000
--- a/Composer.tsx.conflicted
+++ /dev/null
@@ -1,457 +0,0 @@
-/**
- * @license
- * Copyright 2026 Google LLC
- * SPDX-License-Identifier: Apache-2.0
- */
-
-<<<<<<< HEAD
-import { Box, useIsScreenReaderEnabled } from 'ink';
-import { useState, useEffect } from 'react';
-import { useConfig } from '../contexts/ConfigContext.js';
-import { useSettings } from '../contexts/SettingsContext.js';
-import { useUIState } from '../contexts/UIStateContext.js';
-import { useUIActions } from '../contexts/UIActionsContext.js';
-import { useVimMode } from '../contexts/VimModeContext.js';
-import { useAlternateBuffer } from '../hooks/useAlternateBuffer.js';
-import { useTerminalSize } from '../hooks/useTerminalSize.js';
-import { isNarrowWidth } from '../utils/isNarrowWidth.js';
-=======
-import { useState, useEffect, useMemo } from 'react';
-import { Box, useIsScreenReaderEnabled } from 'ink';
-import { CoreToolCallStatus } from '@google/gemini-cli-core';
-import { LoadingIndicator } from './LoadingIndicator.js';
-import { StatusDisplay } from './StatusDisplay.js';
->>>>>>> 96b0876e6 (feat(cli): unify session modes in footer and reorganize composer layout)
-import { ToastDisplay, shouldShowToast } from './ToastDisplay.js';
-import { DetailedMessagesDisplay } from './DetailedMessagesDisplay.js';
-import { ShortcutsHelp } from './ShortcutsHelp.js';
-import { ShortcutsHint } from './ShortcutsHint.js';
-import { InputPrompt } from './InputPrompt.js';
-import { Footer } from './Footer.js';
-import { StatusRow } from './StatusRow.js';
-import { ShowMoreLines } from './ShowMoreLines.js';
-import { QueuedMessageDisplay } from './QueuedMessageDisplay.js';
-import { ContextUsageDisplay } from './ContextUsageDisplay.js';
-import { HorizontalLine } from './shared/HorizontalLine.js';
-import { OverflowProvider } from '../contexts/OverflowContext.js';
-import { isNarrowWidth } from '../utils/isNarrowWidth.js';
-import { useUIState } from '../contexts/UIStateContext.js';
-import { useUIActions } from '../contexts/UIActionsContext.js';
-import { useVimMode } from '../contexts/VimModeContext.js';
-import { useConfig } from '../contexts/ConfigContext.js';
-import { useSettings } from '../contexts/SettingsContext.js';
-import { useAlternateBuffer } from '../hooks/useAlternateBuffer.js';
-import { StreamingState, type HistoryItemToolGroup } from '../types.js';
-import { ConfigInitDisplay } from '../components/ConfigInitDisplay.js';
-import { TodoTray } from './messages/Todo.js';
-<<<<<<< HEAD
-import { useComposerStatus } from '../hooks/useComposerStatus.js';
-=======
-import { getInlineThinkingMode } from '../utils/inlineThinkingMode.js';
-import { isContextUsageHigh } from '../utils/contextUsage.js';
->>>>>>> 96b0876e6 (feat(cli): unify session modes in footer and reorganize composer layout)
-
-export const Composer = ({ isFocused = true }: { isFocused?: boolean }) => {
- const config = useConfig();
- const settings = useSettings();
- const isScreenReaderEnabled = useIsScreenReaderEnabled();
- const uiState = useUIState();
- const uiActions = useUIActions();
- const { vimEnabled, vimMode } = useVimMode();
- const inlineThinkingMode = getInlineThinkingMode(settings);
- const terminalWidth = uiState.terminalWidth;
- const isNarrow = isNarrowWidth(terminalWidth);
- const debugConsoleMaxHeight = Math.floor(Math.max(terminalWidth * 0.2, 5));
- const [suggestionsVisible, setSuggestionsVisible] = useState(false);
-
- const isAlternateBuffer = useAlternateBuffer();
-<<<<<<< HEAD
-=======
- const { showApprovalModeIndicator } = uiState;
->>>>>>> 96b0876e6 (feat(cli): unify session modes in footer and reorganize composer layout)
- const showUiDetails = uiState.cleanUiDetailsVisible;
- const suggestionsPosition = isAlternateBuffer ? 'above' : 'below';
- const hideContextSummary =
- suggestionsVisible && suggestionsPosition === 'above';
-
- const { hasPendingActionRequired, shouldCollapseDuringApproval } =
- useComposerStatus();
-
- const isPassiveShortcutsHelpState =
- uiState.isInputActive &&
- uiState.streamingState === 'idle' &&
- !hasPendingActionRequired;
-
- const { setShortcutsHelpVisible } = uiActions;
-
- useEffect(() => {
- if (uiState.shortcutsHelpVisible && !isPassiveShortcutsHelpState) {
- setShortcutsHelpVisible(false);
- }
- }, [
- uiState.shortcutsHelpVisible,
- isPassiveShortcutsHelpState,
- setShortcutsHelpVisible,
- ]);
-
-<<<<<<< HEAD
- const showShortcutsHelp =
- uiState.shortcutsHelpVisible &&
- uiState.streamingState === 'idle' &&
- !hasPendingActionRequired;
-
-=======
- const hideUiDetailsForSuggestions =
- suggestionsVisible && suggestionsPosition === 'above';
- const hideShortcutsHintForSuggestions = hideUiDetailsForSuggestions;
- const isModelIdle = uiState.streamingState === StreamingState.Idle;
- const isModelResponding =
- uiState.streamingState === StreamingState.Responding;
- const isBufferEmpty = uiState.buffer.text.length === 0;
- const canShowShortcutsHint =
- (isModelIdle || isModelResponding) &&
- isBufferEmpty &&
- !hasPendingActionRequired;
-
- const [showShortcutsHintDebounced, setShowShortcutsHintDebounced] =
- useState(canShowShortcutsHint);
-
- useEffect(() => {
- if (!canShowShortcutsHint) {
- setShowShortcutsHintDebounced(false);
- return;
- }
-
- const timeout = setTimeout(() => {
- setShowShortcutsHintDebounced(true);
- }, 200);
-
- return () => clearTimeout(timeout);
- }, [canShowShortcutsHint]);
-
- /**
- * Use the setting if provided, otherwise default to true for the new UX.
- * This allows tests to override the collapse behavior.
- */
- const shouldCollapseDuringApproval =
- settings.merged.ui.collapseDrawerDuringApproval !== false;
-
->>>>>>> 96b0876e6 (feat(cli): unify session modes in footer and reorganize composer layout)
- if (hasPendingActionRequired && shouldCollapseDuringApproval) {
- return null;
- }
-
- const showShortcutsHelp =
- uiState.shortcutsHelpVisible &&
- uiState.streamingState === StreamingState.Idle &&
- !hasPendingActionRequired;
- const hasToast = shouldShowToast(uiState);
-<<<<<<< HEAD
- const hideUiDetailsForSuggestions =
- suggestionsVisible && suggestionsPosition === 'above';
-
- // Mini Mode VIP Flags (Pure Content Triggers)
- const showMinimalToast = hasToast;
-=======
- const showLoadingIndicator =
- (!uiState.embeddedShellFocused || uiState.isBackgroundShellVisible) &&
- uiState.streamingState === StreamingState.Responding &&
- !hasPendingActionRequired;
-
- const hasMinimalStatusBleedThrough = shouldShowToast(uiState);
-
- const showMinimalContextBleedThrough =
- !settings.merged.ui.footer.hideContextPercentage &&
- isContextUsageHigh(
- uiState.sessionStats.lastPromptTokenCount,
- typeof uiState.currentModel === 'string'
- ? uiState.currentModel
- : undefined,
- );
-
- const shouldReserveSpaceForShortcutsHint =
- settings.merged.ui.showShortcutsHint &&
- !hideShortcutsHintForSuggestions &&
- !hasPendingActionRequired;
- const showShortcutsHint =
- shouldReserveSpaceForShortcutsHint && showShortcutsHintDebounced;
- const showMinimalInlineLoading = !showUiDetails && showLoadingIndicator;
- const showMinimalBleedThroughRow =
- !showUiDetails &&
- (hasMinimalStatusBleedThrough || showMinimalContextBleedThrough);
- const showMinimalMetaRow =
- !showUiDetails &&
- (showMinimalInlineLoading ||
- showMinimalBleedThroughRow ||
- shouldReserveSpaceForShortcutsHint);
-
- const loadingPhrases = settings.merged.ui.loadingPhrases;
- const showTips = loadingPhrases === 'tips' || loadingPhrases === 'all';
- const showWit = loadingPhrases === 'witty' || loadingPhrases === 'all';
->>>>>>> 96b0876e6 (feat(cli): unify session modes in footer and reorganize composer layout)
-
- return (
-
- {(!uiState.slashCommands ||
- !uiState.isConfigInitialized ||
- uiState.isResuming) && (
-
- )}
-
- {showUiDetails && (
-
- )}
-
- {showUiDetails && }
-
-<<<<<<< HEAD
- {showShortcutsHelp && }
-
- {(showUiDetails || showMinimalToast) && (
-
-
-
- )}
-
-
-
-=======
-
-
-
- {showUiDetails && (hasToast ? : null)}
-
-
- {showUiDetails && showShortcutsHint && }
-
-
- {showMinimalMetaRow && (
-
-
- {showMinimalInlineLoading && (
-
- )}
- {hasMinimalStatusBleedThrough && (
-
-
-
- )}
-
- {(showMinimalContextBleedThrough ||
- shouldReserveSpaceForShortcutsHint) && (
-
- {showMinimalContextBleedThrough && (
-
- )}
-
- {showShortcutsHint && }
-
-
- )}
-
- )}
- {showShortcutsHelp && }
- {showUiDetails && }
- {showUiDetails && (
-
-
- {showLoadingIndicator && (
-
- )}
-
-
-
- {!showLoadingIndicator && (
-
- )}
-
-
- )}
->>>>>>> 96b0876e6 (feat(cli): unify session modes in footer and reorganize composer layout)
-
-
- {showUiDetails && uiState.showErrorDetails && (
-
-
-
-
-
-
- )}
-
- {uiState.isInputActive && (
-
- )}
-
- {showUiDetails &&
- !settings.merged.ui.hideFooter &&
- !isScreenReaderEnabled && }
-
- );
-};
diff --git a/packages/cli/src/ui/components/Composer.tsx b/packages/cli/src/ui/components/Composer.tsx
index 6c79e86dda..5854c2639a 100644
--- a/packages/cli/src/ui/components/Composer.tsx
+++ b/packages/cli/src/ui/components/Composer.tsx
@@ -259,7 +259,9 @@ export const Composer = ({ isFocused = true }: { isFocused?: boolean }) => {
{showUiDetails &&
!settings.merged.ui.hideFooter &&
- !isScreenReaderEnabled && }
+ !isScreenReaderEnabled && (
+
+ )}
);
};
diff --git a/packages/cli/src/ui/components/Footer.tsx b/packages/cli/src/ui/components/Footer.tsx
index 05a329a96a..ea31a711ac 100644
--- a/packages/cli/src/ui/components/Footer.tsx
+++ b/packages/cli/src/ui/components/Footer.tsx
@@ -171,7 +171,7 @@ function isFooterItemId(id: string): id is FooterItemId {
}
interface FooterColumn {
- id: FooterItemId;
+ id: string;
header: string;
element: (maxWidth: number) => React.ReactNode;
width: number;
@@ -233,7 +233,7 @@ export const Footer: React.FC<{ copyModeEnabled?: boolean }> = ({
const potentialColumns: FooterColumn[] = [];
const addCol = (
- id: FooterItemId,
+ id: string,
header: string,
element: (maxWidth: number) => React.ReactNode,
dataWidth: number,
@@ -249,12 +249,10 @@ export const Footer: React.FC<{ copyModeEnabled?: boolean }> = ({
};
// 1. System Indicators (Far Left, high priority)
- // Note: These don't have IDs in ALL_ITEMS yet, but we handle them as specials
if (displayVimMode) {
const vimStr = `[${displayVimMode}]`;
- // We'll use a hacky cast for now or ideally update ALL_ITEMS
addCol(
- 'mode', // Using 'mode' as a placeholder for system indicators
+ 'vim-mode',
'',
() => {vimStr},
vimStr.length,
@@ -462,10 +460,10 @@ export const Footer: React.FC<{ copyModeEnabled?: boolean }> = ({
}
// 3. Transients
- if (corgiMode) addCol('mode', '', () => , 5); // Hacky ID for now
+ if (corgiMode) addCol('corgi-mode', '', () => , 5);
if (showErrorSummary) {
addCol(
- 'mode', // Hacky ID
+ 'error-summary',
'',
() => ,
12,
diff --git a/packages/cli/src/ui/components/__snapshots__/Footer.test.tsx.snap b/packages/cli/src/ui/components/__snapshots__/Footer.test.tsx.snap
index 3980ddbd0a..2f93f3eb26 100644
--- a/packages/cli/src/ui/components/__snapshots__/Footer.test.tsx.snap
+++ b/packages/cli/src/ui/components/__snapshots__/Footer.test.tsx.snap
@@ -1,45 +1,49 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[` > displays "Limit reached" message when remaining is 0 1`] = `
-" workspace (/directory) sandbox /model /stats
- ~/project/foo/bar/and/some/more/directories/to/make/it/long no sandbox gemini-pro limit reached
+" mode (Shift+Tab) workspace (/directory) sandbox /model /stats
+ manual ~/project/foo/bar/and/some/more/directories/to/make/it/lo no sandbox gemini-pro limit reached
"
`;
exports[` > displays the usage indicator when usage is low 1`] = `
-" workspace (/directory) sandbox /model /stats
- ~/project/foo/bar/and/some/more/directories/to/make/it/long no sandbox gemini-pro 85%
+" mode (Shift+Tab) workspace (/directory) sandbox /model /stats
+ manual ~/project/foo/bar/and/some/more/directories/to/make/it/long no sandbox gemini-pro 85%
"
`;
exports[` > footer configuration filtering (golden snapshots) > renders complete footer in narrow terminal (baseline narrow) > complete-footer-narrow 1`] = `
-" workspace (/directory) sandbox /model context
- ...me/more/directories/to/make/it/long no sandbox gemini-pro 14%
+" mode (Shift+Tab) workspace (/directory) sandbox /model
+ manual ...rectories/to/make/it/long no sandbox gemini-pro …
"
`;
exports[` > footer configuration filtering (golden snapshots) > renders complete footer with all sections visible (baseline) > complete-footer-wide 1`] = `
-" workspace (/directory) sandbox /model context
- ~/project/foo/bar/and/some/more/directories/to/make/it/long no sandbox gemini-pro 14% used
+" mode (Shift+Tab) workspace (/directory) sandbox /model context
+ manual ~/project/foo/bar/and/some/more/directories/to/make/it/long no sandbox gemini-pro 14% used
"
`;
exports[` > footer configuration filtering (golden snapshots) > renders footer with CWD and model info hidden to test alignment (only sandbox visible) > footer-only-sandbox 1`] = `
-" sandbox
- no sandbox
+" mode (Shift+Tab) sandbox
+ manual no sandbox
"
`;
-exports[` > footer configuration filtering (golden snapshots) > renders footer with all optional sections hidden (minimal footer) > footer-minimal 1`] = `""`;
+exports[` > footer configuration filtering (golden snapshots) > renders footer with all optional sections hidden (minimal footer) > footer-minimal 1`] = `
+" mode (Shift+Tab)
+ manual
+"
+`;
exports[` > footer configuration filtering (golden snapshots) > renders footer with only model info hidden (partial filtering) > footer-no-model 1`] = `
-" workspace (/directory) sandbox
- ~/project/foo/bar/and/some/more/directories/to/make/it/long no sandbox
+" mode (Shift+Tab) workspace (/directory) sandbox
+ manual ~/project/foo/bar/and/some/more/directories/to/make/it/long no sandbox
"
`;
exports[` > hides the usage indicator when usage is not near limit 1`] = `
-" workspace (/directory) sandbox /model /stats
- ~/project/foo/bar/and/some/more/directories/to/make/it/long no sandbox gemini-pro 15%
+" mode (Shift+Tab) workspace (/directory) sandbox /model /stats
+ manual ~/project/foo/bar/and/some/more/directories/to/make/it/long no sandbox gemini-pro 15%
"
`;