Skip to content

fix(cli): align OpenTUI output-style behavior - #10806

Merged
qqqys merged 1 commit into
QwenLM:mainfrom
qqqys:fix/issue-10767-opentui-output-style
Sep 2, 2026
Merged

qqqys merged 1 commit into
QwenLM:mainfrom
qqqys:fix/issue-10767-opentui-output-style

Conversation

@qqqys

@qqqys qqqys commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator

What this PR does

This aligns the OpenTUI /output-style experience with Ink by keeping the configured style selected even when a system-prompt override temporarily makes it ineffective. It also hides bare picker invocations from the transcript while keeping argument-bearing invocations visible, and adds direct component coverage for selection, navigation, Enter, Esc, persistence, runtime application, and persistence failures.

Why it's needed

With --system-prompt or QWEN_SYSTEM_MD active, the picker previously derived its selection from the temporarily effective style. That made a configured style appear as default, so pressing Enter without moving could overwrite the saved selection. OpenTUI also echoed the bare picker command even though Ink suppresses it.

Reviewer Test Plan

How to verify

  1. Configure the Concise output style, start OpenTUI with either --system-prompt or QWEN_SYSTEM_MD, and open /output-style. Confirm that Concise is selected and pressing Enter preserves Concise in settings while reporting that the override makes it ineffective for the current session.
  2. Starting from default, move to Concise and press Enter. Confirm that the setting is persisted, applied to the running session, and the system instruction is refreshed. Move back to default and confirm that only this explicit selection clears the configured style.
  3. Press Esc and confirm that the dialog closes without changing settings. Make the settings write fail and confirm that the error is surfaced without changing the running style.
  4. Invoke bare /output-style and confirm that no user invocation is added to the transcript. Invoke /output-style Concise and confirm that the argument-bearing invocation remains visible.

Evidence (Before & After)

Scenario Before After
Configured Concise with a system-prompt override Picker highlighted default; pressing Enter could persist default Picker highlights Concise; pressing Enter preserves Concise and explains that the override makes it ineffective for this session
Bare /output-style Invocation appeared in the OpenTUI transcript Invocation is hidden, matching Ink; argument-bearing invocations remain visible

Native OpenTUI was exercised under Bun with an isolated settings directory: Concise remained selected and persisted while a system-prompt override was active. The focused regression suite passed 109 tests.

Tested on

OS Status
🍏 macOS
🪟 Windows ⚠️
🐧 Linux ⚠️

Environment (optional)

macOS local validation used Node.js 25.9.0 for focused tests, lint, the full build, and typecheck, plus Bun 1.3.13 for native OpenTUI interaction.

Risk & Scope

  • Main risk or tradeoff: The change only affects the OpenTUI picker's initial selection and transcript bookkeeping for /output-style; direct component and dispatcher tests pin both behaviors.
  • Not validated / out of scope: Windows and Linux interaction were not run locally. User- and project-defined style catalog integration remains owned by feat: load custom output styles from ~/.qwen/output-styles and .qwen/output-styles #10761 and should consume its precedence-resolved catalog when that work lands.
  • Breaking changes / migration notes: None.

Linked Issues

Fixes #10767.

中文说明

本 PR 做了什么

本 PR 让 OpenTUI 的 /output-style 体验与 Ink 对齐:即使系统提示词覆盖暂时让已配置样式不生效,选择器仍会保持选中该配置。它还会从转写记录中隐藏只用于打开选择器的裸命令,同时保留带参数命令,并新增直接组件测试,覆盖初始选中、导航、Enter、Esc、持久化、运行时应用和持久化失败。

为什么需要

启用 --system-promptQWEN_SYSTEM_MD 时,选择器此前根据当前临时生效的样式推导选中项。这会让已配置样式显示成 default,因此用户不移动选项直接按 Enter 时可能覆盖已保存的选择。OpenTUI 还会回显裸选择器命令,而 Ink 会隐藏它。

Reviewer 测试计划

如何验证

  1. 配置 Concise 输出样式,使用 --system-promptQWEN_SYSTEM_MD 启动 OpenTUI,然后打开 /output-style。确认 Concise 已选中,按 Enter 后设置仍为 Concise,同时提示该覆盖使它在当前会话不生效。
  2. default 开始移动到 Concise 并按 Enter。确认设置被持久化、应用到当前会话并刷新系统指令。再移回 default,确认只有明确选择 default 才会清除已配置样式。
  3. 按 Esc,确认对话框关闭且不修改设置。模拟设置写入失败,确认错误会展示且当前运行样式不会改变。
  4. 调用裸 /output-style,确认转写记录中不会新增用户调用项。调用 /output-style Concise,确认带参数的调用仍然可见。

证据(Before 与 After)

