Skip to content

fix(web-shell): stop stacking duplicate copies when restoring prompt text - #7134

Merged
wenshao merged 1 commit into
QwenLM:mainfrom
zjunothing:fix/7128-queued-prompt-restore
Jul 17, 2026
Merged

fix(web-shell): stop stacking duplicate copies when restoring prompt text#7134
wenshao merged 1 commit into
QwenLM:mainfrom
zjunothing:fix/7128-queued-prompt-restore

Conversation

@zjunothing

Copy link
Copy Markdown
Collaborator

What this PR does

Makes the composer's prompt-restoration merge idempotent, so a prompt whose text is already sitting at the top of the editor is not stacked again. Restoration paths (failed queue submits, failed mid-turn inserts, queue clears racing an abort) prepend a failed prompt's text above the current draft; several of them can fire for the same prompt across reconnects and page refreshes, and a user retrying an identical message restores identical text — each pass previously prepended another copy. The merge logic is extracted into an exported pure function, mergeRestoredPromptText(current, text), with the duplicate check documented in place; restoring different text still prepends above the draft exactly as before.

Why it's needed

#7128 reports that after two failed sends of the same message, a page refresh shows the two texts concatenated into one string in the input box. The triage analysis identified three defects; this PR fixes the text-stacking one (bug 3): restoreTextToEditor unconditionally computed `${text}\n${current}`, so duplicate restore passes and identical retried messages accumulated copies. The suggested fix directions were "replace instead of prepend, track restored prompts, or clear before batch restoration" — this PR implements the minimal-behavior-change variant: keep the prepend (a user's in-progress draft must not be destroyed), but make an already-restored text a no-op. The SSE-reconnect-on-prompt question (bug 1) is a behavioral default best decided by maintainers and is intentionally out of scope.

Reviewer Test Plan

How to verify

  1. Trigger any restoration path twice for the same prompt text (e.g. queue a message during streaming, make its submission fail, reconnect so a second restore fires — or retry an identical message after a failed send): the composer holds one copy, not a growing concatenation.
  2. With a different draft in the composer, trigger a restore: the failed prompt's text still lands above the draft, separated by a newline.
  3. Normal send flows are untouched: submit succeeds → editor clears, as before.

Automated coverage: 6 new unit tests for mergeRestoredPromptText — empty editor, prepend-above-draft, exact-duplicate no-op, duplicate-at-top no-op, idempotence across repeated restores of the reported CJK message, and a same-prefix-different-line case that must NOT be deduplicated. npm run typecheck, the web-shell package build, eslint and prettier are clean.

Evidence (Before & After)

Sequence before fix after fix
restore msg into empty editor msg msg
second restore of the same msg msg\nmsg msg
third restore (reconnect/refresh path re-fires) msg\nmsg\nmsg msg
restore msg above a draft draft msg\ndraft msg\ndraft (unchanged)

Regression safety was additionally checked in a real browser: a Playwright run against a live qwen serve daemon exercised the normal send path (submit accepted, editor cleared) and a network-refused direct submit — both behave identically before and after this change. The exact multi-restore browser reproduction depends on a mid-stream reconnect timing that is impractical to script deterministically, which is why the contract is pinned at the unit level.

Tested on

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

Environment (optional)

macOS (Darwin 24.6), Node v22.23.1; vitest for the unit contract, Playwright Chromium against a live daemon for send-path regression.

Risk & Scope

  • Main risk or tradeoff: if a user legitimately wants two literal copies of the same failed message restored, they now get one — the information content is identical, and the stacking behavior was the reported defect.
  • Not validated / out of scope: the SSE restartEventStreamOnPrompt default (triage bug 1) and the editor-clearing semantics on failed sends in App.tsx/ChatPane.tsx (triage bug 2) — both are behavioral decisions that deserve their own review.
  • Breaking changes / migration notes: none; mergeRestoredPromptText is a new export used internally.

Linked Issues

Fixes #7128

中文说明

本 PR 做了什么

