fix(web-shell): keep the sidebar New task on a workspace - #10777
Conversation
The standalone-chats work made the sidebar's top-level New task create a projectless session on any daemon advertising standalone_sessions_v1, which every current `qwen serve` does. Because the workspace tree was gated on the active session being workspace-bound, the same click also dropped the Projects group with its session lists, the Plugins, Channels, Scheduled Tasks and Goals entries, Settings and the overview buttons: the sidebar ended up with no route back to a workspace, and the new chat was not on the primary workspace either. Treat that button as project navigation rather than a global entry point. It now inherits the current explicit context, so a workspace chat stays in its workspace, a Live chat stays Live, and a cold draft lands on the primary workspace. Projectless chats keep an explicit entry point in the Recents group header, which is now the only place the capability gate applies. The workspace tree, its session catalogs and its live state stay rendered outside a workspace context. The affordances inside it that open a project panel or dialog -- the git chip, add workspace, manage workspaces and the workspace management menu -- stay gated, because those surfaces only render inside a workspace context and would otherwise be dead clicks.
|
|
Workspace row actions are visibility-hidden until the row is hovered, so counting them through a role locator depended on an incidental mouse position and made the navigation test flaky. Hover the row first, then assert and click its New task.
The comment named the sidebar New task among the paths that leave selectedWorkspaceCwd undefined. Since the button inherits the current explicit context, only a chat with no explicit context leaves the selection unset.
|
Thanks for the PR! Template ✓ — all sections present, including the bilingual summary. Problem: observed regression, not theory. #10514 made every Direction: aligned — this restores workspace-first navigation as the default while keeping standalone chats reachable through an explicit, capability-gated entry point, which is what the original design intended. CHANGELOG: no direct reference, but the area is qwen-code's own Web Shell surface. Size: ~1,181 production lines (App.tsx 10, StandaloneRecents.tsx 23, WebShellSidebar.tsx 1,146, i18n.tsx 2), ~340 test lines, 13 docs lines. No core paths touched. The 1,146-line sidebar number looks large but is almost entirely de-indent from unwrapping the single Approach: the scope feels right — three minimal moves: a cwd-less New task reuses the existing Risk: no elevated risk signals — no high-risk paths touched. The one acknowledged tradeoff (projectless/Live chats now issue workspace catalog and live-state reads for the navigation tree) is documented in Risk & Scope. Moving on to code review. 🔍 中文说明感谢贡献! 模板 ✓ —— 各节齐全,含中文摘要。 问题: 已观测到的回归,不是理论问题。#10514 让每个 方向: 对齐 —— 恢复「工作区优先」的默认导航,同时保留一个显式的、按能力门控的独立会话入口,这正是原设计的本意。CHANGELOG:无直接参考,但该领域是 qwen-code 自己的 Web Shell 界面。 规模: 约 1,181 行生产代码(App.tsx 10、StandaloneRecents.tsx 23、WebShellSidebar.tsx 1,146、i18n.tsx 2),约 340 行测试,13 行文档。未触及核心路径。侧栏的 1,146 行看起来很大,但几乎全部来自解除包裹工作区树的单一 方案: 范围合理 —— 三个最小改动:无 cwd 的 New task 复用现有 风险: 无升级风险信号 —— 未触及高风险路径。唯一已声明的取舍(无项目/Live 会话现在会为导航树发起工作区目录与运行状态读取)已在 Risk & Scope 中说明。 进入代码审查。🔍 — Qwen Code · qwen3.8-max Reviewed at |
Code reviewNo blockers. I formed an independent proposal from the description first (restore the
TestingUnattended run — no PR code was built or executed here; the evidence below is this PR's own CI at the reviewed commit, fetched via the API. Final CI results for
One row per check name (latest run); skipped checks omitted; failures sort first. / 每个检查名一行(取最新一次运行),省略 skipped,失败项排在最前。 The One evidence gap, stated plainly: the six Playwright standalone specs assert the real browser interactions, but the Not verified: Windows/Linux rendering of the new Recents button — author tested on macOS only; the button is a standard shadcn 中文说明代码审查无阻塞问题。我先根据 PR 描述形成了独立方案(为无 cwd 的 New task 恢复
测试无人值守运行——这里没有构建或执行任何 PR 代码;以下证据来自该 PR 自身在被审查提交上的 CI,通过 API 获取。单元套件( 如实说明一个证据缺口:六个 Playwright standalone 用例断言的是真实浏览器交互,但 未验证:新版 Recents 按钮在 Windows/Linux 上的渲染——作者仅在 macOS 上测试;该按钮是标准 shadcn — Qwen Code · qwen3.8-max Reviewed at |
|
Confidence: 5/5 — clean across every stage; this is a well-scoped regression fix I'd merge without hesitation once CI lands green. My independent proposal from the PR description alone — reuse the The problem was verified as an observed regression from #10514, not a hypothesis. Approval is deferred only because the unit suite and the visuals job were still running at review time — approval lands automatically once every check on the reviewed commit completes green. 中文说明置信度:5/5 —— 各阶段都干净;这是一个范围恰当的回归修复,CI 全绿后我会毫不犹豫地合并。 我仅凭 PR 描述形成的独立方案——为无 cwd 的 New task 复用 问题已核实为来自 #10514 的已观测回归,而非假设。暂缓批准只是因为审查时单元套件与视觉任务仍在运行——被审查提交上的所有检查全绿后会自动完成批准。 — Qwen Code · qwen3.8-max 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 5 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 review did not complete successfully. The review pipeline failed before a review could be posted. A transient error is retried automatically; if you are seeing this, retry with |
The icon-only trigger in the Recents header was not self-explanatory. Render the localized label next to the icon using the compact xs button size so the header row keeps its height.
The Recents header button added a parallel affordance for starting a projectless chat. The composer workspace picker already answers "which workspace does this chat belong to", so "no workspace" belongs there as one of its options instead. Offer a projectless radio entry in WorkspaceSelector, gated on the standalone capability and on no locked workspace, and keep the picker enabled for projectless drafts so the target can be chosen or changed before the first prompt. Drop the Recents header button along with its prop chain. An attached projectless session still hides the picker, matching the chat-surface hiding rules.
The picker was hidden in a projectless draft because the composer only received the workspace list inside a workspace context, which made the new no-workspace option unreachable right after choosing it. Pass the workspace list whenever the picker itself is enabled.
E2E review report — head
|
| Check | Result |
|---|---|
App.test.tsx at head |
660/660 pass |
client/components/sidebar/ (all 21 suites) at head |
396/396 pass |
ChatEditor.test.tsx + WorkspaceSelector.test.tsx |
107/107 and 4/4 pass |
Mutation B — sidebar intent reverted to { kind: 'global' } |
exactly 1 failure: App session callbacks > keeps the sidebar New task on the primary workspace on a capable daemon |
Mutation A — picker gate reverted to workspaceContextActive |
survives: 660/660 still pass |
| Restore after every mutation | sha256 identical for both files (9530cc04…, e0b51fba…) |
Mutation B is the important one: the PR's central behavior change is pinned by a test that fails when it is reverted, and nothing else moves.
Non-blocking, and not a defect — a mutation-proven coverage gap. Mutation A survives because the new test creates a standalone session from the composer no-workspace target asserts selectedStandaloneTarget (which is passed ungated) but not standaloneTargetSupported after the projectless target has been selected. So the behavior the last commit exists for — the picker staying usable in a projectless draft, so the target can still be changed before the first prompt — is currently unpinned, and a future refactor of that gate would silently drop it. One extra assertion in that test covers it: expect(testState.latestChatEditorProps?.standaloneTargetSupported).toBe(true) right after the existing selectedStandaloneTarget check. I verified that assertion passes at head (660/660) and, with the gate reverted to workspaceContextActive, becomes the only failure in the file (AssertionError: expected false to be true).
Two caveats about the automated evidence on this PR, both from the bot's own comments rather than from my runs:
- The web-shell visuals lane reports no screenshot changed against base while listing five render-shaping files — i.e. no visual scenario reaches the projectless draft state this PR is about, so that lane is a coverage gap here, not a clean bill of health.
- The review pipeline for the current head did not complete (
qwen-review-fallbackat 06:56Z). The earlier无阻塞问题code-review verdict was posted at 06:27Z against the previous design (the Recents-group entry point that the last two commits replaced), so as of posting there is no completed automated review off5555ccf904aeither. My verdict above is independent of both.
复查结论(head f5555ccf904a,未发现阻塞问题):侧边栏 New task 由 {kind:'global'} 改为复用既有的 {kind:'inherit'},冷启动草稿落到主工作区、工作区会话留在原工作区、已连接的独立会话继承 standalone(该路径 base 已有测试钉住);被删掉的 globalNewSessionUsesStandalone 全仓库无残留引用,WebShellSidebar 只被 App.tsx 渲染。侧边栏工作区树在非工作区上下文恢复渲染不会撞上服务端限制——packages/cli/src/serve/routes/* 没有按会话类型限制工作区级会话/目录读取,独立会话只是会话种类而非权限范围;会打开项目面板/对话框的入口(添加工作区、管理工作区、canManage、onOpenGitDiff/onOpenCommit)仍按 projectFeaturesEnabled 收窄。组合器选择器双向切换正确:switchWorkspace 会用工作区上下文覆盖 pendingSessionContext,不会残留独立草稿。设计文档修订与实现一致({kind:'global'} 仅剩选择器、host API、missing-session 恢复三处),i18n 两个语言包都补了 key。本地:App.test.tsx 660/660、components/sidebar/ 全部 21 个套件 396/396、ChatEditor 107/107、WorkspaceSelector 4/4;变异 B(把 intent 改回 global)恰好只有 keeps the sidebar New task on the primary workspace on a capable daemon 失败,说明核心修复被钉住;变异 A(把选择器开关改回 workspaceContextActive)无人拦截,因为新测试只断言了不受该开关控制的 selectedStandaloneTarget——非阻塞的覆盖缺口,补一行 standaloneTargetSupported 断言即可(已验证:head 通过,变异后成为唯一失败项)。每次变异后文件 sha256 均复原一致。本机没有浏览器二进制,Playwright 用例未能在本地执行,以 CI 车道为准;另外 visuals 车道对本 PR「无截图变化」其实是场景未覆盖无工作区草稿状态,而当前 head 的自动 review 流水线失败(06:56Z fallback),此前 06:27Z 的「无阻塞问题」结论针对的是已被最后两个提交替换掉的旧设计。未 approve:该 head 尚无 gate review,产品车道仍在跑。
|
Qwen Code review did not complete successfully. The review pipeline failed before a review could be posted. A transient error is retried automatically; if you are seeing this, retry with |
Code Coverage Summary
CLI Package - Full Text ReportCore Package - Full Text ReportFor detailed HTML reports, please see the 'coverage-reports-22.x-ubuntu-latest' artifact from the main CI run. |
ytahdn
left a comment
There was a problem hiding this comment.
PR 主旨 / What this PR does
中文: 修复 PR 10514 带来的侧栏回归:Web Shell 顶部「New task」不再默认开无项目会话,而是走 createNewSession({ kind: 'inherit' }) 继承当前上下文(工作区会话留在本区、Live 仍 Live、冷启动草稿落在主工作区)。无项目入口被显式移到 composer 的工作区选择器里——一个「No workspace (standalone)」单选项,仅在 daemon 具备 standalone 能力且未锁定工作区时出现,点击走 { kind: 'global' }。同时把工作区导航树(会话目录、置顶/分组、频道分组、运行状态)从单一 projectFeaturesEnabled 门控解耦,使其在无项目/Live 会话里继续渲染,而树内会打开面板/对话框的入口(git chip、添加工作区、管理工作区、git diff/commit)保持被门控,避免死点击。globalNewSessionUsesStandalone prop 被彻底移除。
English: Fixes the PR 10514 sidebar regression: the top "New task" now inherits the current context via createNewSession({ kind: 'inherit' }) (workspace chat stays put, Live stays Live, a cold draft lands on the primary workspace) instead of starting a projectless chat. The projectless entry point is an explicit "No workspace (standalone)" radio item inside the composer's workspace picker, shown only when the daemon advertises standalone support and no workspace is locked, dispatching { kind: 'global' }. The workspace navigation tree (session catalogs, pin/group organization, channel grouping, live state) is decoupled from the single projectFeaturesEnabled gate so it keeps rendering in projectless/Live chats, while the in-tree panel/dialog openers stay gated to avoid dead clicks. globalNewSessionUsesStandalone is removed entirely.
核对情况 / Verification
在 head f5555ccf9 上静态走查(未运行测试/构建),逐条以 head tree 源码为证:
kind:'inherit'真实存在且被处理(App.tsx:1292定义、9430处理),无新增解析逻辑;Live 分支单列,locked workspace 在 inherit 之前优先命中(9428)。globalNewSessionUsesStandalone全仓零残留(跨packages/grep 为 0),侧栏handleNewSession简化为workspaceCwd ?? primaryWorkspaceCwd无孤立引用。- 门控拆分完整:四个树级开关(
organizationEnabled954、channelGroupingEnabled975、sessionCatalogRequestsEnabled = connection.capabilities !== undefined1000、workspaceSessionLiveStateEnabled1067)去掉projectFeaturesEnabled;添加工作区(5517)、管理工作区(5930)、canManage(5719)、git openers(5608–5612) 仍带门控。子组件WorkspaceSection.tsx:775仅在传入onOpenGitDiff时渲染 git chip,故门控下线时芯片随 handler 一起消失,不是死点击。 - i18n
sidebar.noWorkspace在 EN 与 ZH 两处均补齐(web-shell 仅 en/zh-CN 两语言,无第三处遗漏),类型层为t(key: string)无 key 字面量约束。 - composer 新分支渲染安全:
selectedStandalone且 workspaces 为空/未定义时,triggerLabel/图标/tooltip/radiovalue自洽不崩;hooks 全在早返回前。 - 测试非空断言:改名的
refreshes the primary workspace after a cwd-less New Task恰是旧预期的字面反转;变更前该 fixture 走{kind:'global'},故not.toHaveBeenCalledWith(standalone)会失败——用例真实钉住新行为;能力关闭场景亦有覆盖。
未发现 Critical 或 Important 问题。 以下为不阻塞的小项:
小建议 / Minor (non-blocking)
-
WebShellSidebar.tsx:2748的清理副作用语义已过期。 该 effect 原本因「surface 在!projectFeaturesEnabled时被隐藏」而存在;现在无项目模式也会渲染并交互,于是它只在 workspace→projectless 切换瞬间重置 search/rename/折叠/分组菜单。功能无害,但若并非有意做「切换即重置」,可删;若有意,建议加一行注释说明,以免后人误删。
English: The teardown effect's original rationale (surface hidden when!projectFeaturesEnabled) no longer holds; it now just resets transient UI on the workspace→projectless transition. Harmless — remove it if the reset wasn't intended, or add a comment if it was.* -
App.tsx:15761selectedStandaloneTarget缺少与两个兄弟 prop 一致的composerWorkspaceSelectEnabled守卫。 目前无害(ChatEditor 只在workspaceSelectVisible下渲染选择器,而它要求onSelectWorkspace,仅在 enabled 时传入),但为可维护性建议对齐:selectedStandaloneTarget={composerWorkspaceSelectEnabled && effectiveSessionContext?.kind === 'standalone'}。
English: Sibling-prop guard asymmetry; unreachable today, tighten for consistency.* -
PR 描述与实现已漂移(文档)。 正文「Breaking changes」称
globalNewSessionUsesStandalone被onNewStandaloneSession取代、入口在「Recents 分组标题上的新建按钮」;但当前 head 上onNewStandaloneSession从未出现、StandaloneRecents是 main 上既有且本 PR 未改动、真实入口是 composer 选择器里的「No workspace (standalone)」项。Reviewer Test Plan 第 3 步按「Recents 标题按钮」描述,会让照做的审阅者找不到控件。建议把正文/验收矩阵更新到 composer-picker 方案。
English: The PR body's prop-rename and "Recents group-header button" narrative no longer matches head; update the description/test plan to the composer-picker reality.*
结论 / Verdict
💚 APPROVE — 修复方向正确、门控拆分完整、prop 移除干净无残留、inherit 复用既有路由、测试真实钉住行为变更;仅有若干不阻塞的小建议。
LGTM — correct direction, complete gate split, clean prop removal, inherited routing reuses existing resolution, and tests genuinely pin the change; only non-blocking nits.
静态审查于 head f5555ccf904abe414f69ecbceccfd6a6ea763dd9 / Static review at head f5555cc. 未运行测试与构建 / no tests or build executed.
|
Released in v0.23.0. |












What this PR does
The Web Shell sidebar's top-level New task button goes back to being project navigation: it inherits the current explicit session context instead of starting a projectless (standalone) chat. A workspace chat therefore stays in its workspace, a Live chat stays Live, and a cold draft with no session yet lands on the daemon's primary workspace. Projectless chats keep an explicit entry point — a new-chat button in the Recents group header, offered only when the daemon advertises the capability and no workspace is locked.
The workspace navigation tree also stops disappearing outside a workspace context. It shared a single gate with the project-only controls, so entering a projectless chat hid the whole Projects group together with its session lists and left the sidebar with no route back to any workspace. The tree, its session catalogs, pin/group organization and live-state indicators now stay rendered. The affordances inside it that open a project panel or dialog — the workspace git chip, add workspace, manage workspaces and the workspace management menu — stay gated, because those surfaces only render inside a workspace context and would otherwise be dead clicks.
Why it's needed
The standalone-chats work in #10514 made every current
qwen servedaemon turn a plain New task click into a projectless chat, since the daemon always advertisesstandalone_sessions_v1. Two user-visible regressions followed. The new chat was not on the main workspace, and the sidebar lost its workspace list along with the Plugins, Channels, Scheduled Tasks and Goals entries, Settings and the overview buttons, with nothing left that could navigate back to a workspace. Reloading did not help: the standalone deep link restores the same context.Reviewer Test Plan
How to verify
qwen servewith at least one registered workspace and open the Web Shell. With no session loaded, click the sidebar's New task, type a prompt and submit. ExpectPOST /sessioncarrying the primary workspace path and noPOST /standalone/sessions. Before this PR the same click created a standalone session and left the sidebar without its workspace list.POST /standalone/sessionsand the URL gainscontext=standalone, while the workspace tree stays visible with its session lists. Hover a workspace row, click its own New task, submit a prompt, and expectPOST /sessionwith that workspace path — the way back out of a projectless chat.Evidence (Before & After)
Automated evidence: the full Web Shell unit suite passed 5,665 tests across 252 files; the full Playwright chromium suite passed 66 tests with 1 intentional skip, including six standalone tests (three rewritten for the new entry point and two added for the primary-workspace route and for navigation staying reachable inside a projectless chat).
npm run build, the packagetsc --noEmit, package-wide ESLint and Prettier are clean on every changed file.Screenshots from the deterministic mock-daemon harness (Chromium, 1280x800):
Baseline — a populated sidebar with two sessions in the primary workspace:

Before (origin/main) — the same sidebar right after clicking New task: the projectless draft hides the whole Projects group, its session lists and every project navigation entry, leaving no route back to a workspace:

After — the same click now keeps the workspace draft: the Projects group, its session lists, the project navigation entries and the workspace git chip all stay, and the draft targets the primary workspace:

After — starting a projectless chat from the new Recents entry point keeps the workspace navigation reachable, with the entry point itself visible in the Recents header:

Tested on
Environment (optional)
macOS with Node.js 22,
npm install && npm run buildfrom the repository root, then Vitest and Playwright Chromium against the deterministic mock daemon. No real daemon or model traffic was used.Risk & Scope
createNewSession()callback and the missing-session recovery button still start a projectless chat on a capable daemon, and the chat-surface hiding rules for projectless chats (workspace selector, git status, uploads, project settings, split view) are unchanged.globalNewSessionUsesStandaloneprop is replaced byonNewStandaloneSession. The sidebar component is not part of the package's public exports, and the only caller is the app shell in this repository. The design document's entry-point table and acceptance matrix carry a dated amendment recording the new sidebar behavior.Linked Issues
References #10514, which introduced the entry-point behavior this PR adjusts.
中文说明
这个 PR 做了什么
Web Shell 侧栏顶部的 New task 按钮重新回到「项目导航」的语义:它继承当前的显式会话上下文,而不是开启一个无项目(standalone)会话。因此工作区会话会留在自己的工作区,Live 会话仍然是 Live,而在还没有任何会话的冷启动草稿状态下,新会话会落在 daemon 的主工作区上。无项目会话仍然保留一个显式入口——Recents 分组标题上的新建按钮,只有在 daemon 通告该能力且没有锁定工作区时才提供。
工作区导航树也不再在非工作区上下文里整体消失。它此前与「仅项目」控件共用同一个开关,所以进入无项目会话时,整个 Projects 分组连同其会话列表一起被隐藏,侧栏再没有任何回到工作区的路径。现在导航树、它的会话目录、置顶/分组编排以及运行状态指示都会继续渲染。树内部那些会打开项目面板或对话框的入口——工作区 git chip、添加工作区、管理工作区、工作区管理菜单——仍然保持隐藏,因为这些界面只在工作区上下文里渲染,否则会变成点了没反应的死入口。
为什么需要
#10514 的 standalone 会话改造让当前每个
qwen servedaemon 都把一次普通的 New task 点击变成无项目会话,因为 daemon 总会通告standalone_sessions_v1。随之出现两个用户可见的回归:新会话不在主工作区上;侧栏丢失了工作区列表,以及 Plugins、Channels、Scheduled Tasks、Goals 入口、Settings 和各个 overview 按钮,剩下的界面无法导航回任何工作区。刷新也没有用:standalone 深链会恢复同样的上下文。审阅测试计划
如何验证
qwen serve启动并打开 Web Shell。在没有会话的状态下点击侧栏 New task,输入并提交一个 prompt。预期看到携带主工作区路径的POST /session,且没有POST /standalone/sessions。本 PR 之前,同样的点击会创建 standalone 会话,并让侧栏失去工作区列表。POST /standalone/sessions创建会话,URL 带上context=standalone,同时工作区树连同会话列表仍然可见。将鼠标悬停到某个工作区行,点击该行自己的 New task 并提交 prompt,预期得到带该工作区路径的POST /session——这就是从无项目会话回到工作区的路径。前后对比证据
自动化证据:Web Shell 全量单测 252 个文件 5,665 个用例全部通过;Playwright chromium 全量 66 个用例通过、1 个按设计跳过,其中 standalone 相关 6 个(3 个改写为指向新入口,2 个新增,分别覆盖主工作区路径与无项目会话内导航仍可达)。
npm run build、包内tsc --noEmit、包级 ESLint 与 Prettier 在所有改动文件上均无问题。截图来自确定性 mock daemon 测试环境(Chromium,1280x800):
基线——主工作区带两个会话的完整侧栏:

之前(origin/main)——点击 New task 后的同一个侧栏:无项目草稿把整个 Projects 分组、它的会话列表以及所有项目导航入口都隐藏掉,再没有任何回到工作区的路径:

之后——同样的点击现在保留工作区草稿:Projects 分组、会话列表、项目导航入口与工作区 git chip 都在,草稿指向主工作区:

之后——从 Recents 的新入口开启无项目会话时,工作区导航仍然可达,入口本身就显示在 Recents 分组标题上:

测试环境
环境(可选)
macOS + Node.js 22,仓库根目录执行
npm install && npm run build,随后使用 Vitest 与 Playwright Chromium 配合确定性 mock daemon 验证。未使用真实 daemon,也没有真实模型流量。风险与范围
createNewSession()回调以及「会话丢失」恢复按钮在有能力的 daemon 上仍会开启无项目会话;无项目会话的聊天界面隐藏规则(工作区选择器、git 状态、上传、项目设置、分屏)保持不变。globalNewSessionUsesStandaloneprop 由onNewStandaloneSession取代。侧栏组件不属于该包的公开导出,唯一调用方是仓库内的应用外壳。设计文档的入口表与验收矩阵已加入带日期的修订说明,记录侧栏的新行为。关联 Issue
引用 #10514,本 PR 调整的正是它引入的入口行为。