场景 Before After
已配置 Concise 且系统提示词被覆盖 选择器高亮 default;按 Enter 可能持久化 default 选择器高亮 Concise;按 Enter 保留 Concise,并解释覆盖使其在本会话不生效
/output-style 调用出现在 OpenTUI 转写记录中 调用被隐藏,与 Ink 一致;带参数调用仍然可见

在隔离设置目录下使用 Bun 执行了原生 OpenTUI 交互验证:系统提示词覆盖生效时,Concise 仍保持选中并持久化。聚焦回归测试共 109 个,全部通过。

测试平台

OS 状态
🍏 macOS
🪟 Windows ⚠️
🐧 Linux ⚠️

环境(可选)

macOS 本地验证使用 Node.js 25.9.0 运行聚焦测试、lint、完整 build 和 typecheck,并使用 Bun 1.3.13 做原生 OpenTUI 交互验证。

风险与范围

  • 主要风险或权衡:改动只影响 OpenTUI 选择器的初始选中状态以及 /output-style 的转写记录管理;直接组件测试和命令分发器测试固定了这两个行为。
  • 未验证 / 不在范围内:未在本地运行 Windows 和 Linux 交互验证。用户和项目自定义样式目录集成仍由 feat: load custom output styles from ~/.qwen/output-styles and .qwen/output-styles #10761 负责;该工作合入时应消费其已按优先级解析的样式目录。
  • 破坏性变更 / 迁移说明:无。

关联 Issue

修复 #10767

@github-actions github-actions Bot added the review/self-reported The linked issue was opened by the PR author (self-reported) label Sep 2, 2026
@qwen-code-ci-bot

qwen-code-ci-bot commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator

Qwen Triage finished — CI landed green on 7530079 and the deferred approval was posted. finalize run

Qwen Triage 已完成 —— 7530079 的 CI 全绿,延迟审批已提交。查看 finalize 运行

@qqqys

qqqys commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator Author

@qwen-code /takeover from 2

@qwen-code-dev-bot qwen-code-dev-bot added the autofix/takeover Summon the autofix loop to manage this PR (remove to release; needs triage+) label Sep 2, 2026
@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🤝 Takeover engaged: the autofix loop now manages this PR — it will address new review feedback and resolve base conflicts until the label is removed or the round cap is reached. This is a fork PR, so the first round comes from the next scheduled scan (usually within minutes). This window's round counter starts at 2 (the rounds this PR spent in review before takeover), so the Critical-only brake engages after 3 more change-producing round(s) instead of a full fresh 5. Remove the autofix/takeover label (or comment @qwen-code /takeover stop) to release.

中文说明

🤝 已接管:autofix 循环现在管理此 PR —— 将持续处理新的评审反馈与 base 冲突,直到移除标签或达到轮次上限。本 PR 来自 fork,首轮处理将由下一次定时扫描执行(通常几分钟内)。本窗口轮次计数从 2 起算(即本 PR 托管前已进行的评审轮数),因此再经过 3 个产生改动的轮次即进入 Critical-only,而非重新计满 5 轮。移除 autofix/takeover 标签(或评论 @qwen-code /takeover stop)即可释放。

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Thanks for the PR!

Template looks good ✓

Problem: observed bug with evidence. Linked issue #10767 describes the picker preselecting default while a --system-prompt / QWEN_SYSTEM_MD override is active (so pressing Enter can silently clear the saved style) and the bare-invocation echo, and the issue's triage confirmed the root cause by code inspection: resolveMainSessionOutputStyle intentionally returns no style while an override is active, while the Ink picker initializes from the configured style.

Direction: aligned — this is OpenTUI/Ink parity for an established feature area. Output styles have sustained upstream investment (the claude-code CHANGELOG carries several output-style behavior fixes), and qwen-code's Ink picker already behaves exactly the way this PR makes OpenTUI behave.

Size: not applicable — 4 files under packages/cli/src/ui/opentui/, ~4 production lines (2 changed, 2 removed) and ~326 test lines; no core paths.

Approach: scope feels right and minimal. A one-line selection-source change mirroring Ink's DialogManager, one entry in the hide-invocation set mirroring Ink's existing set, and the direct component tests issue #10767 explicitly asked for. User/project style-catalog integration is correctly left to #10761, which is still open.

Risk: no elevated risk signals.

Moving on to code review. 🔍

中文说明

感谢贡献!

模板完整 ✓

问题:已观测到的 bug,有证据。关联 issue #10767 描述了在 --system-prompt / QWEN_SYSTEM_MD 覆盖生效时选择器预选 default(按 Enter 可能悄悄清掉已保存样式)以及裸命令回显的问题;issue 分诊时已通过代码检查确认根因:覆盖生效时 resolveMainSessionOutputStyle 有意不返回样式,而 Ink 选择器是从已配置样式初始化的。