让 composer 的 prompt 恢复合并逻辑幂等:若某条 prompt 的文本已位于编辑器顶部,不再重复堆叠。恢复路径(排队提交失败、mid-turn 插入失败、清空队列与 abort 竞争)会把失败 prompt 的文本前置到当前草稿之上;同一条 prompt 在重连/刷新过程中可能触发多条恢复路径,用户重试相同消息也会恢复相同文本——此前每次都会再前置一份副本。合并逻辑抽取为导出的纯函数 mergeRestoredPromptText(current, text) 并附注释;恢复不同文本时仍照旧前置到草稿之上。

为什么需要

#7128 报告:同一消息两次发送失败后,刷新页面看到两段文本拼接成一条出现在输入框。triage 分析识别出三个缺陷;本 PR 修复其中的文本堆叠缺陷(bug 3):restoreTextToEditor 无条件计算 ${text}\n${current},重复的恢复触发与重试的相同消息不断累积副本。triage 给出的修复方向是「改覆盖、跟踪已恢复、或批量恢复前清空」——本 PR 实现行为变更最小的变体:保留前置(不能破坏用户正在输入的草稿),但对已恢复的文本作 no-op。SSE 重连默认值问题(bug 1)属于应由 maintainer 决定的行为默认值,刻意不在本 PR 范围内。

审阅测试计划

如何验证

  1. 让同一条 prompt 文本触发两次恢复(如 streaming 中排队、提交失败、重连再次触发恢复;或失败后重试相同消息):composer 中只保留一份,而非不断拼接;
  2. composer 中已有其他草稿时触发恢复:失败 prompt 的文本仍以换行分隔置于草稿上方;
  3. 正常发送流程不受影响:提交成功 → 编辑器清空,与之前一致。

自动化覆盖:mergeRestoredPromptText 的 6 个新单测——空编辑器、草稿上方前置、完全相同 no-op、顶部重复 no-op、对 issue 中中文消息的重复恢复幂等、以及不应被去重的同前缀不同首行用例。typecheck / web-shell 包构建 / eslint / prettier 全部通过。

证据(Before & After)

见上方表格:重复恢复同一消息由 msg\nmsg\nmsg(❌)变为始终一份(✅);向草稿上方恢复不同文本的行为不变。另在真实浏览器中做了回归检查:Playwright 驱动真实 qwen serve daemon 验证正常发送路径(提交成功、编辑器清空)与网络拒绝的直接提交在改动前后行为一致。精确的多次恢复浏览器复现依赖流中重连时序、难以确定性脚本化,故契约以单测层固定。

测试平台

macOS 已本地验证(✅);Windows / Linux 依赖 CI(⚠️)。

环境

macOS(Darwin 24.6)、Node v22.23.1;vitest 单测 + Playwright Chromium 对真实 daemon 的发送路径回归。

风险与范围

  • 主要风险/权衡:若用户确实希望恢复同一失败消息的两份字面副本,现在只得到一份——信息量相同,且堆叠正是被报告的缺陷。
  • 未验证/超出范围:SSE restartEventStreamOnPrompt 默认值(triage bug 1)与 App.tsx/ChatPane.tsx 发送失败时的编辑器清空语义(triage bug 2)——两者都是值得单独评审的行为决策。
  • 破坏性变更/迁移说明:无;mergeRestoredPromptText 为新导出、仅内部使用。

关联 Issue

Fixes #7128

🤖 Generated with Claude Code

…text

