feat(web-shell): add Session Overview panel and in-window split view - #6400
Conversation
Add a large-screen "Session Overview" mission-control panel and an
in-window split view so users can monitor and drive multiple daemon
sessions at once.
- SessionOverviewPanel: ranked live cards (needs-approval -> running ->
idle) merging the workspace session list with the detail=full status
report. Multi-select opens the selected sessions as a split view in
the current tab ("Open in split") or in a new browser tab ("Open in
new tab", via a ?split=a,b URL).
- SplitView + ChatPane: one DaemonWorkspaceProvider hosting N
DaemonSessionProvider panes, each a self-contained interactive chat
(transcript, composer, streaming, tool/ask approvals). Browser focus
scopes the keyboard per pane, so panes never contend over approvals.
- Sidebar entry points gated to large screens; the split view's Back
returns to the Session Overview.
|
Thanks for the PR! Template looks good ✓ — all required sections present, bilingual, with screenshots and a detailed reviewer test plan. Problem: Real, well-defined UX gap. Power users driving multiple parallel daemon sessions have no way to see all sessions at once or interact with 2+ simultaneously. The current web-shell shows one session at a time, requiring constant switching. This is an observed workflow friction, not theoretical hardening. Direction: Aligned. Multi-session management is a natural evolution for the daemon web-shell — the project already has a Size: 1,537 production logic lines (additions + deletions) across 11 production files, plus 984 test lines across 7 test files. No core module paths touched — everything is within Approach: The scope feels right for what's being delivered. The Session Overview (ranked cards with multi-select → open in split or new tab) and Split View (N independent ChatPane components, each under its own One question: the split view intentionally does NOT dismiss when an approval becomes pending on the outer main session (each pane owns its own approval). This is a deliberate design choice — worth confirming this matches user expectations when the main session also has a pending approval that isn't visible in any split pane. Moving on to code review. 🔍 中文说明感谢贡献! 模板完整 ✓ — 所有必填段落齐全,中英双语,附带截图和详细的 reviewer test plan。 问题: 真实、明确的 UX 缺口。重度用户并行驱动多个 daemon 会话时,无法一眼看到所有会话或同时操作 2+ 个会话。当前 web-shell 一次只显示一个会话,需要频繁切换。这是已观测到的工作流痛点,不是理论性加固。 方向: 对齐。多会话管理是 daemon web-shell 的自然演进——项目已有 规模: 11 个生产文件共 1,537 行生产逻辑代码(增 + 删),7 个测试文件共 984 行测试代码。未触及核心模块路径——全部在 方案: 范围与交付内容匹配。会话总览(排序卡片 + 多选 → 分屏/新标签页打开)和分屏(N 个独立 ChatPane,各挂自己的 一个疑问:分屏故意不在外层主会话出现待审批时关闭(每个窗格管自己的审批)。这是有意的设计选择——值得确认当主会话也有待审批、但在任何分屏窗格中不可见时,是否符合用户预期。 进入代码审查 🔍 — Qwen Code · qwen3.7-max |
Code ReviewIndependent proposal (before reading diff): to add multi-session management to the web-shell, I'd create a session overview panel listing all sessions with status/ranking, and a split view rendering N independent chat panes each with their own session provider. I'd reuse The PR's approach matches and exceeds this baseline. Specifically:
One observation: the Test ResultsUnit tests (base branch, 71 files): All 1,054 tests pass. The PR claims 1,098 passing including the new test suites — the delta is the new test files (SessionOverviewPanel, SplitView, ChatPane, useIsLargeScreen, splitUrl, sidebar additions, App additions). Build: Type check: Pre-existing type errors in Real-Scenario Testing (tmux)Daemon boots and serves the web-shell successfully on the base build: Limitation: This CI runner is headless Linux — no browser available for interactive UI testing. The Session Overview and Split View are browser-rendered React components that require a real browser to verify interactive behavior (card ranking, multi-select, split pane isolation, 中文说明代码审查独立方案(读 diff 前):为 web-shell 添加多会话管理,我会创建会话总览面板列出所有会话并显示状态/排序,以及分屏视图渲染 N 个独立聊天窗格各挂自己的 session provider。我会复用 PR 方案与我的基线一致并有超越:
测试结果单元测试(base 分支,71 文件): 1,054 个全部通过。PR 声称含新测试共 1,098 个通过。 构建: 类型检查: 基础分支上已有的类型错误,非 PR 引入。 真实场景测试(tmux)Daemon 成功启动并提供 web-shell 服务(HTTP 200)。 限制: 此 CI 环境是无头 Linux——无浏览器可用。会话总览和分屏是浏览器渲染的 React 组件,需要真实浏览器验证交互行为。PR 作者已提供 Playwright 验证的截图和端到端验证描述。单元测试覆盖了可测试的逻辑。 — Qwen Code · qwen3.7-max |
|
Stepping back to look at the whole picture. This PR adds two well-motivated UI surfaces to the web-shell — a Session Overview (mission control) and a Split View (N independent chat panes). The motivation is genuine: power users running multiple daemon sessions in parallel currently have no way to see or steer them simultaneously. The screenshots in the PR body show a polished, production-quality result. The code is clean and well-structured. The three-component decomposition ( The test coverage is comprehensive: ranking logic, open-in-tab/split wiring, pane add/close, URL helpers, large-screen hook, sidebar entry points, approval interaction, and App-level integration tests. 1,054 existing tests pass on the base branch, and the build compiles cleanly. My independent proposal (before reading the diff) was essentially the same architecture — session overview panel with status ranking, split view with per-pane providers, large-screen gating. The PR's implementation matches and exceeds this baseline in polish (color-coded cards, responsive grid, popup-blocked handling, One design question I raised in Stage 1 (split view not dismissing on main-session approval) is a deliberate choice: each pane owns its own session's approval, so the outer main session's approval is intentionally not surfaced in the split. This is consistent with the per-pane isolation model, though it means a main-session approval could be invisible when the user is in split view. Worth a maintainer's judgment on whether this needs a visual indicator. Honest assessment: This is a well-executed, well-tested feature PR. The code quality is high, the design decisions are reasoned, and the test coverage is thorough. It ships a real capability that users of the web-shell will find valuable. No critical blockers, no security concerns, no code quality issues. Reservations:
Recommending maintainer review rather than auto-approval, given the feature's scope and the fork origin. 中文说明退一步看整体。 这个 PR 为 web-shell 添加了两个动机充分的 UI 界面——会话总览(mission control)和分屏视图(N 个独立聊天窗格)。动机真实:并行驱动多个 daemon 会话的重度用户目前没有同时查看或操控它们的方式。PR 中的截图展示了精致的、生产质量的结果。 代码干净、结构良好。三层组件分解( 测试覆盖全面:排序逻辑、开标签页/分屏接线、窗格加/关、URL 工具、大屏 hook、侧栏入口、审批交互、App 级集成测试。基础分支 1,054 个既有测试全部通过,构建编译干净。 我的独立方案(读 diff 前)基本是相同的架构——带状态排序的会话总览面板、每窗格独立 provider 的分屏视图、大屏限定入口。PR 的实现在精细度上超越了基线(颜色标记卡片、响应式网格、弹窗拦截处理、 Stage 1 提出的一个设计疑问(分屏不在主会话待审批时关闭)是有意选择:每个窗格管自己会话的审批,所以外层主会话的审批故意不在分屏中显示。这与按窗格隔离模型一致,但当用户在分屏视图中时,主会话的待审批可能不可见。值得维护者判断是否需要视觉提示。 坦诚评价: 这是一个执行良好、测试充分的功能 PR。代码质量高,设计决策有理,测试覆盖全面。它交付了 web-shell 用户会觉得有价值的真实能力。无关键阻断、无安全问题、无代码质量问题。 顾虑:
建议维护者审查而非自动批准,鉴于功能范围和 fork 来源。 — Qwen Code · qwen3.7-max |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
LGTM, looks ready to ship. ✅
qqqys
left a comment
There was a problem hiding this comment.
Code Review Summary
Overall: Approve — clean architecture, solid provider isolation, good test coverage. A few actionable items below.
🔴 Major (should fix before merge)
-
Stale
selectedSet when sessions disappear —SessionOverviewPanel.tsx
When a session vanishes from the list, its ID stays in theselectedSet. If it reappears, it's silently pre-selected.toggleSelectAllalso misbehaves when stale IDs inflateprev.size. Fix: pruneselectedin auseEffectkeyed oncards. -
No keyboard navigation in card grid —
SessionOverviewPanel.tsx
The<ul>grid lacksrole="grid"/ arrow-key handling. With 10+ sessions, keyboard users must tab through 3 controls per card. Considerrole="listbox"+aria-multiselectableor arrow-key nav. -
isAskUserPermissionduplicated —ChatPane.tsx:34-43copiesApp.tsx:652-660verbatim. Extract to a shared util to prevent drift.
🟡 Minor (worth addressing, can be follow-up)
- Session picker lacks click-outside / Escape dismiss (
SplitView.tsx) StreamingStatusalways receivesstartedAt={undefined}in split panes (ChatPane.tsx)- Approval overlays lack
aria-live— screen readers won't announce approvals in non-focused panes useIsLargeScreencreates twoMQLinstances for the same query (initializer + effect) — reuse viauseRefbuildSplitUrlhas no URL length cap protection- Hardcoded hex colors in
SessionOverviewPanel.module.cssbypass the CSS variable design system - Cards lack
role="group"semantic grouping for screen readers - Approval effect uses exact match
mainView === 'scheduledTasks'— fragile if newmainViewvalues are added
🧪 Test gaps
- No test for
MAX_PANES(6) enforcement - No test for session disappearing mid-view (stale selection)
- No test for popup-blocked path (
window.openreturns null) - No test for
onOpenSessionNOT firing on checkbox click
✅ Highlights
- Provider isolation is excellent: each pane gets its own
DaemonSessionProviderwith uniqueclientId, no cross-pane state leaks ?split=URL lifecycle is correct: consume →replaceStatestrip → no re-trigger- SSE cleanup relies correctly on React unmount semantics
- Tiered polling (3s list / 10s status) with
document.hiddenand in-flight guards is well-designed - Fully backward compatible — single-session flow unchanged
- No security concerns (React auto-escapes session names, no injection vector in
?split=)
…/ split view - SessionOverviewPanel: prune the selection Set when a session leaves the list (so a reappearing session isn't silently reselected) and make select-all use the intersection rather than prev.size. - Extract isAskUserPermission into a shared util so App.tsx and ChatPane.tsx no longer keep verbatim copies that can drift. - SplitView: dismiss the "add session" picker on Escape or a click outside it. - Tests: MAX_PANES cap, popup-blocked path, checkbox-selects-without-navigating, stale-selection pruning, and a direct test for the extracted util.
0615384
|
Thanks @qqqys for the thorough review — addressed the correctness items and the test gaps in Fixed
Deferred (with reasons)
CI is green and the full suite is 1105 passing. 中文说明感谢 @qqqys 的细致 review,已在 已修
暂缓(附理由)
CI 已绿,全套测试 1105 通过。 |
wenshao
left a comment
There was a problem hiding this comment.
— GPT-5 via Qwen Code /review
|
@qwen-code /resolve |
…n-overview-split # Conflicts: # packages/web-shell/client/App.tsx
|
Qwen Code resolved the merge conflicts, but the head branch changed while resolving, so the update was not pushed. Re-run /resolve. The resolved diff is attached as the Merge Conflict Resolution Summary — PR #6400Conflicted file
ConflictAt the import block (~line 75), the PR branch and origin/main diverged:
Resolution
Auto-merged files (no conflict)
|
doudouOUC
left a comment
There was a problem hiding this comment.
Review Summary
Well-designed, high-quality, purely additive PR. Clean component boundaries, correct provider isolation strategy, and thorough test coverage. No blocking issues found.
Architecture highlights
- Provider isolation is correct: each split pane runs under its own
DaemonSessionProviderwith a uniqueclientId(split-pane:{sessionId}), ensuring SSE streams, transcripts, and approvals never interfere across panes. - Good separation of concerns:
isAskUserPermissionextracted to a shared util prevents the ChatPane and App approval logic from drifting apart. - URL-driven split entry is solid:
?split=a,bbuild/parse logic is concise and round-trip tested. - Responsive degradation is well-handled: large-screen gate + auto-close on viewport shrink protects against stranded-UI states.
Minor suggestions (non-blocking)
-
Repeated constants:
SESSION_PAGE_SIZE = 1000andSESSION_ORGANIZATION_FEATUREare duplicated betweenSessionOverviewPanel.tsxandSplitView.tsx. Extracting to a shared constants file would prevent future drift. -
openSelectedInNewTabuseCallback efficacy:selectedIdsis a new array reference every render (derived fromcards.filter(…)), so theuseCallbackdependency changes every render, defeating memoization. Consider using a ref for the latest ids, or simply dropping useCallback (since the consumer is a button, perf impact is negligible either way). -
StreamingStatus startedAt={undefined}in ChatPane: always passingundefinedis intentional (no timer in split panes), but a brief comment explaining the design choice would help future readers. -
Panel aria-label ternary chain in App.tsx: currently
settings ? … : status ? … : sessionsOverview. If more panel types are added, aRecord<panel, label>map would scale better. -
aria-selected="false"on SplitView picker options: since items are click-to-select-and-dismiss (no persistent selected state), omittingaria-selectedentirely is slightly more correct per WAI-ARIA listbox semantics.
Test coverage
Excellent coverage including: card ranking/priority, select-all/deselect/prune-on-leave, popup-blocked notice, split URL round-trip, useIsLargeScreen sync init + async change + graceful degradation, ChatPane send/cancel/approval/close, SplitView add/remove/cap/dedup/exit, and App-level panel switching + ?split= consumption + viewport-shrink auto-close.
LGTM — ready to merge.
- ToolApproval: add a `keyboardActive` prop; split panes pass false so global Enter/Escape/digit shortcuts can't confirm the wrong session's approval, and the outer session's approval overlay is no longer rendered behind the split (where it would keep its global shortcuts while hidden). - ChatPane: defer the composer commit until sendPrompt resolves, so a rejected prompt (transcript loading / disconnected / turn active) preserves the draft instead of silently dropping it. - SplitView: include a per-mount nonce in each pane's clientId so two tabs opening the same split don't share a client id — which suppressOwnUserEcho would treat as a self-echo and drop from the transcript. - SessionOverviewPanel: cap the split selection to MAX_SPLIT_PANES before building the ?split= URL or opening the in-window split, with a hint when more are selected; also dismiss the split picker on Escape / click-outside. - Tests covering each.
|
Status update:
Full suite is 1120 passing, typecheck/eslint/build clean. 中文
全套测试 1120 通过,typecheck/eslint/build 均干净。 |
wenshao
left a comment
There was a problem hiding this comment.
[Critical] No Escape key exits split view — App.tsx:3740: when mainView === 'split', activePanel is null, so the Escape handler skips the panel-dismiss branch. Escape falls through to decideEscapeIntent which receives blocked: true and returns { kind: 'ignore' }. Every other full-pane view (Settings, Status, Sessions) can be dismissed with Escape; split view is the only exception. Fix: add if (e.key === 'Escape' && live.mainView === 'split') { e.preventDefault(); live.exitSplitView(); return; } before the activePanel check.
3 additional Critical findings inline, plus Suggestion-level recommendations in the summary comment.
— qwen3.7-max via Qwen Code /review
Suggestions — commit
|
| File | Issue | Suggested fix |
|---|---|---|
SessionOverviewPanel.tsx:180 |
selected Set never pruned when sessions disappear — toggleSelectAll misfires when stale entries inflate selected.size |
Prune stale entries in useEffect keyed on cards |
splitUrl.ts:35 |
parseSplitSessionIds accepts any string with no count cap or format validation |
Cap at MAX_PANES (6) and add lightweight format check |
SessionOverviewPanel.tsx:35, SplitView.tsx:21, WebShellSidebar.tsx:34 |
SESSION_PAGE_SIZE/SESSION_ORGANIZATION_FEATURE duplicated across 3 files |
Extract to shared constants module |
SplitView.tsx:118 |
Session picker dropdown has no click-outside or Escape dismiss | Add document pointerdown listener or onBlur handler |
App.tsx:4332 |
Sidebar "Split View" button opens single-pane split (confusing) | Open overview first, or add placeholder pane |
ChatPane.tsx:38 |
isAskUserPermission is a verbatim copy of App.tsx's local helper |
Extract to shared utility (e.g. toolFormatting.ts) |
SessionOverviewPanel.tsx:228 |
popupBlocked notice set once, never cleared on retry |
Reset before retry or add dismiss button |
SessionOverviewPanel.tsx:377 |
ErrorBoundary wrapper untested | Test render throw → fallback UI |
ChatPane.tsx:88 |
handleSubmit empty text guard untested |
Submit empty string, assert sendPrompt not called |
SplitView.tsx:83 |
MAX_PANES=6 cap untested |
Add 7th pane, verify rejection |
— qwen3.7-max via Qwen Code /review
- SplitView: wrap each pane in its own ErrorBoundary, so a render crash in one pane (malformed block, unexpected tool shape) shows an inline fallback with a close action instead of white-screening the whole split. - splitUrl / overview: carry the daemon token into the new-tab split URL's fragment. The current tab has already stripped the token from its URL, so a token-auth (`serve --open`) deployment would otherwise open the split tab unauthenticated. The token rides the hash (never sent to the server / logs). - Tests: per-pane error isolation, token-in-fragment (and none without a token), and the overview polling effects (interval fires, document.hidden skips, and the in-flight guard prevents overlapping polls).
wenshao
left a comment
There was a problem hiding this comment.
Reviewed — no blockers. Suggestion-level recommendations are in the Suggestion summary comment below.
Suggestions — commit
|
| File | Issue | Suggested fix |
|---|---|---|
packages/web-shell/client/components/messages/ToolGroup.tsx (ExpandedBashOutput) |
All shell output truncation was removed — MAX_BASH_LINE_CHARS, maxLines, show-all/show-less toggle. The new component renders full unbounded output in <pre>. shellOutputMaxLines user setting is silently ignored. A verbose command (find /, cat large file) can freeze the browser tab. ExpandedReadContent retains MAX_MARKDOWN_READ_CHARS/MAX_MARKDOWN_READ_LINES guards; the shell path has none. |
Reintroduce a cap (e.g. 500 lines or 50k chars) with a "Show all" toggle, or add max-height + overflow: auto CSS containment. Restore shellOutputMaxLines from settings or document its removal. |
Needs Human Review:
packages/cli/src/ui/hooks/useGeminiStream.ts:2768-2769— The newhideAlwaysAllowfilter inhandleApprovalModeChangemay over-capturepmForcedAsktools. When switching to YOLO/AUTO_EDIT while a PM forced-ask tool is pending, it may be skipped from batch auto-approval and remain stuck inawaiting_approval.packages/web-shell/client/utils/splitUrl.ts:33-42—buildSplitUrlcopies all query params fromcurrentHref(vianew URL(currentHref)) including any?token=still present. It adds the token to the fragment but doesn't strip the query-string copy. In productionremoveDaemonTokenFromUrl()strips it first, but in DEV mode that function returns early. Consider addingurl.searchParams.delete('token').
— qwen3.7-max via Qwen Code /review
wenshao
left a comment
There was a problem hiding this comment.
— GPT-5 via Qwen Code /review
…el contexts - App: hide (display:none) + aria-hide the outer chat subtree whenever mainView !== 'chat', not only when a panel is open. Previously the outer chat/composer/toolbar stayed reachable by keyboard/AT behind the full-page split (it was only covered visually). State is preserved (node stays mounted). - App: wrap SplitView in the app-level WebShellCustomizationProvider and CompactModeContext so split panes render markdown / tool-headers / thinking the same way the single-session chat does. Todo contexts stay chat-only — they belong to the outer session, not the panes.
wenshao
left a comment
There was a problem hiding this comment.
Reviewed — no blockers. Suggestion-level recommendations are in the Suggestion summary comment below.
wenshao
left a comment
There was a problem hiding this comment.
No blockers. One Suggestion-level recommendation is in the Suggestion summary comment below.
…lit UX - ToolApproval: add a dedicated test on the real component that the global keyboard shortcut is armed by default and NOT armed when keyboardActive=false (the cross-pane approval safety mechanism). - SplitView: auto-exit to the Session Overview when the last pane is closed (guarded so an initial empty seed doesn't bounce straight back out). - ChatPane: add tests for the cancel action, the empty/whitespace submit guard, and error routing to the onError prop. - Extract the shared session-list page size + organization feature flag into constants/sessions.ts, used by the overview, split view, and sidebar, so the values can't drift between the three.
|
@qwen-code /resolve |
…n-overview-split # Conflicts: # packages/web-shell/client/App.tsx
|
Qwen Code resolved the merge conflicts, but the head branch changed while resolving, so the update was not pushed. Re-run /resolve. The resolved diff is attached as the Merge Conflict Resolution Summary — PR #6400Conflicted FilesOnly one file had conflicts: Other auto-merged files (no conflicts):
Conflict Details
|
…plit - Split view: when the outer (main) session is waiting on an approval that's hidden behind the split, show a non-blocking notice banner with a "Go to it" button that returns to the chat where the approval lives. - Auto-close the split (like the overview panel) when the viewport shrinks below the large-screen breakpoint, so users aren't stranded. - Session Overview: surface a failed refresh inline (keeping the last-good cards) instead of silently swallowing it once cards are on screen. - Tests: status-report poll cadence, picker dismiss (Escape / outside / inside click), inline refresh-failure banner.
… test - openSelectedInNewTab now clears win.opener (the split tab carries a daemon token in its URL fragment) to prevent reverse tabnabbing, matching the existing bug-report window.open path. - Strengthen the split-view App test so a missing outer-chat subtree fails instead of passing vacuously through an optional chain.
|
@qwen-code /triage |
|
Thanks for the PR! Template looks good ✓ — all required sections filled in, including bilingual description, reviewer test plan with screenshots, and risk/scope analysis. Problem: this is a feature addition (not a bug fix), so the "problem existence" check is about whether the use case is real. Managing multiple parallel daemon sessions is a genuine power-user workflow, and today's web-shell only shows one session at a time. The motivation is clear and well-articulated. Direction: aligned with the web-shell's purpose as a management UI for the daemon. Adding session overview + split view extends the existing multi-session capabilities without changing any core logic. All changes are scoped to Size: not applicable for the core-module gate (all 24 files are in Approach: the scope feels right for the stated goal. The component decomposition (SessionOverviewPanel, SplitView, ChatPane) is clean, each under its own provider subtree. Good refactoring touches: extracting Moving on to code review. 🔍 中文说明感谢贡献! 模板完整 ✓ — 所有必填部分均已填写,包括双语描述、带截图的审查测试计划和风险/范围分析。 问题:这是一个功能新增(非 bug 修复),所以"问题是否存在"检查的是用例是否真实。管理多个并行的 daemon 会话是真实的重度用户工作流,而今天的 web-shell 一次只显示一个会话。动机清晰且表述充分。 方向:与 web-shell 作为 daemon 管理 UI 的定位一致。添加会话总览 + 分屏扩展了既有多会话能力,未改动任何核心逻辑。所有改动都在 规模:核心模块门控不适用(24 个文件全在 方案:范围与目标匹配。组件拆分(SessionOverviewPanel、SplitView、ChatPane)干净,各自挂在独立的 provider 子树下。合理的重构:把 进入代码审查 🔍 — Qwen Code · qwen3.7-max |
Code ReviewI read the full diff (4,030 lines) and compared it against what I would independently build for "multi-session overview + split view in a web shell." Independent baseline: I'd add a session list panel showing all sessions with status badges (needs-approval > running > idle), a split view rendering N chat panes each with their own session provider, URL-based routing for opening the split in a new tab, and a responsive gate to hide both on small screens. I'd extract shared session-list constants and the The PR matches or exceeds this baseline. The component decomposition is clean, the ranking logic is well-tested, and the security/UX details go beyond what I'd have thought of on a first pass (reverse tabnabbing guard, fragment-only token, per-pane error boundaries, approval overlay suppression under the split). No critical blockers found:
No AGENTS.md violations:
Reuse check: The PR correctly reuses existing components ( TestingUnit tests: CI passed on ubuntu-latest (24m42s). The PR author reports 1,098 tests passing, including new suites for:
Real-scenario testing: This is a web UI feature (Session Overview panel and Split View in the browser-based web shell). The tmux-based E2E testing workflow is designed for CLI features and cannot drive a React-rendered browser UI. The PR author provided real screenshots captured against CI checks: 中文说明代码审查阅读了完整 diff(4,030 行),并与我独立构建"多会话总览 + web-shell 内分屏"时会采用的方案进行了比较。 独立基线: 我会添加一个会话列表面板,显示所有会话及其状态徽章(待审批 > 运行中 > 空闲),一个分屏视图渲染 N 个各自拥有独立 session provider 的聊天窗格,基于 URL 路由在新标签页打开分屏,以及响应式门控在小屏上隐藏两者。我会提取共享的会话列表常量和 PR 匹配或超过了这个基线。 组件拆分干净,排序逻辑测试充分,安全/UX 细节超出了我首次会考虑的范围(反向标签页劫持防护、token 仅在 fragment 中、分屏下按窗格隔离的错误边界、分屏下外层审批覆盖层的抑制)。 未发现关键阻断问题:
无 AGENTS.md 违规:
测试单元测试: CI 在 ubuntu-latest 上通过(24m42s)。作者报告 1,098 个测试通过。 真实场景测试: 这是一个 web UI 功能(浏览器中的会话总览和分屏)。基于 tmux 的 E2E 测试工作流是为 CLI 功能设计的,无法驱动 React 渲染的浏览器 UI。作者提供了在真实 daemon 上用 Playwright 截取的截图,单元测试覆盖了逻辑层。 CI 检查: — Qwen Code · qwen3.7-max |
|
This is a well-executed feature PR. After reading the full diff, running the numbers, and comparing against what I'd build independently, I'm confident in the implementation. The two new surfaces — Session Overview (mission control) and Split View (side-by-side panes) — solve a real power-user need without touching any core infrastructure. The component decomposition is clean: each split pane is an independent The shared refactors ( LGTM. ✅ 中文说明这是一个执行良好的功能 PR。在阅读完整 diff、统计数据并与我独立会构建的方案比较后,我对这个实现有信心。 两个新界面——会话总览(mission control)和分屏(并排窗格)——解决了真实的重度用户需求,且未触及任何核心基础设施。组件拆分干净:每个分屏窗格是独立的 共享重构( LGTM ✅ — Qwen Code · qwen3.7-max |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
LGTM, looks ready to ship. ✅
- Refocus the composer after a shrink-driven split close so keyboard users aren't dropped onto <body> (skips when an approval or panel takes over). - Stabilize SplitView onExit via useCallback so its last-pane-close effect doesn't re-fire on every App re-render. - ChatPane: surface a per-pane connection-loss banner instead of silently showing stale messages when a pane's daemon connection drops. - ChatPane: anchor the streaming timer to the active turn's start (last user message timestamp) so a pane opened mid-turn shows real elapsed time. - Tests: split auto-close on shrink, outer-approval split notice + return-to- chat, connection banner, and streaming-timer anchoring.
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
No review findings. Downgraded from Approve to Comment: CI still running.
Reviewed across 9 parallel agents (correctness, security, code quality, performance, test coverage, 3 undirected personas, build/test) + deterministic analysis (tsc: 0 findings, eslint: 0 findings). All 924 unit tests pass. The code demonstrates careful attention to provider isolation, per-pane error boundaries, keyboard scoping, and polling guards.
— qwen3.7-max via Qwen Code /review
|
@qwen-code /triage |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
LGTM, looks ready to ship. ✅
doudouOUC
left a comment
There was a problem hiding this comment.
qwen /review 6400 --comment failed to produce findings.
Error: [API Error: {"code":"Throttling.BurstRate","message":"Request rate increased too quickly. To ensure system stability, please adjust your client logic to scale requests more smoothly over time.","request_id":"3a8b5ed1-3d75-94fd-b536-e276dd3dd232"}]
doudouOUC
left a comment
There was a problem hiding this comment.
Re-review (post-update): Approved
Summary
Re-reviewed the full diff (4,031 lines, 24 files) after the author's updates. All previously raised critical and suggestion-level items have been addressed. The code is clean, well-structured, and thoroughly tested.
Previously Raised Issues — All Resolved
| Issue | Resolution |
|---|---|
| [Critical] Invisible approval when main session not in pane | Addressed: splitApprovalNotice banner now surfaces when the outer session has a pending approval under the split, with a "Go to it" button that exits split and shows the overlay |
| [Suggestion] Viewport shrink leaves user stranded in split | Fixed: a useEffect now closes both the Session Overview panel AND the split view when the screen shrinks below 1024px; focus is restored to the composer via focusComposerAfterSplitCloseRef |
[Suggestion] popupBlocked state never resets |
Fixed: setPopupBlocked(!win) on each attempt — success clears the notice |
| [Suggestion] Status-report polling untested | Fixed: 4 new polling tests (interval firing, tab-hidden skip, in-flight guard, separate status cadence) |
| [Suggestion] Picker-dismiss untested | Fixed: Escape and outside-click dismiss tests now in SplitView.test.tsx, plus a "keeps open on inner click" test |
Architecture Highlights (unchanged assessment)
- Provider isolation: each split pane is a
DaemonSessionProvidersubtree — own SSE, transcript, approvals, streaming state. No cross-pane global state. - Per-mount nonce (
crypto.randomUUID()): preventsclientIdcollisions across tabs for the same session. - Security:
window.opener = nullon new-tab open; daemon token in URL fragment (never sent to server). - Accessibility:
aria-hiddenon the outer chat subtree when split is active;keyboardActive={false}on pane approvals prevents ghost keyboard confirmations;role="status"/role="alert"on notices. - Graceful degradation: per-pane
ErrorBoundary, connection-loss banner per pane, refresh failure inline (last-good cards preserved). - Shared constants (
constants/sessions.ts): eliminates drift between sidebar, overview, and split picker. - Draft-commit-after-acceptance:
sendPromptpromise resolves → commit; rejects → preserves draft. Well-tested.
Non-Blocking Observations (no action required)
- Minor: The initial
autoLoadinsideuseSessionsand the first poll tick (3s) could theoretically overlap since thelistInFlightref only guards the interval handler. Harmless (one extra request at mount), but worth knowing. - Future enhancement: The split view doesn't yet support Escape-to-exit (unlike the scheduled-tasks page); this is intentionally different (Escape in a pane would conflict with Escape-to-dismiss-picker), so the current Back-button-only exit is the correct design for now.
Test Coverage
Comprehensive: 1098+ tests passing. New suites cover deriveSessionCards ranking, overview rendering + interaction, polling behavior, split view lifecycle (add/close/cap/ErrorBoundary/picker-dismiss), chat pane (send/approval/cancel/connection-error/streaming-timer), ToolApproval keyboard gate, splitUrl helpers (build/parse/round-trip/token-in-fragment), useIsLargeScreen hook, sidebar entry points, App-level integration (split URL consumption, approval notice, viewport shrink).
No blockers. Ship it. 🚀
DragonnZhang
left a comment
There was a problem hiding this comment.
Reviewed — no blockers. Suggestion-level recommendations are in the Suggestion summary comment below.
What this PR does
Adds two related web-shell surfaces for working with several daemon sessions at once: a large-screen Session Overview panel (mission control) and an in-window Split View.
The Session Overview lists every workspace session as a live card, ranked needs-approval → running → idle. It merges the cheap session list with the
detail=fullstatus report to surface the per-session "needs approval" signal, current model, and client count. From the multi-select you can open the checked sessions as a split view either in the current tab (Open in split) or in a new browser tab (Open in new tab, which navigates to a?split=a,bURL that the app consumes on load).The Split View renders N independent chat panes side by side — one
DaemonSessionProviderper pane under the single sharedDaemonWorkspaceProvider. Each pane is a self-contained interactive chat (transcript, composer, streaming status, tool/AskUserQuestion approvals) scoped to its own session. Because each pane is its own provider subtree, the browser scopes keyboard focus to whichever pane you click into, so panes never contend over which session an approval or Enter belongs to. Entry points (the sidebar Session Overview / Split View buttons) are gated to large screens, and the split view's Back button returns to the Session Overview.Why it's needed
Power users often drive multiple agent sessions in parallel (e.g. a refactor in one, tests in another, CI triage in a third). Today the web shell shows one session at a time, so keeping tabs on several means constant switching. The overview gives a single place to see which sessions are running or waiting for approval; the split view lets you read and steer 2+ sessions at once on one screen — or pop them into a dedicated tab.
Reviewer Test Plan
How to verify
npm run build --workspace=@qwen-code/web-shellthennode packages/cli/dist/index.js serve --web(open in a wide browser window)./?split=…showing the same split; the?split=param is stripped after load.Automated:
npm run test --workspace=@qwen-code/web-shell— 1098 passing, including new suites for the overview (ranking + open-in-tab/split), split view (N panes + add/close), chat pane (send / approval / close wiring), the split URL helpers, and the large-screen hook.Evidence (Before & After)
Before: N/A — these are new surfaces.
After — Session Overview (mission control): ranked live cards, multi-select,
Open in new tab/Open in split.After — Split View opened in a new tab: three sessions side by side, each its own interactive chat.
Both were captured against a real
node packages/cli/dist/index.js serve --webdaemon. An end-to-end run additionally confirmed per-pane routing (a prompt typed in one pane reaches only that session — verified via two distinctPOST /session/<id>/promptin the daemon log and no cross-contamination between panes) and that Open in new tab opens a?split=tab that loads the split with the selected sessions.Tested on
Environment (optional)
node packages/cli/dist/index.js serve --web(loopback) + Playwright (Chrome) for the real-UI capture; Vitest (jsdom) for unit/integration.Risk & Scope
Linked Issues
中文说明
这个 PR 做了什么
为 web-shell 新增两个协作管理多个 daemon 会话的界面:大屏专用的 会话总览(Session Overview) 面板(mission control)和窗口内 分屏(Split View)。
会话总览把每个工作区会话渲染成一张 live 卡片,按 待审批 → 运行中 → 空闲 排序。它把便宜的会话列表与
detail=full状态报告合并,得到每个会话的"待审批"信号、当前模型、客户端数。通过多选,可以把勾选的会话作为分屏打开:要么在当前标签页(Open in split)、要么在新浏览器标签页(Open in new tab,跳转到?split=a,bURL,应用加载时消费该参数)。分屏把 N 个独立聊天窗格并排渲染——共享同一个
DaemonWorkspaceProvider,每个窗格挂一个自己的DaemonSessionProvider。每个窗格是自包含的可交互聊天(消息、composer、流式状态、工具/AskUserQuestion 审批),作用域是它自己的会话。因为每个窗格是独立的 provider 子树,浏览器把键盘焦点作用到你点中的那个窗格,窗格之间不会争"回车/审批属于哪个会话"。入口(侧栏的会话总览 / 分屏按钮)仅在大屏显示;分屏的"返回"回到会话总览。为什么需要
重度用户常常并行推进多个 agent 会话(比如一个重构、一个写测试、一个查 CI)。现在 web-shell 一次只显示一个会话,盯多个就得反复切换。总览给了一个统一的地方看哪些会话在跑、哪些在等审批;分屏让你在一块屏上同时读和驱动 2+ 个会话——或把它们弹到独立标签页。
复现步骤
npm run build --workspace=@qwen-code/web-shell,然后node packages/cli/dist/index.js serve --web(用宽的浏览器窗口打开)。/?split=…显示同样的分屏;?split=参数加载后被清除。自动化:
npm run test --workspace=@qwen-code/web-shell— 1098 个通过,含总览(排序 + 开标签页/分屏)、分屏(N 窗格 + 加/关)、chat pane(发送/审批/关闭接线)、split URL 工具、大屏 hook 的新测试套件。证据(Before & After)
Before:N/A —— 都是新界面。截图见上(英文部分):会话总览(排序卡片、多选、Open in new tab / Open in split);分屏在新标签页打开(三个会话并排,各自独立聊天)。均在真实
serve --webdaemon 上截取。端到端跑还验证了 per-pane 路由(一个窗格打字只到那个会话——daemon 日志里两个不同POST /session/<id>/prompt,窗格间不串扰),以及 Open in new tab 会开出一个?split=标签页加载选中会话的分屏。风险与范围