方向:对齐——这是既有功能领域的 OpenTUI/Ink 行为对齐。output style 在上游持续有投入(claude-code CHANGELOG 中有多条 output-style 行为修复),且 qwen-code 的 Ink 选择器本来就是本 PR 让 OpenTUI 达到的行为。

规模:不适用——仅 packages/cli/src/ui/opentui/ 下 4 个文件,约 4 行生产代码(2 行改动、2 行删除)、约 326 行测试;不涉及核心路径。

方案:范围恰当且最小化。一行选择源改动对齐 Ink 的 DialogManager,隐藏调用集合加一项对齐 Ink 现有集合,再加上 issue #10767 明确要求的组件级测试。用户/项目样式目录集成正确地留给仍未合入的 #10761

风险:无升级风险信号。

进入代码审查 🔍

Qwen Code · qwen3.8-max

Reviewed at 7530079f1362db2a41bc3ddb15c55feb1c79a63c · re-run with @qwen-code /triage

@qwen-code-ci-bot

qwen-code-ci-bot commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator

Code review

The production change is exactly two lines, and it matches what I would have done independently:

  1. dialogs-modes.tsx now derives the picker's initial selection from config.getOutputStyle()?.name (the configured style) instead of resolveMainSessionOutputStyle(config)?.name (the temporarily effective style). That is precisely Ink's behavior — DialogManager.tsx passes currentStyleName={config.getOutputStyle()?.name} to the Ink OutputStyleDialog. The persistence path (applyOutputStyleSelection) is untouched and already does the right thing: it writes settings first with throwOnWriteFailure, applies to the session only on success, and appends the "saved but has no effect in this session" notice when an override is active.
  2. commands-dispatch.ts adds output-style to BARE_SLASH_COMMANDS_HIDE_INVOCATION — Ink's slashCommandProcessor.ts already has this entry on main, so this closes a real parity gap. Semantics are inherited from the shared helper: bare invocation hidden, argument-bearing invocation visible.

No critical issues, no convention problems. The new dialogs-modes.test.tsx follows the same @opentui/react-mocking harness as the sibling tests (dialogs-confirm.test.tsx etc.), uses vi.hoisted correctly, and pins every acceptance criterion from the issue: selection under both override variants (--system-prompt and QWEN_SYSTEM_MD), navigation + Enter, explicit-default clearing, Esc-closes-without-effect, and the write-failure path (error surfaced, no session change). The dispatch tests cover bare-vs-args visibility.

Not verified statically: nothing material — the logic is small enough to read end to end.

Test evidence — the PR's own CI on 7530079f1362db2a41bc3ddb15c55feb1c79a63c

Fetched once via the API; not re-run here (triage never executes PR code). No failures so far. The two OpenTUI-specific gates most relevant to this PR are already green: OpenTUI no-flicker gate and TUI parity snapshots (ink vs opentui). The ubuntu unit suite and the no-AK integration run are still in progress; the macOS/Windows unit jobs and the AK-bearing integration job are skipped, which is normal for fork PRs. The finalize workflow updates the table below once CI settles.

Final CI results for 7530079 (auto-updated by the triage finalize job after CI completed):

Check Conclusion
Classify PR ✅ success
Dependency CVE audit ✅ success
Desktop Shell (ubuntu-22.04) ✅ success
Desktop Shell (windows-2022) ✅ success
Integration Tests (no-AK, No Sandbox) ✅ success
OpenTUI no-flicker gate ✅ success
route ✅ success
Secret scan (TruffleHog) ✅ success
Test (ubuntu-latest, Node 22.x) ✅ success
TUI parity snapshots (ink vs opentui) ✅ success
web-shell E2E Smoke (ubuntu-latest, Node 22.x) ✅ success

One row per check name (latest run); skipped checks omitted; failures sort first. / 每个检查名一行(取最新一次运行),省略 skipped,失败项排在最前。

Sandboxed verification would settle this: @qwen-code /verify (sponsored run — the author lacks write access, so a maintainer triggers it; it carries a pre-execution risk screen and a full workspace wipe) — the new component tests mock the OpenTUI runtime, so they pin the selection and transcript logic but not live terminal behavior; the author's native-OpenTUI exercise under Bun is their claim (macOS only), not independently re-run here. Read the resulting report with the same skepticism as the fork's own CI logs.

中文说明

代码审查

生产代码改动只有两行,与我独立想到的方案一致:

  1. dialogs-modes.tsx 的选择器初始选中项改为读取 config.getOutputStyle()?.name(已配置样式),不再用 resolveMainSessionOutputStyle(config)?.name(临时生效样式)。这与 Ink 完全一致——DialogManager.tsx 传给 Ink OutputStyleDialog 的就是 config.getOutputStyle()?.name。持久化路径(applyOutputStyleSelection)未改动且本就正确:先以 throwOnWriteFailure 写设置,成功后才应用到会话,覆盖生效时还会追加"已保存但本会话不生效"的提示。
  2. commands-dispatch.tsoutput-style 加入 BARE_SLASH_COMMANDS_HIDE_INVOCATION——main 上 Ink 的 slashCommandProcessor.ts 已有此项,这里补上的是真实的行为差距。语义沿用共享逻辑:裸命令隐藏、带参数命令可见。

