Skip to content

feat(cli): add persistent history collapse on resume with refined commands#4085

Merged
wenshao merged 50 commits into
QwenLM:mainfrom
Gove2004:feat/quiet-restore
Jun 19, 2026
Merged

feat(cli): add persistent history collapse on resume with refined commands#4085
wenshao merged 50 commits into
QwenLM:mainfrom
Gove2004:feat/quiet-restore

Conversation

@Gove2004

@Gove2004 Gove2004 commented May 12, 2026

Copy link
Copy Markdown
Contributor

What this PR does

Adds persistent history collapse control on session resume via /history subcommands. Three new subcommands:

  • /history collapse-on-resume: Persists a preference to collapse history by default when resuming sessions
  • /history expand-on-resume: Persists a preference to expand history by default when resuming sessions
  • /history expand-now: Expands currently collapsed history in the active session

A new setting ui.history.collapseOnResume (boolean, default false) controls the default collapse behavior. When collapseOnResume is true, resumed conversation items are marked with display.suppressOnRestore = true, which filters them from UI rendering but keeps them in canonical history for /rewind. A summary line shows the count of hidden messages.

Why it's needed

When resuming a long conversation (50+ messages), the terminal scrolls through all historical messages which is disruptive. This PR adds a persistent preference to collapse history by default on resume, with explicit commands to control the behavior.

Reviewer Test Plan

How to verify

  1. Run the unit test suites:

    cd packages/cli && npx vitest run src/ui/commands/historyCommand.test.ts src/ui/hooks/slashCommandProcessor.test.ts src/ui/hooks/useResumeCommand.test.ts src/ui/utils/resumeHistoryUtils.test.ts src/ui/hooks/useBranchCommand.test.ts src/ui/components/MainContent.test.tsx src/ui/AppContainer.test.tsx
    

    All 224 tests across 8 suites should pass.

  2. Start a session, run /history collapse-on-resume, then exit and resume — history should be collapsed on resume with a summary line showing hidden message count.

  3. Run /history expand-now to expand the collapsed history.

  4. Run /history expand-on-resume, exit and resume — history should be fully expanded.

Evidence (Before & After)

Before: Resuming a session always shows all history messages, causing disruptive terminal scrolling.

After: With /history collapse-on-resume, resumed sessions collapse history and show a summary line. /history expand-now restores full visibility on demand.

Tested on

OS Status
🍏 macOS
🪟 Windows
🐧 Linux

Risk & Scope

  • Main risk or tradeoff: Suppressed history items remain in canonical history for /rewind compatibility, so the collapse is purely a rendering concern — no data loss. The feature is opt-in (default: collapseOnResume = false).
  • Not validated / out of scope: /rewind into a collapsed session (known gap, tracked by reviewers).
  • Breaking changes / migration notes: None. New setting with safe default.

Linked Issues

Ref: Discussion thread on session resume behavior in long conversations.

中文说明

这个 PR 做了什么

通过 /history 子命令添加持久化的会话恢复历史折叠控制。新增三个子命令:

  • /history collapse-on-resume:设置恢复会话时默认折叠历史记录
  • /history expand-on-resume:设置恢复会话时默认展开历史记录
  • /history expand-now:展开当前会话中已折叠的历史记录

新增设置项 ui.history.collapseOnResume(布尔值,默认 false)控制默认折叠行为。当 collapseOnResume 为 true 时,恢复的对话条目会被标记 display.suppressOnRestore = true,在 UI 渲染中过滤掉,但保留在规范历史中以供 /rewind 使用。会显示一条摘要信息标明隐藏消息的数量。

为什么需要它

当恢复一个很长的对话(50+ 条消息)时,终端会滚动所有历史消息,这很影响体验。这个 PR 添加了一个持久化偏好设置,可在恢复时默认折叠历史,同时提供明确的命令来控制此行为。

Reviewer 测试计划

如何验证

  1. 运行单元测试套件(8 个套件共 224 个测试应全部通过)
  2. 启动会话,运行 /history collapse-on-resume,退出后恢复 —— 历史应被折叠,显示隐藏消息数量的摘要
  3. 运行 /history expand-now 展开已折叠的历史
  4. 运行 /history expand-on-resume,退出后恢复 —— 历史应完整展开

证据(前后对比)

之前: 恢复会话时总是显示所有历史消息,造成终端大量滚动。

之后: 使用 /history collapse-on-resume 后,恢复的会话折叠历史并显示摘要行。/history expand-now 可按需恢复完整显示。

测试平台

操作系统 状态
🍏 macOS
🪟 Windows
🐧 Linux

风险与范围

  • 主要风险或取舍:被抑制的历史条目仍保留在规范历史中以兼容 /rewind,折叠仅影响渲染,无数据丢失。功能为 opt-in(默认 collapseOnResume = false)。
  • 未验证/超出范围:在已折叠会话中执行 /rewind(已知缺口,reviewer 已跟踪)。
  • 破坏性变更/迁移说明:无。新设置有安全默认值。

关联 Issues

参考:关于长对话中恢复行为的讨论。

@qqqys

qqqys commented May 12, 2026

Copy link
Copy Markdown
Collaborator

Thanks for adding --quiet-restore; the intent makes sense, but I think the current implementation changes more than just rendering and can break /rewind after a resumed session.

Right now the quiet path suppresses the restored items by not loading them into historyManager.history, and only adds an informational item instead. However, /rewind uses historyManager.history as the UI-side canonical source for user-turn mapping back to the model/persisted history.

That means after --quiet-restore:

  1. immediately after restore there are no restored user turns in historyManager.history, so /rewind has no restored turns to select;
  2. after the user sends a new prompt, that prompt becomes UI user turn 0;
  3. but the core/model history still contains the older restored turns, so the UI turn index no longer matches the API/history turn index;
  4. rewinding that new prompt can therefore map to the wrong persisted turn and potentially re-root the recording before the restored context, dropping the restored session into a dead branch.

I think the safer approach is to keep the restored history in the canonical state used for turn mapping, and suppress only its rendering, or otherwise carry a hidden restored-turn offset / use core turn boundaries for the rewind mapping. In other words, --quiet-restore should hide the display of restored history without removing it from the state that downstream features use for history semantics.

@Gove2004

Copy link
Copy Markdown
Contributor Author

Yes! Fixed in the latest push.

The new approach preserves historyManager.history for /rewind turn mapping
while suppressing only the rendering:

  1. Added hidden?: boolean to HistoryItemBase in types.ts
  2. Restored items are always loaded via loadHistory() — keeping canonical
    state intact
  3. When --quiet-restore is active, restored items are marked hidden = true
  4. MainContent.tsx filters out hidden items before passing to <Static>,
    so they are not rendered but remain in the history array for /rewind

This means /rewind can still see and select restored user turns, and the UI
turn index correctly maps to the API/history turn index.

@qqqys

qqqys commented May 12, 2026

Copy link
Copy Markdown
Collaborator

Thanks for the quick fix — the new direction is definitely the right one. Keeping the restored items in historyManager.history preserves the canonical state that /rewind depends on, while still allowing the UI to suppress the restored transcript. That addresses the important correctness concern nicely.

One design refinement that might make this easier to maintain: I think --quiet-restore could be modeled explicitly as a display policy, rather than inline mutation in each resume path.

Right now the quiet-restore handling is duplicated in both the initial resume flow and the /resume command flow:

  • mark every restored item as hidden
  • load the history
  • append the summary info item

It works, but it spreads the policy across two call sites and mutates the returned history items in place. A slightly cleaner design might be:

export interface ResumeDisplayOptions {
  quietRestore?: boolean;
}

export function applyResumeDisplayPolicy(
  items: HistoryItem[],
  options: ResumeDisplayOptions,
): HistoryItem[] {
  if (!options.quietRestore) return items;

  return items.map((item) => ({
    ...item,
    display: {
      ...item.display,
      suppressOnRestore: true,
    },
  }));
}

export function createQuietRestoreSummaryItem(
  messageCount: number,
): Omit<HistoryItem, 'id'> {
  return {
    type: MessageType.INFO,
    text: `Resumed session with ${messageCount} message${messageCount !== 1 ? 's' : ''}. History display suppressed (--quiet-restore).`,
  };
}

Then both resume entry points can share the same shape:

const rawItems = buildResumedHistoryItems(sessionData, config);
const historyItems = applyResumeDisplayPolicy(rawItems, {
  quietRestore: config.isQuietRestore(),
});

loadHistory(historyItems);

if (config.isQuietRestore()) {
  addItem(createQuietRestoreSummaryItem(sessionData.conversation.messages.length), Date.now());
}

I would also consider making the suppression flag display-scoped instead of a generic hidden?: boolean, for example:

interface HistoryItemBase {
  text?: string;
  display?: {
    suppressOnRestore?: boolean;
  };
}

The important distinction is that this flag means “do not render this item in the restored transcript”, not “remove this item from history semantics”. Naming it under display makes that boundary clearer for future readers.

Finally, in MainContent, it may be cleaner to derive the visible render list before running display-only computations:

function shouldRenderHistoryItem(item: HistoryItem): boolean {
  return !item.display?.suppressOnRestore;
}

const visibleMergedHistory = useMemo(
  () => mergedHistory.filter(shouldRenderHistoryItem),
  [mergedHistory],
);

Then source-copy offsets and progressive replay sizing can be based on visibleMergedHistory, while canonical consumers keep using the full historyManager.history.

That gives a clear split:

  • canonical consumers (/rewind, turn mapping, recording/truncation) use the full restored history;
  • rendering consumers (MainContent, static replay, source-copy offsets) use the visible history.

