Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions docs/users/configuration/settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -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` |
Expand Down
10 changes: 10 additions & 0 deletions packages/cli/src/config/settingsSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -842,6 +842,16 @@ const SETTINGS_SCHEMA = {
'Whether to collapse history by default when resuming a session.',
showInDialog: false,
},
collapsePreviewCount: {
type: 'number',

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] Schema declares type: 'number' but the boundary algorithm in applyCollapsePolicyAndSummary requires an integer — it uses strict equality (userTurnCount === collapsePreviewCount) against an integer counter. A float like 2.5 silently produces incorrect behavior (the loop never matches, falls through to unexpected collapse).

Other integer-valued settings in this same file use jsonSchemaOverride to enforce integer constraints (e.g., quorumSize at line 2288, stopHookBlockCap at line 2629, fileHistoryRetentionDays at line 1568).

Suggested change
type: 'number',
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.',
jsonSchemaOverride: {
type: 'integer',
minimum: -1,
},
showInDialog: false,
},

— qwen3.7-max via Qwen Code /review

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: {
Expand Down
3 changes: 3 additions & 0 deletions packages/cli/src/ui/AppContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
4 changes: 4 additions & 0 deletions packages/cli/src/ui/hooks/useBranchCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -276,6 +279,7 @@ export function useBranchCommand(
setSessionName,
remount,
options.settings.merged.ui?.history?.collapseOnResume,
options.settings.merged.ui?.history?.collapsePreviewCount,
],
);

Expand Down
4 changes: 4 additions & 0 deletions packages/cli/src/ui/hooks/useResumeCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -218,6 +221,7 @@ export function useResumeCommand(
setSessionName,
remount,
settings.merged.ui?.history?.collapseOnResume,
settings.merged.ui?.history?.collapsePreviewCount,
],
);

Expand Down
85 changes: 84 additions & 1 deletion packages/cli/src/ui/utils/resumeHistoryUtils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -496,6 +497,88 @@ describe('resumeHistoryUtils', () => {
});
});

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] Good unit test coverage for applyCollapsePolicyAndSummary itself, but the three call sites (AppContainer.tsx, useResumeCommand.ts, useBranchCommand.ts) that wire settings.merged.ui?.history?.collapsePreviewCount through to this function are not integration-tested with a non-zero collapsePreviewCount. If a developer accidentally omits the parameter at a call site or introduces a typo in the settings key, no test would catch it.

Consider adding at least one integration test (e.g., in useResumeCommand.test.ts) that sets collapsePreviewCount: 2 in the settings mock and asserts the loaded history has both suppressed and visible items plus a summary with the correct hidden count.

— qwen3.7-max via Qwen Code /review

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' },
}),
);
});

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] The userTurnCount < collapsePreviewCount fallback branch at resumeHistoryUtils.ts:586-588 is never exercised by the current test suite. The existing "covers all user turns" test uses previewCount=3 with exactly 3 user turns — the loop hits break (3 === 3), so boundary is set to 0 inside the loop, not via the post-loop fallback.

A test with previewCount exceeding the actual turn count would cover this distinct code path:

it('shows all items without a summary when preview count exceeds user turns', () => {
  const rawItems = makeItems(); // 3 user turns
  const result = applyCollapsePolicyAndSummary(rawItems, true, 5);
  expect(result).toEqual(rawItems);
  result.forEach(expectVisible);
});

— qwen3.7-max via Qwen Code /review

it('keeps the most recent N user turns visible and summarizes only hidden items', () => {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] No test covers collapsePreviewCount=1, the most common non-zero value users will configure. This is a meaningful boundary — it should keep exactly the last user turn (and its assistant response) visible while collapsing everything else. Currently tested values: 0, 2, 3, -1.

  it('keeps only the last user turn visible when previewCount is 1', () => {
    const result = applyCollapsePolicyAndSummary(makeItems(), true, 1);

    expect(result).toHaveLength(7);
    result.slice(0, 4).forEach(expectSuppressed);
    result.slice(4, 6).forEach(expectVisible);
    expect(result[6]).toEqual(
      expect.objectContaining({
        text: expect.stringContaining('4 messages hidden'),
        display: { kind: 'collapse-summary' },
      }),
    );
  });

— qwen3.7-max via Qwen Code /review

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();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] Test doesn't exercise leading non-USER items (common in real sessions)

The makeItems() fixture starts with a USER message. With collapsePreviewCount=3 and 3 user turns, boundary lands at index 0, so no items are hidden. But real sessions frequently start with INFO/system messages. With a leading INFO item, the same inputs produce boundary > 0 — items ARE hidden and a summary IS appended, contradicting the test name's implication.

Consider adding:

it('still hides leading non-user items when preview count covers all user turns', () => {
  const items = [
    { id: 0, type: MessageType.INFO, text: 'system context' },
    ...makeItems(),
  ] as HistoryItem[];
  const result = applyCollapsePolicyAndSummary(items, true, 3);
  expect(result[0].display).toEqual(
    expect.objectContaining({ suppressOnRestore: true }),
  );
  expect(result).toHaveLength(8);
  expect(result[7].text).toContain('1 messages hidden');
});

— bailian/glm-5.2 via Qwen Code /review

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', () => {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Nice to have] No test exercises expandCollapsedHistory with the mixed-shape history that collapsePreviewCount > 0 produces — some items with suppressOnRestore: true (hidden prefix), some without (visible preview tail), and a collapse-summary sentinel. The existing expandCollapsedHistory tests only cover fully-collapsed or no-collapse inputs.

Consider adding a test case that constructs a partially-collapsed history (e.g., 2 suppressed + 4 visible + 1 collapse-summary) and asserts that expandCollapsedHistory returns all 6 original items with suppressOnRestore stripped and the collapse-summary removed.

— qwen3.7-max via Qwen Code /review

const item = { id: 1, type: 'user', text: 'hello' } as HistoryItem;
Expand Down
27 changes: 24 additions & 3 deletions packages/cli/src/ui/utils/resumeHistoryUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;

let boundary = rawItems.length;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] Negative values other than -1 silently fall through to full collapse

The === -1 check only catches the documented sentinel. Any other negative value (e.g., -2, -100) bypasses both the early-return here and the > 0 branch below, leaving boundary = rawItems.length — which collapses everything with a summary. The schema has no minimum constraint, so this is reachable from settings.

Suggested change
let boundary = rawItems.length;
if (collapsePreviewCount < 0) return rawItems;

— bailian/glm-5.2 via Qwen Code /review

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;
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] userTurnCount === collapsePreviewCount uses strict equality against an always-integer counter. If collapsePreviewCount is a float (e.g. 1.5 — schema declares type: 'number' without integer constraints), the comparison never matches, the backward walk exhausts all items, and boundary stays at rawItems.length, collapsing everything instead of keeping ~1 turn visible.

Suggested change
}
if (userTurnCount >= collapsePreviewCount) {

Using >= makes the loop stop at the Nth user turn for any N (integer or fractional) and makes the post-loop userTurnCount < collapsePreviewCount fallback consistent.

— qwen3.7-max via Qwen Code /review

}
}
if (userTurnCount < collapsePreviewCount) {
boundary = 0;
}
}

const uiHistoryItems = applyResumeDisplayPolicy(rawItems);
const hiddenItems = applyResumeDisplayPolicy(rawItems.slice(0, boundary));
const visibleItems = rawItems.slice(boundary);
const uiHistoryItems = [...hiddenItems, ...visibleItems];

if (rawItems.length > 0) {
if (boundary > 0) {
const nextId = rawItems[rawItems.length - 1].id + 1;
return [
...uiHistoryItems,
{ id: nextId, ...createHistoryCollapseSummaryItem(rawItems.length) },
{ id: nextId, ...createHistoryCollapseSummaryItem(boundary) },
];
}

Expand Down
5 changes: 5 additions & 0 deletions packages/vscode-ide-companion/schemas/settings.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
},
Expand Down
Loading