Several restoration paths return a failed prompt's text to the composer
by prepending it above the current draft: failed queue submits, failed
mid-turn inserts, and queue clears. More than one of them can fire for
the same prompt across reconnects and refreshes, and a user retrying an
identical message produces identical text — each pass stacked another
copy, which surfaced as multiple sent messages concatenated back into
the input box after a page refresh (QwenLM#7128).

Extract the merge into mergeRestoredPromptText() and make it
idempotent: restoring text that is already at the top of the editor is
a no-op. Restoring different text still prepends above the draft.

This addresses the text-stacking defect (bug 3 in the triage analysis).
The SSE-reconnect-on-prompt question (bug 1) is a behavioral decision
left to maintainers.

Fixes QwenLM#7128

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@zjunothing

Copy link
Copy Markdown
Collaborator Author

Verification Report

Base: ec0b7a6e7.

1. Unit contract — the exact reported sequence

mergeRestoredPromptText (extracted from restoreTextToEditor) pinned by 6 new tests, including the issue's own message replayed through three restore passes:

Sequence before fix after fix
restore 用python写一个hello world ×1 that text that text
restore ×2 (duplicate path / identical retry) text\ntext ❌ one copy ✅
restore ×3 (reconnect re-fires) three copies ❌ one copy ✅
restore different text above a draft prepends (by design) prepends (unchanged)
same-prefix-different-first-line prepends prepends (NOT deduplicated — guarded by test)
npx vitest run client/hooks/useQueuedPrompts.test.ts   (packages/web-shell)
Tests  6 passed (6)

2. Browser regression — real daemon + Playwright Chromium

Against a live qwen serve daemon: the normal send path (submit accepted → editor clears) and a network-refused direct submit both behave identically before and after the change — the fix only affects the duplicate-restore case. The full multi-restore reproduction requires a mid-stream reconnect timing that is impractical to script deterministically; that is why the contract is pinned at the unit level and the fix is a pure-function semantics change.

3. Static checks

npm run typecheck passes across all workspaces; web-shell package build (vite + lib tsc) passes; eslint / prettier clean.

Scope notes

  • This addresses bug 3 from the triage analysis (text stacking). Bug 1 (restartEventStreamOnPrompt default) and bug 2 (editor-clearing semantics on failed sends) are behavioral decisions intentionally left for maintainers — happy to follow up on either if there's a preferred direction.
中文版本

验证报告

基线:ec0b7a6e7

1. 单元契约 — 精确复刻 issue 中的序列

mergeRestoredPromptText(自 restoreTextToEditor 抽出)由 6 个新单测固定,包含用 issue 原文消息重放三次恢复:修复前 1/2/3 次恢复分别得到 1/2/3 份拼接副本(❌),修复后始终一份(✅);向草稿上方恢复不同文本的行为不变;同前缀不同首行的用例不会被误去重(有测试守护)。6/6 通过。

2. 浏览器回归 — 真实 daemon + Playwright Chromium

对真实 qwen serve daemon:正常发送路径(提交成功 → 编辑器清空)与网络拒绝的直接提交在改动前后行为一致——修复只影响重复恢复场景。完整的多次恢复复现依赖流中重连时序、难以确定性脚本化,因此契约固定在单测层,且修复本身是纯函数语义变更。

3. 静态检查

typecheck / web-shell 包构建 / eslint / prettier 全部通过。

范围说明

本 PR 解决 triage 分析中的 bug 3(文本堆叠)。bug 1(SSE 重连默认值)与 bug 2(发送失败的编辑器清空语义)是刻意留给 maintainer 的行为决策——如有倾向方向乐意跟进。

🤖 Generated with Claude Code

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Thanks for the PR!

Template looks good ✓

Problem: observed bug with a clear 100%-reproducible scenario in #7128 — sending the same message twice, refreshing the page, and seeing the two copies concatenated in the input box. This is a real defect, not theoretical hardening.

Direction: aligned. The fix targets exactly the stacking defect (triage bug 3 from the issue analysis) while leaving the SSE-reconnect default and editor-clear semantics for separate discussion. Scoped to one pure function and one call site — minimal blast radius.

Size: not applicable — changes are in packages/web-shell/, not core infrastructure. 15 production lines (pure function + call site), 44 test lines.

Approach: the scope feels right. Extracting mergeRestoredPromptText as an exported pure function makes the idempotency contract testable and the call site trivially readable. The if (next !== current) setText(next) guard avoids unnecessary editor re-renders. No scope creep, no unrelated changes.

Moving on to code review. 🔍

中文说明

感谢贡献!

模板完整 ✓

问题:已观测的 bug,#7128 中有 100% 可复现的场景——同一消息发送两次后刷新页面,输入框中出现拼接文本。这是真实缺陷,不是理论性加固。

方向:对齐。修复精确针对堆叠缺陷(issue 分析中的 triage bug 3),将 SSE 重连默认值和编辑器清空语义留给后续单独讨论。范围控制在一个纯函数和一个调用点——最小影响半径。

规模:不适用——变更在 packages/web-shell/,非核心基础设施。15 行生产代码(纯函数 + 调用点),44 行测试。

方案:范围合理。将 mergeRestoredPromptText 抽取为导出的纯函数,使幂等契约可测试、调用点易读。if (next !== current) setText(next) 守卫避免不必要的编辑器重渲染。无范围蔓延,无无关改动。

进入代码审查 🔍

Qwen Code · qwen3.7-max

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

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

🖼️ web-shell visual preview

Rendered against a mock daemon (no real backend): the PR base vs this PR head ea83788. Only screenshots that changed are shown (flows below, if any, are head-only) — refreshes on every push.

Screenshots · before / after

workspace-sidebar-dark before/after

Full-resolution recordings (.webm) are attached to the workflow run.

Qwen Code · web-shell visuals

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Code Review

Clean implementation. The pure function mergeRestoredPromptText is straightforward and the three-branch logic (empty → replace, already-present → no-op, otherwise → prepend) is correct and easy to reason about. The call-site change replaces the inline ternary with a function call and adds a next !== current guard to skip redundant setText calls — nice touch to avoid unnecessary editor re-renders.

Reuse check: no existing shared function covers this merge-dedup logic; the extraction is justified.

No correctness issues, no AGENTS.md violations.

Testing

✓ hooks/useQueuedPrompts.test.ts (6 tests) 3ms

 Test Files  1 passed (1)
      Tests  6 passed (6)

All 6 unit tests pass. The test suite covers:

ESLint: clean. TypeScript typecheck (web-shell package): clean.

Real-scenario testing (tmux): not applicable — this is a browser-side React hook in packages/web-shell, not CLI behavior. The idempotency contract is pinned at the unit level; the multi-restore browser reproduction depends on SSE reconnect timing that is impractical to script deterministically, as the PR description notes.

中文说明

代码审查

实现干净。纯函数 mergeRestoredPromptText 直截了当,三分支逻辑(空 → 替换,已存在 → no-op,否则 → 前置)正确且易于推理。调用点变更用函数调用替换了内联三元表达式,并添加 next !== current 守卫跳过多余的 setText 调用——避免不必要的编辑器重渲染,很好的细节。

复用检查: 没有已有的共享函数覆盖此合并去重逻辑;抽取合理。

无正确性问题,无 AGENTS.md 违规。

测试

6 个单测全部通过。测试覆盖:空编辑器填充、不同草稿前置、完全相同 no-op、顶部重复 no-op、CJK 消息重复恢复幂等(#7128 的精确场景)、同前缀不同首行正确不去重。

ESLint:通过。TypeScript typecheck(web-shell 包):通过。

真实场景测试(tmux): 不适用——这是 packages/web-shell 中的浏览器端 React hook,非 CLI 行为。幂等契约在单测层固定;多次恢复的浏览器复现依赖 SSE 重连时序,难以确定性脚本化。

Qwen Code · qwen3.7-max

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

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Confidence: 5/5

This is a clean, minimal fix for a real bug. The mergeRestoredPromptText function is a well-designed pure function with three clear branches, the call site is trivially readable, and the 6 unit tests pin the exact contract needed — including the CJK idempotence scenario from the original report. Lint, typecheck, and tests all pass.

No concerns to escalate. The change is scoped to packages/web-shell, touches no core infrastructure, auth, sandbox, or public contract. The fork-refactor guardrail doesn't apply (this is a fix, not a refactor).

Verdict: Approve

中文说明

置信度:5/5

这是一个干净、最小化的真实 bug 修复。mergeRestoredPromptText 是一个设计良好的纯函数,三分支逻辑清晰,调用点易读,6 个单测精确固定了所需契约——包括原始报告中的 CJK 幂等场景。Lint、typecheck 和测试全部通过。

无需上报的顾虑。变更范围限于 packages/web-shell,不涉及核心基础设施、认证、沙箱或公共契约。fork-refactor 守卫不适用(这是 fix 而非 refactor)。

结论:通过

Qwen Code · qwen3.7-max

Reviewed at ea83788d8fbde55613b63d8bab9e5678da83fac2 · 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. ✅

@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.

Reviewed — no blockers. Suggestions are inline.

— qwen3.7-max via Qwen Code /review

Comment on lines 308 to 310
if (images && images.length > 0) {
editorRef.current?.restoreImages(images);
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] restoreImages is outside the if (next !== current) dedup guard — images are unconditionally appended on every restore call via setPastedImages((prev) => [...prev, ...images]), while text dedup correctly skips setText. — Failure scenario: a prompt with attached images fails to submit, restore fires, then a reconnect/retry triggers a second restore of the same prompt → text stays correct (dedup works) but images silently double, producing an inconsistent editor state.

Suggested change
if (images && images.length > 0) {
editorRef.current?.restoreImages(images);
}
if (next !== current) {
editorRef.current?.setText(next);
if (images && images.length > 0) {
editorRef.current?.restoreImages(images);
}
}

— qwen3.7-max via Qwen Code /review

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Good catch — fixed in follow-up PR #7169: restoreImages now runs only alongside an actual text change, so a deduplicated restore no longer doubles the attachments.

@wenshao

wenshao commented Jul 17, 2026

Copy link
Copy Markdown
Collaborator

✅ Local verification report — LGTM, recommend merge

Verified at head ea83788d8 on an isolated worktree. macOS (Darwin 24.6), Node v22.23.1. I built the real hook path and drove the actual production code — not just the extracted helper — through the exact scenario from #7128.

Summary: the fix is correct, minimal, and targeted. Repeated restores of the same prompt text collapse to one copy; restoring different text still prepends above the user's draft, byte-for-byte unchanged.


1. Real production-path controlled experiment (the decisive one)

I wrote a hook-level test that renders the actual useQueuedPrompts hook (jsdom + createRoot/act, daemon SDK stubbed) and drives the reported path end-to-end: enqueuePrompt("用python写一个hello world") → the mocked submitPrompt rejects → the hook's real failure handler runs restoreTextToEditor, writing into a live stateful editor. Fired twice for the identical message, exactly like a user retrying a failed send. The only thing that differs between the two runs below is the one merge line this PR changes.

This proves the bug reproduces on the real production path and that the new guard is load-bearing.

controlled experiment

2. Deterministic Before/After matrix

Real exported mergeRestoredPromptText (NEW) vs the exact unconditional-prepend line it replaced (OLD), over the issue scenario. Duplicate restores go msgmsg\nmsgmsg\nmsg\nmsg under OLD and collapse to a single copy under NEW; prepend-above-a-different-draft and same-prefix-but-different-first-line are byte-identical before and after (no behavior change, no over-eager dedup).

before/after matrix

3. Automated coverage & regression

Check Result
PR's own unit tests (useQueuedPrompts.test.ts) 6/6 pass
My real-hook E2E (useQueuedPrompts.e2e.verify.test.tsx) 2/2 pass on NEW; the one-copy assertion fails on OLD with the reported concatenation
Full web-shell vitest suite 108 files / 1716 tests pass *
eslint + prettier on changed files clean
typecheck (tsc --noEmit) 0 new errors introduced **

* The only initial red was build-artifact.test.ts, which reads the built dist/index.js; after vite build + vite build --config vite.lib.config.ts all 8 of its tests pass. It's a build-artifact prerequisite, unrelated to this hook.

** tsc reports 68 pre-existing errors in this symlinked worktree (SDK/webui built dist drifting from the PR base). Controlled check: reverting the PR diff yields the identical 68, and none are in useQueuedPrompts.ts. The change's own signature ((string, string) => string) is trivially sound.

Notes for merge

  • Out-of-scope items are correctly deferred: the SSE restartEventStreamOnPrompt default (triage bug 1) and the failed-send editor-clearing semantics in App.tsx/ChatPane.tsx (triage bug 2). This PR cleanly fixes only the text-stacking defect (bug 3).
  • Minor, non-blocking: dedup keys on an exact top-of-editor match (current === text || current.startsWith(text + "\n")). A draft equal to the message plus trailing whitespace (e.g. "hello ") wouldn't dedupe — but every restoration path writes the trimmed text exactly, so idempotence holds in practice; only a manually-typed whitespace variant escapes it, which is acceptable.

Verdict: ✅ ready to merge.

中文说明(点击展开)

✅ 本地验证报告 — LGTM,建议合并

在隔离 worktree 上于 head ea83788d8 验证。macOS(Darwin 24.6)、Node v22.23.1。我构建了真实的 hook 路径,驱动真实的生产代码(而非仅测试抽取出的纯函数)复现 #7128 的确切场景。

结论: 修复正确、最小化、精准。同一条 prompt 文本重复恢复会收敛为一份;恢复不同文本时仍照旧前置到用户草稿之上,逐字节不变。

1. 真实生产路径的对照实验(最关键)

我写了一个 hook 级测试,渲染真实的 useQueuedPrompts hook(jsdom + createRoot/act,daemon SDK 打桩),端到端驱动上报路径:enqueuePrompt("用python写一个hello world") → 被 mock 的 submitPrompt reject → hook 真实的失败处理调用 restoreTextToEditor,写入一个有状态的实时编辑器。对同一条消息触发两次,正如用户重试失败的发送。下方两次运行之间唯一的差异就是本 PR 改动的那行合并逻辑。

这证明该缺陷在真实生产路径上确实复现,且新 guard 是关键性修复。(截图见上方英文部分)

2. 确定性的 Before/After 矩阵

真实导出的 mergeRestoredPromptText(NEW)对比它替换掉的那行无条件前置(OLD),覆盖 issue 场景。重复恢复在 OLD 下为 msgmsg\nmsgmsg\nmsg\nmsg,在 NEW 下收敛为单份;「向不同草稿上方前置」与「同前缀但首行不同」在改动前后逐字节一致(无行为变更、无过度去重)。(截图见上方英文部分)

3. 自动化覆盖与回归

检查项 结果
PR 自带单测(useQueuedPrompts.test.ts 6/6 通过
我的真实 hook E2E(useQueuedPrompts.e2e.verify.test.tsx NEW 下 2/2 通过;OLD 下「只保留一份」断言失败并出现上报的拼接
web-shell 完整 vitest 套件 108 文件 / 1716 测试通过 *
改动文件 eslint + prettier 干净
类型检查(tsc --noEmit 未引入任何新错误 **

* 唯一初始报红的是 build-artifact.test.ts,它读取构建产物 dist/index.js;执行 vite build + vite build --config vite.lib.config.ts 后其 8 个测试全部通过。这是构建产物前置条件,与本 hook 无关。

** 在此符号链接 worktree 中 tsc 报告 68 个既有错误(SDK/webui 的构建 dist 与 PR base 漂移)。对照实验:回退 PR 改动后得到完全相同的 68 个,且没有一个useQueuedPrompts.ts 中。改动自身签名((string, string) => string)类型上显然安全。

合并备注

  • 超出范围的项已正确推迟:SSE restartEventStreamOnPrompt 默认值(triage bug 1)与 App.tsx/ChatPane.tsx 发送失败时的编辑器清空语义(triage bug 2)。本 PR 只干净地修复文本堆叠缺陷(bug 3)。
  • 次要、非阻塞:去重以「编辑器顶部完全匹配」为键(current === text || current.startsWith(text + "\n"))。若草稿恰为「消息 + 尾随空白」(如 "hello ")则不会去重——但所有恢复路径写入的都是 trim 后的确切文本,故实践中幂等成立;仅用户手动输入的空白变体会绕过,可接受。

结论:✅ 可以合并。

@wenshao
wenshao added this pull request to the merge queue Jul 17, 2026
Merged via the queue into QwenLM:main with commit 288140b Jul 17, 2026
64 checks passed
pull Bot pushed a commit to edisplay/qwen-code that referenced this pull request Jul 18, 2026
…t handler (QwenLM#7169)

Post-merge review follow-ups from QwenLM#7134 and QwenLM#7135:

- restoreImages now runs only alongside an actual text change: it
  appends to the pasted-image list, so a deduplicated restore of the
  same prompt (reconnect/retry) previously doubled the attachments
  while the text correctly stayed single.

- The Cmd+B/Ctrl+B handler no longer toggles the sidebar while a
  non-composer editable element is focused (sidebar search, session
  rename, settings inputs) — matching the codebase's isEditableTarget
  convention; the composer keeps the editor-convention behavior and
  still toggles, verified in a real browser.

- All state updates moved out of React updater functions (purity
  contract, mirroring the hamburger handler); the effect re-binds on
  the involved state so the listener closure stays fresh.

- A drawer forced open on a wide viewport now takes the drawer path:
  the shortcut closes the overlay instead of collapsing the rail
  invisibly underneath it.

Refs QwenLM#7102 QwenLM#7128

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

刷新页面后,已发送的多条消息文本被错误拼接后写回输入框

3 participants