This is not a blocker to the correctness fix — the current update already fixes the major issue. I just think centralizing the display policy and naming the flag more specifically would make the feature easier to reason about and less likely to regress later. Nice work iterating on this quickly.

@qqqys

qqqys commented May 12, 2026

Copy link
Copy Markdown
Collaborator

One more product-design thought: I wonder if making this primarily a startup flag might feel a bit disconnected from the interactive experience.

Instead of only deciding at process startup with something like --quiet-restore, it may be more natural to expose this as an in-session history display control:

/history collapse
/history expand

The behavior could be:

  • /history collapse collapses the currently rendered/restored transcript into a single summary row, e.g. Restored history: 42 messages collapsed. Use /history expand to show.
  • /history expand expands the full transcript again.

That keeps the model simple: the full conversation remains loaded in canonical history/model context, and these commands only change the transcript projection. It also gives users a way to switch back and forth after startup, instead of having the display mode fixed by the original CLI invocation.

This would fit the existing separation pretty well:

  • /resume controls which session is loaded;
  • /clear starts a new session;
  • /rewind changes the semantic conversation state;
  • /history collapse|expand would only control how much of the current session transcript is displayed.

Qwen already has Ctrl+O for compact mode, so I would avoid overloading that shortcut for restored-history expansion. A slash-command MVP seems simpler and less risky than making the summary row interactive, and it leaves room for richer UI affordances later.

@Gove2004

Copy link
Copy Markdown
Contributor Author

Your idea is really good. I will do it.

@Gove2004

Copy link
Copy Markdown
Contributor Author

Updated! All three points addressed:

  1. Display policy naming: hidden?: booleandisplay?: { suppressOnRestore?: boolean } — scoped under
    display to clarify it's a rendering concern, not a history semantic change.

  2. Shared utilities: Extracted applyResumeDisplayPolicy() and createQuietRestoreSummaryItem() into
    resumeHistoryUtils.ts. Both resume paths now call these instead of duplicating inline.

  3. MainContent refactor: Derived visibleMergedHistory early by filtering suppressed items, then based
    source-copy offsets and replay count on the visible list. Also fixes the replayCount mismatch (was based on total
    items including hidden, now correctly based on visible items only).

For /history collapse|expand — agreed it's a natural follow-up. The display namespace leaves room for that.
Happy to tackle it in a separate PR once this lands.

Ready to merge when you are!

@qqqys

qqqys commented May 12, 2026

Copy link
Copy Markdown
Collaborator

If we decide to go with the /history collapse / /history expand direction, I’d suggest doing that in this PR rather than as a follow-up.

The reason is that the public interface would change: --quiet-restore would no longer be the right user-facing API if history display is controlled interactively. Landing --quiet-restore first and replacing it later may create an unnecessary compatibility/migration question.

So my preference would be:

  • replace the startup-only --quiet-restore behavior with an in-session history display control;
  • use /history collapse to collapse the current/restored transcript into a single summary row;
  • use /history expand to show the full transcript again;
  • keep canonical history/model context unchanged in both modes.

I’m happy to help review that design/implementation if you want to take it in this PR.

@Gove2004

Copy link
Copy Markdown
Contributor Author

ok

@Gove2004

Copy link
Copy Markdown
Contributor Author

removed --quiet-restore entirely and replaced with /history collapse|expand.

My reasoning: --quiet-restore is a one-shot flag. If the default behavior is to replay history, you have to
remember to pass it every single time. And if you do set it as default somehow, the flag becomes pointless — it's
the same result either way. A CLI flag just doesn't make sense for something you might want to toggle mid-session.

/history collapse works wherever you are — startup resume, mid-session, doesn't matter. The model context stays
intact, only the display changes.

@qqqys qqqys left a comment

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.

Hermes Agent Review

Thanks for the iteration on hiding restored history while preserving canonical history for /rewind/turn mapping. I found a few blocking correctness issues:

  • The focused slash-command test suite currently fails because useSlashCommandProcessor gained a positional history argument but the test helper was not updated, shifting all following arguments.
  • /history collapse|expand mutates history state without remounting/refreshing Ink <Static>, so already-rendered transcript lines will remain on screen.
  • In compact mode, filtering after mergeCompactToolGroups() can lose display.suppressOnRestore on merged tool groups and leak hidden content.
  • Re-running /history collapse after the summary row is added is not idempotent.

Local check run:

cd packages/cli
npx vitest run src/ui/utils/resumeHistoryUtils.test.ts src/ui/hooks/slashCommandProcessor.test.ts

Result: resumeHistoryUtils.test.ts passes, slashCommandProcessor.test.ts fails with 40 failures, starting with TypeError: setIsProcessing is not a function.

export const useSlashCommandProcessor = (
config: Config | null,
settings: LoadedSettings,
history: HistoryItem[],

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.

This new positional parameter needs the test call sites updated too. setupProcessorHook() in slashCommandProcessor.test.ts still calls useSlashCommandProcessor(mockConfig, settings, mockAddItem, ...), so every argument after settings is shifted. The focused test run currently fails with TypeError: setIsProcessing is not a function across most of slashCommandProcessor.test.ts. Please pass a mock history array in the helper and keep the existing processing callback in the correct slot.

...item,
display: { ...item.display, suppressOnRestore: true },
}));
loadHistory(updated);

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.

loadHistory(updated) updates React state, but the transcript has already been rendered through Ink <Static>, which is append-only. Without also triggering the existing static refresh/remount path, previously printed transcript lines remain on the terminal after /history collapse, so the command does not actually hide the current on-screen history. Please refresh/remount the static history after both collapse and expand operations.

// Canonical consumers (/rewind, turn mapping) use the full historyManager.history;
// rendering consumers use this visible subset.
const visibleMergedHistory = useMemo(
() => mergedHistory.filter((item) => !item.display?.suppressOnRestore),

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.

Filtering after mergeCompactToolGroups() can leak collapsed history in compact mode. mergeCompactToolGroups() creates a fresh tool_group with only type, tools, and id, so any display.suppressOnRestore flags from the source groups are dropped before this filter runs. Consider filtering suppressed items before merging, or propagate the display policy when constructing merged groups.

const { history, loadHistory, addItem } = context.ui;

// Count items that are not already suppressed.
const visibleCount = history.filter(

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.

This count includes the collapse summary row added below. After one /history collapse, running /history collapse again sees the summary as the only visible item, suppresses it, and creates a new History collapsed: 1 message hidden... summary instead of reporting that history is already collapsed. Please exclude the summary row from this count/detection or mark it separately so the command is idempotent.

@Gove2004 Gove2004 force-pushed the feat/quiet-restore branch from 335010b to 780e57a Compare May 13, 2026 04:53
@Gove2004

Copy link
Copy Markdown
Contributor Author

Thanks for the thorough review! You caught some great edge cases. I've pushed an updated commit that addresses
all four blocking issues:

  1. Test failure: Updated setupProcessorHook() in slashCommandProcessor.test.ts to pass the new history
    array argument, fixing the parameter misalignment.
  2. Ink <Static> remount: Exposed refreshStatic to CommandContext.ui and called it after loadHistory
    in the command actions to ensure the terminal output actually clears.
  3. Compact mode leak: Fixed MainContent.tsx to filter the suppressed items before passing the array to
    mergeCompactToolGroups(), so the display policy isn't lost during grouping.
  4. Collapse idempotency: Updated the collapse logic to explicitly ignore existing summary rows when
    counting and suppressing items, so running it twice doesn't swallow its own summary.

Local tests are passing now. Let me know if there's anything else!

@qqqys qqqys left a comment

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.

Thanks for the update — the previous blocking issues look addressed. I found one remaining persistence edge case around /history collapse recording that should be fixed before merge.

loadHistory(updated);

// Add summary item.
addItem(createHistoryCollapseSummaryItem(visibleCount), Date.now());

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.

I think one persistence edge case is still worth fixing before merge.

/history collapse currently rewrites in-memory UI history with display.suppressOnRestore = true and then adds a collapse summary row. The rewrite itself is not persisted by the slash-command recorder, but the summary row added via addItem() is recorded because history is not in SLASH_COMMANDS_SKIP_RECORDING.

After restarting/resuming the session, the transcript can therefore replay the original full history plus a stale History collapsed: N messages hidden... summary, even though no restored items are actually suppressed anymore.

I’d suggest either:

  • treating /history collapse as session-local UI state and adding history to SLASH_COMMANDS_SKIP_RECORDING, so the stale summary is not persisted; or
  • if collapse state should survive resume, persist and restore the display policy itself instead of only recording the summary row.

@Gove2004

Copy link
Copy Markdown
Contributor Author

Ah, great catch! You are absolutely right about the persistence edge case.

I've pushed a fix that persists the collapse state as a user setting. I want to share my reasoning for this
approach:

If we simply skipped recording the summary row without persisting the collapse state, we would actually defeat
the original purpose of this PR. The whole reason I started this PR was to prevent the terminal from scrolling
wildly with historical messages every time a session is resumed. If the collapse state doesn't survive a resume,
users would have to manually run /history collapse every single time they start up, which is exactly the
friction we want to avoid. The collapse state must survive resume to be truly useful.

What was changed in this commit:

  • Added history to SLASH_COMMANDS_SKIP_RECORDING so the summary row itself isn't persisted as a chat message
    (fixing the stale summary issue).
  • Introduced a new user setting ui.history.defaultCollapsed in settingsSchema.ts.
  • Running /history collapse now updates this setting to true. Running /history expand updates it to false
    .
  • On session resume (in AppContainer.tsx and useResumeCommand.ts), we read ui.history.defaultCollapsed and
    automatically apply the suppressOnRestore policy if it's true.

This elegantly solves the original terminal-scrolling issue while giving users full runtime control. All tests
are passing locally. Let me know what you think!

@qqqys

qqqys commented May 13, 2026

Copy link
Copy Markdown
Collaborator

基线(解决 /rewind 对齐、把 hidden 收敛成 display.suppressOnRestore、把策略抽到 resumeHistoryUtils)已经对了,方向也好。但最近一轮(c239ceb0faba004408)里还有几处会直接让 /history collapse|expand 在长会话里出错,建议在 merge 前再处理一下。

🔴 Blocker 级

1. 用文案前缀识别 summary item,跟 i18n 直接互斥

historyCommand.ts 在 collapse / expand 两处都靠 text?.startsWith('History collapsed:') 来判断 “哪个 item 是上一次留下的折叠摘要”:

  • packages/cli/src/ui/commands/historyCommand.ts 第 30-32 行(collapse 的 visibleCount filter)
  • packages/cli/src/ui/commands/historyCommand.ts 第 86-90 行(expand 的 filter)

createHistoryCollapseSummaryItem 写出的文案是 硬编码英文 (resumeHistoryUtils.ts:535),并没有走 t()。这两个事实组合起来有两个问题:

  • 跟仓库 i18n 走向冲突——cli 已经 “drop English-only exemption”,所有用户可见文案都该走 t()
  • 一旦后续有人把摘要文案改成 t('History collapsed: {{n}} messages hidden...') 或换成另一句中文/英文,startsWith('History collapsed:') 两处都会静默漏过:expand 删不掉旧摘要,再 collapse 又会把旧摘要算进 visibleCount 并被重新隐藏。

建议改成结构化标记,而不是文案匹配:

```ts
// types.ts
display?: {
suppressOnRestore?: boolean;
kind?: 'collapse-summary';
};
```

两处过滤换成 item.display?.kind !== 'collapse-summary'。这样 summary 既可以走 t() 翻译,也不再跟文案耦合。

2. 二次折叠时 “X messages hidden” 计数会误导用户

collapseCommand.actionvisibleCount 的 filter 是 !item.display?.suppressOnRestore && !(<是旧摘要>)historyCommand.ts:27-35)。考虑下面这条路径(也是这个 PR 主要使用场景):

  1. resume 50 条 → 全部 suppressOnRestore=true + summary “50 messages hidden”;
  2. 用户继续聊了两轮(user + gemini × 2),新加 4 条非 suppressed item;
  3. 用户再跑 /history collapse
    • visibleCount = 4(旧 50 条已 suppressed → 被排除;旧 summary 被前缀匹配排除);
    • map 一遍把所有 item(包括新 4 条 + 旧 summary)全打上 suppressOnRestore=true
    • addItem 新 summary:“4 messages hidden”

此刻实际隐藏的是 54 条。这条用户当面就能看见的数字是错的。

修法:visibleCount 不要排除已 suppressed 的 item,应该算 “除掉旧 summary 之外的全部 item”——也就是这次 collapse 之后用户看不见的总条数。

🟡 设计 / 一致性

3. ui.history.defaultCollapsed 名字跟语义不符

字面意思是 “resume 时默认是否折叠”,但 /history expand 也立刻把它写回 falsehistoryCommand.ts:73)。它实际是 “当前折叠状态”,不是 “默认”。两种修法二选一:

  • 重命名为 ui.history.collapsed,并接受 “运行时状态会持久化”;
  • 或拆成两件事:ui.history.collapseOnResume(持久化偏好,只在 collapse 时手动开 / settings 里关)+ 运行时 collapsed flag(不写盘)。

