Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
90a67a7
fix(cli): improve slash command history feedback
DragonnZhang Aug 2, 2026
e7183bb
fix(cli): centralize auxiliary model guard
DragonnZhang Aug 2, 2026
facb38f
test(cli): cover model picker left key
DragonnZhang Aug 2, 2026
296961e
Merge branch 'main' into dragon/slash-command-feedback
wenshao Aug 2, 2026
d7a14bf
fix(cli): persist slash command invocation hiding across resume
DragonnZhang Aug 7, 2026
ce6fbb9
Merge remote-tracking branch 'upstream/main' into dragon/slash-comman…
DragonnZhang Aug 7, 2026
4b2b65f
Merge branch 'main' into dragon/slash-command-feedback
qwen-code-dev-bot Aug 7, 2026
8d19d46
fix: keep model picker feedback consistent
DragonnZhang Aug 7, 2026
81618a6
docs(cli): align design doc with /stats root-level hiding
qwen-code-dev-bot Aug 7, 2026
15227e1
fix(cli): preserve model picker history safely
DragonnZhang Aug 7, 2026
d1140e6
fix: preserve hidden command feedback on resume
DragonnZhang Aug 7, 2026
90f0145
fix(desktop): match slash results to invocations
DragonnZhang Aug 7, 2026
c1dfe04
fix: keep slash command feedback paired with invocations
Aug 7, 2026
b44d4d3
fix(desktop): bound slash result lookup to user turn
DragonnZhang Aug 7, 2026
deed974
test(cli): cover hidden command result replay on resume
DragonnZhang Aug 7, 2026
24b12bf
Merge main into dragon/slash-command-feedback
DragonnZhang Aug 11, 2026
c2ab3fc
Merge remote-tracking branch 'origin/main' into dragon/slash-command-…
qwen-code-dev-bot Aug 11, 2026
cf3cc8c
Merge branch 'main' into dragon/slash-command-feedback
qwen-code-dev-bot Aug 11, 2026
64fc673
fix(cli): guard dialog result recording
DragonnZhang Aug 11, 2026
751deb0
fix(cli,desktop): tighten slash-command feedback recording
qwen-code-dev-bot Aug 11, 2026
a3df7ee
Merge remote-tracking branch 'origin/main' into dragon/slash-command-…
qwen-code-dev-bot Aug 11, 2026
0502eed
chore(cli): merge main and reconcile /effort feedback recording
qwen-code-dev-bot Aug 12, 2026
cd49a42
fix(cli): guard partially persisted model switches
DragonnZhang Aug 12, 2026
92d1dc3
Merge branch 'main' into dragon/slash-command-feedback
qwen-code-dev-bot Aug 12, 2026
b6b0cc9
test(cli): cover isPickerOnlyModelInvocation regex boundaries directly
qwen-code-dev-bot Aug 12, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions docs/design/slash-command-feedback.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Slash command history feedback

## Problem

Interactive slash commands are added to the TUI history before their action is
known. Commands that only open a dialog can therefore leave a bare invocation
behind after the dialog closes. The model picker has the same problem when it
is dismissed without a selection.

## Design

- Do not add the built-in `/auth`, `/settings`, `/status`, `/help`, `/theme`,
`/editor`, or `/diff` invocations to visible TUI history. Bare `/effort`,
`/statusline`, and `/stats` pickers are hidden too. Their existing UI remains
unchanged, as do chat recording and slash-command telemetry. User and project
commands that override those names keep their invocation history.
- Resolve the command before adding its invocation so aliases use the canonical
command name for this decision.
- Preserve invocations for commands that directly perform work, change session
state, write data, or enter a management/security workflow. Argument-sensitive
commands only hide their bare picker form; for example, `/effort` is hidden
while `/effort high` remains visible.
- When the primary model picker is dismissed without a selection, add an info
message identifying the unchanged model. Successful selections keep their
existing feedback.
60 changes: 58 additions & 2 deletions packages/cli/src/ui/components/ModelDialog.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1254,8 +1254,8 @@ describe('<ModelDialog />', () => {
expect(typeof childOnHighlight).toBe('function');
});

