diff --git a/composer-layout-spec.md b/composer-layout-spec.md
new file mode 100644
index 0000000000..eb4ede2799
--- /dev/null
+++ b/composer-layout-spec.md
@@ -0,0 +1,63 @@
+# Layout Refinement: Approval Mode & Composer Organization
+
+## Goal
+
+The primary objective is to streamline the main interaction area by moving the
+Approval Mode status to the footer and reorganizing the remaining status
+indicators within the Composer for a better information hierarchy.
+
+## Phase 1: The Move (Approval Mode to Footer)
+
+The `ApprovalModeIndicator` (manual, auto-accept, plan, YOLO) is removed
+entirely from the Composer component and its "bleed-through" logic. It is
+repositioned as the absolute first item (far left) in the application footer.
+
+- **Header:** `mode (Shift+Tab)`
+- **Data:** Streamlined labels (`manual`, `auto-accept`, `plan`, `YOLO`) using
+ the existing semantic colors.
+- **Default:** Visible by default for all users.
+
+## Phase 2: The Swap (Composer Internal Layout)
+
+Once the `ApprovalModeIndicator` is removed, the remaining transient and state
+elements within the Composer are swapped across the horizontal divider.
+
+### 1. The "Above Divider" Zone (Transient Environment)
+
+This area is for transient notifications that contextualize the current
+environment but are not the active process.
+
+- **Toast Messages:** (e.g., "Press Ctrl+C again to exit")
+- **Shell Mode Indicator**
+- **Raw Markdown Indicator**
+- **Shortcuts Hint:** Remains flush right.
+
+### 2. The "Below Divider" Zone (Active Processing)
+
+This area is reserved exclusively for what the application is _currently doing_.
+It sits directly above the input prompt.
+
+- **Loading Indicator:** (e.g., "Thinking...", "Executing Hooks")
+- **Status Display:** (Context usage summary)
+
+## Target Layout Mockup
+
+```text
+[ConfigInitDisplay]
+[QueuedMessageDisplay]
+[TodoTray]
+
+[ToastDisplay | ShellModeIndicator] [ShortcutsHint]
+----------------------------------------------------------------------
+[LoadingIndicator (e.g., Thinking...)]
+ [StatusDisplay]
+
+[InputPrompt]
+```
+
+## Key Principles
+
+- **Clean Input:** The main input area should feel less crowded by offloading
+ persistent status (Mode) to the footer.
+- **Logical Flow:** Transient "Alerts" (Toasts) go above the line; active "Work"
+ (Loading) goes below the line, closest to the prompt.
diff --git a/packages/cli/src/config/footerItems.test.ts b/packages/cli/src/config/footerItems.test.ts
index 420246811b..2dfd3899c2 100644
--- a/packages/cli/src/config/footerItems.test.ts
+++ b/packages/cli/src/config/footerItems.test.ts
@@ -15,6 +15,7 @@ describe('deriveItemsFromLegacySettings', () => {
}).merged;
const items = deriveItemsFromLegacySettings(settings);
expect(items).toEqual([
+ 'approval-mode',
'workspace',
'git-branch',
'sandbox',
@@ -82,6 +83,7 @@ describe('deriveItemsFromLegacySettings', () => {
}).merged;
const items = deriveItemsFromLegacySettings(settings);
expect(items).toEqual([
+ 'approval-mode',
'git-branch',
'sandbox',
'context-used',
diff --git a/packages/cli/src/config/footerItems.ts b/packages/cli/src/config/footerItems.ts
index 8410d0b5ec..b030a9d153 100644
--- a/packages/cli/src/config/footerItems.ts
+++ b/packages/cli/src/config/footerItems.ts
@@ -7,6 +7,11 @@
import type { MergedSettings } from './settings.js';
export const ALL_ITEMS = [
+ {
+ id: 'approval-mode',
+ header: 'mode (Shift+Tab)',
+ description: 'Current approval mode',
+ },
{
id: 'workspace',
header: 'workspace (/directory)',
@@ -62,6 +67,7 @@ export const ALL_ITEMS = [
export type FooterItemId = (typeof ALL_ITEMS)[number]['id'];
export const DEFAULT_ORDER = [
+ 'approval-mode',
'workspace',
'git-branch',
'sandbox',
@@ -78,6 +84,7 @@ export function deriveItemsFromLegacySettings(
settings: MergedSettings,
): string[] {
const defaults = [
+ 'approval-mode',
'workspace',
'git-branch',
'sandbox',
diff --git a/packages/cli/src/ui/__snapshots__/ToolConfirmationFullFrame-Full-Terminal-Tool-Confirmation-Snapshot-renders-tool-confirmation-box-in-the-frame-of-the-entire-terminal.snap.svg b/packages/cli/src/ui/__snapshots__/ToolConfirmationFullFrame-Full-Terminal-Tool-Confirmation-Snapshot-renders-tool-confirmation-box-in-the-frame-of-the-entire-terminal.snap.svg
index e8f43ed9fa..d52c508564 100644
--- a/packages/cli/src/ui/__snapshots__/ToolConfirmationFullFrame-Full-Terminal-Tool-Confirmation-Snapshot-renders-tool-confirmation-box-in-the-frame-of-the-entire-terminal.snap.svg
+++ b/packages/cli/src/ui/__snapshots__/ToolConfirmationFullFrame-Full-Terminal-Tool-Confirmation-Snapshot-renders-tool-confirmation-box-in-the-frame-of-the-entire-terminal.snap.svg
@@ -225,15 +225,16 @@
█
Initializing...
────────────────────────────────────────────────────────────────────────────────────────────────────
- Shift+Tab to accept edits
undefined undefined file
- workspace (/directory)
- sandbox
- /model
+ mode (Shift+Tab)
+ workspace (/directory)
+ sandbox
+ /model
context
- /directory
- no sandbox
- gemini-pro
+ manual
+ /directory
+ no sandbox
+ gemini-pro
17% used
\ No newline at end of file
diff --git a/packages/cli/src/ui/__snapshots__/ToolConfirmationFullFrame.test.tsx.snap b/packages/cli/src/ui/__snapshots__/ToolConfirmationFullFrame.test.tsx.snap
index 3e99760310..9513229a8d 100644
--- a/packages/cli/src/ui/__snapshots__/ToolConfirmationFullFrame.test.tsx.snap
+++ b/packages/cli/src/ui/__snapshots__/ToolConfirmationFullFrame.test.tsx.snap
@@ -37,8 +37,8 @@ exports[`Full Terminal Tool Confirmation Snapshot > renders tool confirmation bo
Initializing...
────────────────────────────────────────────────────────────────────────────────────────────────────
- Shift+Tab to accept edits undefined undefined file
- workspace (/directory) sandbox /model context
- /directory no sandbox gemini-pro 17% used
+ undefined undefined file
+ mode (Shift+Tab) workspace (/directory) sandbox /model context
+ manual /directory no sandbox gemini-pro 17% used
"
`;
diff --git a/packages/cli/src/ui/components/ApprovalModeIndicator.test.tsx b/packages/cli/src/ui/components/ApprovalModeIndicator.test.tsx
index 1b2decbe16..0e42f9238c 100644
--- a/packages/cli/src/ui/components/ApprovalModeIndicator.test.tsx
+++ b/packages/cli/src/ui/components/ApprovalModeIndicator.test.tsx
@@ -19,10 +19,7 @@ describe('ApprovalModeIndicator', () => {
it('renders correctly for AUTO_EDIT mode with plan enabled', async () => {
const { lastFrame } = await render(
- ,
+ ,
);
expect(lastFrame()).toMatchSnapshot();
});
@@ -50,10 +47,7 @@ describe('ApprovalModeIndicator', () => {
it('renders correctly for DEFAULT mode with plan enabled', async () => {
const { lastFrame } = await render(
- ,
+ ,
);
expect(lastFrame()).toMatchSnapshot();
});
diff --git a/packages/cli/src/ui/components/ApprovalModeIndicator.tsx b/packages/cli/src/ui/components/ApprovalModeIndicator.tsx
index 7e8f388c82..7ed5da9c7c 100644
--- a/packages/cli/src/ui/components/ApprovalModeIndicator.tsx
+++ b/packages/cli/src/ui/components/ApprovalModeIndicator.tsx
@@ -8,62 +8,40 @@ import type React from 'react';
import { Box, Text } from 'ink';
import { theme } from '../semantic-colors.js';
import { ApprovalMode } from '@google/gemini-cli-core';
-import { formatCommand } from '../key/keybindingUtils.js';
-import { Command } from '../key/keyBindings.js';
interface ApprovalModeIndicatorProps {
approvalMode: ApprovalMode;
- allowPlanMode?: boolean;
}
export const ApprovalModeIndicator: React.FC = ({
approvalMode,
- allowPlanMode,
}) => {
let textColor = '';
let textContent = '';
- let subText = '';
-
- const cycleHint = formatCommand(Command.CYCLE_APPROVAL_MODE);
- const yoloHint = formatCommand(Command.TOGGLE_YOLO);
switch (approvalMode) {
case ApprovalMode.AUTO_EDIT:
textColor = theme.status.warning;
- textContent = 'auto-accept edits';
- subText = allowPlanMode
- ? `${cycleHint} to plan`
- : `${cycleHint} to manual`;
+ textContent = 'auto-accept';
break;
case ApprovalMode.PLAN:
textColor = theme.status.success;
textContent = 'plan';
- subText = `${cycleHint} to manual`;
break;
case ApprovalMode.YOLO:
textColor = theme.status.error;
textContent = 'YOLO';
- subText = yoloHint;
break;
case ApprovalMode.DEFAULT:
default:
textColor = theme.text.accent;
- textContent = '';
- subText = `${cycleHint} to accept edits`;
+ textContent = 'manual';
break;
}
return (
-
- {textContent ? textContent : null}
- {subText ? (
-
- {textContent ? ' ' : ''}
- {subText}
-
- ) : null}
-
+ {textContent}
);
};
diff --git a/packages/cli/src/ui/components/Composer.test.tsx b/packages/cli/src/ui/components/Composer.test.tsx
index 8df5f690e7..f80e53e5ef 100644
--- a/packages/cli/src/ui/components/Composer.test.tsx
+++ b/packages/cli/src/ui/components/Composer.test.tsx
@@ -80,10 +80,6 @@ vi.mock('./HookStatusDisplay.js', () => ({
HookStatusDisplay: () => HookStatusDisplay,
}));
-vi.mock('./ApprovalModeIndicator.js', () => ({
- ApprovalModeIndicator: () => ApprovalModeIndicator,
-}));
-
vi.mock('./ShellModeIndicator.js', () => ({
ShellModeIndicator: () => ShellModeIndicator,
}));
@@ -471,7 +467,7 @@ describe('Composer', () => {
expect(output).toContain('LoadingIndicator');
});
- it('renders both LoadingIndicator and ApprovalModeIndicator when streaming in full UI mode', async () => {
+ it('renders LoadingIndicator when streaming in full UI mode', async () => {
const uiState = createMockUIState({
streamingState: StreamingState.Responding,
thought: {
@@ -485,7 +481,6 @@ describe('Composer', () => {
const output = lastFrame();
expect(output).toContain('LoadingIndicator: Thinking');
- expect(output).toContain('ApprovalModeIndicator');
});
it('does NOT render LoadingIndicator when embedded shell is focused and background shell is NOT visible', async () => {
@@ -535,7 +530,7 @@ describe('Composer', () => {
});
describe('Context and Status Display', () => {
- it('shows StatusDisplay and ApprovalModeIndicator in normal state', async () => {
+ it('shows StatusDisplay in normal state', async () => {
const uiState = createMockUIState({
ctrlCPressedOnce: false,
ctrlDPressedOnce: false,
@@ -546,11 +541,10 @@ describe('Composer', () => {
const output = lastFrame();
expect(output).toContain('StatusDisplay');
- expect(output).toContain('ApprovalModeIndicator');
expect(output).not.toContain('ToastDisplay');
});
- it('shows ToastDisplay and hides ApprovalModeIndicator when a toast is present', async () => {
+ it('shows ToastDisplay when a toast is present', async () => {
const uiState = createMockUIState({
ctrlCPressedOnce: true,
});
@@ -559,7 +553,6 @@ describe('Composer', () => {
const output = lastFrame();
expect(output).toContain('ToastDisplay');
- expect(output).not.toContain('ApprovalModeIndicator');
expect(output).toContain('StatusDisplay');
});
@@ -575,7 +568,6 @@ describe('Composer', () => {
const output = lastFrame();
expect(output).toContain('ToastDisplay');
- expect(output).not.toContain('ApprovalModeIndicator');
});
});
@@ -591,7 +583,6 @@ describe('Composer', () => {
expect(output).toContain('ShortcutsHint');
expect(output).toContain('InputPrompt');
expect(output).not.toContain('Footer');
- expect(output).not.toContain('ApprovalModeIndicator');
expect(output).not.toContain('ContextSummaryDisplay');
});
@@ -615,25 +606,6 @@ describe('Composer', () => {
expect(lastFrame()).not.toContain('InputPrompt');
});
- it.each([
- [ApprovalMode.DEFAULT],
- [ApprovalMode.AUTO_EDIT],
- [ApprovalMode.PLAN],
- [ApprovalMode.YOLO],
- ])(
- 'shows ApprovalModeIndicator when approval mode is %s and shell mode is inactive',
- async (mode) => {
- const uiState = createMockUIState({
- showApprovalModeIndicator: mode,
- shellModeActive: false,
- });
-
- const { lastFrame } = await renderComposer(uiState);
-
- expect(lastFrame()).toMatch(/ApprovalModeIndic[\s\S]*ator/);
- },
- );
-
it('shows ShellModeIndicator when shell mode is active', async () => {
const uiState = createMockUIState({
shellModeActive: true,
@@ -664,55 +636,6 @@ describe('Composer', () => {
expect(lastFrame()).not.toContain('raw markdown mode');
});
- it.each([
- [ApprovalMode.YOLO, 'YOLO'],
- [ApprovalMode.PLAN, 'plan'],
- [ApprovalMode.AUTO_EDIT, 'auto edit'],
- ])(
- 'shows minimal mode badge "%s" when clean UI details are hidden',
- async (mode, label) => {
- const uiState = createMockUIState({
- cleanUiDetailsVisible: false,
- showApprovalModeIndicator: mode,
- });
-
- const { lastFrame } = await renderComposer(uiState);
- expect(lastFrame()).toContain(label);
- },
- );
-
- it('hides minimal mode badge while loading in clean mode', async () => {
- const uiState = createMockUIState({
- cleanUiDetailsVisible: false,
- streamingState: StreamingState.Responding,
- elapsedTime: 1,
- showApprovalModeIndicator: ApprovalMode.PLAN,
- });
-
- const { lastFrame } = await renderComposer(uiState);
- const output = lastFrame();
- expect(output).toContain('LoadingIndicator');
- expect(output).not.toContain('plan');
- expect(output).not.toContain('ShortcutsHint');
- });
-
- it('hides minimal mode badge while action-required state is active', async () => {
- const uiState = createMockUIState({
- cleanUiDetailsVisible: false,
- showApprovalModeIndicator: ApprovalMode.PLAN,
- customDialog: (
-
- Prompt
-
- ),
- });
-
- const { lastFrame } = await renderComposer(uiState);
- const output = lastFrame();
- expect(output).not.toContain('plan');
- expect(output).not.toContain('ShortcutsHint');
- });
-
it('shows Esc rewind prompt in minimal mode without showing full UI', async () => {
const uiState = createMockUIState({
cleanUiDetailsVisible: false,
@@ -939,9 +862,7 @@ describe('Composer', () => {
showApprovalModeIndicator: ApprovalMode.YOLO,
});
- const { lastFrame } = await renderComposer(uiState);
-
- expect(lastFrame()).not.toContain('ApprovalModeIndicator');
+ await renderComposer(uiState);
});
it('keeps shortcuts hint when suggestions are visible below input in regular buffer', async () => {
diff --git a/packages/cli/src/ui/components/Composer.tsx b/packages/cli/src/ui/components/Composer.tsx
index 053aaa5260..cfb2c0c474 100644
--- a/packages/cli/src/ui/components/Composer.tsx
+++ b/packages/cli/src/ui/components/Composer.tsx
@@ -5,16 +5,11 @@
*/
import { useState, useEffect, useMemo } from 'react';
-import { Box, Text, useIsScreenReaderEnabled } from 'ink';
-import {
- ApprovalMode,
- checkExhaustive,
- CoreToolCallStatus,
-} from '@google/gemini-cli-core';
+import { Box, useIsScreenReaderEnabled } from 'ink';
+import { CoreToolCallStatus } from '@google/gemini-cli-core';
import { LoadingIndicator } from './LoadingIndicator.js';
import { StatusDisplay } from './StatusDisplay.js';
import { ToastDisplay, shouldShowToast } from './ToastDisplay.js';
-import { ApprovalModeIndicator } from './ApprovalModeIndicator.js';
import { ShellModeIndicator } from './ShellModeIndicator.js';
import { DetailedMessagesDisplay } from './DetailedMessagesDisplay.js';
import { RawMarkdownIndicator } from './RawMarkdownIndicator.js';
@@ -39,7 +34,6 @@ import { ConfigInitDisplay } from '../components/ConfigInitDisplay.js';
import { TodoTray } from './messages/Todo.js';
import { getInlineThinkingMode } from '../utils/inlineThinkingMode.js';
import { isContextUsageHigh } from '../utils/contextUsage.js';
-import { theme } from '../semantic-colors.js';
export const Composer = ({ isFocused = true }: { isFocused?: boolean }) => {
const config = useConfig();
@@ -112,34 +106,8 @@ export const Composer = ({ isFocused = true }: { isFocused?: boolean }) => {
!hasPendingActionRequired;
const hideUiDetailsForSuggestions =
suggestionsVisible && suggestionsPosition === 'above';
- const showApprovalIndicator =
- !uiState.shellModeActive && !hideUiDetailsForSuggestions;
const showRawMarkdownIndicator = !uiState.renderMarkdown;
- let modeBleedThrough: { text: string; color: string } | null = null;
- switch (showApprovalModeIndicator) {
- case ApprovalMode.YOLO:
- modeBleedThrough = { text: 'YOLO', color: theme.status.error };
- break;
- case ApprovalMode.PLAN:
- modeBleedThrough = { text: 'plan', color: theme.status.success };
- break;
- case ApprovalMode.AUTO_EDIT:
- modeBleedThrough = { text: 'auto edit', color: theme.status.warning };
- break;
- case ApprovalMode.DEFAULT:
- modeBleedThrough = null;
- break;
- default:
- checkExhaustive(showApprovalModeIndicator);
- modeBleedThrough = null;
- break;
- }
- const hideMinimalModeHintWhileBusy =
- !showUiDetails && (showLoadingIndicator || hasPendingActionRequired);
- const minimalModeBleedThrough = hideMinimalModeHintWhileBusy
- ? null
- : modeBleedThrough;
const hasMinimalStatusBleedThrough = shouldShowToast(uiState);
const showMinimalContextBleedThrough =
@@ -177,14 +145,10 @@ export const Composer = ({ isFocused = true }: { isFocused?: boolean }) => {
!hasPendingActionRequired;
const showShortcutsHint =
shouldReserveSpaceForShortcutsHint && showShortcutsHintDebounced;
- const showMinimalModeBleedThrough =
- !hideUiDetailsForSuggestions && Boolean(minimalModeBleedThrough);
const showMinimalInlineLoading = !showUiDetails && showLoadingIndicator;
const showMinimalBleedThroughRow =
!showUiDetails &&
- (showMinimalModeBleedThrough ||
- hasMinimalStatusBleedThrough ||
- showMinimalContextBleedThrough);
+ (hasMinimalStatusBleedThrough || showMinimalContextBleedThrough);
const showMinimalMetaRow =
!showUiDetails &&
(showMinimalInlineLoading ||
@@ -226,26 +190,29 @@ export const Composer = ({ isFocused = true }: { isFocused?: boolean }) => {
alignItems={isNarrow ? 'flex-start' : 'center'}
flexGrow={1}
>
- {showUiDetails && showLoadingIndicator && (
-
- )}
+ {showUiDetails &&
+ (hasToast ? (
+
+ ) : (
+
+ {uiState.shellModeActive && (
+
+
+
+ )}
+ {showRawMarkdownIndicator && (
+
+
+
+ )}
+
+ ))}
{
elapsedTime={uiState.elapsedTime}
/>
)}
- {showMinimalModeBleedThrough && minimalModeBleedThrough && (
-
- ● {minimalModeBleedThrough.text}
-
- )}
{hasMinimalStatusBleedThrough && (
-
+
)}
@@ -356,54 +312,25 @@ export const Composer = ({ isFocused = true }: { isFocused?: boolean }) => {
alignItems="center"
flexGrow={1}
>
- {hasToast ? (
-
- ) : (
-
- {showApprovalIndicator && (
-
- )}
- {!showLoadingIndicator && (
- <>
- {uiState.shellModeActive && (
-
-
-
- )}
- {showRawMarkdownIndicator && (
-
-
-
- )}
- >
- )}
-
+ {showLoadingIndicator && (
+
)}
diff --git a/packages/cli/src/ui/components/Footer.tsx b/packages/cli/src/ui/components/Footer.tsx
index c6816339f5..925edabfe1 100644
--- a/packages/cli/src/ui/components/Footer.tsx
+++ b/packages/cli/src/ui/components/Footer.tsx
@@ -14,6 +14,7 @@ import {
checkExhaustive,
} from '@google/gemini-cli-core';
import { ConsoleSummaryDisplay } from './ConsoleSummaryDisplay.js';
+import { ApprovalModeIndicator } from './ApprovalModeIndicator.js';
import process from 'node:process';
import { MemoryUsageDisplay } from './MemoryUsageDisplay.js';
import { ContextUsageDisplay } from './ContextUsageDisplay.js';
@@ -261,6 +262,19 @@ export const Footer: React.FC = () => {
const header = itemConfig?.header ?? id;
switch (id) {
+ case 'approval-mode': {
+ addCol(
+ id,
+ header,
+ () => (
+
+ ),
+ 11, // 'auto-accept' is 11 chars
+ );
+ break;
+ }
case 'workspace': {
const fullPath = tildeifyPath(targetDir);
const debugSuffix = debugMode ? ' ' + (debugMessage || '--debug') : '';
diff --git a/packages/cli/src/ui/components/FooterConfigDialog.test.tsx b/packages/cli/src/ui/components/FooterConfigDialog.test.tsx
index 12829cd99a..c797162f2c 100644
--- a/packages/cli/src/ui/components/FooterConfigDialog.test.tsx
+++ b/packages/cli/src/ui/components/FooterConfigDialog.test.tsx
@@ -46,7 +46,7 @@ describe('', () => {
});
await waitFor(() => {
- expect(lastFrame()).toContain('[ ] workspace');
+ expect(lastFrame()).toContain('[ ] approval-mode');
});
act(() => {
@@ -54,7 +54,7 @@ describe('', () => {
});
await waitFor(() => {
- expect(lastFrame()).toContain('[✓] workspace');
+ expect(lastFrame()).toContain('[✓] approval-mode');
});
});
@@ -65,26 +65,26 @@ describe('', () => {
{ settings },
);
- // Initial order: workspace, git-branch, ...
+ // Initial order: approval-mode, workspace, ...
const output = lastFrame();
+ const modeIdx = output.indexOf('] approval-mode');
const cwdIdx = output.indexOf('] workspace');
- const branchIdx = output.indexOf('] git-branch');
+ expect(modeIdx).toBeGreaterThan(-1);
expect(cwdIdx).toBeGreaterThan(-1);
- expect(branchIdx).toBeGreaterThan(-1);
- expect(cwdIdx).toBeLessThan(branchIdx);
+ expect(modeIdx).toBeLessThan(cwdIdx);
- // Move workspace down (right arrow)
+ // Move approval-mode down (right arrow)
act(() => {
stdin.write('\u001b[C'); // Right arrow
});
await waitFor(() => {
const outputAfter = lastFrame();
+ const modeIdxAfter = outputAfter.indexOf('] approval-mode');
const cwdIdxAfter = outputAfter.indexOf('] workspace');
- const branchIdxAfter = outputAfter.indexOf('] git-branch');
+ expect(modeIdxAfter).toBeGreaterThan(-1);
expect(cwdIdxAfter).toBeGreaterThan(-1);
- expect(branchIdxAfter).toBeGreaterThan(-1);
- expect(branchIdxAfter).toBeLessThan(cwdIdxAfter);
+ expect(cwdIdxAfter).toBeLessThan(modeIdxAfter);
});
});
@@ -116,7 +116,7 @@ describe('', () => {
expect(lastFrame()).toContain('~/project/path');
// Move focus down to 'code-changes' (which has colored elements)
- for (let i = 0; i < 8; i++) {
+ for (let i = 0; i < 9; i++) {
act(() => {
stdin.write('\u001b[B'); // Down arrow
});
@@ -188,22 +188,22 @@ describe('', () => {
const workspaceIdx = output.indexOf('] workspace');
expect(workspaceIdx).toBeLessThan(branchIdx);
- // Try to move workspace up (left arrow) while it's at the top
+ // Try to move approval-mode up (left arrow) while it's at the top
act(() => {
stdin.write('\u001b[D'); // Left arrow
});
- // Move workspace down (right arrow)
+ // Move approval-mode down (right arrow)
act(() => {
stdin.write('\u001b[C'); // Right arrow
});
await waitFor(() => {
const outputAfter = lastFrame();
- const bIdxAfter = outputAfter.indexOf('] git-branch');
- const wIdxAfter = outputAfter.indexOf('] workspace');
- // workspace should now be after git-branch
- expect(bIdxAfter).toBeLessThan(wIdxAfter);
+ const modeIdxAfter = outputAfter.indexOf('] approval-mode');
+ const cwdIdxAfter = outputAfter.indexOf('] workspace');
+ // approval-mode should now be after workspace
+ expect(cwdIdxAfter).toBeLessThan(modeIdxAfter);
});
});
diff --git a/packages/cli/src/ui/components/__snapshots__/ApprovalModeIndicator.test.tsx.snap b/packages/cli/src/ui/components/__snapshots__/ApprovalModeIndicator.test.tsx.snap
index 8ddb141478..e9e1674197 100644
--- a/packages/cli/src/ui/components/__snapshots__/ApprovalModeIndicator.test.tsx.snap
+++ b/packages/cli/src/ui/components/__snapshots__/ApprovalModeIndicator.test.tsx.snap
@@ -1,31 +1,31 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`ApprovalModeIndicator > renders correctly for AUTO_EDIT mode 1`] = `
-"auto-accept edits Shift+Tab to manual
+"auto-accept
"
`;
exports[`ApprovalModeIndicator > renders correctly for AUTO_EDIT mode with plan enabled 1`] = `
-"auto-accept edits Shift+Tab to plan
+"auto-accept
"
`;
exports[`ApprovalModeIndicator > renders correctly for DEFAULT mode 1`] = `
-"Shift+Tab to accept edits
+"manual
"
`;
exports[`ApprovalModeIndicator > renders correctly for DEFAULT mode with plan enabled 1`] = `
-"Shift+Tab to accept edits
+"manual
"
`;
exports[`ApprovalModeIndicator > renders correctly for PLAN mode 1`] = `
-"plan Shift+Tab to manual
+"plan
"
`;
exports[`ApprovalModeIndicator > renders correctly for YOLO mode 1`] = `
-"YOLO Ctrl+Y
+"YOLO
"
`;
diff --git a/packages/cli/src/ui/components/__snapshots__/AskUserDialog.test.tsx.snap b/packages/cli/src/ui/components/__snapshots__/AskUserDialog.test.tsx.snap
index 3992cdd60c..1ab67a46db 100644
--- a/packages/cli/src/ui/components/__snapshots__/AskUserDialog.test.tsx.snap
+++ b/packages/cli/src/ui/components/__snapshots__/AskUserDialog.test.tsx.snap
@@ -11,17 +11,6 @@ Enter to submit · Esc to cancel
"
`;
-exports[`AskUserDialog > Choice question placeholder > uses default placeholder when not provided 2`] = `
-"Select your preferred language:
-
- 1. TypeScript
- 2. JavaScript
-● 3. Enter a custom value
-
-Enter to submit · Esc to cancel
-"
-`;
-
exports[`AskUserDialog > Choice question placeholder > uses placeholder for "Other" option when provided 1`] = `
"Select your preferred language:
@@ -33,17 +22,6 @@ Enter to submit · Esc to cancel
"
`;
-exports[`AskUserDialog > Choice question placeholder > uses placeholder for "Other" option when provided 2`] = `
-"Select your preferred language:
-
- 1. TypeScript
- 2. JavaScript
-● 3. Type another language...
-
-Enter to submit · Esc to cancel
-"
-`;
-
exports[`AskUserDialog > Scroll Arrows (useAlternateBuffer: false) > shows scroll arrows correctly when useAlternateBuffer is false 1`] = `
"Choose an option
@@ -60,20 +38,6 @@ Enter to select · ↑/↓ to navigate · Esc to cancel
"
`;
-exports[`AskUserDialog > Scroll Arrows (useAlternateBuffer: false) > shows scroll arrows correctly when useAlternateBuffer is false 2`] = `
-"Choose an option
-
-▲
-● 1. Option 1
- Description 1
- 2. Option 2
- Description 2
-▼
-
-Enter to select · ↑/↓ to navigate · Esc to cancel
-"
-`;
-
exports[`AskUserDialog > Scroll Arrows (useAlternateBuffer: true) > shows scroll arrows correctly when useAlternateBuffer is true 1`] = `
"Choose an option
@@ -113,45 +77,6 @@ Enter to select · ↑/↓ to navigate · Esc to cancel
"
`;
-exports[`AskUserDialog > Scroll Arrows (useAlternateBuffer: true) > shows scroll arrows correctly when useAlternateBuffer is true 2`] = `
-"Choose an option
-
-● 1. Option 1
- Description 1
- 2. Option 2
- Description 2
- 3. Option 3
- Description 3
- 4. Option 4
- Description 4
- 5. Option 5
- Description 5
- 6. Option 6
- Description 6
- 7. Option 7
- Description 7
- 8. Option 8
- Description 8
- 9. Option 9
- Description 9
- 10. Option 10
- Description 10
- 11. Option 11
- Description 11
- 12. Option 12
- Description 12
- 13. Option 13
- Description 13
- 14. Option 14
- Description 14
- 15. Option 15
- Description 15
- 16. Enter a custom value
-
-Enter to select · ↑/↓ to navigate · Esc to cancel
-"
-`;
-
exports[`AskUserDialog > Text type questions > renders text input for type: "text" 1`] = `
"What should we name this component?
@@ -294,19 +219,3 @@ exports[`AskUserDialog > verifies "All of the above" visual state with snapshot
Enter to select · ↑/↓ to navigate · Esc to cancel
"
`;
-
-exports[`AskUserDialog > verifies "All of the above" visual state with snapshot 2`] = `
-"Which features?
-(Select all that apply)
-
- 1. [x] TypeScript
- 2. [x] ESLint
-● 3. [x] All of the above
- Select all options
- 4. [ ] Enter a custom value
- Done
- Finish selection
-
-Enter to select · ↑/↓ to navigate · Esc to cancel
-"
-`;
diff --git a/packages/cli/src/ui/components/__snapshots__/Composer.test.tsx.snap b/packages/cli/src/ui/components/__snapshots__/Composer.test.tsx.snap
index 452663d719..d20c918b6a 100644
--- a/packages/cli/src/ui/components/__snapshots__/Composer.test.tsx.snap
+++ b/packages/cli/src/ui/components/__snapshots__/Composer.test.tsx.snap
@@ -3,7 +3,7 @@
exports[`Composer > Snapshots > matches snapshot in idle state 1`] = `
" ShortcutsHint
────────────────────────────────────────────────────────────────────────────────────────────────────
- ApprovalModeIndicator StatusDisplay
+ StatusDisplay
InputPrompt: Type your message or @path/to/file
Footer
"
@@ -25,7 +25,6 @@ exports[`Composer > Snapshots > matches snapshot in narrow view 1`] = `
"
ShortcutsHint
────────────────────────────────────────
- ApprovalModeIndicator
StatusDisplay
InputPrompt: Type your message or
@@ -35,9 +34,9 @@ Footer
`;
exports[`Composer > Snapshots > matches snapshot while streaming 1`] = `
-" LoadingIndicator: Thinking
+"
────────────────────────────────────────────────────────────────────────────────────────────────────
- ApprovalModeIndicator
+ LoadingIndicator: Thinking
InputPrompt: Type your message or @path/to/file
Footer
"
diff --git a/packages/cli/src/ui/components/__snapshots__/ExitPlanModeDialog.test.tsx.snap b/packages/cli/src/ui/components/__snapshots__/ExitPlanModeDialog.test.tsx.snap
index 9e210e3438..073c106ceb 100644
--- a/packages/cli/src/ui/components/__snapshots__/ExitPlanModeDialog.test.tsx.snap
+++ b/packages/cli/src/ui/components/__snapshots__/ExitPlanModeDialog.test.tsx.snap
@@ -27,33 +27,6 @@ Enter to select · ↑/↓ to navigate · Ctrl+X to edit plan · Esc to cancel
"
`;
-exports[`ExitPlanModeDialog > useAlternateBuffer: false > bubbles up Ctrl+C when feedback is empty while editing 2`] = `
-"Overview
-
-Add user authentication to the CLI application.
-
-Implementation Steps
-
- 1. Create src/auth/AuthService.ts with login/logout methods
- 2. Add session storage in src/storage/SessionStore.ts
- 3. Update src/commands/index.ts to check auth status
- 4. Add tests in src/auth/__tests__/
-
-Files to Modify
-
- - src/index.ts - Add auth middleware
- - src/config.ts - Add auth configuration options
-
- 1. Yes, automatically accept edits
- Approves plan and allows tools to run automatically
- 2. Yes, manually accept edits
- Approves plan but requires confirmation for each tool
-● 3. Type your feedback...
-
-Enter to submit · Ctrl+X to edit plan · Esc to cancel
-"
-`;
-
exports[`ExitPlanModeDialog > useAlternateBuffer: false > calls onFeedback when feedback is typed and submitted 1`] = `
"Overview
@@ -81,33 +54,6 @@ Enter to select · ↑/↓ to navigate · Ctrl+X to edit plan · Esc to cancel
"
`;
-exports[`ExitPlanModeDialog > useAlternateBuffer: false > calls onFeedback when feedback is typed and submitted 2`] = `
-"Overview
-
-Add user authentication to the CLI application.
-
-Implementation Steps
-
- 1. Create src/auth/AuthService.ts with login/logout methods
- 2. Add session storage in src/storage/SessionStore.ts
- 3. Update src/commands/index.ts to check auth status
- 4. Add tests in src/auth/__tests__/
-
-Files to Modify
-
- - src/index.ts - Add auth middleware
- - src/config.ts - Add auth configuration options
-
- 1. Yes, automatically accept edits
- Approves plan and allows tools to run automatically
- 2. Yes, manually accept edits
- Approves plan but requires confirmation for each tool
-● 3. Add tests
-
-Enter to submit · Ctrl+X to edit plan · Esc to cancel
-"
-`;
-
exports[`ExitPlanModeDialog > useAlternateBuffer: false > displays error state when file read fails 1`] = `
" Error reading plan: File not found
"
@@ -194,33 +140,6 @@ Enter to select · ↑/↓ to navigate · Ctrl+X to edit plan · Esc to cancel
"
`;
-exports[`ExitPlanModeDialog > useAlternateBuffer: true > bubbles up Ctrl+C when feedback is empty while editing 2`] = `
-"Overview
-
-Add user authentication to the CLI application.
-
-Implementation Steps
-
- 1. Create src/auth/AuthService.ts with login/logout methods
- 2. Add session storage in src/storage/SessionStore.ts
- 3. Update src/commands/index.ts to check auth status
- 4. Add tests in src/auth/__tests__/
-
-Files to Modify
-
- - src/index.ts - Add auth middleware
- - src/config.ts - Add auth configuration options
-
- 1. Yes, automatically accept edits
- Approves plan and allows tools to run automatically
- 2. Yes, manually accept edits
- Approves plan but requires confirmation for each tool
-● 3. Type your feedback...
-
-Enter to submit · Ctrl+X to edit plan · Esc to cancel
-"
-`;
-
exports[`ExitPlanModeDialog > useAlternateBuffer: true > calls onFeedback when feedback is typed and submitted 1`] = `
"Overview
@@ -248,33 +167,6 @@ Enter to select · ↑/↓ to navigate · Ctrl+X to edit plan · Esc to cancel
"
`;
-exports[`ExitPlanModeDialog > useAlternateBuffer: true > calls onFeedback when feedback is typed and submitted 2`] = `
-"Overview
-
-Add user authentication to the CLI application.
-
-Implementation Steps
-
- 1. Create src/auth/AuthService.ts with login/logout methods
- 2. Add session storage in src/storage/SessionStore.ts
- 3. Update src/commands/index.ts to check auth status
- 4. Add tests in src/auth/__tests__/
-
-Files to Modify
-
- - src/index.ts - Add auth middleware
- - src/config.ts - Add auth configuration options
-
- 1. Yes, automatically accept edits
- Approves plan and allows tools to run automatically
- 2. Yes, manually accept edits
- Approves plan but requires confirmation for each tool
-● 3. Add tests
-
-Enter to submit · Ctrl+X to edit plan · Esc to cancel
-"
-`;
-
exports[`ExitPlanModeDialog > useAlternateBuffer: true > displays error state when file read fails 1`] = `
" Error reading plan: File not found
"
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%
"
`;
diff --git a/packages/cli/src/ui/components/__snapshots__/FooterConfigDialog--FooterConfigDialog-highlights-the-active-item-in-the-preview.snap.svg b/packages/cli/src/ui/components/__snapshots__/FooterConfigDialog--FooterConfigDialog-highlights-the-active-item-in-the-preview.snap.svg
index d4da9c5fa0..db2bc32923 100644
--- a/packages/cli/src/ui/components/__snapshots__/FooterConfigDialog--FooterConfigDialog-highlights-the-active-item-in-the-preview.snap.svg
+++ b/packages/cli/src/ui/components/__snapshots__/FooterConfigDialog--FooterConfigDialog-highlights-the-active-item-in-the-preview.snap.svg
@@ -1,8 +1,8 @@
-