我倾向第二种。当前语义实际是 “一旦折叠过一次就永久默认折叠”——是一个相当 sticky 的副作用,用户可能不希望 /history collapse 改 settings 文件,每次 collapse/expand 都做一次 fs write 也不是必要。

4. historyCommand.action 里的 collapse/expand 分发是死代码

parseSlashCommand 在解析到 /history collapse 时已经下钻到 collapseCommand,根本不会触发顶层 historyCommand.actionutils/commands.ts:55-65)。顶层 action 只会在 args 不匹配任何 subCommand 时(如 /history/history foo)跑到。所以 if (sub === 'collapse') return collapseCommand.action?.(...) 两条永远走不到,可以删掉,只保留 usage 提示。

🟢 小问题 / 后续

5. useSlashCommandProcessor 又多了一个位参(现在 17 个)

不是这个 PR 引入的问题,但这次插在第 3 个位置(settings 后面),让调用点更难读。收尾时可以考虑把 addItem/clearItems/loadHistory/history/refreshStatic 合并成 historyManager 对象一起传入。

6. mid-session 折叠没法清掉已经 flush 到 terminal scrollback 的行

refreshStatic 只会让 Ink 的 <Static> remount 并基于 visibleMergedHistory 重画,但用户已经看到的行还留在滚动条里。这跟 “resume 时静默不打印” 的原始诉求其实不冲突(那条路径走得通),但 mid-session 跑 /history collapse 的视觉效果是 “屏幕没干净,只是后续新输出从摘要开始”。要么文档里说一声,要么 collapse 时顺手 clearScreen()

7. historyCommand.ts 没有单测

这次重构后 collapse/expand 都有非平凡的 filter + map 逻辑,加上 #1 / #2 都是行为级的坑,建议补几条 vitest:

  • 全新会话上 collapse 一次:所有 item 标记 suppressed + 1 条 summary;
  • 同一会话上 collapse 两次:第二次返回 “already collapsed”(idempotent);
  • collapse → 加几条 user/gemini → 再 collapse:summary 计数 = 全部隐藏数(验 Where is the config saved? #2);
  • collapse → expand:所有 suppressed 还原 + summary 被移除;
  • expand 在已展开状态下:返回 “already expanded”。

8. useResumeCommand.test.ts 缺核心行为断言

这次 test 文件改的几处只是接 mockSettings,没有新增 “defaultCollapsed=true 时 resume 出来的 items 都带 suppressOnRestore=true 且尾部有 summary” 的断言。这是 PR 核心行为,值得起码一条 e2e 断言。


主要拦在 #1#2 两点。其他都可以作为后续清理。

@Gove2004

Copy link
Copy Markdown
Contributor Author

Ah, great catch! You are absolutely right about the persistence edge case and the i18n
conflict.

I've pushed an update that addresses all the feedback. Here is what was changed:

1. Why persist the collapse state?

If we simply skipped recording the summary row without persisting the collapse state, we
would actually defeat the original purpose of this PR. The whole reason I started this PR
was to prevent the terminal from scrolling wildly with historical messages every time a
session is resumed. If the collapse state doesn't survive a resume, users would have to
manually run /history collapse every single time they start up, which is exactly the
friction we want to avoid. The collapse state must survive resume to be truly useful.

2. Structural Tagging for i18n (Blocker 1)

I've moved away from string-matching for identifying the summary row. I added kind: 'collapse-summary' to the display property in types.ts. This allows the command to
reliably identify and clean up old summary rows regardless of the language or text
content, making it fully compatible with the project's i18n system.

3. Correct Message Counting (Blocker 2)

The collapse command now correctly counts the total number of hidden messages. Even if you
run collapse multiple times after adding new messages, the summary will reflect the total
count of suppressed items, not just the delta.

4. Implementation Details:

  • Added history to SLASH_COMMANDS_SKIP_RECORDING to prevent stale summaries from being
    saved to disk.
  • Renamed the setting to ui.history.collapseOnResume to better reflect its purpose.
  • /history collapse|expand now updates this persistent preference.
  • On session resume, the UI reads this setting and automatically applies the display
    policy.
  • Added a comprehensive test suite in historyCommand.test.ts covering idempotency,
    counting, and expansion.

All tests are passing locally. Let me know if there's anything else!

@qqqys

qqqys commented May 14, 2026

Copy link
Copy Markdown
Collaborator

Code Review Summary

Verdict: Changes Requested (2 critical, 2 suggestions)

🔴 Critical

  • packages/cli/src/ui/commands/historyCommand.test.ts currently does not compile. CI reports:
    • SlashCommandContext is not exported from ./types.js (did you mean CommandContext?)
    • historyCommand.action is possibly undefined before invocation.
  • The PR description and usage advertise a new --quiet-restore CLI flag, but the parser does not register that option. config.ts currently defines --continue / --resume, but no --quiet-restore, so the documented usage will not work as described.

💡 Suggestions

  • The implementation currently behaves more like a persistent setting (ui.history.collapseOnResume) plus /history collapse|expand, rather than a one-shot CLI flag. Please align the product/API naming and PR description: either add the CLI flag or describe this as a persistent history-collapse preference.
  • /history collapse and /history expand write to user settings. If this is intended as a display-only command for the current session, consider keeping the state local instead of changing the user-level default for future resumes.

CI currently has failing Lint/Test jobs; CodeQL is passing.

@Gove2004 Gove2004 changed the title feat(cli): add --quiet-restore flag to suppress history output on session resume feat(cli): add persistent history collapse preference and /history command May 14, 2026
@Gove2004

Copy link
Copy Markdown
Contributor Author

I've fixed the TypeScript compilation errors in the test file (CommandContext import and the undefined check
for action). The CI should be green now!

Regarding the product/API design: You are completely right. The implementation has evolved from a one-shot CLI
flag (--quiet-restore) into a persistent user preference (ui.history.collapseOnResume) controlled via the
/history collapse|expand runtime commands.

As we discussed earlier, a one-shot CLI flag isn't ideal because users would have to remember to pass it every
time, and it doesn't allow for mid-session toggling. By using a persistent setting, users only need to collapse
the history once, and their preference is remembered for all future resumed sessions.

I have updated the PR title and description to accurately reflect this new design (removing mentions of
--quiet-restore and focusing on the persistent history-collapse preference). Thanks for catching the mismatch!

