fix(web-shell): isolate history and session drafts - #7810
Conversation
|
✅ Qwen Triage finished — CI landed green on ✅ Qwen Triage 已完成 —— |
|
Thanks for the PR! Template looks good ✓ Problem: the issues described are inherent to the current design — a single global storage key for prompt history means it leaks across workspaces by construction, and there is no draft persistence at all. These are not theoretical; any user with multiple workspaces hits them. No linked issue, but the problems are self-evident from the architecture. Direction: well-aligned. Workspace isolation for history and drafts is a natural requirement for the multi-workspace web shell. The delayed-submission source validation is a real correctness gap that this closes. Size: not applicable — no core paths touched. All changes are in Approach: the scope feels right. History isolation, draft persistence, delayed-submission safety, and stable props are each needed for the stated goal and interlock (e.g. draft persistence needs the identity-switch effect, which also resets history navigation). The stable-props work ( Moving on to code review. 🔍 中文说明感谢贡献! 模板完整 ✓ 问题:所描述的问题是当前设计的固有缺陷——全局单一存储键意味着提示词历史必然在 workspace 间泄漏,且完全没有草稿持久化。这些不是理论问题;任何使用多 workspace 的用户都会遇到。没有关联 issue,但问题从架构上就是显而易见的。 方向:对齐。按 workspace 隔离历史和草稿是多 workspace Web Shell 的自然需求。延迟提交的来源校验是一个真实的正确性缺口,本 PR 将其关闭。 规模:不适用——未触及核心路径。所有改动在 方案:范围合理。历史隔离、草稿持久化、延迟提交安全性、稳定属性各自都是目标所必需的,且相互关联(例如草稿持久化需要身份切换 effect,而该 effect 同时重置历史导航)。稳定属性的工作( 进入代码审查 🔍 — Qwen Code · qwen3.8-max-preview Reviewed at |
Code ReviewIndependent proposal: I would have done essentially what this PR does — derive a workspace-scoped storage key, reload history on key change with a legacy fallback, persist drafts keyed by session/workspace with debounced writes, and validate delayed submissions against their source context. The PR matches this closely. Findings: no critical blockers, no convention violations. The implementation is well-structured. A few things worth noting:
Tests cover the important paths: workspace isolation, legacy fallback migration, session-switch draft restore, delayed acceptance (same session, cross-session, cross-workspace, queued), unscoped draft priority, history search reset, and stable-props verification. 231 targeted tests per the author's claim; the test diff adds ~865 lines across TestingThis is an unattended CI run — no local build or tmux testing was performed. Evidence below is from the PR's own CI checks on commit Pre-checks (lint, format, build) passed. The main unit test suite and web-shell visual capture are still running. Final CI results for
One row per check name (latest run); skipped checks omitted; failures sort first. / 每个检查名一行(取最新一次运行),省略 skipped,失败项排在最前。 Not verified: live TUI behavior (CI run — no tmux). The web-shell is a browser-based UI, so a maintainer can check the PR out locally and drive it in a browser, or trigger the isolated 中文说明代码审查独立方案: 我的做法与本 PR 基本一致——按 workspace 派生存储键、在键变化时重新加载历史并回退旧全局键、按 session/workspace 持久化草稿并防抖写入、在延迟提交时校验来源上下文。PR 的实现与此高度吻合。 发现: 无关键阻塞项,无规范违反。 实现结构良好。几个值得注意的点:
测试覆盖了重要路径:workspace 隔离、旧历史回退迁移、session 切换草稿恢复、延迟接受(同 session、跨 session、跨 workspace、排队)、无范围草稿优先级、历史搜索重置、稳定属性验证。 测试本次为无人值守 CI 运行——未执行本地构建或 tmux 测试。以下证据来自 PR 自身在 commit 预检查(lint、格式化、构建)已通过。主单元测试套件和 web-shell 视觉捕获仍在运行中。 未验证:实际 TUI 行为(CI 运行——无 tmux)。Web Shell 是基于浏览器的 UI,维护者可以在本地检出 PR 并在浏览器中操作。 — Qwen Code · qwen3.8-max-preview Reviewed at |
|
Confidence: 4/5 — solid, well-tested implementation that solves real multi-workspace problems; only reservation is CI hasn't finished yet. This PR does what it sets out to do, cleanly. The history isolation is the right fix for a design that was always going to leak in multi-workspace environments — scoping the storage key by workspace cwd is the obvious path, and the legacy fallback migration is handled gracefully. The draft persistence is well-scoped (plain text only, debounced, flushed on all the right lifecycle events) and the unscoped-to-scoped transition handles the workspace-discovery race correctly. The delayed-submission validation is the part I scrutinized most. The triple check (session ID, workspace cwd, source version counter) is thorough without being over-engineered — each catches a different timing window. The The stable-props work is small but earns its place — background git-status polling and workspace-list refreshes would otherwise rerender the composer on every cycle, which is exactly the kind of thing that makes a web UI feel janky. No linked issue, but the problems are architectural rather than anecdotal — a single global storage key leaks by construction. The test suite covers the edge cases I care about: cross-session delayed acceptance, unscoped draft priority, history search reset on switch, and the stable-props invariants. Approval deferred until CI lands green on 中文说明置信度:4/5 —— 实现扎实、测试充分,解决了真实的多 workspace 问题;唯一的保留是 CI 尚未完成。 本 PR 干净地完成了其目标。历史隔离是对多 workspace 环境下必然泄漏的设计的正确修复——按 workspace cwd 限定存储键是显而易见的路径,旧历史回退迁移也处理得很优雅。草稿持久化范围恰当(仅纯文本、防抖、在所有正确的生命周期事件上刷新),无范围到有范围的过渡正确处理了 workspace 发现的竞态。 延迟提交校验是我审查最仔细的部分。三重检查(session ID、workspace cwd、来源版本计数器)全面而不过度工程化——每个捕获不同的时间窗口。 稳定属性的工作体量小但有其价值——后台 git 状态轮询和 workspace 列表刷新否则会在每个周期重渲染输入组件,这正是让 Web UI 感觉卡顿的那类问题。 没有关联 issue,但问题是架构性的而非轶事性的——单一全局存储键从构造上就会泄漏。测试套件覆盖了我关心的边界情况:跨 session 延迟接受、无范围草稿优先级、切换时历史搜索重置、稳定属性不变量。 审批推迟到 CI 在 — Qwen Code · qwen3.8-max-preview Reviewed at |
🖼️ web-shell visual previewRendered against a mock daemon (no real backend): the PR base vs this PR head Screenshots · before / afterℹ️ No screenshot changed against the PR base — but this PR edits 3 render-shaping files:
Either the change has no visual effect (logic, plumbing, a state the scenarios never reach), or no scenario renders this UI — in which case the preview cannot see it, and an empty result is a coverage gap rather than a clean bill of health. To make it visible, add a scenario to Full-resolution recordings (.webm) are attached to the workflow run. — Qwen Code · web-shell visuals |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
LGTM, looks ready to ship — CI landed green after the review. ✅
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Reviewed — no blockers. Suggestions are inline.
中文说明
已审查——无阻断问题。 建议见行内评论。
— qwen3.7-max via Qwen Code /review
doudouOUC
left a comment
There was a problem hiding this comment.
Reviewed — no blockers. Suggestions are inline. 1 Suggestion-level finding(s) could not be anchored to a changed line and were dropped; nothing further to act on here.
中文说明
已审查——无阻断问题。 建议见行内评论。 1 条建议级发现无法锚定到改动行,已丢弃;此处无需进一步处理。
— qwen3.7-max via Qwen Code /review
| if (fallbackStorageKey && readHistory(storageKey).length === 0) { | ||
| saveHistory(storageKey, history); | ||
| } |
There was a problem hiding this comment.
[Suggestion] Legacy fallback migration copies all global history entries into the first workspace that submits — Concrete cost: pushInputHistoryEntry (and the symmetric push callback at lines 113–122) calls loadHistory(storageKey, fallbackStorageKey). When the workspace key is empty, it reads the entire legacy global history and writes it to the workspace key along with the new entry. This means a workspace's scoped history is seeded with entries from all other workspaces that were submitted under the global key, partially defeating the workspace isolation this PR introduces.
For example: user has legacy history ["a", "b", "c"] (from various workspaces). First submission "hello" in workspace /proj/X produces ["a", "b", "c", "hello"] in the workspace-scoped key. ArrowUp then shows "c" — a prompt from a different workspace.
When the primary key is empty (first submit in the workspace), consider saving only [text] to the workspace key instead of the full fallback-seeded array. This preserves the workspace isolation guarantee while still recording the user's submission.
中文说明
[Suggestion] Legacy 回退迁移会将所有全局历史条目复制到第一个提交的工作区 — 具体成本:pushInputHistoryEntry(以及对称的 push 回调,第 113–122 行)调用 loadHistory(storageKey, fallbackStorageKey)。当工作区键为空时,它会读取整个 legacy 全局历史,并与新条目一起写入工作区键。这意味着一个工作区的隔离历史会被来自所有其他工作区的全局键条目所污染,部分削弱了本 PR 引入的工作区隔离效果。
例如:用户有 legacy 历史 ["a", "b", "c"](来自不同工作区)。在工作区 /proj/X 中首次提交 "hello" 后,工作区键中产生 ["a", "b", "c", "hello"]。ArrowUp 会显示 "c" — 这是来自另一个工作区的提示词。
建议当主键为空(工作区首次提交)时,仅将 [text] 保存到工作区键,而非完整的回退填充数组。这样既保持了工作区隔离保证,又记录了用户的提交。
— qwen3.7-max via Qwen Code /review
There was a problem hiding this comment.
Declined — the fallback seeding is the intentional migration path: when workspace-scoped history is empty, the legacy global history is used so users don't lose their pre-existing prompts. Saving only [text] on first submit would discard the user's legacy history from ArrowUp navigation in the new workspace, which is a product-level migration tradeoff rather than a bug. The current behavior is consistent between pushInputHistoryEntry and the push callback.
中文说明
已拒绝 — 回退填充是有意为之的迁移路径:当工作区级历史为空时,使用 legacy 全局历史,以确保用户不会丢失已有的提示词。首次提交时仅保存 [text] 会导致用户在新工作区中通过 ArrowUp 无法访问 legacy 历史,这是产品层面的迁移权衡,而非 bug。当前行为在 pushInputHistoryEntry 和 push 回调之间保持一致。
0cba29b to
7a2afa5
Compare
|
Please do not rebase or force-push to an active PR as it invalidates existing review comments. Note for future reference, the bots always squash all changes into a single commit automatically as part of the integration. 中文请勿对活跃的 PR 执行 rebase 或 force-push,因为这会使已有的评审评论失效。另外,供日后参考:作为集成流程的一部分,机器人始终会自动将所有改动压缩(squash)为单个提交。 |
|
@qwen-code /takeover |
wenshao
left a comment
There was a problem hiding this comment.
中文说明
— qwen3.7-max via Qwen Code /review
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Not reviewed: reverse audit — an auditor ran and opened its brief, but no agent was launched with the prompt the CLI built — the launch was written by hand, and what the agent was actually asked is not what this skill certifies.
中文说明
未审查:反向审计——有审计 agent 运行并打开了自己的 brief,但没有 agent 是用 CLI 构建的 prompt 启动的——启动 prompt 是手写的,agent 实际被要求做的并不是本 skill 所认证的内容。
— qwen3.7-max via Qwen Code /review
|
🤝 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. Remove the 中文说明🤝 已接管:autofix 循环现在管理此 PR —— 将持续处理新的评审反馈与 base 冲突,直到移除标签或达到轮次上限。移除 |
chiga0
left a comment
There was a problem hiding this comment.
Review — fix(web-shell): isolate history and session drafts
Verdict: APPROVE. Independent blind review found no Critical/Major issues. The scoping redesign is sound: identity snapshots validate delayed commits against the submission-time session/workspace, the legacy global history key is treated as a read-only fallback (copy-on-first-push, never deleted — no data-loss risk), and the draft-flush lifecycle (2s debounce with deadline, blur, unmount, visibilitychange, pagehide, identity change) covers the exit paths comprehensively. Two Minor items inline, plus Low/Nit observations below — none blocking.
Cross-validation
| Finding | Reviewer | My assessment |
|---|---|---|
Critical: historyBrowseActiveRef not reset when the !composerUnchanged early return fires → "draft saves suppressed for subsequent typing" (useComposerCore.ts:2236) |
qwen-code-ci-bot | False positive — verified against code. The scenario requires the user to edit the composer between submit and accept, but any genuine edit (input/delete user event) synchronously clears historyBrowseActiveRef in the CodeMirror updateListener (lines 2689-2694) during dispatch — strictly before commitAccepted can run, so the flag is already false when the early return fires. Even if it were true (via a non-doc change such as a shell-mode toggle), the consequence cannot materialize: the next keystroke itself clears the flag before the debounced save (flushDraftAfterIdle) fires, and navigatePrevHistory/navigateNextHistory are gated on disabledRef while a submission is pending, so browse mode cannot be re-entered in that window. The reset at line 2244 is belt-and-braces for the unchanged-composer path, which works correctly. |
Suggestion: visibilitychange/pagehide flush lacks test coverage |
qwen-code-ci-bot | Agree — every other flush trigger has a dedicated test; these two listeners are the only gap. Test-only. |
Suggestion: mobile onBlur draft save lacks test coverage |
doudouOUC | Agree — the desktop blur path is exercised via blurEditor(), but the mobile textarea's onBlur isn't wired into the mobile harness. Test-only. |
| Suggestion: legacy fallback seeds the first workspace push with the entire global history (useInputHistory.ts:55) | doudouOUC | Confirmed — independently noted the same behavior (my Low below). Intentional copy-not-move migration, but it partially weakens workspace isolation on the first push; a design-doc note would help. |
| Downgraded Approve → Comment: CI still running | wenshao | Procedural — no code finding. |
| CHANGES_REQUESTED "reverse audit" | qwen-code-ci-bot | Procedural message — not a code finding. |
My findings
Minor-1 and Minor-2 are posted inline.
Low
getComposerWorkspaceCwd(App.tsx:3225-3227) checksconnectionRef.current.sessionIdbeforelockedWorkspaceCwd, whereas theatWorkspaceCwdprop handed to the composer (App.tsx:8259-8264) putslockedWorkspaceCwdfirst. If a session were active while a different workspace is locked, prompts would enqueue against the session's workspace while drafts/history are scoped to the locked one. Reachability looks narrow (a locked workspace filterscomposerWorkspacesto a single entry), but the precedence divergence is worth aligning.useInputHistory.reset()(useInputHistory.ts:172-176) clearsindexRef/searchIndexRefbut notdraftRef. Not a reachable bug — a stale draft is always overwritten by the nextnavigateUpbefore it can be read, andnavigateDownreturnsnullwhileindexRef === -1— hygiene only.
Nit
- The
unscopedDraftEditedRef.current = falsereset at lines 3025-3027 is dead code: line 3003 already resets the flag unconditionally before that point is reachable. - The
getDocTextmemo (lines 2666-2674) retains a reference to the lastTextdoc indefinitely; negligible in practice (single doc, replaced on each edit).
Additional audit coverage
Independent checks beyond the findings above:
- Delayed-commit identity validation: the
sourceChangedbranch ofcommitAcceptedroutes the history push to the submission-time key viapushInputHistoryEntry, andclearComposerDraftIfMatchesonly clears when the stored draft still equals the submitted snapshot — both correct. composerWorkspacesref-memo compares id/cwd/label/primary/trusted by value;isSameGitStatuscorrectly excludes the volatilecomputedAt.- Draft-debounce deadline math (
flushDraftAfterIdle/draftSaveDeadline) and thewasBrowsingHistory/wasSearchingHistoryguards in the identity-switch flush effect — correct. - Legacy key safety: the fallback path only reads the global key and never mutates or deletes it;
pushInputHistoryEntry's dedupe (history[length-1] === text) still seeds the scoped key when the scoped store is empty, which is the intended migration behavior.
| workspaceCwd: string | undefined, | ||
| ): string | undefined { | ||
| if (sessionId) { | ||
| return `${SESSION_DRAFT_STORAGE_PREFIX}${encodeURIComponent(sessionId)}`; |
There was a problem hiding this comment.
[Minor] Session-scoped draft keys (qwen-web-shell-session-draft:<sessionId>) are only removed on an accepted submission (saveComposerDraft(key, '')) or via clearComposerDraftIfMatches. A session abandoned with unsent draft text leaves its key in localStorage forever — I couldn't find any eviction/lifecycle logic in the PR, and the design doc doesn't mention cleanup either.
Growth is slow (one small string per abandoned-with-draft session), so this is hygiene rather than urgent — a periodic sweep of session-draft: keys that don't match a live session (or a TTL written into the value) would close it. Non-blocking.
There was a problem hiding this comment.
Declined — this is a storage hygiene suggestion, not a correctness issue. Growth is bounded by the number of sessions abandoned with unsent draft text (one small string each). Adding a periodic sweep or TTL is out of scope for this review round and would add lifecycle complexity beyond the PR's intent.
中文说明
已拒绝 — 这是存储卫生建议,而非正确性问题。增长受限于带有未发送草稿文本的废弃会话数量(每个一个小字符串)。添加定期清理或 TTL 超出了本轮审查的范围,且会增加超出 PR 意图的生命周期复杂性。
|
🤖 Addressed the latest review feedback (round 1/100). What changed, and what I pushed back on: · 已处理最新评审反馈(第 1/100 轮)。改动内容与我反驳保留之处如下: Review feedback addressedCritical
Suggestions implemented
Suggestions declined
Verification
中文说明已处理的审查反馈严重问题
已实施的建议
已拒绝的建议
验证
Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。 Re-review when you have a moment. After round 100 this bot stops and leaves the PR for a human. · 有空请复审;第 100 轮后本 bot 停止并将 PR 交给人工。 🧠 Handled by Qwen Code · model/模型 |
|
🔀 Base updated: red check(s) [review-pr] pass on current main — merged current main via update-branch; CI will re-run. 中文说明🔀 已更新 base:红色检查 [review-pr] 在当前 main 上通过 —— 已通过 update-branch 合入当前 main,CI 将重新运行。 |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
No issues found. LGTM! ✅
— qwen3.7-max via Qwen Code /review
…on-history-drafts # Conflicts: # packages/web-shell/client/hooks/useComposerCore.ts
|
🤖 Addressed the latest review feedback (round 2/100). What changed, and what I pushed back on: · 已处理最新评审反馈(第 2/100 轮)。改动内容与我反驳保留之处如下: Address Review Summary — PR #7810FeedbackNo review feedback, inline comments, or failed checks were present in this round. Conflict ResolutionMerged
Verification
中文说明处理审查总结 — PR #7810反馈本轮没有审查反馈、行内评论或失败的检查。 冲突解决将
验证
Base-conflict check · 基分支冲突检查: conflicted with main — resolved in this push. · 与 main 有冲突——已在本次推送中解决。 Re-review when you have a moment. After round 100 this bot stops and leaves the PR for a human. · 有空请复审;第 100 轮后本 bot 停止并将 PR 交给人工。 🧠 Handled by Qwen Code · model/模型 |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Reviewed. Suggestions are inline. 1 Suggestion-level finding(s) could not be anchored to a changed line and were dropped; nothing further to act on here. Not reviewed: reverse audit of chunk 5 (round 2) — the auditor returned nothing substantive twice.
中文说明
已审查。 建议见行内评论。 1 条建议级发现无法锚定到改动行,已丢弃;此处无需进一步处理。 未审查:reverse audit of chunk 5 (round 2) — the auditor returned nothing substantive twice。
— qwen3.7-max via Qwen Code /review
|
🔀 Base updated: red check(s) [web-shell E2E Smoke (ubuntu-latest, Node 22.x)] pass on current main — merged current main via update-branch; CI will re-run. 中文说明🔀 已更新 base:红色检查 [web-shell E2E Smoke (ubuntu-latest, Node 22.x)] 在当前 main 上通过 —— 已通过 update-branch 合入当前 main,CI 将重新运行。 |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Reviewed. Suggestions are inline.
中文说明
已审查。 建议见行内评论。
— qwen3.7-max via Qwen Code /review
…on-history-drafts # Conflicts: # packages/web-shell/client/App.test.tsx
|
🤖 Addressed the latest review feedback (round 3/100). What changed, and what I pushed back on: · 已处理最新评审反馈(第 3/100 轮)。改动内容与我反驳保留之处如下: Review feedback addressedAll three inline suggestions are resolved in code. 1. Large paste helpers had no test coverage (rc:3658622241) — implementedAdded a 2.
|
|
🔀 Base updated: red check(s) [web-shell E2E Smoke (ubuntu-latest, Node 22.x)] pass on current main — merged current main via update-branch; CI will re-run. 中文说明🔀 已更新 base:红色检查 [web-shell E2E Smoke (ubuntu-latest, Node 22.x)] 在当前 main 上通过 —— 已通过 update-branch 合入当前 main,CI 将重新运行。 |
doudouOUC
left a comment
There was a problem hiding this comment.
Reviewed. 1 Suggestion-level finding(s) could not be anchored to a changed line and were dropped; nothing further to act on here.
中文说明
已审查。 1 条建议级发现无法锚定到改动行,已丢弃;此处无需进一步处理。
— qwen3.7-max via Qwen Code /review
…, and mobile draft notify (QwenLM#7810)
|
🤖 Addressed the latest review feedback (round 4/100). What changed, and what I pushed back on: · 已处理最新评审反馈(第 4/100 轮)。改动内容与我反驳保留之处如下: Review feedback addressedAll four findings (2 Critical, 2 Suggestion) are resolved in this commit. [rc:3660151630] Critical: Delete-then-undo permanently loses large-paste mappingDecision: Fixed. The prune condition now only fires on Change: In the [rc:3660151637] Critical: Effect cleanup ordering causes draft data loss on unmountDecision: Fixed. Rather than reordering effects (which introduced a test regression due to React's cleanup ordering in this component), the fix adds Change: One line added in the EditorView effect cleanup: [rc:3660151645] Suggestion:
|
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
中文说明
— qwen3.7-max via Qwen Code /review
|
🔀 Base updated: red check(s) [web-shell E2E Smoke (ubuntu-latest, Node 22.x)] pass on current main — merged current main via update-branch; CI will re-run. 中文说明🔀 已更新 base:红色检查 [web-shell E2E Smoke (ubuntu-latest, Node 22.x)] 在当前 main 上通过 —— 已通过 update-branch 合入当前 main,CI 将重新运行。 |
doudouOUC
left a comment
There was a problem hiding this comment.
中文说明
— qwen3.7-max via Qwen Code /review
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
中文说明
— qwen3.7-max via Qwen Code /review
|
🤖 Addressed the latest review feedback (round 5/100). What changed, and what I pushed back on: · 已处理最新评审反馈(第 5/100 轮)。改动内容与我反驳保留之处如下: Review feedback addressedCI failure: web-shell E2E Smoke (ubuntu-latest, Node 22.x)Classification: Required (broken CI test) Root cause: The PR introduced large paste placeholder handling in Fix: Updated the smoke test to match the new behavior:
The test was renamed from "pastes long plain text as editable composer content" to "pastes long plain text as a placeholder and expands it on submit" to accurately describe the verified behavior. Changes: No conflict resolution was needed ( Verification
中文说明已处理的审查反馈CI 失败:web-shell E2E Smoke (ubuntu-latest, Node 22.x)分类: 必须修复(CI 测试失败) 根本原因: 本 PR 在 修复: 更新冒烟测试以匹配新行为:
测试名称从 "pastes long plain text as editable composer content" 更名为 "pastes long plain text as a placeholder and expands it on submit",以准确描述验证的行为。 变更: 无需解决冲突( 验证
Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。 Re-review when you have a moment. After round 100 this bot stops and leaves the PR for a human. · 有空请复审;第 100 轮后本 bot 停止并将 PR 交给人工。 🧠 Handled by Qwen Code · model/模型 |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
No issues found. LGTM! ✅
中文说明
未发现问题。LGTM!✅
— qwen3.7-max via Qwen Code /review
doudouOUC
left a comment
There was a problem hiding this comment.
No issues found. LGTM! ✅
中文说明
未发现问题。LGTM!✅
— qwen3.7-max via Qwen Code /review
|
Released in v0.21.1. |
What this PR does
This PR scopes prompt history to the effective workspace while retaining the legacy unscoped history as a one-time fallback for workspaces that do not yet have their own entries. It also resets history navigation whenever the active session or workspace changes.
Unsent plain-text composer drafts are persisted per session, or per target workspace on the New Task page, with a two-second idle delay and flushes when the composer loses focus, changes identity, unmounts, or the page is hidden. A draft that is entered before workspace discovery completes remains authoritative over an older persisted draft.
Accepted delayed submissions, including queued prompts and the first prompt that creates a session, commit history exactly once without clearing newer input or another session's composer. Host-gated submissions are cancelled if their source session or target workspace changes while approval is pending.
Equivalent workspace-list and Git-status refreshes preserve the composer-facing references so background polling does not cause avoidable composer rerenders.
Why it's needed
Prompt history previously leaked across workspaces, queued and delayed submission paths could leave history or composer state inconsistent, and history navigation retained stale cursor state after switching sessions. Unsent input was also lost when users left a session or returned to a New Task page.
These issues are especially disruptive in multi-workspace environments, where recalling or sending content in the wrong workspace can be confusing and potentially unsafe. Persisting scoped drafts and validating delayed submissions against their original source keeps each composer tied to the context in which the user authored it.
Reviewer Test Plan
How to verify
Evidence (Before & After)
Before: prompt history was shared across workspaces, session navigation state leaked across session switches, and unsent composer text was not restored. Delayed acceptance could also act on a composer whose source context had changed.
After: history and drafts follow their workspace/session identity, delayed acceptance is source-safe, and equivalent background refreshes do not churn composer props.
Tested on
Environment (optional)
Local Node.js workspace. Verified the Web Shell production build, TypeScript check, ESLint, Prettier, and 231 targeted unit tests covering the application composer and both CodeMirror and mobile textarea backends.
Risk & Scope
Linked Issues
N/A
中文说明
本 PR 的改动
本 PR 将提示词历史按实际 workspace 隔离;对于尚无独立历史的 workspace,仍会一次性回退读取旧的全局历史,并在下一次提示词被接受时迁移到 workspace 专属存储。同时,切换 session 或 workspace 时会重置历史导航状态。
未发送的纯文本输入会按 session 持久化;在“新建任务”页面则按目标 workspace 持久化。写入采用两秒空闲延迟,并会在输入框失焦、上下文切换、组件卸载或页面隐藏时立即刷新。即使用户在 workspace 尚未解析完成前输入,新输入也会优先于旧的持久化草稿。
延迟接受的提交(包括排队提示词和创建 session 的首条提示词)只会写入历史一次,并且不会清空更新的输入或其他 session 的输入框。当宿主的提交前回调仍在等待时,如果来源 session 或目标 workspace 已发生变化,该提交会被取消。
当 workspace 列表或 Git 状态刷新后内容等价时,会保持传给输入组件的引用稳定,避免后台轮询引发不必要的输入组件重渲染。
为什么需要
此前提示词历史会在不同 workspace 之间串用;排队和延迟提交可能造成历史或输入框状态不一致;切换 session 后历史导航还会残留旧游标。用户离开 session 或返回“新建任务”页面时,未发送的输入也会丢失。
这些问题在多 workspace 环境中尤其明显:召回或发送了错误 workspace 的内容不仅容易混淆,也可能带来安全风险。按上下文保存草稿,并在延迟提交时校验原始来源,可以确保每个输入框始终绑定到用户创作内容时所在的上下文。
Reviewer 测试计划
验证方式
前后对比证据
修复前:提示词历史在 workspace 间共享,session 切换后历史导航状态残留,未发送文本无法恢复;延迟接受还可能作用到来源上下文已经变化的输入框。
修复后:历史和草稿跟随 workspace/session 身份,延迟接受具备来源安全校验,等价的后台刷新不会造成输入组件属性抖动。
测试平台
环境(可选)
本地 Node.js workspace。已验证 Web Shell 生产构建、TypeScript 检查、ESLint、Prettier,以及覆盖应用输入框、CodeMirror 和移动端 textarea 后端的 231 个定向单元测试。
风险与范围
关联 Issue
N/A