feat(ui): add ui.history.collapsePreviewCount to show last N turns when resuming collapsed sessions (#5848)

* feat: add ui.history.collapsePreviewCount to show last N turns on resume

* chore: regenerate settings schema for collapsePreviewCount

---------

Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
This commit is contained in:
Matt Van Horn 2026-06-30 00:19:34 -07:00 committed by GitHub
parent 7a04512bb9
commit f3694dde67
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 135 additions and 4 deletions

View file

@ -116,6 +116,7 @@ Settings are organized into categories. Most settings should be placed within th
| `ui.renderMode` | string | Default Markdown display mode. Use `"render"` for rich visual previews or `"raw"` to show source-oriented Markdown by default. Toggle during a session with `Alt/Option+M`; on macOS the terminal must send Option as Meta. See [Markdown Rendering](../features/markdown-rendering). | `"render"` |
| `ui.showCitations` | boolean | Show citations for generated text in the chat. | `false` |
| `ui.history.collapseOnResume` | boolean | Whether to collapse history by default when resuming a session. Can be toggled via `/history collapse-on-resume` and `/history expand-on-resume`. | `false` |
| `ui.history.collapsePreviewCount` | number | Number of most recent user turns to keep visible when `ui.history.collapseOnResume` is enabled. `0` collapses all restored history by default; `-1` shows all restored history. | `0` |
| `ui.compactMode` | boolean | Hide tool output and thinking for a cleaner view. Toggle with `Ctrl+O` during a session or via the Settings dialog. Tool approval prompts are never hidden, even in compact mode. The setting persists across sessions. | `false` |
| `ui.shellOutputMaxLines` | number | Max number of shell output lines shown inline. Set to `0` to disable the cap and show full output. Hidden lines are surfaced via the `+N lines` indicator. Errors, `!`-prefix user-initiated commands, confirming tools, and focused embedded shells always show full output. | `5` |
| `ui.enableWelcomeBack` | boolean | Show welcome back dialog when returning to a project with conversation history. When enabled, Qwen Code will automatically detect if you're returning to a project with a previously generated project summary (`.qwen/PROJECT_SUMMARY.md`) and show a dialog allowing you to continue your previous conversation or start fresh. If you choose **Start new chat session**, that choice is remembered for the current project until the project summary changes. This feature integrates with the `/summary` command and quit confirmation dialog. | `true` |

View file

@ -842,6 +842,16 @@ const SETTINGS_SCHEMA = {
'Whether to collapse history by default when resuming a session.',
showInDialog: false,
},
collapsePreviewCount: {
type: 'number',
label: 'Collapse Preview Count',
category: 'UI',
requiresRestart: false,
default: 0,
description:
'Number of most recent user turns to keep visible when collapsing history on resume. 0 collapses all restored history by default; -1 shows all restored history.',
showInDialog: false,
},
},
},
showLineNumbers: {

View file

@ -629,10 +629,13 @@ export const AppContainer = (props: AppContainerProps) => {
const rawItems = buildResumedHistoryItems(resumedSessionData, config);
const collapseOnResume =
settings.merged.ui?.history?.collapseOnResume ?? false;
const collapsePreviewCount =
settings.merged.ui?.history?.collapsePreviewCount ?? 0;
const historyItems = applyCollapsePolicyAndSummary(
rawItems,
collapseOnResume,
collapsePreviewCount,
);
historyManager.loadHistory(historyItems);

View file

@ -157,9 +157,12 @@ export function useBranchCommand(
const rawItems = buildResumedHistoryItems(resumed, config);
const collapseOnResume =
options.settings.merged.ui?.history?.collapseOnResume ?? false;
const collapsePreviewCount =
options.settings.merged.ui?.history?.collapsePreviewCount ?? 0;
const uiHistoryItems = applyCollapsePolicyAndSummary(
rawItems,
collapseOnResume,
collapsePreviewCount,
);
startNewSession(newSessionId);
historyManager.clearItems();
@ -276,6 +279,7 @@ export function useBranchCommand(
setSessionName,
remount,
options.settings.merged.ui?.history?.collapseOnResume,
options.settings.merged.ui?.history?.collapsePreviewCount,
],
);

View file

@ -123,10 +123,13 @@ export function useResumeCommand(
const rawItems = buildResumedHistoryItems(sessionData, config);
const collapseOnResume =
settings.merged.ui?.history?.collapseOnResume ?? false;
const collapsePreviewCount =
settings.merged.ui?.history?.collapsePreviewCount ?? 0;
const uiHistoryItems = applyCollapsePolicyAndSummary(
rawItems,
collapseOnResume,
collapsePreviewCount,
);
// 1. Swap core first. Matches useBranchCommand's core-before-UI
@ -218,6 +221,7 @@ export function useResumeCommand(
setSessionName,
remount,
settings.merged.ui?.history?.collapseOnResume,
settings.merged.ui?.history?.collapsePreviewCount,
],
);

View file

@ -6,11 +6,12 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import {
applyCollapsePolicyAndSummary,
buildResumedHistoryItems,
stripSuppressOnRestore,
expandCollapsedHistory,
} from './resumeHistoryUtils.js';
import { ToolCallStatus } from '../types.js';
import { MessageType, ToolCallStatus } from '../types.js';
import type {
AnyDeclarativeTool,
Config,
@ -496,6 +497,88 @@ describe('resumeHistoryUtils', () => {
});
});
describe('applyCollapsePolicyAndSummary', () => {
const makeItems = (): HistoryItem[] =>
[
{ id: 1, type: MessageType.USER, text: 'first' },
{ id: 2, type: MessageType.GEMINI, text: 'first response' },
{ id: 3, type: MessageType.USER, text: 'second' },
{ id: 4, type: MessageType.GEMINI, text: 'second response' },
{ id: 5, type: MessageType.USER, text: 'third' },
{ id: 6, type: MessageType.GEMINI, text: 'third response' },
] as HistoryItem[];
const expectSuppressed = (item: HistoryItem) => {
expect(item.display).toEqual(
expect.objectContaining({ suppressOnRestore: true }),
);
};
const expectVisible = (item: HistoryItem) => {
expect(item.display?.suppressOnRestore).toBeUndefined();
};
it('suppresses all items and shows the full summary count by default', () => {
const result = applyCollapsePolicyAndSummary(makeItems(), true);
expect(result).toHaveLength(7);
result.slice(0, 6).forEach(expectSuppressed);
expect(result[6]).toEqual(
expect.objectContaining({
id: 7,
type: MessageType.INFO,
text: expect.stringContaining('6 messages hidden'),
display: { kind: 'collapse-summary' },
}),
);
});
it('keeps the most recent N user turns visible and summarizes only hidden items', () => {
const result = applyCollapsePolicyAndSummary(makeItems(), true, 2);
expect(result).toHaveLength(7);
result.slice(0, 2).forEach(expectSuppressed);
result.slice(2, 6).forEach(expectVisible);
expect(result[6]).toEqual(
expect.objectContaining({
id: 7,
type: MessageType.INFO,
text: expect.stringContaining('2 messages hidden'),
display: { kind: 'collapse-summary' },
}),
);
});
it('shows all items without a summary when preview count covers all user turns', () => {
const rawItems = makeItems();
const result = applyCollapsePolicyAndSummary(rawItems, true, 3);
expect(result).toEqual(rawItems);
expect(
result.some((item) => item.display?.kind === 'collapse-summary'),
).toBe(false);
result.forEach(expectVisible);
});
it('shows all items without a summary when preview count is -1', () => {
const rawItems = makeItems();
const result = applyCollapsePolicyAndSummary(rawItems, true, -1);
expect(result).toBe(rawItems);
});
it('returns raw items unchanged when collapseOnResume is false', () => {
const rawItems = makeItems();
const result = applyCollapsePolicyAndSummary(rawItems, false, 1);
expect(result).toBe(rawItems);
});
it('returns empty history without a summary', () => {
expect(applyCollapsePolicyAndSummary([], true)).toEqual([]);
});
});
describe('stripSuppressOnRestore', () => {
it('returns item unchanged when display is undefined', () => {
const item = { id: 1, type: 'user', text: 'hello' } as HistoryItem;

View file

@ -568,16 +568,37 @@ export function expandCollapsedHistory(items: HistoryItem[]): HistoryItem[] {
export function applyCollapsePolicyAndSummary(
rawItems: HistoryItem[],
collapseOnResume: boolean,
collapsePreviewCount: number = 0,
): HistoryItem[] {
if (!collapseOnResume) return rawItems;
if (collapsePreviewCount === -1) return rawItems;
const uiHistoryItems = applyResumeDisplayPolicy(rawItems);
let boundary = rawItems.length;
if (collapsePreviewCount > 0) {
let userTurnCount = 0;
for (let i = rawItems.length - 1; i >= 0; i--) {
if (rawItems[i].type === MessageType.USER) {
userTurnCount++;
if (userTurnCount === collapsePreviewCount) {
boundary = i;
break;
}
}
}
if (userTurnCount < collapsePreviewCount) {
boundary = 0;
}
}
if (rawItems.length > 0) {
const hiddenItems = applyResumeDisplayPolicy(rawItems.slice(0, boundary));
const visibleItems = rawItems.slice(boundary);
const uiHistoryItems = [...hiddenItems, ...visibleItems];
if (boundary > 0) {
const nextId = rawItems[rawItems.length - 1].id + 1;
return [
...uiHistoryItems,
{ id: nextId, ...createHistoryCollapseSummaryItem(rawItems.length) },
{ id: nextId, ...createHistoryCollapseSummaryItem(boundary) },
];
}

View file

@ -278,6 +278,11 @@
"description": "Whether to collapse history by default when resuming a session.",
"type": "boolean",
"default": false
},
"collapsePreviewCount": {
"description": "Number of most recent user turns to keep visible when collapsing history on resume. 0 collapses all restored history by default; -1 shows all restored history.",
"type": "number",
"default": 0
}
}
},