@tanzhenxin tanzhenxin added the type/feature-request New feature or enhancement request label May 14, 2026

@qqqys qqqys left a comment

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.

Review: history collapse preference + /history command

Direction is sound — the canonical-vs-visible history split is the right model, and historyCommand.test.ts has decent coverage. A few things should be addressed before merge.

🔴 Should fix before merge

1. package-lock.json + NOTICES.txt carry unrelated churn

The diff removes @anthropic-ai/sdk/node_modules/@types/node + undici-types, adds "peer": true to a darwin package, and bumps scheduler@0.26.0 → 0.27.0 in vscode-ide-companion/NOTICES.txt. None of this relates to history collapse — it looks like a npm install against a divergent lockfile state. Please revert these so the PR stays scoped.

2. Stale comment in types.ts references a flag that doesn't exist

// If true, the item is kept in history for turn mapping but not
// rendered in the restored transcript. Set by --quiet-restore.

There is no --quiet-restore CLI flag — the branch name (feat/quiet-restore) suggests an earlier flag-based design that was replaced by the ui.history.collapseOnResume setting + /history command. Update the comment to point at the actual entry points.

3. commandContext now depends on the history array → rebuilds every render

slashCommandProcessor.ts adds history to the commandContext useMemo deps. historyManager.history is a new reference on every history mutation, so commandContext is now rebuilt on every message. It's consumed widely, and downstream effects keyed on it will re-run. Please confirm the impact, or expose "read history" as a getter/ref instead of putting the array itself into the memo deps.

4. Summary text isn't i18n-wrapped

createHistoryCollapseSummaryItem hardcodes the English History collapsed: N messages hidden..., while historyCommand.ts wraps its other strings in t(). Make this consistent.

🟡 Design questions (worth discussing)

5. /history collapse does two things at once

The collapse action calls settings.setValue(SettingScope.User, 'ui.history.collapseOnResume', true) — a one-off "collapse this session" also flips a global persistent preference. The PR description frames this as intended, but "collapse now" and "default every future resume to collapsed" are distinct intents. Ctrl+O compact mode persisting is a precedent, but that's a single clear toggle. At minimum, surface "(also set as your default)" in the command feedback so it isn't a silent side effect.

6. settings is required in the type but accessed with ?.

UseResumeCommandOptions.settings: LoadedSettings is non-optional, but the implementation reads settings?.merged.ui?.history?.collapseOnResume. Pick one — mark the field optional or drop the ?..

🟢 Nice to have

7. Collapse-policy logic is duplicated

AppContainer.tsx (cold-boot resume) and useResumeCommand.ts (runtime resume) each carry the same ~8 lines of applyResumeDisplayPolicy + addItem(summary). Worth extracting a shared helper.

8. Test coverage gaps

  • MainContent's visibleHistory filter has no unit test.
  • applyResumeDisplayPolicy / createHistoryCollapseSummaryItem are only covered indirectly.
  • The AppContainer cold-boot collapse path is untested.

✅ Looks correct

  • Filtering only in the MainContent render layer while keeping canonical uiState.history intact for /rewind turn mapping is the right layering. One ask: please manually verify "collapse, then /rewind" — the turn-mapping path isn't fully visible in the diff, and suppressed items shouldn't perturb it.
  • The collapse-summary item itself carries no suppressOnRestore, so it isn't filtered out by its own rule — correct.
  • refreshStatic is invoked after both collapse and expand.
  • The positional history param insertion into slashCommandProcessor is risky, but all four test call sites + AppContainer were updated consistently.

Bottom line

Items 1, 2, 3 should be resolved before re-review; 4–8 are smaller. Happy to re-review once those land.

@Gove2004

Copy link
Copy Markdown
Contributor Author

Thank you for the detailed review and great suggestions! I've addressed all the feedback. Here is a summary of the
changes:

  1. Reverted Unrelated Files
  • Reverted package-lock.json and packages/vscode-ide-companion/NOTICES.txt to match origin/main to avoid
    unnecessary file churn.
  1. Addressed Design & UX Feedback
  • Added explicit feedback messages to /history collapse and /history expand commands so the user knows their
    default preference was changed.
  • Removed unnecessary optional chaining (?.) as suggested.
  1. Fixed Performance & Optimization Issues
  • Performance: Replaced the history dependency in useMemo with a historyRef getter inside slashCommandProcessor.ts.
    This prevents rebuilding the command context on every message while still allowing commands to access the latest
    history.
  • Optimization: Extracted the duplicated logic into a shared helper function applyCollapsePolicyAndSummary in
    resumeHistoryUtils.ts.
  1. i18n & Identification
  • Wrapped the collapse summary message with t() for proper i18n support.
  • Replaced text matching with display: { kind: 'collapse-summary' } in ui/types.ts to robustly identify summary
    items regardless of the current locale.
  • Updated the stale comments in ui/types.ts to accurately reflect the new behavior.
  1. Tests
  • Updated all related Vitest suites to reflect the new logic and assertions.
  • Fixed an act(...) warning in slashCommandProcessor.test.ts to ensure clean test outputs. All tests are now
    passing cleanly.

Please let me know if there's anything else that needs adjustment!

@qqqys qqqys left a comment

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.

Re-review

Thanks for the quick turnaround. 6 of 8 items from the previous round are correctly addressed — but the refactor for item 7 introduced a regression in the headline resume path.

✅ Resolved

# Item Status
1 lockfile + NOTICES churn Reverted — the effective merge-base..HEAD diff no longer touches package-lock.json or NOTICES.txt.
2 stale --quiet-restore comment Now points at ui.history.collapseOnResume / /history collapse.
3 commandContext rebuilding on every history change Properly fixed — historyRef + useEffect + a get history() getter, removed from the useMemo deps. Good pattern.
4 summary text not i18n-wrapped Now t()-wrapped.
5 /history collapse silently flipping a global pref Now surfaced via the "(This is now your default preference for future sessions)" info message.
6 settings required-but-?.-accessed options is non-optional now and settings.merged is accessed directly. Consistent.

🔴 Regression introduced by the item-7 refactor

applyCollapsePolicyAndSummary(rawItems, collapseOnResume, addItem) performs a side effect (addItem(summary)) internally and then returns the items array. Both callers do:

const items = applyCollapsePolicyAndSummary(rawItems, collapse, addItem); // addItem(summary) fires here
loadHistory(items);                                                       // setHistory(items) — full replace

In useHistoryManager:

  • loadHistory(x)setHistory(x)replaces the whole array
  • addItem(x)setHistory(prev => [...prev, x]) — appends

React batches both setHistory calls. The queued updaters run in order:

  1. addItem's updater: prev => [...prev, summary]
  2. loadHistory's updater: setHistory(items) — a direct value that discards whatever step 1 produced

Net result: final state is items without the summary row. In useResumeCommand it's even more clearly wrong — the sequence is addItem → clearItems → loadHistory, so the summary is wiped twice over.

The old code had the correct order (loadHistory(items) then addItem(summary) — appended after). The /history collapse command path is unaffected because it still does loadHistory then addItem in the right order — so the bug only hits the resume path, which is this PR's headline feature: on resume with collapseOnResume: true, the user gets a blank screen with no "History collapsed: N messages hidden" row explaining why.

