feat(cli): Show permission mode badge in footer for DEFAULT mode (#6498)

Always display the current permission/approval mode in the footer,
including when in the default (Ask permissions) mode. Previously,
non-default modes showed indicators but the default mode showed nothing,
creating ambiguity for users switching between modes.

- Add grey ⏸ badge with 'Ask permissions' text for DEFAULT mode
- Update both main Footer and AgentFooter to render DEFAULT indicator
- Use theme.text.secondary for subtle, unobtrusive styling
- Badge is i18n-aware using existing t('Ask permissions') key
- Other mode indicators remain unchanged

Closes #6496
This commit is contained in:
DennisYu07 2026-07-08 11:25:32 +08:00 committed by GitHub
parent 29cefd7fb1
commit 79bc668b71
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 70 additions and 6 deletions

View file

@ -0,0 +1,49 @@
/**
* @license
* Copyright 2025 Qwen Team
* SPDX-License-Identifier: Apache-2.0
*/
import { render } from 'ink-testing-library';
import { describe, it, expect } from 'vitest';
import { AutoAcceptIndicator } from './AutoAcceptIndicator.js';
import { ApprovalMode } from '@qwen-code/qwen-code-core';
describe('<AutoAcceptIndicator />', () => {
it('renders DEFAULT mode with pause badge and Ask permissions text', () => {
const { lastFrame } = render(
<AutoAcceptIndicator approvalMode={ApprovalMode.DEFAULT} />,
);
const frame = lastFrame()!;
expect(frame).toContain('⏸');
expect(frame).toContain('Ask permissions');
});
it('renders PLAN mode indicator', () => {
const { lastFrame } = render(
<AutoAcceptIndicator approvalMode={ApprovalMode.PLAN} />,
);
expect(lastFrame()).toContain('plan mode');
});
it('renders AUTO_EDIT mode indicator', () => {
const { lastFrame } = render(
<AutoAcceptIndicator approvalMode={ApprovalMode.AUTO_EDIT} />,
);
expect(lastFrame()).toContain('auto-accept edits');
});
it('renders AUTO mode indicator', () => {
const { lastFrame } = render(
<AutoAcceptIndicator approvalMode={ApprovalMode.AUTO} />,
);
expect(lastFrame()).toContain('auto mode (classifier-evaluated)');
});
it('renders YOLO mode indicator', () => {
const { lastFrame } = render(
<AutoAcceptIndicator approvalMode={ApprovalMode.YOLO} />,
);
expect(lastFrame()).toContain('YOLO mode');
});
});

View file

@ -45,6 +45,9 @@ export const AutoAcceptIndicator: React.FC<AutoAcceptIndicatorProps> = ({
subText = cycleText;
break;
case ApprovalMode.DEFAULT:
textContent = `${t('Ask permissions')}`;
subText = cycleText;
break;
default:
break;
}

View file

@ -8,6 +8,7 @@ import { render } from 'ink-testing-library';
import { act } from '@testing-library/react';
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { Footer } from './Footer.js';
import { ApprovalMode } from '@qwen-code/qwen-code-core';
import * as useTerminalSize from '../hooks/useTerminalSize.js';
import * as useStatusLineModule from '../hooks/useStatusLine.js';
import { type UIState, UIStateContext } from '../contexts/UIStateContext.js';
@ -233,6 +234,19 @@ describe('<Footer />', () => {
}
});
it('shows the default approval mode badge when in DEFAULT mode', () => {
const { lastFrame } = renderWithWidth(
120,
createMockUIState({
showAutoAcceptIndicator: ApprovalMode.DEFAULT,
}),
);
const frame = lastFrame()!;
expect(frame).toContain('⏸');
expect(frame).toContain('Ask permissions');
expect(frame).not.toContain('? for shortcuts');
});
it('does not display the working directory or branch name', () => {
const { lastFrame } = renderWithWidth(120, createMockUIState());
expect(lastFrame()).not.toMatch(/\(.*\*\)/);

View file

@ -21,7 +21,6 @@ import { useUIState } from '../contexts/UIStateContext.js';
import { useConfig } from '../contexts/ConfigContext.js';
import { useSettings } from '../contexts/SettingsContext.js';
import { useVimModeState } from '../contexts/VimModeContext.js';
import { ApprovalMode } from '@qwen-code/qwen-code-core';
import { GeminiSpinner } from './GeminiRespondingSpinner.js';
import { GoalPill, useFooterGoalState } from './GoalPill.js';
import { CronPill, useFooterCronTaskCount } from './CronPill.js';
@ -107,8 +106,7 @@ export const Footer: React.FC = () => {
message: uiState.startupIdeConnectionStatus.message,
})}
</Text>
) : showAutoAcceptIndicator !== undefined &&
showAutoAcceptIndicator !== ApprovalMode.DEFAULT ? (
) : showAutoAcceptIndicator !== undefined ? (
<AutoAcceptIndicator approvalMode={showAutoAcceptIndicator} />
) : suppressHint ? null : (
<Text color={theme.text.secondary}>{t('? for shortcuts')}</Text>

View file

@ -12,7 +12,7 @@
import type React from 'react';
import { Box, Text } from 'ink';
import { ApprovalMode } from '@qwen-code/qwen-code-core';
import type { ApprovalMode } from '@qwen-code/qwen-code-core';
import { AutoAcceptIndicator } from '../AutoAcceptIndicator.js';
import { ContextUsageDisplay } from '../ContextUsageDisplay.js';
import { theme } from '../../semantic-colors.js';
@ -30,8 +30,7 @@ export const AgentFooter: React.FC<AgentFooterProps> = ({
contextWindowSize,
terminalWidth,
}) => {
const showApproval =
approvalMode !== undefined && approvalMode !== ApprovalMode.DEFAULT;
const showApproval = approvalMode !== undefined;
const showContext = promptTokenCount > 0 && contextWindowSize !== undefined;
if (!showApproval && !showContext) {

View file

@ -20,6 +20,7 @@ export function getApprovalModeIndicatorColor(
case ApprovalMode.YOLO:
return theme.status.error;
case ApprovalMode.DEFAULT:
return theme.text.secondary;
default:
return undefined;
}