无关键问题,无规范问题。新增的 dialogs-modes.test.tsx 沿用同目录测试(如 dialogs-confirm.test.tsx)的 @opentui/react mock 骨架,正确使用 vi.hoisted,并逐条固定 issue 的验收标准:两种覆盖(--system-promptQWEN_SYSTEM_MD)下的选中、导航 + Enter、显式选 default 才清除、Esc 关闭不生效、写入失败路径(报错且不改变会话)。命令分发测试覆盖裸命令与带参数命令的可见性。

静态审查没有遗漏实质性内容——逻辑足够小,可以完整读完。

测试证据——本 PR 在 7530079f1362db2a41bc3ddb15c55feb1c79a63c 上的 CI

通过 API 一次性读取;此处不重跑(分诊从不执行 PR 代码)。目前无失败。与本 PR 最相关的两个 OpenTUI 门禁已绿:OpenTUI no-flicker gateTUI parity snapshots (ink vs opentui)。ubuntu 单测与 no-AK 集成测试仍在运行;macOS/Windows 单测与带 AK 的集成任务被跳过,这对 fork PR 属正常现象。CI 结束后由 finalize 工作流更新下表。

沙箱验证可以一锤定音:@qwen-code /verify(赞助运行——作者无写权限,需维护者触发;运行前有前置风险筛查与完整工作区清理)——新组件测试 mock 了 OpenTUI 运行时,固定的是选中与转写逻辑而非真实终端行为;作者在 Bun 下的原生 OpenTUI 验证是其自述(仅 macOS),未在此独立复跑。请对验证报告保持与 fork CI 日志同样的审慎。

Qwen Code · qwen3.8-max

Reviewed at 7530079f1362db2a41bc3ddb15c55feb1c79a63c · re-run with @qwen-code /triage

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Confidence: 5/5 — clean across every stage: observed bug, minimal parity fix, tests that pin the issue's acceptance criteria.

My independent proposal before reading the diff was exactly what this PR does — source the initial selection from the configured style (as Ink does), add the missing hide-invocation entry (as Ink has), and add the direct component tests the issue asked for. The PR matches that proposal with nothing extra: no drive-by changes, no scope creep, and the piece that genuinely belongs elsewhere (style catalog integration, issue criterion 5) is explicitly deferred to #10761, which is still open.

The bug is real and well-evidenced: under a system-prompt override the picker highlighted default, so a bare Enter could silently overwrite the user's saved style. The fix is the smallest one available, the "saved but has no effect in this session" messaging already existed in the shared apply path and is now reachable with the right selection, and the write-failure path leaves the running style untouched. Both OpenTUI-specific CI gates are green; the unit suite and no-AK integration run were still in flight at review time, so approval is deferred until CI lands green on the reviewed commit.

中文说明

置信度:5/5 —— 各阶段都干净:已观测到的 bug、最小的对齐修复、逐条固定 issue 验收标准的测试。

我在看 diff 之前独立想到的方案与本 PR 完全一致——初始选中项读取已配置样式(与 Ink 一致)、补上缺失的隐藏调用项(与 Ink 一致)、补充 issue 要求的组件级测试。PR 与这个方案一致且没有任何多余内容:没有顺手改动、没有范围扩张,真正属于别处的部分(样式目录集成,即 issue 验收标准第 5 条)明确留给仍未合入的 #10761

bug 真实且证据充分:系统提示词覆盖生效时选择器高亮 default,直接按 Enter 会悄悄覆盖用户已保存的样式。修复是可行的最小改动;"已保存但本会话不生效"的提示本就存在于共享应用路径中,现在能在正确的选中项下触达;写入失败路径不会改变当前运行样式。两个 OpenTUI 专属 CI 门禁已绿;审查时单测与 no-AK 集成测试仍在运行,因此等 CI 在受审提交上转绿后再批准。

Qwen Code · qwen3.8-max

Reviewed at 7530079f1362db2a41bc3ddb15c55feb1c79a63c · re-run with @qwen-code /triage

@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 — CI landed green after the review. ✅

@qqqys
qqqys added this pull request to the merge queue Sep 2, 2026
Merged via the queue into QwenLM:main with commit 08db8d5 Sep 2, 2026
76 of 77 checks passed
@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Released in v0.23.0.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

autofix/takeover Summon the autofix loop to manage this PR (remove to release; needs triage+) review/self-reported The linked issue was opened by the PR author (self-reported)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(cli): align OpenTUI output-style picker state and transcript behavior

3 participants