mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-07-31 12:11:05 -07:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 686e6d1113 | |||
| d35d0dbab7 | |||
| a010c87c26 | |||
| f8a965da8e | |||
| 8d5b930ef5 | |||
| a44ea49cf7 |
@@ -0,0 +1,79 @@
|
||||
# Layout Refinement: Unified Session State & Composer Organization
|
||||
|
||||
## Goal
|
||||
|
||||
The objective is to consolidate all persistent "Session Modes" into a single,
|
||||
unified status area in the footer and reorganize the remaining transient
|
||||
elements in the Composer for a cleaner information hierarchy.
|
||||
|
||||
## Phase 1: Unified Footer Modes
|
||||
|
||||
The footer's first column is expanded to be the **Unified Mode Indicator**. It
|
||||
natively incorporates the three primary state toggles of the application.
|
||||
|
||||
- **Approval Mode:** (manual, auto-accept, plan, YOLO) - Always visible.
|
||||
- **Shell Mode:** Visible only when active.
|
||||
- **Raw Markdown Mode:** Visible only when active.
|
||||
|
||||
### Footer Layout
|
||||
|
||||
- **Header:** `mode (Shift+Tab)`
|
||||
- **Data Row:** Multiple modes are displayed in their respective semantic
|
||||
colors, separated by a middle dot (`·`).
|
||||
- **Example:** `plan · shell · raw`
|
||||
|
||||
## Phase 2: Composer Cleanup & Swap
|
||||
|
||||
With all modes moved to the footer, the Composer is simplified to handle only
|
||||
transient notifications and active processing states. These two areas are
|
||||
swapped across the horizontal divider.
|
||||
|
||||
### 1. The "Above Divider" Zone (Environment Alerts)
|
||||
|
||||
Reserved for transient notifications that alert the user to environment-level
|
||||
changes.
|
||||
|
||||
- **Toast Messages:** (e.g., "Press Ctrl+C again to exit")
|
||||
- **Shortcuts Hint:** (e.g., "? for shortcuts") - Remains flush right.
|
||||
|
||||
### 2. The "Below Divider" Zone (Active processing)
|
||||
|
||||
Reserved exclusively for the application's current activity. It sits directly
|
||||
above the input prompt for maximum visibility during streaming.
|
||||
|
||||
- **Loading Indicator:** (e.g., "Thinking...", "Executing Hooks")
|
||||
- **Status Display:** (Context usage summary)
|
||||
|
||||
## Target Layout Mockup
|
||||
|
||||
### Composer Area
|
||||
|
||||
```text
|
||||
[ConfigInitDisplay]
|
||||
[QueuedMessageDisplay]
|
||||
[TodoTray]
|
||||
|
||||
[ToastDisplay] [ShortcutsHint]
|
||||
----------------------------------------------------------------------
|
||||
[LoadingIndicator (e.g., Thinking...)]
|
||||
[StatusDisplay]
|
||||
|
||||
[InputPrompt]
|
||||
```
|
||||
|
||||
### Footer Area (Status Line)
|
||||
|
||||
```text
|
||||
mode (Shift+Tab) workspace /model
|
||||
manual · shell · raw ~/src/gemini-cli gemini-pro
|
||||
```
|
||||
|
||||
## Key Principles
|
||||
|
||||
- **Single Source of Truth:** All "modes" now live in the footer. If a user
|
||||
wants to know what state the CLI is in, they only need to look at the far-left
|
||||
footer item.
|
||||
- **Reduced Jitter:** Moving the Shell and Markdown indicators out of the
|
||||
Composer reduces vertical jumping in the main interaction area.
|
||||
- **Immediate Feedback:** The Loading Indicator remains closest to the Input
|
||||
Prompt, providing the most direct feedback during generation.
|
||||
@@ -15,6 +15,7 @@ describe('deriveItemsFromLegacySettings', () => {
|
||||
}).merged;
|
||||
const items = deriveItemsFromLegacySettings(settings);
|
||||
expect(items).toEqual([
|
||||
'mode',
|
||||
'workspace',
|
||||
'git-branch',
|
||||
'sandbox',
|
||||
@@ -82,6 +83,7 @@ describe('deriveItemsFromLegacySettings', () => {
|
||||
}).merged;
|
||||
const items = deriveItemsFromLegacySettings(settings);
|
||||
expect(items).toEqual([
|
||||
'mode',
|
||||
'git-branch',
|
||||
'sandbox',
|
||||
'context-used',
|
||||
|
||||
@@ -5,8 +5,30 @@
|
||||
*/
|
||||
|
||||
import type { MergedSettings } from './settings.js';
|
||||
import { MODE_HEADER_DEFAULT } from '../ui/textConstants.js';
|
||||
|
||||
export const ALL_ITEMS = [
|
||||
export const ALL_ITEMS: ReadonlyArray<{
|
||||
id:
|
||||
| 'mode'
|
||||
| 'workspace'
|
||||
| 'git-branch'
|
||||
| 'sandbox'
|
||||
| 'model-name'
|
||||
| 'context-used'
|
||||
| 'quota'
|
||||
| 'memory-usage'
|
||||
| 'session-id'
|
||||
| 'code-changes'
|
||||
| 'token-count';
|
||||
header: string;
|
||||
description: string;
|
||||
}> = [
|
||||
{
|
||||
id: 'mode',
|
||||
header: MODE_HEADER_DEFAULT,
|
||||
description:
|
||||
'Current session state: approval mode, shell mode, markdown mode',
|
||||
},
|
||||
{
|
||||
id: 'workspace',
|
||||
header: 'workspace (/directory)',
|
||||
@@ -57,11 +79,12 @@ export const ALL_ITEMS = [
|
||||
header: 'tokens',
|
||||
description: 'Total tokens used in the session (not shown when zero)',
|
||||
},
|
||||
] as const;
|
||||
];
|
||||
|
||||
export type FooterItemId = (typeof ALL_ITEMS)[number]['id'];
|
||||
|
||||
export const DEFAULT_ORDER = [
|
||||
export const DEFAULT_ORDER: FooterItemId[] = [
|
||||
'mode',
|
||||
'workspace',
|
||||
'git-branch',
|
||||
'sandbox',
|
||||
@@ -77,7 +100,8 @@ export const DEFAULT_ORDER = [
|
||||
export function deriveItemsFromLegacySettings(
|
||||
settings: MergedSettings,
|
||||
): string[] {
|
||||
const defaults = [
|
||||
const defaults: string[] = [
|
||||
'mode',
|
||||
'workspace',
|
||||
'git-branch',
|
||||
'sandbox',
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2025 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { render } from '../../test-utils/render.js';
|
||||
import { ApprovalModeIndicator } from './ApprovalModeIndicator.js';
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { ApprovalMode } from '@google/gemini-cli-core';
|
||||
|
||||
describe('ApprovalModeIndicator', () => {
|
||||
it('renders correctly for AUTO_EDIT mode', async () => {
|
||||
const { lastFrame } = await render(
|
||||
<ApprovalModeIndicator approvalMode={ApprovalMode.AUTO_EDIT} />,
|
||||
);
|
||||
expect(lastFrame()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('renders correctly for AUTO_EDIT mode with plan enabled', async () => {
|
||||
const { lastFrame } = await render(
|
||||
<ApprovalModeIndicator
|
||||
approvalMode={ApprovalMode.AUTO_EDIT}
|
||||
allowPlanMode={true}
|
||||
/>,
|
||||
);
|
||||
expect(lastFrame()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('renders correctly for PLAN mode', async () => {
|
||||
const { lastFrame } = await render(
|
||||
<ApprovalModeIndicator approvalMode={ApprovalMode.PLAN} />,
|
||||
);
|
||||
expect(lastFrame()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('renders correctly for YOLO mode', async () => {
|
||||
const { lastFrame } = await render(
|
||||
<ApprovalModeIndicator approvalMode={ApprovalMode.YOLO} />,
|
||||
);
|
||||
expect(lastFrame()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('renders correctly for DEFAULT mode', async () => {
|
||||
const { lastFrame } = await render(
|
||||
<ApprovalModeIndicator approvalMode={ApprovalMode.DEFAULT} />,
|
||||
);
|
||||
expect(lastFrame()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('renders correctly for DEFAULT mode with plan enabled', async () => {
|
||||
const { lastFrame } = await render(
|
||||
<ApprovalModeIndicator
|
||||
approvalMode={ApprovalMode.DEFAULT}
|
||||
allowPlanMode={true}
|
||||
/>,
|
||||
);
|
||||
expect(lastFrame()).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -1,69 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2025 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
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<ApprovalModeIndicatorProps> = ({
|
||||
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`;
|
||||
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`;
|
||||
break;
|
||||
}
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Text color={textColor}>
|
||||
{textContent ? textContent : null}
|
||||
{subText ? (
|
||||
<Text color={theme.text.secondary}>
|
||||
{textContent ? ' ' : ''}
|
||||
{subText}
|
||||
</Text>
|
||||
) : null}
|
||||
</Text>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@@ -17,16 +17,11 @@ import {
|
||||
import { ConfigContext } from '../contexts/ConfigContext.js';
|
||||
import { SettingsContext } from '../contexts/SettingsContext.js';
|
||||
import { createMockSettings } from '../../test-utils/settings.js';
|
||||
import {
|
||||
ApprovalMode,
|
||||
tokenLimit,
|
||||
CoreToolCallStatus,
|
||||
} from '@google/gemini-cli-core';
|
||||
import { ApprovalMode, CoreToolCallStatus } from '@google/gemini-cli-core';
|
||||
import type { Config } from '@google/gemini-cli-core';
|
||||
import { StreamingState } from '../types.js';
|
||||
import { TransientMessageType } from '../../utils/events.js';
|
||||
import type { LoadedSettings } from '../../config/settings.js';
|
||||
import type { SessionMetrics } from '../contexts/SessionContext.js';
|
||||
import type { TextBuffer } from './shared/text-buffer.js';
|
||||
|
||||
// Mock VimModeContext hook
|
||||
@@ -54,14 +49,25 @@ vi.mock('./LoadingIndicator.js', () => ({
|
||||
LoadingIndicator: ({
|
||||
thought,
|
||||
thoughtLabel,
|
||||
wittyPhrase,
|
||||
}: {
|
||||
thought?: { subject?: string } | string;
|
||||
thoughtLabel?: string;
|
||||
wittyPhrase?: string;
|
||||
}) => {
|
||||
const fallbackText =
|
||||
typeof thought === 'string' ? thought : thought?.subject;
|
||||
const text = thoughtLabel ?? fallbackText;
|
||||
return <Text>LoadingIndicator{text ? `: ${text}` : ''}</Text>;
|
||||
return (
|
||||
<Box>
|
||||
<Text>LoadingIndicator{text ? `: ${text}` : ''}</Text>
|
||||
{wittyPhrase && (
|
||||
<Box marginLeft={1}>
|
||||
<Text>{wittyPhrase}</Text>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -75,24 +81,14 @@ vi.mock('./ContextSummaryDisplay.js', () => ({
|
||||
ContextSummaryDisplay: () => <Text>ContextSummaryDisplay</Text>,
|
||||
}));
|
||||
|
||||
vi.mock('./HookStatusDisplay.js', () => ({
|
||||
HookStatusDisplay: () => <Text>HookStatusDisplay</Text>,
|
||||
}));
|
||||
|
||||
vi.mock('./ApprovalModeIndicator.js', () => ({
|
||||
ApprovalModeIndicator: ({ approvalMode }: { approvalMode: ApprovalMode }) => (
|
||||
<Text>ApprovalModeIndicator: {approvalMode}</Text>
|
||||
),
|
||||
}));
|
||||
|
||||
vi.mock('./ShellModeIndicator.js', () => ({
|
||||
ShellModeIndicator: () => <Text>ShellModeIndicator</Text>,
|
||||
}));
|
||||
|
||||
vi.mock('./ShortcutsHelp.js', () => ({
|
||||
ShortcutsHelp: () => <Text>ShortcutsHelp</Text>,
|
||||
}));
|
||||
|
||||
vi.mock('./ShortcutsHint.js', () => ({
|
||||
ShortcutsHint: () => <Text>ShortcutsHint</Text>,
|
||||
}));
|
||||
|
||||
vi.mock('./DetailedMessagesDisplay.js', () => ({
|
||||
DetailedMessagesDisplay: () => <Text>DetailedMessagesDisplay</Text>,
|
||||
}));
|
||||
@@ -145,6 +141,12 @@ vi.mock('./QueuedMessageDisplay.js', () => ({
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('./ContextUsageDisplay.js', () => ({
|
||||
ContextUsageDisplay: ({ promptTokenCount }: { promptTokenCount: number }) => (
|
||||
<Text>ContextUsageDisplay: {promptTokenCount}</Text>
|
||||
),
|
||||
}));
|
||||
|
||||
// Mock contexts
|
||||
vi.mock('../contexts/OverflowContext.js', () => ({
|
||||
OverflowProvider: ({ children }: { children: React.ReactNode }) => children,
|
||||
@@ -153,6 +155,7 @@ vi.mock('../contexts/OverflowContext.js', () => ({
|
||||
// Create mock context providers
|
||||
const createMockUIState = (overrides: Partial<UIState> = {}): UIState =>
|
||||
({
|
||||
terminalWidth: 100,
|
||||
streamingState: StreamingState.Idle,
|
||||
isConfigInitialized: true,
|
||||
contextFileNames: [],
|
||||
@@ -186,8 +189,10 @@ const createMockUIState = (overrides: Partial<UIState> = {}): UIState =>
|
||||
sessionStats: {
|
||||
sessionId: 'test-session',
|
||||
sessionStartTime: new Date(),
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
metrics: {} as any,
|
||||
metrics: {
|
||||
files: { totalLinesAdded: 0, totalLinesRemoved: 0 },
|
||||
models: {},
|
||||
},
|
||||
lastPromptTokenCount: 0,
|
||||
promptCount: 0,
|
||||
},
|
||||
@@ -266,7 +271,11 @@ const renderComposer = async (
|
||||
// Wait for shortcuts hint debounce if using fake timers
|
||||
if (vi.isFakeTimers()) {
|
||||
await act(async () => {
|
||||
await vi.advanceTimersByTimeAsync(250);
|
||||
vi.advanceTimersByTime(250);
|
||||
});
|
||||
// Extra tick for state updates
|
||||
await act(async () => {
|
||||
await Promise.resolve();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -301,52 +310,7 @@ describe('Composer', () => {
|
||||
|
||||
const { lastFrame } = await renderComposer(uiState, settings);
|
||||
|
||||
// Check for content that only appears IN the Footer component itself
|
||||
expect(lastFrame()).not.toContain('[NORMAL]'); // Vim mode indicator
|
||||
expect(lastFrame()).not.toContain('(main'); // Branch name with parentheses
|
||||
});
|
||||
|
||||
it('passes correct props to Footer including vim mode when enabled', async () => {
|
||||
const uiState = createMockUIState({
|
||||
branchName: 'feature-branch',
|
||||
corgiMode: true,
|
||||
errorCount: 2,
|
||||
sessionStats: {
|
||||
sessionId: 'test-session',
|
||||
sessionStartTime: new Date(),
|
||||
metrics: {
|
||||
models: {},
|
||||
tools: {},
|
||||
files: {},
|
||||
} as SessionMetrics,
|
||||
lastPromptTokenCount: 150,
|
||||
promptCount: 5,
|
||||
},
|
||||
});
|
||||
const config = createMockConfig({
|
||||
getModel: vi.fn(() => 'gemini-1.5-flash'),
|
||||
getTargetDir: vi.fn(() => '/project/path'),
|
||||
getDebugMode: vi.fn(() => true),
|
||||
});
|
||||
const settings = createMockSettings({
|
||||
ui: {
|
||||
hideFooter: false,
|
||||
showMemoryUsage: true,
|
||||
},
|
||||
});
|
||||
// Mock vim mode for this test
|
||||
const { useVimMode } = await import('../contexts/VimModeContext.js');
|
||||
vi.mocked(useVimMode).mockReturnValueOnce({
|
||||
vimEnabled: true,
|
||||
vimMode: 'INSERT',
|
||||
toggleVimEnabled: vi.fn(),
|
||||
setVimMode: vi.fn(),
|
||||
} as unknown as ReturnType<typeof useVimMode>);
|
||||
|
||||
const { lastFrame } = await renderComposer(uiState, settings, config);
|
||||
|
||||
expect(lastFrame()).toContain('Footer');
|
||||
// Footer should be rendered with all the state passed through
|
||||
expect(lastFrame()).not.toContain('Footer');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -383,12 +347,10 @@ describe('Composer', () => {
|
||||
const { lastFrame } = await renderComposer(uiState, settings);
|
||||
|
||||
const output = lastFrame();
|
||||
// In Refreshed UX, we don't force 'Thinking...' label in renderStatusNode
|
||||
// It uses the subject directly
|
||||
expect(output).toContain('LoadingIndicator: Thinking about code');
|
||||
});
|
||||
|
||||
it('shows shortcuts hint while loading', async () => {
|
||||
it('shows shortcuts hint while loading in minimal mode', async () => {
|
||||
const uiState = createMockUIState({
|
||||
streamingState: StreamingState.Responding,
|
||||
elapsedTime: 1,
|
||||
@@ -400,7 +362,6 @@ describe('Composer', () => {
|
||||
const output = lastFrame();
|
||||
expect(output).toContain('LoadingIndicator');
|
||||
expect(output).toContain('press tab twice for more');
|
||||
expect(output).not.toContain('? for shortcuts');
|
||||
});
|
||||
|
||||
it('renders LoadingIndicator with thought when loadingPhrases is off', async () => {
|
||||
@@ -416,7 +377,6 @@ describe('Composer', () => {
|
||||
|
||||
const output = lastFrame();
|
||||
expect(output).toContain('LoadingIndicator');
|
||||
expect(output).toContain('LoadingIndicator: Hidden');
|
||||
});
|
||||
|
||||
it('does not render LoadingIndicator when waiting for confirmation', async () => {
|
||||
@@ -473,23 +433,6 @@ describe('Composer', () => {
|
||||
expect(output).toContain('LoadingIndicator');
|
||||
});
|
||||
|
||||
it('renders both LoadingIndicator and ApprovalModeIndicator when streaming in full UI mode', async () => {
|
||||
const uiState = createMockUIState({
|
||||
streamingState: StreamingState.Responding,
|
||||
thought: {
|
||||
subject: 'Thinking',
|
||||
description: '',
|
||||
},
|
||||
showApprovalModeIndicator: ApprovalMode.PLAN,
|
||||
});
|
||||
|
||||
const { lastFrame } = await renderComposer(uiState);
|
||||
|
||||
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 () => {
|
||||
const uiState = createMockUIState({
|
||||
streamingState: StreamingState.Responding,
|
||||
@@ -502,6 +445,23 @@ describe('Composer', () => {
|
||||
const output = lastFrame();
|
||||
expect(output).not.toContain('LoadingIndicator');
|
||||
});
|
||||
|
||||
it('renders both Thinking and witty phrase', async () => {
|
||||
const uiState = createMockUIState({
|
||||
streamingState: StreamingState.Responding,
|
||||
thought: { subject: 'Processing', description: '' },
|
||||
currentWittyPhrase: 'Reticulating splines...',
|
||||
});
|
||||
const settings = createMockSettings({
|
||||
ui: { loadingPhrases: 'witty' },
|
||||
});
|
||||
|
||||
const { lastFrame } = await renderComposer(uiState, settings);
|
||||
|
||||
const output = lastFrame();
|
||||
expect(output).toContain('LoadingIndicator: Processing');
|
||||
expect(output).toContain('Reticulating splines...');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Message Queue Display', () => {
|
||||
@@ -521,23 +481,10 @@ describe('Composer', () => {
|
||||
expect(output).toContain('Second queued message');
|
||||
expect(output).toContain('Third queued message');
|
||||
});
|
||||
|
||||
it('renders QueuedMessageDisplay with empty message queue', async () => {
|
||||
const uiState = createMockUIState({
|
||||
messageQueue: [],
|
||||
});
|
||||
|
||||
const { lastFrame } = await renderComposer(uiState);
|
||||
|
||||
// The component should render but return null for empty queue
|
||||
// This test verifies that the component receives the correct prop
|
||||
const output = lastFrame();
|
||||
expect(output).toContain('InputPrompt'); // Verify basic Composer rendering
|
||||
});
|
||||
});
|
||||
|
||||
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,
|
||||
@@ -548,11 +495,9 @@ 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,
|
||||
});
|
||||
@@ -561,10 +506,6 @@ describe('Composer', () => {
|
||||
|
||||
const output = lastFrame();
|
||||
expect(output).toContain('Press Ctrl+C again to exit.');
|
||||
// In Refreshed UX, Row 1 shows toast, and Row 2 shows ApprovalModeIndicator/StatusDisplay
|
||||
// They are no longer mutually exclusive.
|
||||
expect(output).toContain('ApprovalModeIndicator');
|
||||
expect(output).toContain('StatusDisplay');
|
||||
});
|
||||
|
||||
it('shows ToastDisplay for other toast types', async () => {
|
||||
@@ -579,7 +520,6 @@ describe('Composer', () => {
|
||||
|
||||
const output = lastFrame();
|
||||
expect(output).toContain('Warning');
|
||||
expect(output).toContain('ApprovalModeIndicator');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -595,8 +535,7 @@ describe('Composer', () => {
|
||||
const { lastFrame } = await renderComposer(uiState, settings);
|
||||
|
||||
const output = lastFrame();
|
||||
expect(output).not.toContain('press tab twice for more');
|
||||
expect(output).not.toContain('? for shortcuts');
|
||||
expect(output).not.toContain('ShortcutsHint');
|
||||
expect(output).toContain('InputPrompt');
|
||||
expect(output).not.toContain('Footer');
|
||||
});
|
||||
@@ -621,148 +560,41 @@ 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,
|
||||
});
|
||||
|
||||
const { lastFrame } = await renderComposer(uiState);
|
||||
|
||||
expect(lastFrame()).toMatch(/ShellModeIndic[\s\S]*tor/);
|
||||
});
|
||||
|
||||
it('shows RawMarkdownIndicator when renderMarkdown is false', async () => {
|
||||
const uiState = createMockUIState({
|
||||
renderMarkdown: false,
|
||||
});
|
||||
|
||||
const { lastFrame } = await renderComposer(uiState);
|
||||
|
||||
expect(lastFrame()).toContain('raw markdown mode');
|
||||
});
|
||||
|
||||
it('does not show RawMarkdownIndicator when renderMarkdown is true', async () => {
|
||||
const uiState = createMockUIState({
|
||||
renderMarkdown: true,
|
||||
});
|
||||
|
||||
const { lastFrame } = await renderComposer(uiState);
|
||||
|
||||
expect(lastFrame()).not.toContain('raw markdown mode');
|
||||
});
|
||||
|
||||
it.each([
|
||||
{ mode: ApprovalMode.YOLO, label: '● YOLO' },
|
||||
{ mode: ApprovalMode.PLAN, label: '● plan' },
|
||||
{
|
||||
mode: ApprovalMode.AUTO_EDIT,
|
||||
label: '● auto edit',
|
||||
},
|
||||
])(
|
||||
'shows minimal mode badge "$mode" 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).toContain('press tab twice for more');
|
||||
expect(output).not.toContain('? for shortcuts');
|
||||
});
|
||||
|
||||
it('hides minimal mode badge while action-required state is active', async () => {
|
||||
const uiState = createMockUIState({
|
||||
cleanUiDetailsVisible: false,
|
||||
showApprovalModeIndicator: ApprovalMode.PLAN,
|
||||
customDialog: (
|
||||
<Box>
|
||||
<Text>Prompt</Text>
|
||||
</Box>
|
||||
),
|
||||
});
|
||||
|
||||
const { lastFrame } = await renderComposer(uiState);
|
||||
expect(lastFrame({ allowEmpty: true })).toBe('');
|
||||
});
|
||||
|
||||
it('shows Esc rewind prompt in minimal mode without showing full UI', async () => {
|
||||
const uiState = createMockUIState({
|
||||
cleanUiDetailsVisible: false,
|
||||
showEscapePrompt: true,
|
||||
history: [{ id: 1, type: 'user', text: 'msg' }],
|
||||
});
|
||||
|
||||
const { lastFrame } = await renderComposer(uiState);
|
||||
const output = lastFrame();
|
||||
expect(output).toContain('Press Esc again to rewind.');
|
||||
expect(output).not.toContain('ContextSummaryDisplay');
|
||||
});
|
||||
|
||||
it('shows context usage bleed-through when over 60%', async () => {
|
||||
const model = 'gemini-2.5-pro';
|
||||
const uiState = createMockUIState({
|
||||
cleanUiDetailsVisible: false,
|
||||
currentModel: model,
|
||||
sessionStats: {
|
||||
sessionId: 'test-session',
|
||||
lastPromptTokenCount: 700000,
|
||||
metrics: {
|
||||
files: { totalLinesAdded: 0, totalLinesRemoved: 0 },
|
||||
models: {},
|
||||
tools: {
|
||||
totalCalls: 0,
|
||||
totalSuccess: 0,
|
||||
totalFail: 0,
|
||||
totalDurationMs: 0,
|
||||
totalDecisions: {
|
||||
accept: 0,
|
||||
reject: 0,
|
||||
modify: 0,
|
||||
auto_accept: 0,
|
||||
},
|
||||
byName: {},
|
||||
},
|
||||
},
|
||||
sessionId: 'test',
|
||||
sessionStartTime: new Date(),
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
metrics: {} as any,
|
||||
lastPromptTokenCount: Math.floor(tokenLimit(model) * 0.7),
|
||||
promptCount: 0,
|
||||
},
|
||||
currentModel: 'gemini-1.5-pro',
|
||||
});
|
||||
const settings = createMockSettings({
|
||||
ui: {
|
||||
footer: { hideContextPercentage: false },
|
||||
},
|
||||
ui: { footer: { hideContextPercentage: false } },
|
||||
});
|
||||
|
||||
const { lastFrame } = await renderComposer(uiState, settings);
|
||||
|
||||
await act(async () => {
|
||||
await vi.advanceTimersByTimeAsync(250);
|
||||
});
|
||||
|
||||
// StatusDisplay (which contains ContextUsageDisplay) should bleed through in minimal mode
|
||||
expect(lastFrame()).toContain('StatusDisplay');
|
||||
expect(lastFrame()).toContain('70% used');
|
||||
expect(lastFrame()).toContain('ContextUsageDisplay: 700000');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -834,10 +666,6 @@ describe('Composer', () => {
|
||||
|
||||
const { lastFrame } = await renderComposer(uiState);
|
||||
|
||||
await act(async () => {
|
||||
await vi.advanceTimersByTimeAsync(250);
|
||||
});
|
||||
|
||||
expect(lastFrame({ allowEmpty: true })).toContain(
|
||||
'press tab twice for more',
|
||||
);
|
||||
@@ -851,8 +679,7 @@ describe('Composer', () => {
|
||||
|
||||
const { lastFrame } = await renderComposer(uiState);
|
||||
|
||||
expect(lastFrame()).not.toContain('press tab twice for more');
|
||||
expect(lastFrame()).not.toContain('? for shortcuts');
|
||||
expect(lastFrame()).not.toContain('ShortcutsHint');
|
||||
});
|
||||
|
||||
it('hides shortcuts hint when showShortcutsHint setting is false', async () => {
|
||||
@@ -865,7 +692,7 @@ describe('Composer', () => {
|
||||
|
||||
const { lastFrame } = await renderComposer(uiState, settings);
|
||||
|
||||
expect(lastFrame()).not.toContain('? for shortcuts');
|
||||
expect(lastFrame()).not.toContain('ShortcutsHint');
|
||||
});
|
||||
|
||||
it('hides shortcuts hint when a action is required (e.g. dialog is open)', async () => {
|
||||
@@ -891,10 +718,6 @@ describe('Composer', () => {
|
||||
|
||||
const { lastFrame } = await renderComposer(uiState);
|
||||
|
||||
await act(async () => {
|
||||
await vi.advanceTimersByTimeAsync(250);
|
||||
});
|
||||
|
||||
expect(lastFrame()).toContain('press tab twice for more');
|
||||
});
|
||||
|
||||
@@ -905,51 +728,9 @@ describe('Composer', () => {
|
||||
|
||||
const { lastFrame } = await renderComposer(uiState);
|
||||
|
||||
await act(async () => {
|
||||
await vi.advanceTimersByTimeAsync(250);
|
||||
});
|
||||
|
||||
// In Refreshed UX, shortcuts hint is in the top multipurpose status row
|
||||
expect(lastFrame()).toContain('? for shortcuts');
|
||||
});
|
||||
|
||||
it('shows shortcuts hint while loading when full UI details are visible', async () => {
|
||||
const uiState = createMockUIState({
|
||||
cleanUiDetailsVisible: true,
|
||||
streamingState: StreamingState.Responding,
|
||||
});
|
||||
|
||||
const { lastFrame } = await renderComposer(uiState);
|
||||
|
||||
await act(async () => {
|
||||
await vi.advanceTimersByTimeAsync(250);
|
||||
});
|
||||
|
||||
// In experimental layout, status row is visible during loading
|
||||
expect(lastFrame()).toContain('LoadingIndicator');
|
||||
expect(lastFrame()).toContain('? for shortcuts');
|
||||
expect(lastFrame()).not.toContain('press tab twice for more');
|
||||
});
|
||||
|
||||
it('shows shortcuts hint while loading in minimal mode', async () => {
|
||||
const uiState = createMockUIState({
|
||||
cleanUiDetailsVisible: false,
|
||||
streamingState: StreamingState.Responding,
|
||||
elapsedTime: 1,
|
||||
});
|
||||
|
||||
const { lastFrame } = await renderComposer(uiState);
|
||||
|
||||
await act(async () => {
|
||||
await vi.advanceTimersByTimeAsync(250);
|
||||
});
|
||||
|
||||
// In experimental layout, status row is visible in clean mode while busy
|
||||
expect(lastFrame()).toContain('LoadingIndicator');
|
||||
expect(lastFrame()).toContain('press tab twice for more');
|
||||
expect(lastFrame()).not.toContain('? for shortcuts');
|
||||
});
|
||||
|
||||
it('shows shortcuts help in minimal mode when toggled on', async () => {
|
||||
const uiState = createMockUIState({
|
||||
cleanUiDetailsVisible: false,
|
||||
@@ -967,46 +748,11 @@ describe('Composer', () => {
|
||||
|
||||
const uiState = createMockUIState({
|
||||
cleanUiDetailsVisible: false,
|
||||
showApprovalModeIndicator: ApprovalMode.PLAN,
|
||||
});
|
||||
|
||||
const { lastFrame } = await renderComposer(uiState);
|
||||
|
||||
expect(lastFrame()).not.toContain('press tab twice for more');
|
||||
expect(lastFrame()).not.toContain('? for shortcuts');
|
||||
expect(lastFrame()).not.toContain('plan');
|
||||
});
|
||||
|
||||
it('hides approval mode indicator when suggestions are visible above input in alternate buffer', async () => {
|
||||
composerTestControls.isAlternateBuffer = true;
|
||||
composerTestControls.suggestionsVisible = true;
|
||||
|
||||
const uiState = createMockUIState({
|
||||
cleanUiDetailsVisible: true,
|
||||
showApprovalModeIndicator: ApprovalMode.YOLO,
|
||||
});
|
||||
|
||||
const { lastFrame } = await renderComposer(uiState);
|
||||
|
||||
expect(lastFrame()).not.toContain('ApprovalModeIndicator');
|
||||
});
|
||||
|
||||
it('keeps shortcuts hint when suggestions are visible below input in regular buffer', async () => {
|
||||
composerTestControls.isAlternateBuffer = false;
|
||||
composerTestControls.suggestionsVisible = true;
|
||||
|
||||
const uiState = createMockUIState({
|
||||
cleanUiDetailsVisible: false,
|
||||
});
|
||||
|
||||
const { lastFrame } = await renderComposer(uiState);
|
||||
|
||||
await act(async () => {
|
||||
await vi.advanceTimersByTimeAsync(250);
|
||||
});
|
||||
|
||||
// In Refreshed UX, shortcuts hint is in the top status row and doesn't collide with suggestions below
|
||||
expect(lastFrame()).toContain('press tab twice for more');
|
||||
expect(lastFrame()).not.toContain('ShortcutsHint');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1034,22 +780,8 @@ describe('Composer', () => {
|
||||
expect(lastFrame()).not.toContain('ShortcutsHelp');
|
||||
unmount();
|
||||
});
|
||||
it('hides shortcuts help when action is required', async () => {
|
||||
const uiState = createMockUIState({
|
||||
shortcutsHelpVisible: true,
|
||||
customDialog: (
|
||||
<Box>
|
||||
<Text>Test Dialog</Text>
|
||||
</Box>
|
||||
),
|
||||
});
|
||||
|
||||
const { lastFrame, unmount } = await renderComposer(uiState);
|
||||
|
||||
expect(lastFrame({ allowEmpty: true })).toBe('');
|
||||
unmount();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Snapshots', () => {
|
||||
it('matches snapshot in idle state', async () => {
|
||||
const uiState = createMockUIState();
|
||||
|
||||
@@ -4,16 +4,8 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
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 { Box, Text, useIsScreenReaderEnabled } from 'ink';
|
||||
import { ToastDisplay, shouldShowToast } from './ToastDisplay.js';
|
||||
import { DetailedMessagesDisplay } from './DetailedMessagesDisplay.js';
|
||||
import { ShortcutsHelp } from './ShortcutsHelp.js';
|
||||
@@ -22,10 +14,19 @@ import { Footer } from './Footer.js';
|
||||
import { StatusRow } from './StatusRow.js';
|
||||
import { ShowMoreLines } from './ShowMoreLines.js';
|
||||
import { QueuedMessageDisplay } from './QueuedMessageDisplay.js';
|
||||
import { HorizontalLine } from './shared/HorizontalLine.js';
|
||||
import { OverflowProvider } from '../contexts/OverflowContext.js';
|
||||
import { ConfigInitDisplay } from './ConfigInitDisplay.js';
|
||||
import { TodoTray } from './messages/Todo.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 { useComposerStatus } from '../hooks/useComposerStatus.js';
|
||||
import { theme } from '../semantic-colors.js';
|
||||
|
||||
export const Composer = ({ isFocused = true }: { isFocused?: boolean }) => {
|
||||
const uiState = useUIState();
|
||||
@@ -34,7 +35,7 @@ export const Composer = ({ isFocused = true }: { isFocused?: boolean }) => {
|
||||
const config = useConfig();
|
||||
const { vimEnabled, vimMode } = useVimMode();
|
||||
const isScreenReaderEnabled = useIsScreenReaderEnabled();
|
||||
const { columns: terminalWidth } = useTerminalSize();
|
||||
const terminalWidth = uiState.terminalWidth;
|
||||
const isNarrow = isNarrowWidth(terminalWidth);
|
||||
const debugConsoleMaxHeight = Math.floor(Math.max(terminalWidth * 0.2, 5));
|
||||
const [suggestionsVisible, setSuggestionsVisible] = useState(false);
|
||||
@@ -65,20 +66,69 @@ export const Composer = ({ isFocused = true }: { isFocused?: boolean }) => {
|
||||
setShortcutsHelpVisible,
|
||||
]);
|
||||
|
||||
const showShortcutsHelp =
|
||||
uiState.shortcutsHelpVisible &&
|
||||
uiState.streamingState === 'idle' &&
|
||||
const hideUiDetailsForSuggestions =
|
||||
suggestionsVisible && suggestionsPosition === 'above';
|
||||
const isModelIdle = uiState.streamingState === 'idle';
|
||||
const isModelResponding = uiState.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]);
|
||||
|
||||
if (hasPendingActionRequired && shouldCollapseDuringApproval) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const hasToast = shouldShowToast(uiState);
|
||||
const hideUiDetailsForSuggestions =
|
||||
suggestionsVisible && suggestionsPosition === 'above';
|
||||
const showShortcutsHelp =
|
||||
uiState.shortcutsHelpVisible &&
|
||||
uiState.streamingState === 'idle' &&
|
||||
!hasPendingActionRequired;
|
||||
|
||||
const hasToast = shouldShowToast(uiState);
|
||||
|
||||
const shouldReserveSpaceForShortcutsHint =
|
||||
settings.merged.ui.showShortcutsHint &&
|
||||
!hideUiDetailsForSuggestions &&
|
||||
!hasPendingActionRequired;
|
||||
const showShortcutsHint =
|
||||
shouldReserveSpaceForShortcutsHint && showShortcutsHintDebounced;
|
||||
|
||||
const loadingPhrases = settings.merged.ui.loadingPhrases;
|
||||
const showTips = loadingPhrases === 'tips' || loadingPhrases === 'all';
|
||||
|
||||
/**
|
||||
* Determine the ambient text (tip or shortcut hint) to display.
|
||||
*/
|
||||
const ambientContent = (() => {
|
||||
if (showTips && uiState.currentTip) {
|
||||
return { text: `Tip: ${uiState.currentTip}`, isTip: true };
|
||||
}
|
||||
if (showShortcutsHint) {
|
||||
const text = showUiDetails
|
||||
? '? for shortcuts'
|
||||
: 'press tab twice for more';
|
||||
return { text, isTip: false };
|
||||
}
|
||||
return null;
|
||||
})();
|
||||
|
||||
// Mini Mode VIP Flags (Pure Content Triggers)
|
||||
const showMinimalToast = hasToast;
|
||||
|
||||
return (
|
||||
@@ -98,20 +148,60 @@ export const Composer = ({ isFocused = true }: { isFocused?: boolean }) => {
|
||||
|
||||
{showUiDetails && <TodoTray />}
|
||||
|
||||
{showShortcutsHelp && <ShortcutsHelp />}
|
||||
|
||||
{(showUiDetails || showMinimalToast) && (
|
||||
<Box minHeight={1} marginLeft={isNarrow ? 0 : 1}>
|
||||
<ToastDisplay />
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<Box width="100%" flexDirection="column">
|
||||
{/* Above Divider Zone: Alerts, Tips, and Hints */}
|
||||
{showUiDetails && (
|
||||
<Box
|
||||
width="100%"
|
||||
flexDirection={isNarrow ? 'column' : 'row'}
|
||||
alignItems={isNarrow ? 'flex-start' : 'center'}
|
||||
justifyContent={isNarrow ? 'flex-start' : 'space-between'}
|
||||
>
|
||||
<Box
|
||||
marginLeft={1}
|
||||
marginRight={isNarrow ? 0 : 1}
|
||||
flexDirection="row"
|
||||
alignItems={isNarrow ? 'flex-start' : 'center'}
|
||||
flexGrow={1}
|
||||
>
|
||||
{hasToast && <ToastDisplay />}
|
||||
</Box>
|
||||
<Box
|
||||
marginTop={isNarrow ? 1 : 0}
|
||||
flexDirection="column"
|
||||
alignItems={isNarrow ? 'flex-start' : 'flex-end'}
|
||||
minHeight={ambientContent ? 1 : 0}
|
||||
>
|
||||
{ambientContent && (
|
||||
<Box flexDirection="row" justifyContent="flex-end">
|
||||
<Text
|
||||
color={
|
||||
!ambientContent.isTip && uiState.shortcutsHelpVisible
|
||||
? theme.text.accent
|
||||
: theme.text.secondary
|
||||
}
|
||||
wrap="truncate-end"
|
||||
>
|
||||
{ambientContent.text}
|
||||
</Text>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{showShortcutsHelp && <ShortcutsHelp />}
|
||||
{showUiDetails && <HorizontalLine />}
|
||||
|
||||
{/* Below Divider Zone: Active Processing and Status (handled by StatusRow) */}
|
||||
<StatusRow
|
||||
showUiDetails={showUiDetails}
|
||||
isNarrow={isNarrow}
|
||||
terminalWidth={terminalWidth}
|
||||
uiState={uiState}
|
||||
settings={settings}
|
||||
hideContextSummary={hideContextSummary}
|
||||
isNarrow={isNarrow}
|
||||
ambientContent={ambientContent}
|
||||
showUiDetails={showUiDetails}
|
||||
showMinimalToast={showMinimalToast}
|
||||
hideUiDetailsForSuggestions={hideUiDetailsForSuggestions}
|
||||
hasPendingActionRequired={hasPendingActionRequired}
|
||||
/>
|
||||
@@ -165,7 +255,6 @@ export const Composer = ({ isFocused = true }: { isFocused?: boolean }) => {
|
||||
streamingState={uiState.streamingState}
|
||||
suggestionsPosition={suggestionsPosition}
|
||||
onSuggestionsVisibilityChange={setSuggestionsVisible}
|
||||
copyModeEnabled={uiState.copyModeEnabled}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
@@ -18,7 +18,10 @@ import process from 'node:process';
|
||||
import { MemoryUsageDisplay } from './MemoryUsageDisplay.js';
|
||||
import { ContextUsageDisplay } from './ContextUsageDisplay.js';
|
||||
import { QuotaDisplay } from './QuotaDisplay.js';
|
||||
import { DebugProfiler } from './DebugProfiler.js';
|
||||
import {
|
||||
UnifiedModeIndicator,
|
||||
getModeHeaderLabel,
|
||||
} from './UnifiedModeIndicator.js';
|
||||
import { useUIState } from '../contexts/UIStateContext.js';
|
||||
import { useConfig } from '../contexts/ConfigContext.js';
|
||||
import { useSettings } from '../contexts/SettingsContext.js';
|
||||
@@ -246,13 +249,10 @@ export const Footer: React.FC<{ copyModeEnabled?: boolean }> = ({
|
||||
};
|
||||
|
||||
// 1. System Indicators (Far Left, high priority)
|
||||
if (uiState.showDebugProfiler) {
|
||||
addCol('debug', '', () => <DebugProfiler />, 45, true);
|
||||
}
|
||||
if (displayVimMode) {
|
||||
const vimStr = `[${displayVimMode}]`;
|
||||
addCol(
|
||||
'vim',
|
||||
'vim-mode',
|
||||
'',
|
||||
() => <Text color={theme.text.accent}>{vimStr}</Text>,
|
||||
vimStr.length,
|
||||
@@ -264,9 +264,39 @@ export const Footer: React.FC<{ copyModeEnabled?: boolean }> = ({
|
||||
for (const id of items) {
|
||||
if (!isFooterItemId(id)) continue;
|
||||
const itemConfig = ALL_ITEMS.find((i) => i.id === id);
|
||||
const header = itemConfig?.header ?? id;
|
||||
let header = itemConfig?.header ?? id;
|
||||
|
||||
switch (id) {
|
||||
case 'mode': {
|
||||
header = getModeHeaderLabel(
|
||||
uiState.showApprovalModeIndicator,
|
||||
uiState.shellModeActive,
|
||||
);
|
||||
|
||||
// Calculate dynamic width based on which modes are active
|
||||
let contentWidth = 6; // 'manual' or 'plan' or 'YOLO' (max 11 for auto-accept)
|
||||
if (uiState.showApprovalModeIndicator === 'autoEdit') contentWidth = 11;
|
||||
if (uiState.shellModeActive)
|
||||
contentWidth = 5; // 'shell' (obscures others)
|
||||
else if (uiState.showApprovalModeIndicator === 'yolo') contentWidth = 4; // 'YOLO' (obscures others)
|
||||
|
||||
if (!uiState.renderMarkdown) contentWidth += 6; // ' · raw'
|
||||
|
||||
addCol(
|
||||
id,
|
||||
header,
|
||||
() => (
|
||||
<UnifiedModeIndicator
|
||||
approvalMode={uiState.showApprovalModeIndicator}
|
||||
shellModeActive={uiState.shellModeActive}
|
||||
renderMarkdown={uiState.renderMarkdown}
|
||||
/>
|
||||
),
|
||||
Math.max(contentWidth, showLabels ? header.length : 0),
|
||||
true, // high priority, always shown
|
||||
);
|
||||
break;
|
||||
}
|
||||
case 'workspace': {
|
||||
const fullPath = tildeifyPath(targetDir);
|
||||
const debugSuffix = debugMode ? ' ' + (debugMessage || '--debug') : '';
|
||||
@@ -430,10 +460,10 @@ export const Footer: React.FC<{ copyModeEnabled?: boolean }> = ({
|
||||
}
|
||||
|
||||
// 3. Transients
|
||||
if (corgiMode) addCol('corgi', '', () => <CorgiIndicator />, 5);
|
||||
if (corgiMode) addCol('corgi-mode', '', () => <CorgiIndicator />, 5);
|
||||
if (showErrorSummary) {
|
||||
addCol(
|
||||
'error-count',
|
||||
'error-summary',
|
||||
'',
|
||||
() => <ConsoleSummaryDisplay errorCount={errorCount} />,
|
||||
12,
|
||||
@@ -482,7 +512,7 @@ export const Footer: React.FC<{ copyModeEnabled?: boolean }> = ({
|
||||
const estimatedWidth = isWorkspace ? availableForWorkspace : col.width;
|
||||
|
||||
return {
|
||||
key: col.id,
|
||||
key: col.id + index,
|
||||
header: col.header,
|
||||
element: col.element(estimatedWidth),
|
||||
flexGrow: 0,
|
||||
|
||||
@@ -41,6 +41,10 @@ describe('<FooterConfigDialog />', () => {
|
||||
{ settings },
|
||||
);
|
||||
|
||||
act(() => {
|
||||
stdin.write('\u001b[B'); // Down arrow to move from 'mode' to 'workspace'
|
||||
});
|
||||
|
||||
act(() => {
|
||||
stdin.write('\r'); // Enter to toggle
|
||||
});
|
||||
@@ -74,6 +78,9 @@ describe('<FooterConfigDialog />', () => {
|
||||
expect(cwdIdx).toBeLessThan(branchIdx);
|
||||
|
||||
// Move workspace down (right arrow)
|
||||
act(() => {
|
||||
stdin.write('\u001b[B'); // Down arrow to workspace
|
||||
});
|
||||
act(() => {
|
||||
stdin.write('\u001b[C'); // Right arrow
|
||||
});
|
||||
@@ -116,7 +123,7 @@ describe('<FooterConfigDialog />', () => {
|
||||
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
|
||||
});
|
||||
@@ -148,8 +155,8 @@ describe('<FooterConfigDialog />', () => {
|
||||
{ settings },
|
||||
);
|
||||
|
||||
// Default items are the first 5. We toggle them off.
|
||||
for (let i = 0; i < 5; i++) {
|
||||
// Default items are the first 6. We toggle them off.
|
||||
for (let i = 0; i < 6; i++) {
|
||||
act(() => {
|
||||
stdin.write('\r'); // Toggle off
|
||||
});
|
||||
@@ -176,7 +183,7 @@ describe('<FooterConfigDialog />', () => {
|
||||
{ settings },
|
||||
);
|
||||
|
||||
// Default initial items in mock settings are 'git-branch', 'workspace', ...
|
||||
// Default initial items in mock settings are 'mode', 'workspace', 'git-branch', ...
|
||||
await waitFor(() => {
|
||||
const output = lastFrame();
|
||||
expect(output).toContain('] git-branch');
|
||||
@@ -188,22 +195,22 @@ describe('<FooterConfigDialog />', () => {
|
||||
const workspaceIdx = output.indexOf('] workspace');
|
||||
expect(workspaceIdx).toBeLessThan(branchIdx);
|
||||
|
||||
// Try to move workspace up (left arrow) while it's at the top
|
||||
// Focus is on 'mode' at index 0. Try to move it up (left arrow). It should do nothing.
|
||||
act(() => {
|
||||
stdin.write('\u001b[D'); // Left arrow
|
||||
});
|
||||
|
||||
// Move workspace down (right arrow)
|
||||
// Move 'mode' down (right arrow). Order becomes 'workspace', 'mode', 'git-branch'.
|
||||
act(() => {
|
||||
stdin.write('\u001b[C'); // Right arrow
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
const outputAfter = lastFrame();
|
||||
const bIdxAfter = outputAfter.indexOf('] git-branch');
|
||||
const mIdxAfter = outputAfter.indexOf('] mode');
|
||||
const wIdxAfter = outputAfter.indexOf('] workspace');
|
||||
// workspace should now be after git-branch
|
||||
expect(bIdxAfter).toBeLessThan(wIdxAfter);
|
||||
// mode should now be after workspace
|
||||
expect(wIdxAfter).toBeLessThan(mIdxAfter);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -104,7 +104,7 @@ export const FooterConfigDialog: React.FC<FooterConfigDialogProps> = ({
|
||||
key: id,
|
||||
id,
|
||||
label: item.id,
|
||||
description: item.description as string,
|
||||
description: item.description,
|
||||
type: 'config' as const,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2025 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { render } from '../../test-utils/render.js';
|
||||
import { RawMarkdownIndicator } from './RawMarkdownIndicator.js';
|
||||
import { describe, it, expect, afterEach, beforeEach, vi } from 'vitest';
|
||||
|
||||
describe('RawMarkdownIndicator', () => {
|
||||
const originalPlatform = process.platform;
|
||||
|
||||
beforeEach(() => vi.stubEnv('FORCE_GENERIC_KEYBINDING_HINTS', ''));
|
||||
|
||||
afterEach(() => {
|
||||
Object.defineProperty(process, 'platform', {
|
||||
value: originalPlatform,
|
||||
});
|
||||
vi.unstubAllEnvs();
|
||||
});
|
||||
|
||||
it('renders correct key binding for darwin', async () => {
|
||||
Object.defineProperty(process, 'platform', {
|
||||
value: 'darwin',
|
||||
});
|
||||
const { lastFrame, unmount } = await render(<RawMarkdownIndicator />);
|
||||
expect(lastFrame()).toContain('raw markdown mode');
|
||||
expect(lastFrame()).toContain('Option+M to toggle');
|
||||
unmount();
|
||||
});
|
||||
|
||||
it('renders correct key binding for other platforms', async () => {
|
||||
Object.defineProperty(process, 'platform', {
|
||||
value: 'linux',
|
||||
});
|
||||
const { lastFrame, unmount } = await render(<RawMarkdownIndicator />);
|
||||
expect(lastFrame()).toContain('raw markdown mode');
|
||||
expect(lastFrame()).toContain('Alt+M to toggle');
|
||||
unmount();
|
||||
});
|
||||
});
|
||||
@@ -1,23 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2025 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import type React from 'react';
|
||||
import { Box, Text } from 'ink';
|
||||
import { theme } from '../semantic-colors.js';
|
||||
import { formatCommand } from '../key/keybindingUtils.js';
|
||||
import { Command } from '../key/keyBindings.js';
|
||||
|
||||
export const RawMarkdownIndicator: React.FC = () => {
|
||||
const modKey = formatCommand(Command.TOGGLE_MARKDOWN);
|
||||
return (
|
||||
<Box>
|
||||
<Text>
|
||||
raw markdown mode
|
||||
<Text color={theme.text.secondary}> ({modKey} to toggle) </Text>
|
||||
</Text>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@@ -1,18 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2025 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { render } from '../../test-utils/render.js';
|
||||
import { ShellModeIndicator } from './ShellModeIndicator.js';
|
||||
import { describe, it, expect } from 'vitest';
|
||||
|
||||
describe('ShellModeIndicator', () => {
|
||||
it('renders correctly', async () => {
|
||||
const { lastFrame, unmount } = await render(<ShellModeIndicator />);
|
||||
expect(lastFrame()).toContain('shell mode enabled');
|
||||
expect(lastFrame()).toContain('esc to disable');
|
||||
unmount();
|
||||
});
|
||||
});
|
||||
@@ -1,18 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2025 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import type React from 'react';
|
||||
import { Box, Text } from 'ink';
|
||||
import { theme } from '../semantic-colors.js';
|
||||
|
||||
export const ShellModeIndicator: React.FC = () => (
|
||||
<Box>
|
||||
<Text color={theme.ui.symbol}>
|
||||
shell mode enabled
|
||||
<Text color={theme.text.secondary}> (esc to disable)</Text>
|
||||
</Text>
|
||||
</Box>
|
||||
);
|
||||
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2026 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import type React from 'react';
|
||||
import { Box, Text } from 'ink';
|
||||
import { theme } from '../semantic-colors.js';
|
||||
import { useUIState } from '../contexts/UIStateContext.js';
|
||||
|
||||
/**
|
||||
* A concise, ambient hint for shortcuts shown in the multipurpose status row.
|
||||
*/
|
||||
export const ShortcutsHint: React.FC = () => {
|
||||
const { cleanUiDetailsVisible, shortcutsHelpVisible } = useUIState();
|
||||
|
||||
const text = cleanUiDetailsVisible
|
||||
? '? for shortcuts'
|
||||
: 'press tab twice for more';
|
||||
|
||||
const color = shortcutsHelpVisible ? theme.text.accent : theme.text.secondary;
|
||||
|
||||
return (
|
||||
<Box flexDirection="row">
|
||||
<Text color={color}>{text}</Text>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@@ -5,420 +5,209 @@
|
||||
*/
|
||||
|
||||
import type React from 'react';
|
||||
import { useCallback, useRef, useState } from 'react';
|
||||
import { Box, Text, ResizeObserver, type DOMElement } from 'ink';
|
||||
import {
|
||||
isUserVisibleHook,
|
||||
type ThoughtSummary,
|
||||
} from '@google/gemini-cli-core';
|
||||
import stripAnsi from 'strip-ansi';
|
||||
import { type ActiveHook } from '../types.js';
|
||||
import { useUIState } from '../contexts/UIStateContext.js';
|
||||
import { useSettings } from '../contexts/SettingsContext.js';
|
||||
import { theme } from '../semantic-colors.js';
|
||||
import { Box, Text } from 'ink';
|
||||
import { isUserVisibleHook } from '@google/gemini-cli-core';
|
||||
import type { useSettings } from '../contexts/SettingsContext.js';
|
||||
import type { useUIState } from '../contexts/UIStateContext.js';
|
||||
import { GENERIC_WORKING_LABEL } from '../textConstants.js';
|
||||
import { INTERACTIVE_SHELL_WAITING_PHRASE } from '../hooks/usePhraseCycler.js';
|
||||
import { LoadingIndicator } from './LoadingIndicator.js';
|
||||
import { StatusDisplay } from './StatusDisplay.js';
|
||||
import { ContextUsageDisplay } from './ContextUsageDisplay.js';
|
||||
import { HorizontalLine } from './shared/HorizontalLine.js';
|
||||
import { ApprovalModeIndicator } from './ApprovalModeIndicator.js';
|
||||
import { ShellModeIndicator } from './ShellModeIndicator.js';
|
||||
import { RawMarkdownIndicator } from './RawMarkdownIndicator.js';
|
||||
import { useComposerStatus } from '../hooks/useComposerStatus.js';
|
||||
import { isContextUsageHigh } from '../utils/contextUsage.js';
|
||||
import { getInlineThinkingMode } from '../utils/inlineThinkingMode.js';
|
||||
import { ToastDisplay } from './ToastDisplay.js';
|
||||
import { theme } from '../semantic-colors.js';
|
||||
import { StreamingState } from '../types.js';
|
||||
|
||||
/**
|
||||
* Layout constants to prevent magic numbers.
|
||||
*/
|
||||
const LAYOUT = {
|
||||
STATUS_MIN_HEIGHT: 1,
|
||||
TIP_LEFT_MARGIN: 2,
|
||||
TIP_RIGHT_MARGIN_NARROW: 0,
|
||||
TIP_RIGHT_MARGIN_WIDE: 1,
|
||||
INDICATOR_LEFT_MARGIN: 1,
|
||||
CONTEXT_DISPLAY_TOP_MARGIN_NARROW: 1,
|
||||
CONTEXT_DISPLAY_LEFT_MARGIN_NARROW: 1,
|
||||
CONTEXT_DISPLAY_LEFT_MARGIN_WIDE: 0,
|
||||
COLLISION_GAP: 10,
|
||||
};
|
||||
interface AmbientContent {
|
||||
text: string;
|
||||
isTip: boolean;
|
||||
}
|
||||
|
||||
interface StatusRowProps {
|
||||
showUiDetails: boolean;
|
||||
isNarrow: boolean;
|
||||
terminalWidth: number;
|
||||
export interface StatusRowProps {
|
||||
uiState: ReturnType<typeof useUIState>;
|
||||
settings: ReturnType<typeof useSettings>;
|
||||
hideContextSummary: boolean;
|
||||
isNarrow: boolean;
|
||||
ambientContent: AmbientContent | null;
|
||||
showUiDetails: boolean;
|
||||
showMinimalToast: boolean;
|
||||
hideUiDetailsForSuggestions: boolean;
|
||||
hasPendingActionRequired: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the loading or hook execution status.
|
||||
*/
|
||||
export const StatusNode: React.FC<{
|
||||
showTips: boolean;
|
||||
showWit: boolean;
|
||||
thought: ThoughtSummary | null;
|
||||
elapsedTime: number;
|
||||
currentWittyPhrase: string | undefined;
|
||||
activeHooks: ActiveHook[];
|
||||
showLoadingIndicator: boolean;
|
||||
errorVerbosity: 'low' | 'full' | undefined;
|
||||
onResize?: (width: number) => void;
|
||||
}> = ({
|
||||
showTips,
|
||||
showWit,
|
||||
thought,
|
||||
elapsedTime,
|
||||
currentWittyPhrase,
|
||||
activeHooks,
|
||||
showLoadingIndicator,
|
||||
errorVerbosity,
|
||||
onResize,
|
||||
export const StatusRow: React.FC<StatusRowProps> = ({
|
||||
uiState,
|
||||
settings,
|
||||
hideContextSummary,
|
||||
isNarrow,
|
||||
ambientContent,
|
||||
showUiDetails,
|
||||
showMinimalToast,
|
||||
hideUiDetailsForSuggestions,
|
||||
hasPendingActionRequired,
|
||||
}) => {
|
||||
const observerRef = useRef<ResizeObserver | null>(null);
|
||||
const inlineThinkingMode = getInlineThinkingMode(settings);
|
||||
const loadingPhrases = settings.merged.ui.loadingPhrases;
|
||||
const showTips = loadingPhrases === 'tips' || loadingPhrases === 'all';
|
||||
const showWit = loadingPhrases === 'witty' || loadingPhrases === 'all';
|
||||
|
||||
const onRefChange = useCallback(
|
||||
(node: DOMElement | null) => {
|
||||
if (observerRef.current) {
|
||||
observerRef.current.disconnect();
|
||||
observerRef.current = null;
|
||||
}
|
||||
const showLoadingIndicator =
|
||||
(!uiState.embeddedShellFocused || uiState.isBackgroundShellVisible) &&
|
||||
uiState.streamingState === StreamingState.Responding &&
|
||||
!hasPendingActionRequired;
|
||||
|
||||
if (node && onResize) {
|
||||
const observer = new ResizeObserver((entries) => {
|
||||
const entry = entries[0];
|
||||
if (entry) {
|
||||
onResize(Math.round(entry.contentRect.width));
|
||||
}
|
||||
});
|
||||
observer.observe(node);
|
||||
observerRef.current = observer;
|
||||
}
|
||||
},
|
||||
[onResize],
|
||||
);
|
||||
|
||||
if (activeHooks.length === 0 && !showLoadingIndicator) return null;
|
||||
|
||||
let currentLoadingPhrase: string | undefined = undefined;
|
||||
let currentThought: ThoughtSummary | null = null;
|
||||
|
||||
if (activeHooks.length > 0) {
|
||||
const userVisibleHooks = activeHooks.filter((h) =>
|
||||
isUserVisibleHook(h.source),
|
||||
const showMinimalContextBleedThrough =
|
||||
!settings.merged.ui.footer.hideContextPercentage &&
|
||||
isContextUsageHigh(
|
||||
uiState.sessionStats.lastPromptTokenCount,
|
||||
typeof uiState.currentModel === 'string'
|
||||
? uiState.currentModel
|
||||
: undefined,
|
||||
);
|
||||
|
||||
const shouldReserveSpaceForShortcutsHint =
|
||||
settings.merged.ui.showShortcutsHint &&
|
||||
!hideUiDetailsForSuggestions &&
|
||||
!hasPendingActionRequired;
|
||||
|
||||
// Hook Status Logic
|
||||
const allHooks = uiState.activeHooks;
|
||||
const userVisibleHooks = allHooks.filter((h) => isUserVisibleHook(h.source));
|
||||
let hookText: string | undefined = undefined;
|
||||
if (allHooks.length > 0) {
|
||||
hookText = GENERIC_WORKING_LABEL;
|
||||
if (userVisibleHooks.length > 0) {
|
||||
const label =
|
||||
userVisibleHooks.length > 1 ? 'Executing Hooks' : 'Executing Hook';
|
||||
const displayNames = userVisibleHooks.map((h) => {
|
||||
let name = stripAnsi(h.name);
|
||||
let name = h.name;
|
||||
if (h.index && h.total && h.total > 1) {
|
||||
name += ` (${h.index}/${h.total})`;
|
||||
}
|
||||
return name;
|
||||
});
|
||||
currentLoadingPhrase = `${label}: ${displayNames.join(', ')}`;
|
||||
} else {
|
||||
currentLoadingPhrase = GENERIC_WORKING_LABEL;
|
||||
hookText = `${label}: ${displayNames.join(', ')}`;
|
||||
}
|
||||
} else {
|
||||
// Sanitize thought subject to prevent terminal injection
|
||||
currentThought = thought
|
||||
? { ...thought, subject: stripAnsi(thought.subject) }
|
||||
: null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Box ref={onRefChange}>
|
||||
<LoadingIndicator
|
||||
inline
|
||||
showTips={showTips}
|
||||
showWit={showWit}
|
||||
errorVerbosity={errorVerbosity}
|
||||
thought={currentThought}
|
||||
currentLoadingPhrase={currentLoadingPhrase}
|
||||
elapsedTime={elapsedTime}
|
||||
forceRealStatusOnly={false}
|
||||
wittyPhrase={currentWittyPhrase}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
const showMinimalMetaRow =
|
||||
!showUiDetails &&
|
||||
(showLoadingIndicator ||
|
||||
showMinimalToast ||
|
||||
showMinimalContextBleedThrough ||
|
||||
shouldReserveSpaceForShortcutsHint);
|
||||
|
||||
export const StatusRow: React.FC<StatusRowProps> = ({
|
||||
showUiDetails,
|
||||
isNarrow,
|
||||
terminalWidth,
|
||||
hideContextSummary,
|
||||
hideUiDetailsForSuggestions,
|
||||
hasPendingActionRequired,
|
||||
}) => {
|
||||
const uiState = useUIState();
|
||||
const settings = useSettings();
|
||||
const {
|
||||
isInteractiveShellWaiting,
|
||||
showLoadingIndicator,
|
||||
showTips,
|
||||
showWit,
|
||||
modeContentObj,
|
||||
showMinimalContext,
|
||||
} = useComposerStatus();
|
||||
|
||||
const [statusWidth, setStatusWidth] = useState(0);
|
||||
const [tipWidth, setTipWidth] = useState(0);
|
||||
const tipObserverRef = useRef<ResizeObserver | null>(null);
|
||||
|
||||
const onTipRefChange = useCallback((node: DOMElement | null) => {
|
||||
if (tipObserverRef.current) {
|
||||
tipObserverRef.current.disconnect();
|
||||
tipObserverRef.current = null;
|
||||
}
|
||||
|
||||
if (node) {
|
||||
const observer = new ResizeObserver((entries) => {
|
||||
const entry = entries[0];
|
||||
if (entry) {
|
||||
setTipWidth(Math.round(entry.contentRect.width));
|
||||
}
|
||||
});
|
||||
observer.observe(node);
|
||||
tipObserverRef.current = observer;
|
||||
}
|
||||
}, []);
|
||||
|
||||
const tipContentStr = (() => {
|
||||
// 1. Proactive Tip (Priority)
|
||||
if (
|
||||
showTips &&
|
||||
uiState.currentTip &&
|
||||
!(
|
||||
isInteractiveShellWaiting &&
|
||||
uiState.currentTip === INTERACTIVE_SHELL_WAITING_PHRASE
|
||||
)
|
||||
) {
|
||||
return uiState.currentTip;
|
||||
}
|
||||
|
||||
// 2. Shortcut Hint (Fallback)
|
||||
if (
|
||||
settings.merged.ui.showShortcutsHint &&
|
||||
!hideUiDetailsForSuggestions &&
|
||||
!hasPendingActionRequired &&
|
||||
uiState.buffer.text.length === 0
|
||||
) {
|
||||
return showUiDetails ? '? for shortcuts' : 'press tab twice for more';
|
||||
}
|
||||
|
||||
return undefined;
|
||||
})();
|
||||
|
||||
// Collision detection using measured widths
|
||||
const willCollideTip =
|
||||
statusWidth + tipWidth + LAYOUT.COLLISION_GAP > terminalWidth;
|
||||
|
||||
const showTipLine = Boolean(
|
||||
!hasPendingActionRequired && tipContentStr && !willCollideTip && !isNarrow,
|
||||
);
|
||||
|
||||
const showRow1Minimal =
|
||||
showLoadingIndicator || uiState.activeHooks.length > 0 || showTipLine;
|
||||
const showRow2Minimal =
|
||||
(Boolean(modeContentObj) && !hideUiDetailsForSuggestions) ||
|
||||
showMinimalContext;
|
||||
|
||||
const showRow1 = showUiDetails || showRow1Minimal;
|
||||
const showRow2 = showUiDetails || showRow2Minimal;
|
||||
|
||||
const statusNode = (
|
||||
<StatusNode
|
||||
const renderLoadingIndicator = () => (
|
||||
<LoadingIndicator
|
||||
inline
|
||||
thought={
|
||||
uiState.streamingState === StreamingState.WaitingForConfirmation ||
|
||||
inlineThinkingMode === 'full'
|
||||
? undefined
|
||||
: uiState.thought
|
||||
}
|
||||
currentLoadingPhrase={loadingPhrases === 'off' ? undefined : hookText}
|
||||
thoughtLabel={
|
||||
inlineThinkingMode === 'full'
|
||||
? typeof uiState.thought === 'string'
|
||||
? uiState.thought
|
||||
: uiState.thought?.subject || 'Thinking...'
|
||||
: undefined
|
||||
}
|
||||
elapsedTime={uiState.elapsedTime}
|
||||
showTips={showTips}
|
||||
showWit={showWit}
|
||||
thought={uiState.thought}
|
||||
elapsedTime={uiState.elapsedTime}
|
||||
currentWittyPhrase={uiState.currentWittyPhrase}
|
||||
activeHooks={uiState.activeHooks}
|
||||
showLoadingIndicator={showLoadingIndicator}
|
||||
errorVerbosity={
|
||||
settings.merged.ui.errorVerbosity as 'low' | 'full' | undefined
|
||||
}
|
||||
onResize={setStatusWidth}
|
||||
wittyPhrase={uiState.currentWittyPhrase}
|
||||
errorVerbosity={settings.merged.ui.errorVerbosity}
|
||||
/>
|
||||
);
|
||||
|
||||
const renderTipNode = () => {
|
||||
if (!tipContentStr) return null;
|
||||
|
||||
const isShortcutHint =
|
||||
tipContentStr === '? for shortcuts' ||
|
||||
tipContentStr === 'press tab twice for more';
|
||||
const color =
|
||||
isShortcutHint && uiState.shortcutsHelpVisible
|
||||
? theme.text.accent
|
||||
: theme.text.secondary;
|
||||
|
||||
return (
|
||||
<Box flexDirection="row" justifyContent="flex-end" ref={onTipRefChange}>
|
||||
<Text
|
||||
color={color}
|
||||
wrap="truncate-end"
|
||||
italic={
|
||||
!isShortcutHint && tipContentStr === uiState.currentWittyPhrase
|
||||
}
|
||||
>
|
||||
{tipContentStr === uiState.currentTip
|
||||
? `Tip: ${tipContentStr}`
|
||||
: tipContentStr}
|
||||
</Text>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
if (!showUiDetails && !showRow1Minimal && !showRow2Minimal) {
|
||||
return <Box height={LAYOUT.STATUS_MIN_HEIGHT} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Box flexDirection="column" width="100%">
|
||||
{/* Row 1: Status & Tips */}
|
||||
{showRow1 && (
|
||||
<>
|
||||
{/* Minimal UI Mode Meta Row */}
|
||||
{showMinimalMetaRow && (
|
||||
<Box
|
||||
width="100%"
|
||||
flexDirection="row"
|
||||
alignItems="center"
|
||||
justifyContent="space-between"
|
||||
minHeight={LAYOUT.STATUS_MIN_HEIGHT}
|
||||
>
|
||||
<Box flexDirection="row" flexGrow={1} flexShrink={1}>
|
||||
{!showUiDetails && showRow1Minimal ? (
|
||||
<Box flexDirection="row" columnGap={1}>
|
||||
{statusNode}
|
||||
{!showUiDetails && showRow2Minimal && modeContentObj && (
|
||||
<Box>
|
||||
<Text color={modeContentObj.color}>
|
||||
● {modeContentObj.text}
|
||||
</Text>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
) : isInteractiveShellWaiting ? (
|
||||
<Box width="100%" marginLeft={LAYOUT.INDICATOR_LEFT_MARGIN}>
|
||||
<Text color={theme.status.warning}>
|
||||
! Shell awaiting input (Tab to focus)
|
||||
</Text>
|
||||
</Box>
|
||||
) : (
|
||||
<Box
|
||||
flexDirection="row"
|
||||
alignItems={isNarrow ? 'flex-start' : 'center'}
|
||||
flexGrow={1}
|
||||
flexShrink={0}
|
||||
marginLeft={LAYOUT.INDICATOR_LEFT_MARGIN}
|
||||
>
|
||||
{statusNode}
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
<Box
|
||||
flexShrink={0}
|
||||
marginLeft={LAYOUT.TIP_LEFT_MARGIN}
|
||||
marginRight={
|
||||
isNarrow
|
||||
? LAYOUT.TIP_RIGHT_MARGIN_NARROW
|
||||
: LAYOUT.TIP_RIGHT_MARGIN_WIDE
|
||||
}
|
||||
>
|
||||
{/*
|
||||
We always render the tip node so it can be measured by ResizeObserver,
|
||||
but we control its visibility based on the collision detection.
|
||||
*/}
|
||||
<Box display={showTipLine ? 'flex' : 'none'}>
|
||||
{!isNarrow && tipContentStr && renderTipNode()}
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* Internal Separator */}
|
||||
{showRow1 &&
|
||||
showRow2 &&
|
||||
(showUiDetails || (showRow1Minimal && showRow2Minimal)) && (
|
||||
<Box width="100%">
|
||||
<HorizontalLine dim />
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* Row 2: Modes & Context */}
|
||||
{showRow2 && (
|
||||
<Box
|
||||
width="100%"
|
||||
flexDirection={isNarrow ? 'column' : 'row'}
|
||||
alignItems={isNarrow ? 'flex-start' : 'center'}
|
||||
justifyContent="space-between"
|
||||
>
|
||||
<Box
|
||||
marginLeft={1}
|
||||
marginRight={isNarrow ? 0 : 1}
|
||||
flexDirection="row"
|
||||
alignItems="center"
|
||||
marginLeft={LAYOUT.INDICATOR_LEFT_MARGIN}
|
||||
alignItems={isNarrow ? 'flex-start' : 'center'}
|
||||
flexGrow={1}
|
||||
>
|
||||
{showUiDetails ? (
|
||||
<>
|
||||
{!hideUiDetailsForSuggestions && !uiState.shellModeActive && (
|
||||
<ApprovalModeIndicator
|
||||
approvalMode={uiState.showApprovalModeIndicator}
|
||||
allowPlanMode={uiState.allowPlanMode}
|
||||
/>
|
||||
)}
|
||||
{uiState.shellModeActive && (
|
||||
<Box marginLeft={LAYOUT.INDICATOR_LEFT_MARGIN}>
|
||||
<ShellModeIndicator />
|
||||
</Box>
|
||||
)}
|
||||
{!uiState.renderMarkdown && (
|
||||
<Box marginLeft={LAYOUT.INDICATOR_LEFT_MARGIN}>
|
||||
<RawMarkdownIndicator />
|
||||
</Box>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
showRow2Minimal &&
|
||||
modeContentObj && (
|
||||
<Text color={modeContentObj.color}>
|
||||
● {modeContentObj.text}
|
||||
</Text>
|
||||
)
|
||||
{!showUiDetails && showLoadingIndicator && renderLoadingIndicator()}
|
||||
{showMinimalToast && (
|
||||
<Box marginLeft={!showUiDetails && showLoadingIndicator ? 1 : 0}>
|
||||
<ToastDisplay />
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
<Box
|
||||
marginTop={isNarrow ? LAYOUT.CONTEXT_DISPLAY_TOP_MARGIN_NARROW : 0}
|
||||
flexDirection="row"
|
||||
alignItems="center"
|
||||
marginLeft={
|
||||
isNarrow
|
||||
? LAYOUT.CONTEXT_DISPLAY_LEFT_MARGIN_NARROW
|
||||
: LAYOUT.CONTEXT_DISPLAY_LEFT_MARGIN_WIDE
|
||||
}
|
||||
>
|
||||
{(showUiDetails || showMinimalContext) && (
|
||||
<StatusDisplay hideContextSummary={hideContextSummary} />
|
||||
)}
|
||||
{showMinimalContext && !showUiDetails && (
|
||||
<Box marginLeft={LAYOUT.INDICATOR_LEFT_MARGIN}>
|
||||
{(showMinimalContextBleedThrough ||
|
||||
(ambientContent && !showUiDetails)) && (
|
||||
<Box
|
||||
marginTop={isNarrow && showMinimalToast ? 1 : 0}
|
||||
flexDirection={isNarrow ? 'column' : 'row'}
|
||||
alignItems={isNarrow ? 'flex-start' : 'flex-end'}
|
||||
minHeight={1}
|
||||
>
|
||||
{showMinimalContextBleedThrough && (
|
||||
<ContextUsageDisplay
|
||||
promptTokenCount={uiState.sessionStats.lastPromptTokenCount}
|
||||
model={
|
||||
typeof uiState.currentModel === 'string'
|
||||
? uiState.currentModel
|
||||
: undefined
|
||||
}
|
||||
terminalWidth={terminalWidth}
|
||||
model={uiState.currentModel}
|
||||
terminalWidth={uiState.terminalWidth}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
{ambientContent && !showUiDetails && (
|
||||
<Box
|
||||
marginLeft={
|
||||
showMinimalContextBleedThrough && !isNarrow ? 1 : 0
|
||||
}
|
||||
marginTop={showMinimalContextBleedThrough && isNarrow ? 1 : 0}
|
||||
>
|
||||
<Text color={theme.text.secondary} wrap="truncate-end">
|
||||
{ambientContent.text}
|
||||
</Text>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* Below Divider Zone: Active Processing and Status */}
|
||||
{showUiDetails && (
|
||||
<Box
|
||||
justifyContent="space-between"
|
||||
width="100%"
|
||||
flexDirection={isNarrow ? 'column' : 'row'}
|
||||
alignItems={isNarrow ? 'flex-start' : 'center'}
|
||||
>
|
||||
<Box
|
||||
marginLeft={1}
|
||||
marginRight={isNarrow ? 0 : 1}
|
||||
flexDirection="row"
|
||||
alignItems="center"
|
||||
flexGrow={1}
|
||||
>
|
||||
{showLoadingIndicator && renderLoadingIndicator()}
|
||||
</Box>
|
||||
|
||||
<Box
|
||||
marginTop={isNarrow ? 1 : 0}
|
||||
flexDirection="column"
|
||||
alignItems={isNarrow ? 'flex-start' : 'flex-end'}
|
||||
>
|
||||
{!showLoadingIndicator && (
|
||||
<StatusDisplay hideContextSummary={hideContextSummary} />
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2026 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { renderWithProviders } from '../../test-utils/render.js';
|
||||
import {
|
||||
UnifiedModeIndicator,
|
||||
getModeHeaderLabel,
|
||||
} from './UnifiedModeIndicator.js';
|
||||
import {
|
||||
MODE_HEADER_SHELL,
|
||||
MODE_HEADER_YOLO,
|
||||
MODE_HEADER_DEFAULT,
|
||||
} from '../textConstants.js';
|
||||
import { ApprovalMode } from '@google/gemini-cli-core';
|
||||
|
||||
describe('UnifiedModeIndicator', () => {
|
||||
describe('getModeHeaderLabel', () => {
|
||||
it('returns shell exit label when shell is active', () => {
|
||||
expect(getModeHeaderLabel(ApprovalMode.DEFAULT, true)).toBe(
|
||||
MODE_HEADER_SHELL,
|
||||
);
|
||||
});
|
||||
|
||||
it('returns yolo toggle label when YOLO is active and shell is NOT active', () => {
|
||||
expect(getModeHeaderLabel(ApprovalMode.YOLO, false)).toBe(
|
||||
MODE_HEADER_YOLO,
|
||||
);
|
||||
});
|
||||
|
||||
it('returns default mode label for other modes', () => {
|
||||
expect(getModeHeaderLabel(ApprovalMode.DEFAULT, false)).toBe(
|
||||
MODE_HEADER_DEFAULT,
|
||||
);
|
||||
expect(getModeHeaderLabel(ApprovalMode.PLAN, false)).toBe(
|
||||
MODE_HEADER_DEFAULT,
|
||||
);
|
||||
expect(getModeHeaderLabel(ApprovalMode.AUTO_EDIT, false)).toBe(
|
||||
MODE_HEADER_DEFAULT,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Rendering', () => {
|
||||
it('renders shell mode with precedence over YOLO', async () => {
|
||||
const { lastFrame } = await renderWithProviders(
|
||||
<UnifiedModeIndicator
|
||||
approvalMode={ApprovalMode.YOLO}
|
||||
shellModeActive={true}
|
||||
renderMarkdown={true}
|
||||
/>,
|
||||
);
|
||||
expect(lastFrame()).toContain('shell');
|
||||
expect(lastFrame()).not.toContain('YOLO');
|
||||
});
|
||||
|
||||
it('renders YOLO mode with precedence over background mode', async () => {
|
||||
const { lastFrame } = await renderWithProviders(
|
||||
<UnifiedModeIndicator
|
||||
approvalMode={ApprovalMode.YOLO}
|
||||
shellModeActive={false}
|
||||
renderMarkdown={true}
|
||||
/>,
|
||||
);
|
||||
expect(lastFrame()).toContain('YOLO');
|
||||
expect(lastFrame()).not.toContain('manual');
|
||||
});
|
||||
|
||||
it('renders background mode (manual)', async () => {
|
||||
const { lastFrame } = await renderWithProviders(
|
||||
<UnifiedModeIndicator
|
||||
approvalMode={ApprovalMode.DEFAULT}
|
||||
shellModeActive={false}
|
||||
renderMarkdown={true}
|
||||
/>,
|
||||
);
|
||||
expect(lastFrame()).toContain('manual');
|
||||
});
|
||||
|
||||
it('renders background mode (plan)', async () => {
|
||||
const { lastFrame } = await renderWithProviders(
|
||||
<UnifiedModeIndicator
|
||||
approvalMode={ApprovalMode.PLAN}
|
||||
shellModeActive={false}
|
||||
renderMarkdown={true}
|
||||
/>,
|
||||
);
|
||||
expect(lastFrame()).toContain('plan');
|
||||
});
|
||||
|
||||
it('renders background mode (auto-accept)', async () => {
|
||||
const { lastFrame } = await renderWithProviders(
|
||||
<UnifiedModeIndicator
|
||||
approvalMode={ApprovalMode.AUTO_EDIT}
|
||||
shellModeActive={false}
|
||||
renderMarkdown={true}
|
||||
/>,
|
||||
);
|
||||
expect(lastFrame()).toContain('auto-accept');
|
||||
});
|
||||
|
||||
it('renders raw markdown modifier', async () => {
|
||||
const { lastFrame } = await renderWithProviders(
|
||||
<UnifiedModeIndicator
|
||||
approvalMode={ApprovalMode.DEFAULT}
|
||||
shellModeActive={false}
|
||||
renderMarkdown={false}
|
||||
/>,
|
||||
);
|
||||
expect(lastFrame()).toContain('manual');
|
||||
expect(lastFrame()).toContain('·');
|
||||
expect(lastFrame()).toContain('raw');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,109 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2026 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import type React from 'react';
|
||||
import { Box, Text } from 'ink';
|
||||
import { ApprovalMode } from '@google/gemini-cli-core';
|
||||
import { theme } from '../semantic-colors.js';
|
||||
|
||||
import {
|
||||
MODE_HEADER_SHELL,
|
||||
MODE_HEADER_YOLO,
|
||||
MODE_HEADER_DEFAULT,
|
||||
} from '../textConstants.js';
|
||||
|
||||
export interface UnifiedModeIndicatorProps {
|
||||
approvalMode: ApprovalMode;
|
||||
shellModeActive: boolean;
|
||||
renderMarkdown: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the dynamic header label for the mode section.
|
||||
*/
|
||||
export function getModeHeaderLabel(
|
||||
approvalMode: ApprovalMode,
|
||||
shellModeActive: boolean,
|
||||
): string {
|
||||
if (shellModeActive) {
|
||||
return MODE_HEADER_SHELL;
|
||||
}
|
||||
if (approvalMode === ApprovalMode.YOLO) {
|
||||
return MODE_HEADER_YOLO;
|
||||
}
|
||||
return MODE_HEADER_DEFAULT;
|
||||
}
|
||||
|
||||
/**
|
||||
* A unified indicator that handles ApprovalMode, ShellMode, and RawMarkdownMode.
|
||||
* It enforces a visual hierarchy where special modes like Shell and YOLO
|
||||
* obscure the background mode.
|
||||
*/
|
||||
export const UnifiedModeIndicator: React.FC<UnifiedModeIndicatorProps> = ({
|
||||
approvalMode,
|
||||
shellModeActive,
|
||||
renderMarkdown,
|
||||
}) => {
|
||||
const parts: React.ReactNode[] = [];
|
||||
|
||||
// 1. Primary Mode (Shell > YOLO > Others)
|
||||
let modeTextColor = theme.text.accent;
|
||||
let modeText = 'manual';
|
||||
|
||||
if (shellModeActive) {
|
||||
modeTextColor = theme.ui.symbol;
|
||||
modeText = 'shell';
|
||||
} else if (approvalMode === ApprovalMode.YOLO) {
|
||||
modeTextColor = theme.status.error;
|
||||
modeText = 'YOLO';
|
||||
} else {
|
||||
switch (approvalMode) {
|
||||
case ApprovalMode.AUTO_EDIT:
|
||||
modeTextColor = theme.status.warning;
|
||||
modeText = 'auto-accept';
|
||||
break;
|
||||
case ApprovalMode.PLAN:
|
||||
modeTextColor = theme.status.success;
|
||||
modeText = 'plan';
|
||||
break;
|
||||
case ApprovalMode.DEFAULT:
|
||||
default:
|
||||
modeTextColor = theme.text.accent;
|
||||
modeText = 'manual';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
parts.push(
|
||||
<Text key="mode" color={modeTextColor}>
|
||||
{modeText}
|
||||
</Text>,
|
||||
);
|
||||
|
||||
// 2. Secondary Modifier: Raw Markdown Mode
|
||||
if (!renderMarkdown) {
|
||||
parts.push(
|
||||
<Text key="raw" color={theme.text.secondary}>
|
||||
raw
|
||||
</Text>,
|
||||
);
|
||||
}
|
||||
|
||||
// Join parts with middle dot separator
|
||||
const renderedParts: React.ReactNode[] = [];
|
||||
parts.forEach((part, index) => {
|
||||
if (index > 0) {
|
||||
renderedParts.push(
|
||||
<Text key={`sep-${index}`} color={theme.ui.comment}>
|
||||
{' · '}
|
||||
</Text>,
|
||||
);
|
||||
}
|
||||
renderedParts.push(part);
|
||||
});
|
||||
|
||||
return <Box>{renderedParts}</Box>;
|
||||
};
|
||||
@@ -1,33 +1,32 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`Composer > Snapshots > matches snapshot in idle state 1`] = `
|
||||
"
|
||||
? for shortcuts
|
||||
" ? for shortcuts
|
||||
────────────────────────────────────────────────────────────────────────────────────────────────────
|
||||
ApprovalModeIndicator: default StatusDisplay
|
||||
StatusDisplay
|
||||
InputPrompt: Type your message or @path/to/file
|
||||
Footer
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`Composer > Snapshots > matches snapshot in minimal UI mode 1`] = `
|
||||
" press tab twice for more
|
||||
" press tab twice for more
|
||||
InputPrompt: Type your message or @path/to/file
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`Composer > Snapshots > matches snapshot in minimal UI mode while loading 1`] = `
|
||||
"LoadingIndicator press tab twice for more
|
||||
" LoadingIndicator press tab twice for more
|
||||
InputPrompt: Type your message or @path/to/file
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`Composer > Snapshots > matches snapshot in narrow view 1`] = `
|
||||
"
|
||||
? for shortcuts
|
||||
? for shortcuts
|
||||
────────────────────────────────────────
|
||||
ApprovalModeIndicator: StatusDispl
|
||||
default ay
|
||||
|
||||
StatusDisplay
|
||||
InputPrompt: Type your message or
|
||||
@path/to/file
|
||||
Footer
|
||||
@@ -35,10 +34,9 @@ Footer
|
||||
`;
|
||||
|
||||
exports[`Composer > Snapshots > matches snapshot while streaming 1`] = `
|
||||
"
|
||||
LoadingIndicator: Thinking ? for shortcuts
|
||||
" ? for shortcuts
|
||||
────────────────────────────────────────────────────────────────────────────────────────────────────
|
||||
ApprovalModeIndicator: default StatusDisplay
|
||||
LoadingIndicator: Thinking
|
||||
InputPrompt: Type your message or @path/to/file
|
||||
Footer
|
||||
"
|
||||
|
||||
@@ -1,45 +1,49 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`<Footer /> > 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[`<Footer /> > 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 /> > 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 /> > 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 /> > 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 /> > footer configuration filtering (golden snapshots) > renders footer with all optional sections hidden (minimal footer) > footer-minimal 1`] = `""`;
|
||||
exports[`<Footer /> > footer configuration filtering (golden snapshots) > renders footer with all optional sections hidden (minimal footer) > footer-minimal 1`] = `
|
||||
" mode (Shift+Tab)
|
||||
manual
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`<Footer /> > 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[`<Footer /> > 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%
|
||||
"
|
||||
`;
|
||||
|
||||
+71
-64
@@ -1,8 +1,8 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="920" height="700" viewBox="0 0 920 700">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="920" height="734" viewBox="0 0 920 734">
|
||||
<style>
|
||||
text { font-family: Consolas, "Courier New", monospace; font-size: 14px; dominant-baseline: text-before-edge; white-space: pre; }
|
||||
</style>
|
||||
<rect width="920" height="700" fill="#000000" />
|
||||
<rect width="920" height="734" fill="#000000" />
|
||||
<g transform="translate(10, 10)">
|
||||
<text x="0" y="2" fill="#333333" textLength="900" lengthAdjust="spacingAndGlyphs">╭──────────────────────────────────────────────────────────────────────────────────────────────────╮</text>
|
||||
<text x="0" y="19" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
@@ -19,140 +19,147 @@
|
||||
<text x="891" y="87" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="104" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="45" y="104" fill="#d7ffd7" textLength="27" lengthAdjust="spacingAndGlyphs">[✓]</text>
|
||||
<text x="72" y="104" fill="#ffffff" textLength="90" lengthAdjust="spacingAndGlyphs"> workspace</text>
|
||||
<text x="72" y="104" fill="#ffffff" textLength="45" lengthAdjust="spacingAndGlyphs"> mode</text>
|
||||
<text x="891" y="104" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="121" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="45" y="121" fill="#afafaf" textLength="234" lengthAdjust="spacingAndGlyphs"> Current working directory</text>
|
||||
<text x="45" y="121" fill="#afafaf" textLength="576" lengthAdjust="spacingAndGlyphs"> Current session state: approval mode, shell mode, markdown mode</text>
|
||||
<text x="891" y="121" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="138" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="45" y="138" fill="#d7ffd7" textLength="27" lengthAdjust="spacingAndGlyphs">[✓]</text>
|
||||
<text x="72" y="138" fill="#ffffff" textLength="99" lengthAdjust="spacingAndGlyphs"> git-branch</text>
|
||||
<text x="72" y="138" fill="#ffffff" textLength="90" lengthAdjust="spacingAndGlyphs"> workspace</text>
|
||||
<text x="891" y="138" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="155" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="45" y="155" fill="#afafaf" textLength="477" lengthAdjust="spacingAndGlyphs"> Current git branch name (not shown when unavailable)</text>
|
||||
<text x="45" y="155" fill="#afafaf" textLength="234" lengthAdjust="spacingAndGlyphs"> Current working directory</text>
|
||||
<text x="891" y="155" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="172" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="45" y="172" fill="#d7ffd7" textLength="27" lengthAdjust="spacingAndGlyphs">[✓]</text>
|
||||
<text x="72" y="172" fill="#ffffff" textLength="72" lengthAdjust="spacingAndGlyphs"> sandbox</text>
|
||||
<text x="72" y="172" fill="#ffffff" textLength="99" lengthAdjust="spacingAndGlyphs"> git-branch</text>
|
||||
<text x="891" y="172" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="189" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="45" y="189" fill="#afafaf" textLength="297" lengthAdjust="spacingAndGlyphs"> Sandbox type and trust indicator</text>
|
||||
<text x="45" y="189" fill="#afafaf" textLength="477" lengthAdjust="spacingAndGlyphs"> Current git branch name (not shown when unavailable)</text>
|
||||
<text x="891" y="189" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="206" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="45" y="206" fill="#d7ffd7" textLength="27" lengthAdjust="spacingAndGlyphs">[✓]</text>
|
||||
<text x="72" y="206" fill="#ffffff" textLength="99" lengthAdjust="spacingAndGlyphs"> model-name</text>
|
||||
<text x="72" y="206" fill="#ffffff" textLength="72" lengthAdjust="spacingAndGlyphs"> sandbox</text>
|
||||
<text x="891" y="206" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="223" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="45" y="223" fill="#afafaf" textLength="225" lengthAdjust="spacingAndGlyphs"> Current model identifier</text>
|
||||
<text x="45" y="223" fill="#afafaf" textLength="297" lengthAdjust="spacingAndGlyphs"> Sandbox type and trust indicator</text>
|
||||
<text x="891" y="223" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="240" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="45" y="240" fill="#d7ffd7" textLength="27" lengthAdjust="spacingAndGlyphs">[✓]</text>
|
||||
<text x="72" y="240" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs"> quota</text>
|
||||
<text x="72" y="240" fill="#ffffff" textLength="99" lengthAdjust="spacingAndGlyphs"> model-name</text>
|
||||
<text x="891" y="240" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="257" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="45" y="257" fill="#afafaf" textLength="540" lengthAdjust="spacingAndGlyphs"> Remaining usage on daily limit (not shown when unavailable)</text>
|
||||
<text x="45" y="257" fill="#afafaf" textLength="225" lengthAdjust="spacingAndGlyphs"> Current model identifier</text>
|
||||
<text x="891" y="257" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="274" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="45" y="274" fill="#afafaf" textLength="27" lengthAdjust="spacingAndGlyphs">[ ]</text>
|
||||
<text x="72" y="274" fill="#ffffff" textLength="117" lengthAdjust="spacingAndGlyphs"> context-used</text>
|
||||
<text x="45" y="274" fill="#d7ffd7" textLength="27" lengthAdjust="spacingAndGlyphs">[✓]</text>
|
||||
<text x="72" y="274" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs"> quota</text>
|
||||
<text x="891" y="274" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="291" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="45" y="291" fill="#afafaf" textLength="306" lengthAdjust="spacingAndGlyphs"> Percentage of context window used</text>
|
||||
<text x="45" y="291" fill="#afafaf" textLength="540" lengthAdjust="spacingAndGlyphs"> Remaining usage on daily limit (not shown when unavailable)</text>
|
||||
<text x="891" y="291" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="308" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="45" y="308" fill="#afafaf" textLength="27" lengthAdjust="spacingAndGlyphs">[ ]</text>
|
||||
<text x="72" y="308" fill="#ffffff" textLength="117" lengthAdjust="spacingAndGlyphs"> memory-usage</text>
|
||||
<text x="72" y="308" fill="#ffffff" textLength="117" lengthAdjust="spacingAndGlyphs"> context-used</text>
|
||||
<text x="891" y="308" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="325" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="45" y="325" fill="#afafaf" textLength="279" lengthAdjust="spacingAndGlyphs"> Memory used by the application</text>
|
||||
<text x="45" y="325" fill="#afafaf" textLength="306" lengthAdjust="spacingAndGlyphs"> Percentage of context window used</text>
|
||||
<text x="891" y="325" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="342" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="45" y="342" fill="#afafaf" textLength="27" lengthAdjust="spacingAndGlyphs">[ ]</text>
|
||||
<text x="72" y="342" fill="#ffffff" textLength="99" lengthAdjust="spacingAndGlyphs"> session-id</text>
|
||||
<text x="72" y="342" fill="#ffffff" textLength="117" lengthAdjust="spacingAndGlyphs"> memory-usage</text>
|
||||
<text x="891" y="342" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="359" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="45" y="359" fill="#afafaf" textLength="378" lengthAdjust="spacingAndGlyphs"> Unique identifier for the current session</text>
|
||||
<text x="45" y="359" fill="#afafaf" textLength="279" lengthAdjust="spacingAndGlyphs"> Memory used by the application</text>
|
||||
<text x="891" y="359" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="376" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<rect x="27" y="374" width="9" height="17" fill="#001a00" />
|
||||
<text x="27" y="376" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs">></text>
|
||||
<rect x="36" y="374" width="9" height="17" fill="#001a00" />
|
||||
<rect x="45" y="374" width="27" height="17" fill="#001a00" />
|
||||
<text x="45" y="376" fill="#d7ffd7" textLength="27" lengthAdjust="spacingAndGlyphs">[✓]</text>
|
||||
<rect x="72" y="374" width="117" height="17" fill="#001a00" />
|
||||
<text x="72" y="376" fill="#00cd00" textLength="117" lengthAdjust="spacingAndGlyphs"> code-changes</text>
|
||||
<rect x="189" y="374" width="684" height="17" fill="#001a00" />
|
||||
<text x="45" y="376" fill="#afafaf" textLength="27" lengthAdjust="spacingAndGlyphs">[ ]</text>
|
||||
<text x="72" y="376" fill="#ffffff" textLength="99" lengthAdjust="spacingAndGlyphs"> session-id</text>
|
||||
<text x="891" y="376" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="393" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<rect x="27" y="391" width="18" height="17" fill="#001a00" />
|
||||
<rect x="45" y="391" width="513" height="17" fill="#001a00" />
|
||||
<text x="45" y="393" fill="#afafaf" textLength="513" lengthAdjust="spacingAndGlyphs"> Lines added/removed in the session (not shown when zero)</text>
|
||||
<rect x="558" y="391" width="315" height="17" fill="#001a00" />
|
||||
<text x="45" y="393" fill="#afafaf" textLength="378" lengthAdjust="spacingAndGlyphs"> Unique identifier for the current session</text>
|
||||
<text x="891" y="393" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="410" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="45" y="410" fill="#afafaf" textLength="27" lengthAdjust="spacingAndGlyphs">[ ]</text>
|
||||
<text x="72" y="410" fill="#ffffff" textLength="108" lengthAdjust="spacingAndGlyphs"> token-count</text>
|
||||
<rect x="27" y="408" width="9" height="17" fill="#001a00" />
|
||||
<text x="27" y="410" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs">></text>
|
||||
<rect x="36" y="408" width="9" height="17" fill="#001a00" />
|
||||
<rect x="45" y="408" width="27" height="17" fill="#001a00" />
|
||||
<text x="45" y="410" fill="#d7ffd7" textLength="27" lengthAdjust="spacingAndGlyphs">[✓]</text>
|
||||
<rect x="72" y="408" width="117" height="17" fill="#001a00" />
|
||||
<text x="72" y="410" fill="#00cd00" textLength="117" lengthAdjust="spacingAndGlyphs"> code-changes</text>
|
||||
<rect x="189" y="408" width="684" height="17" fill="#001a00" />
|
||||
<text x="891" y="410" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="427" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="45" y="427" fill="#afafaf" textLength="495" lengthAdjust="spacingAndGlyphs"> Total tokens used in the session (not shown when zero)</text>
|
||||
<rect x="27" y="425" width="18" height="17" fill="#001a00" />
|
||||
<rect x="45" y="425" width="513" height="17" fill="#001a00" />
|
||||
<text x="45" y="427" fill="#afafaf" textLength="513" lengthAdjust="spacingAndGlyphs"> Lines added/removed in the session (not shown when zero)</text>
|
||||
<rect x="558" y="425" width="315" height="17" fill="#001a00" />
|
||||
<text x="891" y="427" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="444" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="45" y="444" fill="#d7ffd7" textLength="27" lengthAdjust="spacingAndGlyphs">[✓]</text>
|
||||
<text x="72" y="444" fill="#ffffff" textLength="171" lengthAdjust="spacingAndGlyphs"> Show footer labels</text>
|
||||
<text x="45" y="444" fill="#afafaf" textLength="27" lengthAdjust="spacingAndGlyphs">[ ]</text>
|
||||
<text x="72" y="444" fill="#ffffff" textLength="108" lengthAdjust="spacingAndGlyphs"> token-count</text>
|
||||
<text x="891" y="444" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="461" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="45" y="461" fill="#afafaf" textLength="495" lengthAdjust="spacingAndGlyphs"> Total tokens used in the session (not shown when zero)</text>
|
||||
<text x="891" y="461" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="478" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="45" y="478" fill="#ffffff" textLength="207" lengthAdjust="spacingAndGlyphs">Reset to default footer</text>
|
||||
<text x="45" y="478" fill="#d7ffd7" textLength="27" lengthAdjust="spacingAndGlyphs">[✓]</text>
|
||||
<text x="72" y="478" fill="#ffffff" textLength="171" lengthAdjust="spacingAndGlyphs"> Show footer labels</text>
|
||||
<text x="891" y="478" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="495" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="891" y="495" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="512" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="45" y="512" fill="#ffffff" textLength="207" lengthAdjust="spacingAndGlyphs">Reset to default footer</text>
|
||||
<text x="891" y="512" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="529" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="27" y="529" fill="#afafaf" textLength="585" lengthAdjust="spacingAndGlyphs">Enter to select · ↑/↓ to navigate · ←/→ to reorder · Esc to close</text>
|
||||
<text x="891" y="529" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="546" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="891" y="546" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="563" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="27" y="563" fill="#333333" textLength="846" lengthAdjust="spacingAndGlyphs">┌────────────────────────────────────────────────────────────────────────────────────────────┐</text>
|
||||
<text x="27" y="563" fill="#afafaf" textLength="585" lengthAdjust="spacingAndGlyphs">Enter to select · ↑/↓ to navigate · ←/→ to reorder · Esc to close</text>
|
||||
<text x="891" y="563" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="580" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="27" y="580" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="45" y="580" fill="#ffffff" textLength="72" lengthAdjust="spacingAndGlyphs" font-weight="bold">Preview:</text>
|
||||
<text x="864" y="580" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="891" y="580" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="597" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="27" y="597" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="45" y="597" fill="#afafaf" textLength="198" lengthAdjust="spacingAndGlyphs">workspace (/directory)</text>
|
||||
<text x="297" y="597" fill="#afafaf" textLength="54" lengthAdjust="spacingAndGlyphs">branch</text>
|
||||
<text x="405" y="597" fill="#afafaf" textLength="63" lengthAdjust="spacingAndGlyphs">sandbox</text>
|
||||
<text x="513" y="597" fill="#afafaf" textLength="54" lengthAdjust="spacingAndGlyphs">/model</text>
|
||||
<text x="693" y="597" fill="#afafaf" textLength="54" lengthAdjust="spacingAndGlyphs">/stats</text>
|
||||
<rect x="801" y="595" width="36" height="17" fill="#001a00" />
|
||||
<text x="801" y="597" fill="#ffffff" textLength="36" lengthAdjust="spacingAndGlyphs">diff</text>
|
||||
<rect x="837" y="595" width="18" height="17" fill="#001a00" />
|
||||
<text x="864" y="597" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="27" y="597" fill="#333333" textLength="846" lengthAdjust="spacingAndGlyphs">┌────────────────────────────────────────────────────────────────────────────────────────────┐</text>
|
||||
<text x="891" y="597" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="614" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="27" y="614" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="45" y="614" fill="#ffffff" textLength="126" lengthAdjust="spacingAndGlyphs">~/project/path</text>
|
||||
<text x="297" y="614" fill="#ffffff" textLength="36" lengthAdjust="spacingAndGlyphs">main</text>
|
||||
<text x="405" y="614" fill="#00cd00" textLength="54" lengthAdjust="spacingAndGlyphs">docker</text>
|
||||
<text x="513" y="614" fill="#ffffff" textLength="126" lengthAdjust="spacingAndGlyphs">gemini-2.5-pro</text>
|
||||
<text x="693" y="614" fill="#ffffff" textLength="27" lengthAdjust="spacingAndGlyphs">97%</text>
|
||||
<rect x="801" y="612" width="27" height="17" fill="#001a00" />
|
||||
<text x="801" y="614" fill="#d7ffd7" textLength="27" lengthAdjust="spacingAndGlyphs">+12</text>
|
||||
<rect x="828" y="612" width="9" height="17" fill="#001a00" />
|
||||
<rect x="837" y="612" width="18" height="17" fill="#001a00" />
|
||||
<text x="837" y="614" fill="#ff87af" textLength="18" lengthAdjust="spacingAndGlyphs">-4</text>
|
||||
<text x="45" y="614" fill="#ffffff" textLength="72" lengthAdjust="spacingAndGlyphs" font-weight="bold">Preview:</text>
|
||||
<text x="864" y="614" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="891" y="614" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="631" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="27" y="631" fill="#333333" textLength="846" lengthAdjust="spacingAndGlyphs">└────────────────────────────────────────────────────────────────────────────────────────────┘</text>
|
||||
<text x="27" y="631" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="45" y="631" fill="#afafaf" textLength="198" lengthAdjust="spacingAndGlyphs">workspace (/directory)</text>
|
||||
<text x="297" y="631" fill="#afafaf" textLength="54" lengthAdjust="spacingAndGlyphs">branch</text>
|
||||
<text x="405" y="631" fill="#afafaf" textLength="63" lengthAdjust="spacingAndGlyphs">sandbox</text>
|
||||
<text x="513" y="631" fill="#afafaf" textLength="54" lengthAdjust="spacingAndGlyphs">/model</text>
|
||||
<text x="693" y="631" fill="#afafaf" textLength="54" lengthAdjust="spacingAndGlyphs">/stats</text>
|
||||
<rect x="801" y="629" width="36" height="17" fill="#001a00" />
|
||||
<text x="801" y="631" fill="#ffffff" textLength="36" lengthAdjust="spacingAndGlyphs">diff</text>
|
||||
<rect x="837" y="629" width="18" height="17" fill="#001a00" />
|
||||
<text x="864" y="631" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="891" y="631" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="648" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="27" y="648" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="45" y="648" fill="#ffffff" textLength="126" lengthAdjust="spacingAndGlyphs">~/project/path</text>
|
||||
<text x="297" y="648" fill="#ffffff" textLength="36" lengthAdjust="spacingAndGlyphs">main</text>
|
||||
<text x="405" y="648" fill="#00cd00" textLength="54" lengthAdjust="spacingAndGlyphs">docker</text>
|
||||
<text x="513" y="648" fill="#ffffff" textLength="126" lengthAdjust="spacingAndGlyphs">gemini-2.5-pro</text>
|
||||
<text x="693" y="648" fill="#ffffff" textLength="27" lengthAdjust="spacingAndGlyphs">97%</text>
|
||||
<rect x="801" y="646" width="27" height="17" fill="#001a00" />
|
||||
<text x="801" y="648" fill="#d7ffd7" textLength="27" lengthAdjust="spacingAndGlyphs">+12</text>
|
||||
<rect x="828" y="646" width="9" height="17" fill="#001a00" />
|
||||
<rect x="837" y="646" width="18" height="17" fill="#001a00" />
|
||||
<text x="837" y="648" fill="#ff87af" textLength="18" lengthAdjust="spacingAndGlyphs">-4</text>
|
||||
<text x="864" y="648" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="891" y="648" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="665" fill="#333333" textLength="900" lengthAdjust="spacingAndGlyphs">╰──────────────────────────────────────────────────────────────────────────────────────────────────╯</text>
|
||||
<text x="0" y="665" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="27" y="665" fill="#333333" textLength="846" lengthAdjust="spacingAndGlyphs">└────────────────────────────────────────────────────────────────────────────────────────────┘</text>
|
||||
<text x="891" y="665" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="682" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="891" y="682" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="699" fill="#333333" textLength="900" lengthAdjust="spacingAndGlyphs">╰──────────────────────────────────────────────────────────────────────────────────────────────────╯</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 17 KiB |
+56
-52
@@ -1,8 +1,8 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="920" height="700" viewBox="0 0 920 700">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="920" height="734" viewBox="0 0 920 734">
|
||||
<style>
|
||||
text { font-family: Consolas, "Courier New", monospace; font-size: 14px; dominant-baseline: text-before-edge; white-space: pre; }
|
||||
</style>
|
||||
<rect width="920" height="700" fill="#000000" />
|
||||
<rect width="920" height="734" fill="#000000" />
|
||||
<g transform="translate(10, 10)">
|
||||
<text x="0" y="2" fill="#333333" textLength="900" lengthAdjust="spacingAndGlyphs">╭──────────────────────────────────────────────────────────────────────────────────────────────────╮</text>
|
||||
<text x="0" y="19" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
@@ -23,131 +23,135 @@
|
||||
<rect x="36" y="102" width="9" height="17" fill="#001a00" />
|
||||
<rect x="45" y="102" width="27" height="17" fill="#001a00" />
|
||||
<text x="45" y="104" fill="#d7ffd7" textLength="27" lengthAdjust="spacingAndGlyphs">[✓]</text>
|
||||
<rect x="72" y="102" width="90" height="17" fill="#001a00" />
|
||||
<text x="72" y="104" fill="#00cd00" textLength="90" lengthAdjust="spacingAndGlyphs"> workspace</text>
|
||||
<rect x="162" y="102" width="711" height="17" fill="#001a00" />
|
||||
<rect x="72" y="102" width="45" height="17" fill="#001a00" />
|
||||
<text x="72" y="104" fill="#00cd00" textLength="45" lengthAdjust="spacingAndGlyphs"> mode</text>
|
||||
<rect x="117" y="102" width="756" height="17" fill="#001a00" />
|
||||
<text x="891" y="104" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="121" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<rect x="27" y="119" width="18" height="17" fill="#001a00" />
|
||||
<rect x="45" y="119" width="234" height="17" fill="#001a00" />
|
||||
<text x="45" y="121" fill="#afafaf" textLength="234" lengthAdjust="spacingAndGlyphs"> Current working directory</text>
|
||||
<rect x="279" y="119" width="594" height="17" fill="#001a00" />
|
||||
<rect x="45" y="119" width="576" height="17" fill="#001a00" />
|
||||
<text x="45" y="121" fill="#afafaf" textLength="576" lengthAdjust="spacingAndGlyphs"> Current session state: approval mode, shell mode, markdown mode</text>
|
||||
<rect x="621" y="119" width="252" height="17" fill="#001a00" />
|
||||
<text x="891" y="121" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="138" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="45" y="138" fill="#d7ffd7" textLength="27" lengthAdjust="spacingAndGlyphs">[✓]</text>
|
||||
<text x="72" y="138" fill="#ffffff" textLength="99" lengthAdjust="spacingAndGlyphs"> git-branch</text>
|
||||
<text x="72" y="138" fill="#ffffff" textLength="90" lengthAdjust="spacingAndGlyphs"> workspace</text>
|
||||
<text x="891" y="138" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="155" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="45" y="155" fill="#afafaf" textLength="477" lengthAdjust="spacingAndGlyphs"> Current git branch name (not shown when unavailable)</text>
|
||||
<text x="45" y="155" fill="#afafaf" textLength="234" lengthAdjust="spacingAndGlyphs"> Current working directory</text>
|
||||
<text x="891" y="155" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="172" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="45" y="172" fill="#d7ffd7" textLength="27" lengthAdjust="spacingAndGlyphs">[✓]</text>
|
||||
<text x="72" y="172" fill="#ffffff" textLength="72" lengthAdjust="spacingAndGlyphs"> sandbox</text>
|
||||
<text x="72" y="172" fill="#ffffff" textLength="99" lengthAdjust="spacingAndGlyphs"> git-branch</text>
|
||||
<text x="891" y="172" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="189" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="45" y="189" fill="#afafaf" textLength="297" lengthAdjust="spacingAndGlyphs"> Sandbox type and trust indicator</text>
|
||||
<text x="45" y="189" fill="#afafaf" textLength="477" lengthAdjust="spacingAndGlyphs"> Current git branch name (not shown when unavailable)</text>
|
||||
<text x="891" y="189" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="206" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="45" y="206" fill="#d7ffd7" textLength="27" lengthAdjust="spacingAndGlyphs">[✓]</text>
|
||||
<text x="72" y="206" fill="#ffffff" textLength="99" lengthAdjust="spacingAndGlyphs"> model-name</text>
|
||||
<text x="72" y="206" fill="#ffffff" textLength="72" lengthAdjust="spacingAndGlyphs"> sandbox</text>
|
||||
<text x="891" y="206" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="223" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="45" y="223" fill="#afafaf" textLength="225" lengthAdjust="spacingAndGlyphs"> Current model identifier</text>
|
||||
<text x="45" y="223" fill="#afafaf" textLength="297" lengthAdjust="spacingAndGlyphs"> Sandbox type and trust indicator</text>
|
||||
<text x="891" y="223" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="240" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="45" y="240" fill="#d7ffd7" textLength="27" lengthAdjust="spacingAndGlyphs">[✓]</text>
|
||||
<text x="72" y="240" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs"> quota</text>
|
||||
<text x="72" y="240" fill="#ffffff" textLength="99" lengthAdjust="spacingAndGlyphs"> model-name</text>
|
||||
<text x="891" y="240" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="257" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="45" y="257" fill="#afafaf" textLength="540" lengthAdjust="spacingAndGlyphs"> Remaining usage on daily limit (not shown when unavailable)</text>
|
||||
<text x="45" y="257" fill="#afafaf" textLength="225" lengthAdjust="spacingAndGlyphs"> Current model identifier</text>
|
||||
<text x="891" y="257" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="274" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="45" y="274" fill="#afafaf" textLength="27" lengthAdjust="spacingAndGlyphs">[ ]</text>
|
||||
<text x="72" y="274" fill="#ffffff" textLength="117" lengthAdjust="spacingAndGlyphs"> context-used</text>
|
||||
<text x="45" y="274" fill="#d7ffd7" textLength="27" lengthAdjust="spacingAndGlyphs">[✓]</text>
|
||||
<text x="72" y="274" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs"> quota</text>
|
||||
<text x="891" y="274" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="291" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="45" y="291" fill="#afafaf" textLength="306" lengthAdjust="spacingAndGlyphs"> Percentage of context window used</text>
|
||||
<text x="45" y="291" fill="#afafaf" textLength="540" lengthAdjust="spacingAndGlyphs"> Remaining usage on daily limit (not shown when unavailable)</text>
|
||||
<text x="891" y="291" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="308" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="45" y="308" fill="#afafaf" textLength="27" lengthAdjust="spacingAndGlyphs">[ ]</text>
|
||||
<text x="72" y="308" fill="#ffffff" textLength="117" lengthAdjust="spacingAndGlyphs"> memory-usage</text>
|
||||
<text x="72" y="308" fill="#ffffff" textLength="117" lengthAdjust="spacingAndGlyphs"> context-used</text>
|
||||
<text x="891" y="308" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="325" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="45" y="325" fill="#afafaf" textLength="279" lengthAdjust="spacingAndGlyphs"> Memory used by the application</text>
|
||||
<text x="45" y="325" fill="#afafaf" textLength="306" lengthAdjust="spacingAndGlyphs"> Percentage of context window used</text>
|
||||
<text x="891" y="325" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="342" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="45" y="342" fill="#afafaf" textLength="27" lengthAdjust="spacingAndGlyphs">[ ]</text>
|
||||
<text x="72" y="342" fill="#ffffff" textLength="99" lengthAdjust="spacingAndGlyphs"> session-id</text>
|
||||
<text x="72" y="342" fill="#ffffff" textLength="117" lengthAdjust="spacingAndGlyphs"> memory-usage</text>
|
||||
<text x="891" y="342" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="359" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="45" y="359" fill="#afafaf" textLength="378" lengthAdjust="spacingAndGlyphs"> Unique identifier for the current session</text>
|
||||
<text x="45" y="359" fill="#afafaf" textLength="279" lengthAdjust="spacingAndGlyphs"> Memory used by the application</text>
|
||||
<text x="891" y="359" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="376" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="45" y="376" fill="#afafaf" textLength="27" lengthAdjust="spacingAndGlyphs">[ ]</text>
|
||||
<text x="72" y="376" fill="#ffffff" textLength="117" lengthAdjust="spacingAndGlyphs"> code-changes</text>
|
||||
<text x="72" y="376" fill="#ffffff" textLength="99" lengthAdjust="spacingAndGlyphs"> session-id</text>
|
||||
<text x="891" y="376" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="393" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="45" y="393" fill="#afafaf" textLength="513" lengthAdjust="spacingAndGlyphs"> Lines added/removed in the session (not shown when zero)</text>
|
||||
<text x="45" y="393" fill="#afafaf" textLength="378" lengthAdjust="spacingAndGlyphs"> Unique identifier for the current session</text>
|
||||
<text x="891" y="393" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="410" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="45" y="410" fill="#afafaf" textLength="27" lengthAdjust="spacingAndGlyphs">[ ]</text>
|
||||
<text x="72" y="410" fill="#ffffff" textLength="108" lengthAdjust="spacingAndGlyphs"> token-count</text>
|
||||
<text x="72" y="410" fill="#ffffff" textLength="117" lengthAdjust="spacingAndGlyphs"> code-changes</text>
|
||||
<text x="891" y="410" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="427" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="45" y="427" fill="#afafaf" textLength="495" lengthAdjust="spacingAndGlyphs"> Total tokens used in the session (not shown when zero)</text>
|
||||
<text x="45" y="427" fill="#afafaf" textLength="513" lengthAdjust="spacingAndGlyphs"> Lines added/removed in the session (not shown when zero)</text>
|
||||
<text x="891" y="427" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="444" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="45" y="444" fill="#d7ffd7" textLength="27" lengthAdjust="spacingAndGlyphs">[✓]</text>
|
||||
<text x="72" y="444" fill="#ffffff" textLength="171" lengthAdjust="spacingAndGlyphs"> Show footer labels</text>
|
||||
<text x="45" y="444" fill="#afafaf" textLength="27" lengthAdjust="spacingAndGlyphs">[ ]</text>
|
||||
<text x="72" y="444" fill="#ffffff" textLength="108" lengthAdjust="spacingAndGlyphs"> token-count</text>
|
||||
<text x="891" y="444" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="461" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="45" y="461" fill="#afafaf" textLength="495" lengthAdjust="spacingAndGlyphs"> Total tokens used in the session (not shown when zero)</text>
|
||||
<text x="891" y="461" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="478" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="45" y="478" fill="#ffffff" textLength="207" lengthAdjust="spacingAndGlyphs">Reset to default footer</text>
|
||||
<text x="45" y="478" fill="#d7ffd7" textLength="27" lengthAdjust="spacingAndGlyphs">[✓]</text>
|
||||
<text x="72" y="478" fill="#ffffff" textLength="171" lengthAdjust="spacingAndGlyphs"> Show footer labels</text>
|
||||
<text x="891" y="478" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="495" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="891" y="495" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="512" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="45" y="512" fill="#ffffff" textLength="207" lengthAdjust="spacingAndGlyphs">Reset to default footer</text>
|
||||
<text x="891" y="512" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="529" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="27" y="529" fill="#afafaf" textLength="585" lengthAdjust="spacingAndGlyphs">Enter to select · ↑/↓ to navigate · ←/→ to reorder · Esc to close</text>
|
||||
<text x="891" y="529" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="546" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="891" y="546" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="563" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="27" y="563" fill="#333333" textLength="846" lengthAdjust="spacingAndGlyphs">┌────────────────────────────────────────────────────────────────────────────────────────────┐</text>
|
||||
<text x="27" y="563" fill="#afafaf" textLength="585" lengthAdjust="spacingAndGlyphs">Enter to select · ↑/↓ to navigate · ←/→ to reorder · Esc to close</text>
|
||||
<text x="891" y="563" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="580" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="27" y="580" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="45" y="580" fill="#ffffff" textLength="72" lengthAdjust="spacingAndGlyphs" font-weight="bold">Preview:</text>
|
||||
<text x="864" y="580" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="891" y="580" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="597" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="27" y="597" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<rect x="45" y="595" width="198" height="17" fill="#001a00" />
|
||||
<text x="45" y="597" fill="#ffffff" textLength="198" lengthAdjust="spacingAndGlyphs">workspace (/directory)</text>
|
||||
<text x="324" y="597" fill="#afafaf" textLength="54" lengthAdjust="spacingAndGlyphs">branch</text>
|
||||
<text x="459" y="597" fill="#afafaf" textLength="63" lengthAdjust="spacingAndGlyphs">sandbox</text>
|
||||
<text x="594" y="597" fill="#afafaf" textLength="54" lengthAdjust="spacingAndGlyphs">/model</text>
|
||||
<text x="801" y="597" fill="#afafaf" textLength="54" lengthAdjust="spacingAndGlyphs">/stats</text>
|
||||
<text x="864" y="597" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="27" y="597" fill="#333333" textLength="846" lengthAdjust="spacingAndGlyphs">┌────────────────────────────────────────────────────────────────────────────────────────────┐</text>
|
||||
<text x="891" y="597" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="614" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="27" y="614" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<rect x="45" y="612" width="126" height="17" fill="#001a00" />
|
||||
<text x="45" y="614" fill="#ffffff" textLength="126" lengthAdjust="spacingAndGlyphs">~/project/path</text>
|
||||
<rect x="171" y="612" width="72" height="17" fill="#001a00" />
|
||||
<text x="324" y="614" fill="#ffffff" textLength="36" lengthAdjust="spacingAndGlyphs">main</text>
|
||||
<text x="459" y="614" fill="#00cd00" textLength="54" lengthAdjust="spacingAndGlyphs">docker</text>
|
||||
<text x="594" y="614" fill="#ffffff" textLength="126" lengthAdjust="spacingAndGlyphs">gemini-2.5-pro</text>
|
||||
<text x="801" y="614" fill="#ffffff" textLength="27" lengthAdjust="spacingAndGlyphs">97%</text>
|
||||
<text x="45" y="614" fill="#ffffff" textLength="72" lengthAdjust="spacingAndGlyphs" font-weight="bold">Preview:</text>
|
||||
<text x="864" y="614" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="891" y="614" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="631" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="27" y="631" fill="#333333" textLength="846" lengthAdjust="spacingAndGlyphs">└────────────────────────────────────────────────────────────────────────────────────────────┘</text>
|
||||
<text x="27" y="631" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="45" y="631" fill="#afafaf" textLength="198" lengthAdjust="spacingAndGlyphs">workspace (/directory)</text>
|
||||
<text x="324" y="631" fill="#afafaf" textLength="54" lengthAdjust="spacingAndGlyphs">branch</text>
|
||||
<text x="459" y="631" fill="#afafaf" textLength="63" lengthAdjust="spacingAndGlyphs">sandbox</text>
|
||||
<text x="594" y="631" fill="#afafaf" textLength="54" lengthAdjust="spacingAndGlyphs">/model</text>
|
||||
<text x="801" y="631" fill="#afafaf" textLength="54" lengthAdjust="spacingAndGlyphs">/stats</text>
|
||||
<text x="864" y="631" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="891" y="631" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="648" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="27" y="648" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="45" y="648" fill="#ffffff" textLength="126" lengthAdjust="spacingAndGlyphs">~/project/path</text>
|
||||
<text x="324" y="648" fill="#ffffff" textLength="36" lengthAdjust="spacingAndGlyphs">main</text>
|
||||
<text x="459" y="648" fill="#00cd00" textLength="54" lengthAdjust="spacingAndGlyphs">docker</text>
|
||||
<text x="594" y="648" fill="#ffffff" textLength="126" lengthAdjust="spacingAndGlyphs">gemini-2.5-pro</text>
|
||||
<text x="801" y="648" fill="#ffffff" textLength="27" lengthAdjust="spacingAndGlyphs">97%</text>
|
||||
<text x="864" y="648" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="891" y="648" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="665" fill="#333333" textLength="900" lengthAdjust="spacingAndGlyphs">╰──────────────────────────────────────────────────────────────────────────────────────────────────╯</text>
|
||||
<text x="0" y="665" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="27" y="665" fill="#333333" textLength="846" lengthAdjust="spacingAndGlyphs">└────────────────────────────────────────────────────────────────────────────────────────────┘</text>
|
||||
<text x="891" y="665" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="682" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="891" y="682" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="699" fill="#333333" textLength="900" lengthAdjust="spacingAndGlyphs">╰──────────────────────────────────────────────────────────────────────────────────────────────────╯</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
+57
-50
@@ -1,8 +1,8 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="920" height="683" viewBox="0 0 920 683">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="920" height="717" viewBox="0 0 920 717">
|
||||
<style>
|
||||
text { font-family: Consolas, "Courier New", monospace; font-size: 14px; dominant-baseline: text-before-edge; white-space: pre; }
|
||||
</style>
|
||||
<rect width="920" height="683" fill="#000000" />
|
||||
<rect width="920" height="717" fill="#000000" />
|
||||
<g transform="translate(10, 10)">
|
||||
<text x="0" y="2" fill="#333333" textLength="900" lengthAdjust="spacingAndGlyphs">╭──────────────────────────────────────────────────────────────────────────────────────────────────╮</text>
|
||||
<text x="0" y="19" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
@@ -19,125 +19,132 @@
|
||||
<text x="891" y="87" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="104" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="45" y="104" fill="#d7ffd7" textLength="27" lengthAdjust="spacingAndGlyphs">[✓]</text>
|
||||
<text x="72" y="104" fill="#ffffff" textLength="90" lengthAdjust="spacingAndGlyphs"> workspace</text>
|
||||
<text x="72" y="104" fill="#ffffff" textLength="45" lengthAdjust="spacingAndGlyphs"> mode</text>
|
||||
<text x="891" y="104" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="121" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="45" y="121" fill="#afafaf" textLength="234" lengthAdjust="spacingAndGlyphs"> Current working directory</text>
|
||||
<text x="45" y="121" fill="#afafaf" textLength="576" lengthAdjust="spacingAndGlyphs"> Current session state: approval mode, shell mode, markdown mode</text>
|
||||
<text x="891" y="121" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="138" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="45" y="138" fill="#d7ffd7" textLength="27" lengthAdjust="spacingAndGlyphs">[✓]</text>
|
||||
<text x="72" y="138" fill="#ffffff" textLength="99" lengthAdjust="spacingAndGlyphs"> git-branch</text>
|
||||
<text x="72" y="138" fill="#ffffff" textLength="90" lengthAdjust="spacingAndGlyphs"> workspace</text>
|
||||
<text x="891" y="138" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="155" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="45" y="155" fill="#afafaf" textLength="477" lengthAdjust="spacingAndGlyphs"> Current git branch name (not shown when unavailable)</text>
|
||||
<text x="45" y="155" fill="#afafaf" textLength="234" lengthAdjust="spacingAndGlyphs"> Current working directory</text>
|
||||
<text x="891" y="155" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="172" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="45" y="172" fill="#d7ffd7" textLength="27" lengthAdjust="spacingAndGlyphs">[✓]</text>
|
||||
<text x="72" y="172" fill="#ffffff" textLength="72" lengthAdjust="spacingAndGlyphs"> sandbox</text>
|
||||
<text x="72" y="172" fill="#ffffff" textLength="99" lengthAdjust="spacingAndGlyphs"> git-branch</text>
|
||||
<text x="891" y="172" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="189" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="45" y="189" fill="#afafaf" textLength="297" lengthAdjust="spacingAndGlyphs"> Sandbox type and trust indicator</text>
|
||||
<text x="45" y="189" fill="#afafaf" textLength="477" lengthAdjust="spacingAndGlyphs"> Current git branch name (not shown when unavailable)</text>
|
||||
<text x="891" y="189" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="206" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="45" y="206" fill="#d7ffd7" textLength="27" lengthAdjust="spacingAndGlyphs">[✓]</text>
|
||||
<text x="72" y="206" fill="#ffffff" textLength="99" lengthAdjust="spacingAndGlyphs"> model-name</text>
|
||||
<text x="72" y="206" fill="#ffffff" textLength="72" lengthAdjust="spacingAndGlyphs"> sandbox</text>
|
||||
<text x="891" y="206" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="223" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="45" y="223" fill="#afafaf" textLength="225" lengthAdjust="spacingAndGlyphs"> Current model identifier</text>
|
||||
<text x="45" y="223" fill="#afafaf" textLength="297" lengthAdjust="spacingAndGlyphs"> Sandbox type and trust indicator</text>
|
||||
<text x="891" y="223" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="240" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="45" y="240" fill="#d7ffd7" textLength="27" lengthAdjust="spacingAndGlyphs">[✓]</text>
|
||||
<text x="72" y="240" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs"> quota</text>
|
||||
<text x="72" y="240" fill="#ffffff" textLength="99" lengthAdjust="spacingAndGlyphs"> model-name</text>
|
||||
<text x="891" y="240" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="257" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="45" y="257" fill="#afafaf" textLength="540" lengthAdjust="spacingAndGlyphs"> Remaining usage on daily limit (not shown when unavailable)</text>
|
||||
<text x="45" y="257" fill="#afafaf" textLength="225" lengthAdjust="spacingAndGlyphs"> Current model identifier</text>
|
||||
<text x="891" y="257" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="274" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="45" y="274" fill="#afafaf" textLength="27" lengthAdjust="spacingAndGlyphs">[ ]</text>
|
||||
<text x="72" y="274" fill="#ffffff" textLength="117" lengthAdjust="spacingAndGlyphs"> context-used</text>
|
||||
<text x="45" y="274" fill="#d7ffd7" textLength="27" lengthAdjust="spacingAndGlyphs">[✓]</text>
|
||||
<text x="72" y="274" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs"> quota</text>
|
||||
<text x="891" y="274" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="291" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="45" y="291" fill="#afafaf" textLength="306" lengthAdjust="spacingAndGlyphs"> Percentage of context window used</text>
|
||||
<text x="45" y="291" fill="#afafaf" textLength="540" lengthAdjust="spacingAndGlyphs"> Remaining usage on daily limit (not shown when unavailable)</text>
|
||||
<text x="891" y="291" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="308" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="45" y="308" fill="#afafaf" textLength="27" lengthAdjust="spacingAndGlyphs">[ ]</text>
|
||||
<text x="72" y="308" fill="#ffffff" textLength="117" lengthAdjust="spacingAndGlyphs"> memory-usage</text>
|
||||
<text x="72" y="308" fill="#ffffff" textLength="117" lengthAdjust="spacingAndGlyphs"> context-used</text>
|
||||
<text x="891" y="308" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="325" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="45" y="325" fill="#afafaf" textLength="279" lengthAdjust="spacingAndGlyphs"> Memory used by the application</text>
|
||||
<text x="45" y="325" fill="#afafaf" textLength="306" lengthAdjust="spacingAndGlyphs"> Percentage of context window used</text>
|
||||
<text x="891" y="325" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="342" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="45" y="342" fill="#afafaf" textLength="27" lengthAdjust="spacingAndGlyphs">[ ]</text>
|
||||
<text x="72" y="342" fill="#ffffff" textLength="99" lengthAdjust="spacingAndGlyphs"> session-id</text>
|
||||
<text x="72" y="342" fill="#ffffff" textLength="117" lengthAdjust="spacingAndGlyphs"> memory-usage</text>
|
||||
<text x="891" y="342" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="359" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="45" y="359" fill="#afafaf" textLength="378" lengthAdjust="spacingAndGlyphs"> Unique identifier for the current session</text>
|
||||
<text x="45" y="359" fill="#afafaf" textLength="279" lengthAdjust="spacingAndGlyphs"> Memory used by the application</text>
|
||||
<text x="891" y="359" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="376" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="45" y="376" fill="#afafaf" textLength="27" lengthAdjust="spacingAndGlyphs">[ ]</text>
|
||||
<text x="72" y="376" fill="#ffffff" textLength="117" lengthAdjust="spacingAndGlyphs"> code-changes</text>
|
||||
<text x="72" y="376" fill="#ffffff" textLength="99" lengthAdjust="spacingAndGlyphs"> session-id</text>
|
||||
<text x="891" y="376" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="393" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="45" y="393" fill="#afafaf" textLength="513" lengthAdjust="spacingAndGlyphs"> Lines added/removed in the session (not shown when zero)</text>
|
||||
<text x="45" y="393" fill="#afafaf" textLength="378" lengthAdjust="spacingAndGlyphs"> Unique identifier for the current session</text>
|
||||
<text x="891" y="393" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="410" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="45" y="410" fill="#afafaf" textLength="27" lengthAdjust="spacingAndGlyphs">[ ]</text>
|
||||
<text x="72" y="410" fill="#ffffff" textLength="108" lengthAdjust="spacingAndGlyphs"> token-count</text>
|
||||
<text x="72" y="410" fill="#ffffff" textLength="117" lengthAdjust="spacingAndGlyphs"> code-changes</text>
|
||||
<text x="891" y="410" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="427" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="45" y="427" fill="#afafaf" textLength="495" lengthAdjust="spacingAndGlyphs"> Total tokens used in the session (not shown when zero)</text>
|
||||
<text x="45" y="427" fill="#afafaf" textLength="513" lengthAdjust="spacingAndGlyphs"> Lines added/removed in the session (not shown when zero)</text>
|
||||
<text x="891" y="427" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="444" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<rect x="27" y="442" width="9" height="17" fill="#001a00" />
|
||||
<text x="27" y="444" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs">></text>
|
||||
<rect x="36" y="442" width="9" height="17" fill="#001a00" />
|
||||
<rect x="45" y="442" width="27" height="17" fill="#001a00" />
|
||||
<text x="45" y="444" fill="#afafaf" textLength="27" lengthAdjust="spacingAndGlyphs">[ ]</text>
|
||||
<rect x="72" y="442" width="171" height="17" fill="#001a00" />
|
||||
<text x="72" y="444" fill="#00cd00" textLength="171" lengthAdjust="spacingAndGlyphs"> Show footer labels</text>
|
||||
<rect x="243" y="442" width="630" height="17" fill="#001a00" />
|
||||
<text x="72" y="444" fill="#ffffff" textLength="108" lengthAdjust="spacingAndGlyphs"> token-count</text>
|
||||
<text x="891" y="444" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="461" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<rect x="27" y="459" width="846" height="17" fill="#001a00" />
|
||||
<text x="45" y="461" fill="#afafaf" textLength="495" lengthAdjust="spacingAndGlyphs"> Total tokens used in the session (not shown when zero)</text>
|
||||
<text x="891" y="461" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="478" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="45" y="478" fill="#ffffff" textLength="207" lengthAdjust="spacingAndGlyphs">Reset to default footer</text>
|
||||
<rect x="27" y="476" width="9" height="17" fill="#001a00" />
|
||||
<text x="27" y="478" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs">></text>
|
||||
<rect x="36" y="476" width="9" height="17" fill="#001a00" />
|
||||
<rect x="45" y="476" width="27" height="17" fill="#001a00" />
|
||||
<text x="45" y="478" fill="#afafaf" textLength="27" lengthAdjust="spacingAndGlyphs">[ ]</text>
|
||||
<rect x="72" y="476" width="171" height="17" fill="#001a00" />
|
||||
<text x="72" y="478" fill="#00cd00" textLength="171" lengthAdjust="spacingAndGlyphs"> Show footer labels</text>
|
||||
<rect x="243" y="476" width="630" height="17" fill="#001a00" />
|
||||
<text x="891" y="478" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="495" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<rect x="27" y="493" width="846" height="17" fill="#001a00" />
|
||||
<text x="891" y="495" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="512" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="45" y="512" fill="#ffffff" textLength="207" lengthAdjust="spacingAndGlyphs">Reset to default footer</text>
|
||||
<text x="891" y="512" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="529" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="27" y="529" fill="#afafaf" textLength="585" lengthAdjust="spacingAndGlyphs">Enter to select · ↑/↓ to navigate · ←/→ to reorder · Esc to close</text>
|
||||
<text x="891" y="529" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="546" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="891" y="546" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="563" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="27" y="563" fill="#333333" textLength="846" lengthAdjust="spacingAndGlyphs">┌────────────────────────────────────────────────────────────────────────────────────────────┐</text>
|
||||
<text x="27" y="563" fill="#afafaf" textLength="585" lengthAdjust="spacingAndGlyphs">Enter to select · ↑/↓ to navigate · ←/→ to reorder · Esc to close</text>
|
||||
<text x="891" y="563" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="580" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="27" y="580" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="45" y="580" fill="#ffffff" textLength="72" lengthAdjust="spacingAndGlyphs" font-weight="bold">Preview:</text>
|
||||
<text x="864" y="580" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="891" y="580" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="597" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="27" y="597" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="45" y="597" fill="#afafaf" textLength="126" lengthAdjust="spacingAndGlyphs">~/project/path</text>
|
||||
<text x="207" y="597" fill="#afafaf" textLength="27" lengthAdjust="spacingAndGlyphs"> · </text>
|
||||
<text x="279" y="597" fill="#afafaf" textLength="36" lengthAdjust="spacingAndGlyphs">main</text>
|
||||
<text x="351" y="597" fill="#afafaf" textLength="27" lengthAdjust="spacingAndGlyphs"> · </text>
|
||||
<text x="432" y="597" fill="#00cd00" textLength="54" lengthAdjust="spacingAndGlyphs">docker</text>
|
||||
<text x="522" y="597" fill="#afafaf" textLength="27" lengthAdjust="spacingAndGlyphs"> · </text>
|
||||
<text x="594" y="597" fill="#afafaf" textLength="126" lengthAdjust="spacingAndGlyphs">gemini-2.5-pro</text>
|
||||
<text x="756" y="597" fill="#afafaf" textLength="27" lengthAdjust="spacingAndGlyphs"> · </text>
|
||||
<text x="828" y="597" fill="#afafaf" textLength="27" lengthAdjust="spacingAndGlyphs">97%</text>
|
||||
<text x="864" y="597" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="27" y="597" fill="#333333" textLength="846" lengthAdjust="spacingAndGlyphs">┌────────────────────────────────────────────────────────────────────────────────────────────┐</text>
|
||||
<text x="891" y="597" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="614" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="27" y="614" fill="#333333" textLength="846" lengthAdjust="spacingAndGlyphs">└────────────────────────────────────────────────────────────────────────────────────────────┘</text>
|
||||
<text x="27" y="614" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="45" y="614" fill="#ffffff" textLength="72" lengthAdjust="spacingAndGlyphs" font-weight="bold">Preview:</text>
|
||||
<text x="864" y="614" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="891" y="614" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="631" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="27" y="631" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="45" y="631" fill="#afafaf" textLength="126" lengthAdjust="spacingAndGlyphs">~/project/path</text>
|
||||
<text x="207" y="631" fill="#afafaf" textLength="27" lengthAdjust="spacingAndGlyphs"> · </text>
|
||||
<text x="279" y="631" fill="#afafaf" textLength="36" lengthAdjust="spacingAndGlyphs">main</text>
|
||||
<text x="351" y="631" fill="#afafaf" textLength="27" lengthAdjust="spacingAndGlyphs"> · </text>
|
||||
<text x="432" y="631" fill="#00cd00" textLength="54" lengthAdjust="spacingAndGlyphs">docker</text>
|
||||
<text x="522" y="631" fill="#afafaf" textLength="27" lengthAdjust="spacingAndGlyphs"> · </text>
|
||||
<text x="594" y="631" fill="#afafaf" textLength="126" lengthAdjust="spacingAndGlyphs">gemini-2.5-pro</text>
|
||||
<text x="756" y="631" fill="#afafaf" textLength="27" lengthAdjust="spacingAndGlyphs"> · </text>
|
||||
<text x="828" y="631" fill="#afafaf" textLength="27" lengthAdjust="spacingAndGlyphs">97%</text>
|
||||
<text x="864" y="631" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="891" y="631" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="648" fill="#333333" textLength="900" lengthAdjust="spacingAndGlyphs">╰──────────────────────────────────────────────────────────────────────────────────────────────────╯</text>
|
||||
<text x="0" y="648" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="27" y="648" fill="#333333" textLength="846" lengthAdjust="spacingAndGlyphs">└────────────────────────────────────────────────────────────────────────────────────────────┘</text>
|
||||
<text x="891" y="648" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="665" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="891" y="665" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="682" fill="#333333" textLength="900" lengthAdjust="spacingAndGlyphs">╰──────────────────────────────────────────────────────────────────────────────────────────────────╯</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 16 KiB |
@@ -7,6 +7,8 @@ exports[`<FooterConfigDialog /> > highlights the active item in the preview 1`]
|
||||
│ │
|
||||
│ Select which items to display in the footer. │
|
||||
│ │
|
||||
│ [✓] mode │
|
||||
│ Current session state: approval mode, shell mode, markdown mode │
|
||||
│ [✓] workspace │
|
||||
│ Current working directory │
|
||||
│ [✓] git-branch │
|
||||
@@ -50,7 +52,9 @@ exports[`<FooterConfigDialog /> > renders correctly with default settings 1`] =
|
||||
│ │
|
||||
│ Select which items to display in the footer. │
|
||||
│ │
|
||||
│ > [✓] workspace │
|
||||
│ > [✓] mode │
|
||||
│ Current session state: approval mode, shell mode, markdown mode │
|
||||
│ [✓] workspace │
|
||||
│ Current working directory │
|
||||
│ [✓] git-branch │
|
||||
│ Current git branch name (not shown when unavailable) │
|
||||
@@ -94,7 +98,9 @@ exports[`<FooterConfigDialog /> > renders correctly with default settings 2`] =
|
||||
│ │
|
||||
│ Select which items to display in the footer. │
|
||||
│ │
|
||||
│ > [✓] workspace │
|
||||
│ > [✓] mode │
|
||||
│ Current session state: approval mode, shell mode, markdown mode │
|
||||
│ [✓] workspace │
|
||||
│ Current working directory │
|
||||
│ [✓] git-branch │
|
||||
│ Current git branch name (not shown when unavailable) │
|
||||
@@ -137,6 +143,8 @@ exports[`<FooterConfigDialog /> > updates the preview when Show footer labels is
|
||||
│ │
|
||||
│ Select which items to display in the footer. │
|
||||
│ │
|
||||
│ [✓] mode │
|
||||
│ Current session state: approval mode, shell mode, markdown mode │
|
||||
│ [✓] workspace │
|
||||
│ Current working directory │
|
||||
│ [✓] git-branch │
|
||||
|
||||
@@ -20,3 +20,7 @@ export const getRedirectionWarningTipText = (shiftTabHint: string) =>
|
||||
`Toggle auto-edit (${shiftTabHint}) to allow redirection in the future.`;
|
||||
|
||||
export const GENERIC_WORKING_LABEL = 'Working...';
|
||||
|
||||
export const MODE_HEADER_SHELL = 'exit shell (!)';
|
||||
export const MODE_HEADER_YOLO = 'toggle yolo (Ctrl+Y)';
|
||||
export const MODE_HEADER_DEFAULT = 'mode (Shift+Tab)';
|
||||
|
||||
Reference in New Issue
Block a user