it('calls onClose prop when "escape" key is pressed', () => {
const { props } = renderComponent();
it('reports the unchanged model when "escape" closes the primary picker', () => {
const { props, mockHistoryManager } = renderComponent();

expect(mockedUseKeypress).toHaveBeenCalled();

Expand All @@ -1272,6 +1272,13 @@ describe('<ModelDialog />', () => {
paste: false,
sequence: '',
});
expect(mockHistoryManager.addItem).toHaveBeenCalledWith(
{
type: 'info',
text: `Kept model as ${DEFAULT_QWEN_MODEL}`,
},
expect.any(Number),
);
expect(props.onClose).toHaveBeenCalledTimes(1);

keyPressHandler({
Expand All @@ -1282,9 +1289,58 @@ describe('<ModelDialog />', () => {
paste: false,
sequence: '',
});
expect(mockHistoryManager.addItem).toHaveBeenCalledTimes(1);
expect(props.onClose).toHaveBeenCalledTimes(1);
});
Comment on lines +1413 to +1415

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed that the double-Escape latch test could pin the recorder call count as well as live history. The production latch behavior is unchanged and no current defect was reproduced here. Under the round-5 Critical-only guidance, I am deferring this test follow-up and leaving the thread unresolved.


it('does not close the primary picker on "left"', () => {
const { props, mockHistoryManager } = renderComponent();

const keyPressHandler = mockedUseKeypress.mock.calls[0][0];
keyPressHandler({
name: 'left',
ctrl: false,
meta: false,
shift: false,
paste: false,
sequence: '',
});

expect(mockHistoryManager.addItem).not.toHaveBeenCalled();
expect(props.onClose).not.toHaveBeenCalled();
});

it.each([
[{ isFastModelMode: true }, 'escape'],
[{ isVoiceModelMode: true }, 'escape'],
[{ isVisionModelMode: true }, 'escape'],
[{ isCompactionModelMode: true }, 'escape'],
[{ isImageModelMode: true }, 'escape'],
[{ isFastModelMode: true }, 'left'],
[{ isVoiceModelMode: true }, 'left'],
[{ isVisionModelMode: true }, 'left'],
[{ isCompactionModelMode: true }, 'left'],
[{ isImageModelMode: true }, 'left'],
])(
'does not report the primary model when closing an auxiliary picker (%j, %s)',
(modeProps, keyName) => {
const { props, mockHistoryManager } = renderComponent(modeProps);

const keyPressHandler = mockedUseKeypress.mock.calls[0][0];
keyPressHandler({
name: keyName,
ctrl: false,
meta: false,
shift: false,
paste: false,
sequence: '',
});

expect(mockHistoryManager.addItem).not.toHaveBeenCalled();
expect(props.onClose).toHaveBeenCalledTimes(1);
Comment on lines +1486 to +1487

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed that the auxiliary-dismissal tests could also assert zero recorder calls. The production guard remains unchanged and the current Critical fix covers successful auxiliary selections only. Because this is review round 5 and the repository guidance is Critical-only at this stage, I am deferring the extra negative assertion and leaving the thread unresolved.

},
);

it('updates initialIndex when config context changes', () => {
const mockGetModel = vi.fn(() => DEFAULT_QWEN_MODEL);
const mockGetAuthType = vi.fn(() => 'qwen-oauth');
Expand Down
40 changes: 27 additions & 13 deletions packages/cli/src/ui/components/ModelDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -520,16 +520,17 @@ export function ModelDialog({
: isImageModelMode && parsedImageModelSetting
? parsedImageModelSetting.modelId
: config?.getModel() || MAINLINE_CODER_MODEL;
// Check if current model is a runtime model
// Runtime snapshot ID is already in $runtime|${authType}|${modelId} format
const activeRuntimeSnapshot =
const isAuxiliaryModelMode =
isFastModelMode ||
isVoiceModelMode ||
Comment thread
DragonnZhang marked this conversation as resolved.
Resolved
isVisionModelMode ||
isCompactionModelMode ||
isImageModelMode
? undefined
: config?.getActiveRuntimeModelSnapshot?.();
isImageModelMode;
// Check if current model is a runtime model
// Runtime snapshot ID is already in $runtime|${authType}|${modelId} format
const activeRuntimeSnapshot = isAuxiliaryModelMode
? undefined
: config?.getActiveRuntimeModelSnapshot?.();
const currentBaseUrl = config
?.getModelsConfig()
.getGenerationConfig()?.baseUrl;
Expand Down Expand Up @@ -656,18 +657,31 @@ export function ModelDialog({
)
: '';

const closeWithoutSelection = useCallback(() => {
if (!isAuxiliaryModelMode) {
uiState?.historyManager.addItem(
Comment thread
DragonnZhang marked this conversation as resolved.
Resolved
{
type: 'info',
text: t('Kept model as {{model}}', { model: preferredModelId }),
},
Date.now(),
);
}
onClose();
}, [
isAuxiliaryModelMode,
onClose,
preferredModelId,
uiState,
]);

useKeypress(
(key) => {
if (
key.name === 'escape' ||
(key.name === 'left' &&
(isFastModelMode ||
isVoiceModelMode ||
isVisionModelMode ||
isCompactionModelMode ||
isImageModelMode))
(key.name === 'left' && isAuxiliaryModelMode)
) {
onClose();
closeWithoutSelection();
Comment thread
DragonnZhang marked this conversation as resolved.
Resolved
}
},
{ isActive: true },
Expand Down
Loading
Loading