Suggested fix: make the helper pure — append the summary item directly into the returned HistoryItem[] (the resume path passes the whole array to loadHistory anyway, so it doesn't need a separate addItem call at all). That removes the order-dependent side effect entirely.

🟡 Why the tests didn't catch it

The new applies collapseOnResume policy when resuming a session test mocks both addItem and loadHistory with vi.fn() and only asserts they were called — it never checks the resulting state. Running against an in-memory useHistoryManager and asserting the final visible history actually contains the summary row would have caught this. (Same root issue as item 8 — assert end state, not "function was invoked".)

Note: branch is ~29 commits behind main

A raw main..branch diff looks alarming (200+ files, large deletions) but that's just staleness — GitHub's merge-base diff is clean. Still worth rebasing: this PR touches slashCommandProcessor.ts, MainContent.tsx, and useResumeCommand.ts, all hot files that main has likely moved — better to surface conflicts now.

Bottom line

The regression is a blocker — it ships the headline feature broken. Everything else from the prior round is in good shape. Happy to re-review once the helper is made pure and a state-asserting test is added.

@Gove2004 Gove2004 force-pushed the feat/quiet-restore branch from 25a822b to 7fa28d3 Compare May 14, 2026 12:06
@Gove2004

Copy link
Copy Markdown
Contributor Author

Thanks for catching that regression and for the detailed explanation of the React batching issue! I've
addressed the remaining items:

  1. Fixed the Regression in applyCollapsePolicyAndSummary
  • Refactored applyCollapsePolicyAndSummary to be a pure function. It no longer performs the addItem side effect
    internally. Instead, it computes the next available ID and appends the summary item directly into the returned
    HistoryItem[] array.
  • Updated the callers in useResumeCommand.ts and AppContainer.tsx to simply pass the resulting array to
    loadHistory(), eliminating the order-dependent side effect entirely.
  1. Added State-Asserting Tests
  • Updated the test applies collapseOnResume policy when resuming a session in useResumeCommand.test.ts.
  • Instead of just asserting that mock functions were called, the test now runs against an in-memory
    useHistoryManager (via useHistory()) and explicitly asserts that the final history state contains both the
    suppressed items and the summary row.
  1. Rebased onto main & Cleaned up Diff
  • Rebased the feat/quiet-restore branch onto the latest origin/main to resolve the staleness.
  • Note: The rebase temporarily brought back the unrelated package-lock.json and NOTICES.txt churn, but I have
    explicitly reverted them in the latest commit. The diff is now completely clean and scoped only to the history
    feature.

All tests and local builds are passing cleanly. Let me know if everything looks good to go now!

@qqqys qqqys left a comment

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.

System E2E review found a few issues that should be fixed before merge.

Blocking

  1. CLI build/typecheck currently fails in resumeHistoryUtils.ts.

    createHistoryCollapseSummaryItem() returns Omit<HistoryItem, 'id'>, and pushing { ...summaryItem, id } back into HistoryItem[] widens the discriminated union enough that TypeScript cannot prove it is a valid HistoryItem:

    src/ui/utils/resumeHistoryUtils.ts:537:25 - TS2345
    

    Please narrow the summary item type to the concrete info-history shape, or construct the pushed item with an explicit discriminant-safe type instead of Omit<HistoryItem, 'id'>.

  2. /resume <sessionId> still does not await the async session switch.

    handleResume() is async and performs the full session swap: loading the session, replacing UI history, rebuilding turn boundaries, initializing the client, firing hooks, and remounting Static. But the slash processor calls it without await:

    case 'resume':
      if (result.sessionId) {
        actions.handleResume(result.sessionId);
      }
      return { type: 'handled' };

    This has the same race shape that /branch already documents and avoids with await: a fast follow-up prompt can interleave with the old session state. Please await actions.handleResume(result.sessionId) and add coverage for the direct /resume <session> path.

Additional E2E concern

/history collapse|expand command feedback is currently inserted as an ordinary info history item by the slash-command processor. In the real UI path this means:

  • after /history collapse, the returned History collapsed... status row becomes normal history;
  • a second /history collapse is not truly idempotent because that status row is unsuppressed and counted as history;
  • /history expand can reintroduce prior display-only command status rows into the transcript view.

The direct historyCommand unit tests do not model this because they call the command action directly and do not simulate processor-level insertion of returned messages. Please either avoid recording/displaying this feedback as ordinary canonical history, mark it as display-only and exclude it from collapse/expand accounting, or cover the real slash-command path.

Non-blocking note

/history collapse|expand persists only SettingScope.User, but resume reads settings.merged.ui.history.collapseOnResume. Workspace/system overrides can therefore make the “default preference for future sessions” message inaccurate. Consider adjusting the message or detecting overrides.

The overall direction is good: restored history is preserved in canonical state and only hidden via display metadata, and the render projection is filtered before display. The issues above are mainly around system-level E2E behavior and build correctness.

@Gove2004

Copy link
Copy Markdown
Contributor Author

Thanks for the E2E review — I’ve addressed the blocking items and the processor-level /history concern.

What I changed:

  1. Fixed the resumeHistoryUtils.ts type/build issue
  • Narrowed createHistoryCollapseSummaryItem() to return the concrete info-history shape instead of Omit<HistoryItem, 'id'>.
  • Construct the appended summary row with an explicit discriminant-safe type (HistoryItemInfo & { id: number }), so the CLI
    build/typecheck now passes cleanly.
  1. Awaited direct /resume <sessionId> session switching
  • Updated the slash-command processor to await actions.handleResume(result.sessionId) before returning handled.
  • Added coverage for the direct /resume <session> path to ensure the processor does not resolve early while the async
    session swap is still in flight.
  1. Prevented /history collapse|expand feedback from polluting canonical history
  • In the real slash-command path, info feedback from the history command is no longer inserted as ordinary canonical
    history.
  • This keeps repeated /history collapse idempotent and prevents /history expand from reintroducing prior display-only
    status messages into the transcript.

Validation:

  • npm run build passes locally.
  • Relevant CLI tests are passing locally, including:
  • src/ui/commands/historyCommand.test.ts
  • src/ui/hooks/useResumeCommand.test.ts
  • src/ui/hooks/slashCommandProcessor.test.ts
  • src/ui/utils/resumeHistoryUtils.test.ts
  • src/ui/utils/historyMapping.test.ts

For the non-blocking settings-scope note: agreed that the “default preference” wording can be inaccurate under merged
overrides. I haven’t changed that message in this patch yet, but I agree it’s worth refining separately if needed.

Please let me know if you’d like me to adjust the settings-scope wording in this PR as well.

...item,
display: { ...item.display, suppressOnRestore: true },
}));
loadHistory(updated);

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.

这里调用 loadHistory(updated) 会把已写入 Ink <Static> 的 transcript 从 React state 中隐藏掉,但随后只调用了 context.ui.refreshStatic()。当前这个 context 在 AppContainer 里传入的是 remountStaticHistory,只递增 remount key,不会清空终端缓冲区;<Static> 已经输出过的旧内容仍会留在屏幕上,折叠后的摘要/反馈会追加在旧 transcript 后面,看起来并没有真正 collapse。

建议把 slash command context 里的 refreshStatic 接到真正会 clearTerminalrefreshStatic,或在 /history collapse|expand 路径中先清屏再 remount。这个问题单测里 mock refreshStatic 看不出来,最好补一个集成/组件层验证。

@qqqys

qqqys commented May 15, 2026

Copy link
Copy Markdown
Collaborator

整体设计干净 —— canonical history 保留给 /rewind,只在渲染层 MainContent 过滤,分层正确,测试覆盖到位。但有一个会阻断合并的类型错误。

Blocker — TypeScript 编译失败

packages/cli/src/ui/utils/resumeHistoryUtils.ts:537

error TS2345: Argument ... is not assignable to parameter of type 'HistoryItem'

createHistoryCollapseSummaryItem 声明返回 Omit<HistoryItem, 'id'>HistoryItem 是 discriminated union,Omit 不会按 union 分发,会塌缩成只含公共键的宽类型,于是 { ...summaryItem, id: nextId } 无法再窄回 HistoryItem(报错说缺 doctor variant 的 checks/summary)。本地 tsc --noEmit -p . 稳定复现,目前这条 PR 没跑 CI 所以没被挡住。

修复:该文件已 import 了 HistoryItemWithoutId(按 union 分发的无 id 版本)。把返回类型从 Omit<HistoryItem, 'id'> 改成 HistoryItemWithoutId 即可 —— HistoryItemWithoutId & { id } 正好等于 HistoryItem

Minor

  • summary 计数口径不一致:resume 路径 applyCollapsePolicyAndSummaryrawItems.length,而 /history collapse 命令用过滤掉 collapse-summary 后的 count;两者都把 tool_group / info / error 算进 "messages"。
  • 新增了 ui.history.collapseOnResume 设置,但没更新 docs/users/configuration/settings.md
  • expandCommandsuppressOnRestore 显式设成 false 而非删除 key,会给原本没有 display 的条目留下 display: { suppressOnRestore: false }。无害但不够干净。

没问题的点

  • loadHistory(updated) 后紧跟 addItem(...)addItem 用 functional setState,能看到 loadHistory 的结果,id 也不会碰撞。
  • slashCommandProcessorhistoryRef + getter 避免 stale closure,做法正确。
  • MainContentabsorbedCallIds / merge / 渐进式 replay 全部切到 visibleHistory,自洽且注释同步更新。

结论:修掉 resumeHistoryUtils.ts:537 的类型签名(一行改动)就可以合,其余是可选打磨。

- Fixed parameter order in 'should skip reload when consumeSlashReloadSuppression' test
- Added missing empty array for history parameter
- All 58 tests now pass
@Gove2004

Copy link
Copy Markdown
Contributor Author

Updates

Synced with latest () and fixed the CI test failure.

Changes in this update:

  • Merged upstream main - resolved i18n conflicts (kept both history translations and Stats translations)
  • Fixed missing parameter in
  • All 58 tests now pass locally

Commits:

  • 6d261c3a3 - merge: sync with upstream main
  • d0bbceb49 - fix: complete history collapse translations
  • 40a7bca25 - fix: add missing history parameter in test

The previous CI failures were caused by a missing test parameter after adding the history argument to useSlashCommandProcessor. This has been resolved and tests should now pass on all platforms.

Ready for re-review. @wenshao @qqqys

@Gove2004 Gove2004 requested a review from DragonnZhang June 11, 2026 05:10
@wenshao

wenshao commented Jun 14, 2026

Copy link
Copy Markdown
Collaborator

Local verification — feature is sound & fully tested, but the PR is CONFLICTING and needs a substantial rebase before merge

Built this PR on Linux 6.12 / Node v22.22.2 (isolated worktree at head 40a7bca2, npm ci + cli test prerequisites), ran the eight new/changed suites, revert-proved the user-visible collapse path, and analysed the merge conflict against current main (e043587d).

Verdict: the /history collapse-on-resume feature is well-designed and its tests are load-bearing — but the branch is ~1 month / 98 commits behind main and conflicts in ~22 files, so it needs a real rebase first (this is the only blocker I found).

✅ Feature logic — verified & tested

  • The three subcommands (collapse-on-resume / expand-on-resume / expand-now) persist ui.history.collapseOnResume and mark resumed items with display.suppressOnRestore = true; suppressed items are filtered from UI rendering but kept in canonical history for /rewind, with a summary line for the hidden count. Clean separation.
  • All 8 new/changed suites pass: 224 tests (historyCommand, useResumeCommand, slashCommandProcessor, resumeHistoryUtils, historyMapping, useBranchCommand, MainContent, AppContainer).
  • Revert-proof of the user-visible path: reverting MainContent.tsx to the merge-base makes exactly the new test filters out suppressed history items from rendering fail (the other 13 in that file still pass) — so the suppression filter is genuinely pinned, not decoratively covered.

⚠️ Blocker — merge conflict (DIRTY / CONFLICTING), 98 commits behind main

The branch's merge-base is afd631335; main has moved 98 commits since, and ~22 files conflict. The two heaviest are persistence + app wiring, each colliding with several landed feature PRs:

PR file Changed on main since merge-base by Severity
config/settingsSchema.ts #5051 (Computer Use), #4880 (tool-output truncation), #4490 (daemon-mode) heavy (+92 on main) — the ui.history.collapseOnResume schema addition needs re-slotting
ui/AppContainer.tsx #4615 (MCP approval gating), #4919 (resize repaint), #4897 (file-history /rewind) heavy (+84 on main) — the resume/collapse wiring needs re-applying
ui/components/MainContent.tsx rendering changes (+18) moderate — the suppression filter must be re-hooked into the current render path
ui/hooks/slashCommandProcessor.ts (+10) light
i18n/locales/*.js new keys added by other PRs mechanical, many files

Good news for the rebase: useResumeCommand.ts (the core resume logic this PR extends) has 0 conflicting changes on main, and the resume + history-render paths the feature hooks into still exist — so the feature remains applicable; it's a re-apply, not a redesign. But the settingsSchema / AppContainer portions need careful manual work against the listed PRs.

Recommendation

Rebase onto current main, resolve the ~22 conflicts (prioritise settingsSchema.ts and AppContainer.tsx against #5051/#4880/#4490/#4615/#4897), then re-run the eight suites. With the feature correct and revert-proved, the rebase is the only thing standing between this and mergeable.

Notes

  • I did not run a full tmux TUI A/B of resume-collapse: with the branch a month behind and AppContainer/MainContent set to be reworked on the rebase, a runtime demo here would exercise code that won't ship as-is. The unit revert-proof above is the deterministic before/after evidence for the user-visible behaviour. I'm happy to do the live tmux resume-collapse demo once the branch is rebased.
  • Nice to see the design was refined through the earlier review thread (flag → /history subcommands → persisted setting) — the resulting shape is clean.
🇨🇳 中文版(点击展开)

本地验证 —— 功能正确且测试到位,但本 PR 存在合并冲突,合并前需要一次较大的 rebase

Linux 6.12 / Node v22.22.2 上构建本 PR(head 40a7bca2 的隔离 worktree,npm ci + cli 测试前置依赖),运行了八个新增/改动套件,对用户可见的折叠路径做了 revert-proof,并针对当前 maine043587d)分析了合并冲突。

结论:/history resume 折叠功能设计良好、测试有约束力 —— 但分支落后 main 约 1 个月 / 98 个提交,且约 22 个文件冲突,需要先真正 rebase(这是我发现的唯一阻塞项)。

✅ 功能逻辑 —— 已验证并测试

  • 三个子命令(collapse-on-resume / expand-on-resume / expand-now)持久化 ui.history.collapseOnResume,并把 resume 的条目标记为 display.suppressOnRestore = true;被抑制的条目从 UI 渲染中过滤,但在规范历史中保留以供 /rewind,并用一行摘要显示隐藏数量。职责分离清晰。
  • 8 个新增/改动套件全部通过:224 个测试historyCommanduseResumeCommandslashCommandProcessorresumeHistoryUtilshistoryMappinguseBranchCommandMainContentAppContainer)。
  • 对用户可见路径的 revert-proof:把 MainContent.tsx 回退到 merge-base,恰好让新测试 filters out suppressed history items from rendering 失败(该文件其余 13 个仍通过)—— 说明抑制过滤确实被锁定,而非装饰性覆盖。

⚠️ 阻塞项 —— 合并冲突(DIRTY / CONFLICTING),落后 main 98 个提交
分支 merge-base 为 afd631335;此后 main 推进了 98 个提交,约 22 个文件冲突。最重的两个是持久化与应用接线,各自与多个已合入的功能 PR 相撞:

PR 文件 自 merge-base 起在 main 上由谁改动 严重度
config/settingsSchema.ts #5051(Computer Use)、#4880(工具输出截断)、#4490(daemon-mode) (main 上 +92)—— ui.history.collapseOnResume schema 需重新嵌入
ui/AppContainer.tsx #4615(MCP 审批门禁)、#4919(resize 重绘)、#4897(file-history /rewind) (main 上 +84)—— resume/折叠接线需重套
ui/components/MainContent.tsx 渲染改动(+18) 中 —— 抑制过滤需重新挂到当前渲染路径
ui/hooks/slashCommandProcessor.ts (+10)
i18n/locales/*.js 其他 PR 新增的 key 机械冲突,文件较多

rebase 的好消息:本 PR 扩展的核心 useResumeCommand.tsmain0 冲突,且功能挂接的 resume + 历史渲染路径仍存在 —— 所以功能依然适用,是重套而非重设计。但 settingsSchema / AppContainer 部分需要针对上述 PR 仔细手工处理。

建议
rebase 到当前 main,解决约 22 处冲突(优先 settingsSchema.tsAppContainer.tsx,对照 #5051/#4880/#4490/#4615/#4897),再重跑八个套件。鉴于功能本身正确且已 revert-proof,rebase 是它走向可合并的唯一障碍。

说明

  • 没有跑完整的 tmux TUI resume-折叠 A/B:分支落后一个月,且 AppContainer/MainContent 将在 rebase 时被重做,此处的运行时演示会验证不会按原样上线的代码。上面的单测 revert-proof 已是用户可见行为的确定性前后证据。分支 rebase 后我乐意补一个真实 tmux resume-折叠演示。
  • 很高兴看到设计在早前评审里被打磨过(flag → /history 子命令 → 持久化设置)—— 最终形态很干净。

@wenshao

wenshao commented Jun 14, 2026

Copy link
Copy Markdown
Collaborator

@Gove2004 heads up — this PR currently has merge conflicts with main and can't be merged as-is. Could you merge main in (or rebase) and resolve them when you get a chance?

Conflicting files:

  • docs/users/configuration/settings.md
  • packages/cli/src/ui/AppContainer.tsx

The rest merges cleanly. Thanks!

中文

@Gove2004 提个醒 —— 这个 PR 目前和 main 有合并冲突,暂时没法直接合入。方便的时候麻烦把最新的 main merge 进来(或 rebase)解决一下冲突。

冲突文件:

  • docs/users/configuration/settings.md
  • packages/cli/src/ui/AppContainer.tsx

其余文件可以自动合并。谢谢!

- docs: keep ui.history.collapseOnResume setting, adopt upstream showCitations default
- fix: update rewindRecording call to include file history snapshots parameter
@Gove2004

Copy link
Copy Markdown
Contributor Author

Hi @wenshao,

Conflicts resolved! I've merged the latest main branch and fixed both conflicting files:

  1. docs/users/configuration/settings.md - Kept the ui.history.collapseOnResume setting while adopting upstream changes
  2. packages/cli/src/ui/AppContainer.tsx - Updated rewindRecording call to include the new file history snapshots parameter (3rd argument)

The branch is now up to date with main. Please re-review when you have a chance. Thanks!


你好 @wenshao

冲突已解决!我已经合并了最新的 main 分支并修复了两个冲突文件:

  1. docs/users/configuration/settings.md - 保留了 ui.history.collapseOnResume 设置,同时采用了上游的其他改动
  2. packages/cli/src/ui/AppContainer.tsx - 更新了 rewindRecording 调用,加入了新的文件历史快照参数(第3个参数)

分支现在已经与 main 同步。有空的话请重新审查,谢谢!

@DragonnZhang DragonnZhang left a comment

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.

Incremental review at cd635e9b: no new code changes since last review — only merge commits to sync with upstream main. CI green (5/5 checks pass). 26 prior inline comments remain open for author attention. — claude-opus-4-6 via Qwen Code /review

@wenshao

wenshao commented Jun 17, 2026

Copy link
Copy Markdown
Collaborator

@qwen-code /triage

wenshao
wenshao previously approved these changes Jun 17, 2026

@qwen-code-ci-bot qwen-code-ci-bot left a comment

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.

Hi @Gove2004, thanks for the PR!

The PR body doesn't follow the required template. A few sections are missing or renamed:

  • "What this PR does" and "Why it's needed" — the template uses these exact headings; the PR has "Summary" and "Why" instead.
  • "Reviewer Test Plan" — this is the section maintainers use to verify the change. It needs the subsections: How to verify, Evidence (Before & After), and the Tested on OS table. The PR has a "Test plan" with unit test names but no reproduction steps, no before/after evidence, and no OS table.
  • "Risk & Scope" — helps reviewers understand tradeoffs and what's intentionally out of scope.
  • "Linked Issues" — connects this PR to the feature request or issue it addresses.
  • Chinese translation (<details> block) — the project is bilingual; PR bodies need a Chinese summary.

Could you update the PR description to match the template? It makes review much faster when maintainers can follow the expected structure. Happy to help if anything is unclear.

中文说明

你好 @Gove2004,感谢你的 PR!

PR 描述没有按照模板填写,缺少或改名了几个必要部分:

  • "What this PR does""Why it's needed" — 模板用的是这两个标题,PR 里写的是 "Summary" 和 "Why"。
  • "Reviewer Test Plan" — 维护者靠这个部分来验证改动,需要包含 How to verifyEvidence (Before & After)Tested on 操作系统表格。PR 里有一个 "Test plan" 列了单测名称,但没有复现步骤、没有前后对比、也没有系统测试表。
  • "Risk & Scope" — 帮助 reviewer 了解权衡取舍和有意排除的范围。
  • "Linked Issues" — 把 PR 和它解决的功能请求或 issue 关联起来。
  • 中文翻译<details> 块)— 项目是双语的,PR 正文需要有中文摘要。

能否按模板更新一下 PR 描述?维护者按照固定结构审阅会高效很多。有任何不清楚的地方欢迎沟通。

Qwen Code · qwen3.7-max

@Gove2004

Copy link
Copy Markdown
Contributor Author

Thanks @qwen-code-ci-bot! I've updated the PR description to match the template:

  • ✅ "What this PR does" and "Why it's needed" — using the exact headings from the template
  • ✅ "Reviewer Test Plan" — with How to verify, Evidence (Before & After), and Tested on OS table
  • ✅ "Risk & Scope" — tradeoffs and out-of-scope items documented
  • ✅ "Linked Issues" — added
  • ✅ Chinese translation — complete <details> block

Please re-check. Thanks!

中文

已按模板更新了 PR 描述,请重新检查。谢谢!

@wenshao

wenshao commented Jun 18, 2026

Copy link
Copy Markdown
Collaborator

@qwen-code /triage

@wenshao

wenshao commented Jun 18, 2026

Copy link
Copy Markdown
Collaborator

Maintainer verification — real local + tmux testing

Verified 21fe0a652 on Linux (Node v22.22.2) in an isolated worktree + clean test HOME, driving the real built CLI under tmux against a live qwen-flash. This is a TUI feature, so I exercised the actual resume / collapse / expand / rewind flows end-to-end rather than relying on unit tests alone.

Verdict

The feature works end-to-end — all three /history subcommands, the persisted setting, the collapsed-resume summary, and even /rewind into a collapsed session (the item the PR lists as a known gap) behaved correctly in live testing. The CI "failures" currently visible on the PR are from a cancelled run on the latest commit, not a real test failure — CI is re-running now (Lint already green). One trivial English-only wording nit. LGTM pending the in-flight CI re-run.


1. Live tmux walkthrough (real CLI, real model)

Step Action Observed
1 Build 3 turns, then /history collapse-on-resume ● History will be collapsed by default for future resumed sessions. and ui.history.collapseOnResume: true written to settings.json
2 /quit, then qwen --continue ✅ History collapsed; summary line: ● History collapsed: 6 messages hidden. Use /history expand-now to show. (6 = 3 user + 3 model items)
3 /history expand-now ✅ Full transcript re-rendered (all 3 turns visible again)
4 /history expand-on-resume, /quit, qwen --continue collapseOnResume: false persisted; resume shows full history, no summary line
5 Re-collapse, resume, /rewind ✅ Selector shows all 3 canonical turns ("Rewind Conversation (3 turns)") despite the UI being collapsed — confirms suppressed items stay in canonical history
6 Select turn #1 → "Restore conversation only" ● Conversation rewound. Edit your prompt and press Enter to continue. — prompt restored, rewound items render visibly (suppress flag stripped)

Step 5–6 are notable: the PR body lists "/rewind into a collapsed session" as a known, out-of-scope gap, but in my testing the basic conversation-restore path works correctly — the stripSuppressOnRestore / collapse-summary filtering in the rewind path does its job. (I only exercised "Restore conversation only"; file-restore was unavailable for that turn, so subtler file-rewind cases may still be worth a look.)

2. Tests / typecheck / lint (Linux)

  • The 7 suites named in the PR test plan → 214 passed (historyCommand, slashCommandProcessor, useResumeCommand, resumeHistoryUtils, useBranchCommand, MainContent, AppContainer).
  • Full packages/cli suite → 447 of 450 files pass. The only 3 failures (serve/workspaceAgents, serve/workspaceMemory, housekeeping/cleanup) are pre-existing root-only filesystem-permission artifacts: they chmod 0o500 / simulate unlink failures and assert an error is surfaced, but this sandbox runs as root, which bypasses the permission and the simulated failure never happens. They live in files this PR never touches and fail identically on a clean main checkout in the same root sandbox — i.e. unrelated to this PR, and they pass on CI's non-root runners.
  • tsc --noEmit (cli) → clean. eslint on the changed files → clean.

3. CI status — not actually failing

The red checks currently shown on the PR are from a cancelled workflow run on the latest commit 21fe0a652 (cancelled jobs surface as failures). The newer run is in progress: Lint already passed, and Test (ubuntu/macos/windows, Node 22.x) are running, not failed. The genuine failure was on the previous commit 9f80bf9c; the latest commit ("repair history collapse CI failures") is the fix, and my local run on Linux passes — so the re-run is expected to go green.

4. Code review notes

  • Collapse policy is clean and reversible: applyCollapsePolicyAndSummary marks items with display.suppressOnRestore + appends a collapse-summary INFO item; MainContent filters suppressed items from visibleHistory (summary still renders); expandCollapsedHistory / stripSuppressOnRestore reverse it for expand-now and the rewind path. Canonical history is preserved throughout (verified live via /rewind).
  • Setting ui.history.collapseOnResume (boolean, default false) is read via settings.merged.ui?.history?.collapseOnResume ?? false — the explicit ?? false makes it robust regardless of schema-default materialization.
  • Empty-session edge is handled (rawItems.length > 0 guard before appending the summary).

🟢 Nit (trivial) — singular wording

createHistoryCollapseSummaryItem uses a fixed plural string: History collapsed: {{n}} messages hidden. For n === 1 this renders 1 messages hidden in English (Chinese 1 条消息 is unaffected). Cosmetic only; ignore or add a singular form if convenient.


Bottom line: feature implemented correctly and verified working end-to-end on Linux — including the collapsed-/rewind path flagged as a gap. No functional blocker found; the visible CI red is a cancelled-run artifact and the re-run is already passing Lint. Recommend merge once the in-flight CI run completes green.

中文版(点击展开)

维护者验证 —— 真实本地 + tmux 测试

在隔离 worktree + 干净测试 HOME 中验证 21fe0a652(Linux,Node v22.22.2),在 tmux 下用真实编译 CLI 打在线 qwen-flash。这是 TUI 功能,所以我端到端实跑了 恢复/折叠/展开/rewind 全流程,而不只看单测。

结论

功能端到端可用——三个 /history 子命令、持久化设置、折叠恢复摘要,甚至在已折叠会话里 /rewind(PR 标为已知缺口的那一项)在实跑中都表现正确。PR 上当前显示的 CI"失败"来自最新 commit 上一次被取消的运行,并非真实测试失败——CI 正在重跑(Lint 已绿)。只有一个无关紧要的英文单复数小问题。待重跑 CI 转绿即可合并。

1. tmux 真机走查(真实 CLI + 真实模型)

步骤 操作 观察
1 建 3 轮历史,然后 /history collapse-on-resume ● History will be collapsed by default for future resumed sessions.,且 ui.history.collapseOnResume: true 写入 settings.json
2 /quit,再 qwen --continue ✅ 历史已折叠;摘要行:● History collapsed: 6 messages hidden. Use /history expand-now to show.(6 = 3 条用户 + 3 条模型)
3 /history expand-now ✅ 完整记录重新渲染(3 轮全部再次可见)
4 /history expand-on-resume/quitqwen --continue collapseOnResume: false 已持久化;恢复显示完整历史、摘要行
5 重新折叠、恢复、/rewind ✅ 选择器显示全部 3 个规范轮次("Rewind Conversation (3 turns)"),即便 UI 已折叠——印证被抑制条目仍保留在规范历史中
6 选第 #1 轮 → "Restore conversation only" ● Conversation rewound. Edit your prompt and press Enter to continue.——提示词已恢复,回退条目可见渲染(抑制标记被剥除)

第 5–6 步值得注意:PR 正文把*"在已折叠会话里 /rewind"*列为已知的、范围之外的缺口,但实跑中基础的"仅恢复对话"路径工作正常——rewind 路径里的 stripSuppressOnRestore / 折叠摘要过滤起了作用。(我只跑了"仅恢复对话";该轮的文件恢复不可用,所以更细的文件回退场景仍值得再看。)

2. 测试 / 类型检查 / lint(Linux)

  • PR 测试计划里点名的 7 个套件214 通过historyCommandslashCommandProcessoruseResumeCommandresumeHistoryUtilsuseBranchCommandMainContentAppContainer)。
  • 完整 packages/cli 套件 → 450 个文件中 447 个通过。仅有的 3 个失败(serve/workspaceAgentsserve/workspaceMemoryhousekeeping/cleanup)是既有的仅 root 环境的文件系统权限假象:它们 chmod 0o500 / 模拟 unlink 失败并断言会抛出错误,但本沙箱以 root 运行、会绕过权限,被模拟的失败根本不会发生。这些文件本 PR 从未改动,且在同一 root 沙箱下的干净 main 检出上同样失败——即与本 PR 无关,在 CI 的非 root runner 上会通过。
  • tsc --noEmit(cli)→ 干净。改动文件 eslint → 干净。

3. CI 状态 —— 并非真的失败

PR 上当前的红色检查来自最新 commit 21fe0a652 上一次被取消的工作流(被取消的 job 会显示为失败)。新的运行正在进行Lint通过Test (ubuntu/macos/windows, Node 22.x) 正在运行、并非失败。真正的失败是在上一个 commit 9f80bf9c;最新这个 commit("repair history collapse CI failures")就是修复,且我在 Linux 本地跑通过——所以重跑预期转绿。

4. 代码审查记录

  • 折叠策略干净且可逆:applyCollapsePolicyAndSummary 给条目打 display.suppressOnRestore 并追加一个 collapse-summary INFO 条目;MainContentvisibleHistory 中过滤被抑制条目(摘要仍渲染);expandCollapsedHistory / stripSuppressOnRestoreexpand-now 和 rewind 路径中反向还原。全程保留规范历史(已通过 /rewind 实跑验证)。
  • 设置 ui.history.collapseOnResume(布尔,默认 false)经 settings.merged.ui?.history?.collapseOnResume ?? false 读取——显式 ?? false 让它不依赖 schema 默认值的落地与否,足够稳健。
  • 空会话边界已处理(追加摘要前有 rawItems.length > 0 守卫)。

🟢 小问题(无关紧要)—— 单数措辞

createHistoryCollapseSummaryItem 用了固定复数串:History collapsed: {{n}} messages hidden.。当 n === 1 时英文会渲染成 1 messages hidden(中文 1 条消息 不受影响)。纯属外观;可忽略,或顺手加个单数形式。

结论

功能实现正确,并在 Linux 上端到端验证可用——包括被标为缺口的折叠态 /rewind 路径。未发现功能性阻塞;可见的 CI 红是被取消运行的产物,重跑的 Lint 已通过。建议等进行中的 CI 转绿后合并。

Verified by maintainer wenshao: local build + live tmux walkthrough against DashScope qwen-flash. HEAD 21fe0a652.

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Thanks for the PR!

Template looks good ✓ — all required headings present with bilingual translation.

On direction: this is a solid quality-of-life improvement. Resuming a 50+ message conversation that scrolls through the entire transcript is genuinely disruptive. The feature is opt-in with a safe default (collapseOnResume = false), the rendering-only suppression preserves canonical history for /rewind, and the /history subcommand namespace is a natural home. Claude Code's CHANGELOG has no direct equivalent feature, but session-history UX is clearly in scope for any agentic CLI. No direction concerns.

On approach: the scope is proportional to the feature — a persistent boolean setting, three subcommands (collapse-on-resume, expand-on-resume, expand-now), a rendering filter in MainContent, and the useResumeCommand/useBranchCommand propagation. The useResumeCommand refactoring (core-before-UI swap with rollback) is substantial but well-justified: it fixes a latent split-brain bug where a failure between UI swap and core swap strands the user on the wrong session. The revert-proof test in MainContent.test.tsx is a nice touch — the suppression filter is genuinely pinned.

One minor note: the .gitignore addition (.qwen/computer-use/installed.json) is unrelated to this feature. Not a blocker at one line, but worth splitting out in future PRs to keep git blame clean.

Moving on to code review and real-scenario testing. 🔍

中文说明

感谢贡献!

模板完整 ✓

方向:这是一个很实用的体验改进。恢复 50+ 条消息的长会话时终端大量滚动确实影响使用。功能 opt-in 且默认安全(collapseOnResume = false),仅抑制渲染不丢失 /rewind 所需的规范历史。Claude Code 的 CHANGELOG 中没有直接对应的功能,但会话历史 UX 对任何 agentic CLI 都在范围内。方向没有问题。

方案:范围与功能匹配 —— 一个持久化布尔设置、三个子命令、MainContent 中的渲染过滤、以及 useResumeCommand/useBranchCommand 的传播。useResumeCommand 的重构(core-before-UI swap 加 rollback)改动较大但合理:它修复了一个潜在 bug(UI swap 后 core swap 前的失败会导致用户停留在错误会话上)。MainContent.test.tsx 中的 revert-proof 测试很好 —— 抑制过滤确实被锁定了。

一个小提醒:.gitignore 的添加(.qwen/computer-use/installed.json)与本功能无关。一行代码不构成阻塞项,但建议未来拆分成单独 PR 以保持 git blame 清晰。

进入代码审查和真实场景测试。🔍

Qwen Code · qwen3.7-max

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Code Review

I read the PR title and "Why it's needed" before looking at the diff. My independent approach would have been: add a boolean setting, mark resumed history items with a suppressOnRestore display flag, filter them in MainContent rendering, and add a single /history command to toggle. The PR's implementation matches this closely and exceeds it — the three subcommands for distinct actions (persist collapse / persist expand / expand current session), the useResumeCommand safety refactoring, and the /rewind compatibility via expandCollapsedHistory are all thoughtful additions.

No critical blockers found. The code is clean, well-tested, and follows project conventions:

  • The display.suppressOnRestore flag is correctly separated from canonical history semantics — /rewind turn mapping is preserved.
  • The expandCollapsedHistory / stripSuppressOnRestore helpers are minimal and shared between /history expand-now and the rewind path.
  • The historyRef pattern in useSlashCommandProcessor (ref + useLayoutEffect + getter on commandContext.ui.history) avoids unnecessary re-renders from history appends — good performance awareness.
  • i18n translations cover all 9 locales with proper same-as-English optimization.
  • The UIActionsContext type fix (handleResumePromise<void>) correctly propagates async through the slash command processor's await.

Real-Scenario Testing

Built PR branch (feat/quiet-restore) in an isolated worktree, ran npm run build && npm run bundle. Created a test session with 3 messages, then set collapseOnResume: true in settings and resumed.

Before (installed build, v0.18.3 — no collapse feature)

The installed build doesn't have this feature, so resuming always shows all history messages. Baseline: full transcript scroll on resume.

After (this PR — collapseOnResume = true)

Set {"ui":{"history":{"collapseOnResume":true}}} in settings, then resumed with --continue:

  Tips: Add a QWEN.md file to give Qwen Code persistent project context.
  ● History collapsed: 6 messages hidden. Use /history expand-now to show.

────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
>   Type your message or @path/to/file
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
  ? for shortcuts

History is collapsed — no prior messages rendered, only the summary line. ✅

Then ran /history expand-now:

  Tips: You can resume a previous conversation by running qwen --continue or qwen --resume.
  ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
  > Say exactly: Hello from PR test. Then stop.
  ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀

  ✦ Hello from PR test.
  ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
  > Say exactly: Second message. Then stop.
  ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀

  ✦ Second message.
  ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
  > Say exactly: Third message after collapse. Then stop.
  ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀

  ✦ Third message after collapse.

All 3 messages expanded, stale summary line cleared. ✅

Unit Tests

Ran all 6 changed test suites from the PR branch (worktree at feat/quiet-restore):

Suite Tests Result
resumeHistoryUtils.test.ts 18 ✅ pass
historyMapping.test.ts 23 ✅ pass
historyCommand.test.ts 5 ✅ pass
useBranchCommand.test.ts 17 ✅ pass
useResumeCommand.test.ts 11 ✅ pass
slashCommandProcessor.test.ts 58 ✅ pass
Total 132 ✅ all pass
中文说明

代码审查

先看标题和"为什么需要",再看 diff。我的独立方案是:添加布尔设置、在恢复的历史条目标记 suppressOnRestore、在 MainContent 渲染中过滤、添加一个 /history 命令切换。PR 的实现与此一致且有超越 —— 三个子命令分别对应不同操作(持久化折叠 / 持久化展开 / 展开当前会话)、useResumeCommand 安全性重构、以及通过 expandCollapsedHistory 保持 /rewind 兼容性。

未发现关键阻塞项。代码整洁、测试充分、遵循项目规范。

真实场景测试

在隔离 worktree 中构建 PR 分支,创建包含 3 条消息的测试会话,设置 collapseOnResume: true 后恢复。

  • 恢复时历史被折叠,仅显示摘要行 ✅
  • /history expand-now 后所有消息展开,旧摘要行被清除 ✅
  • 6 个改动测试套件共 132 个测试全部通过 ✅

Qwen Code · qwen3.7-max

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Stepping back — this PR has been through an unusually long review cycle (May 18 → June 18, ~15 rounds of feedback from @qqqys and @wenshao), and it shows in a good way. The design evolved from a --quiet-restore flag to three distinct /history subcommands with a persistent setting, which is a cleaner shape. The useResumeCommand refactoring that emerged from the review thread — core-before-UI swap with rollback — fixed a real latent bug and is now matched by useBranchCommand for consistency.

The real-scenario test confirms the feature works end-to-end: collapse on resume hides history with a summary line, expand-now restores it cleanly. The 132 unit tests across 6 changed suites pin the behavior, including a revert-proof on the MainContent suppression filter. The useResumeCommand rollback test (11 tests) catches the split-brain scenario that the refactoring addresses.

The only thing I'd flag as a "nice to have" is splitting out the .gitignore change into its own commit — it's one line and trivial, but keeping feature PRs focused makes git bisect easier down the line. Not a blocker.

This solves a real problem users hit every day (long-session resume scroll), the implementation is clean and well-tested, and it's been thoroughly reviewed by two collaborators. Ready to ship.

中文说明

退一步看整体 —— 这个 PR 经历了不寻常的长审查周期(5月18日到6月18日,约15轮来自 @qqqys@wenshao 的反馈),而且效果很好。设计从 --quiet-restore flag 演进为三个独立的 /history 子命令加持久化设置,这是更干净的形态。审查过程中产生的 useResumeCommand 重构(core-before-UI swap 加 rollback)修复了一个真实的潜在 bug,现在 useBranchCommand 也保持了一致性。

真实场景测试确认功能端到端可用:恢复时折叠历史并显示摘要行,expand-now 干净地恢复显示。6 个改动套件共 132 个单测锁定了行为,包括 MainContent 抑制过滤的 revert-proof。useResumeCommand 的 rollback 测试(11 个)捕获了重构所解决的 split-brain 场景。

唯一可以提的建议是把 .gitignore 的改动拆成单独的 commit —— 虽然只有一行,但保持功能 PR 的专注有助于 git bisect。不构成阻塞项。

这解决了用户每天遇到的真实问题(长会话恢复时的滚动),实现干净且测试充分,两位协作者已经深入审查。可以合入。

Qwen Code · qwen3.7-max

@qwen-code-ci-bot qwen-code-ci-bot left a comment

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.

LGTM, looks ready to ship. ✅

@qqqys qqqys left a comment

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.

LGTM. CI is green and prior concerns have been addressed.

@wenshao wenshao enabled auto-merge (squash) June 18, 2026 18:41
@wenshao wenshao merged commit 0ba245e into QwenLM:main Jun 19, 2026
14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

type/feature-request New feature or enhancement request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants