fix(web-shell): land on the split's first pane when a shrink folds the split - #7000
Conversation
…e split When the viewport shrinks below the large-screen breakpoint the split view folds back to the single chat. If that chat had no session of its own — the common case when the split was entered from the Session Overview or a `?split=a,b` link — the user was stranded on an empty "new chat" instead of the split's first (leftmost) pane. This restores a fallback that was tried and then superseded during #6746: the fold-time re-point to the first pane was dropped because it wiped the git branch and changed the session+URL of a chat that already had a session. Re-add it but guarded on the *empty* chat (`!currentSessionId`) — an empty chat has no git branch or session to preserve — so it fixes the empty-new-chat case without regressing the branch-preservation case. Best-effort and standalone-split-only, as before. Covered by two tests: lands on the first pane when the outer chat is empty, and keeps the existing session (no re-point) when it isn't.
|
Thanks for the PR! Template looks good ✓ — all required sections present including bilingual summary. Problem: Observed regression with solid evidence. The split-to-single-chat fold strands users on an empty "new chat" when entered from Session Overview or a Direction: Aligned. This is a targeted fix to the Web Shell split-view fold behavior — a purely client-side UX concern. The Size: 13 production lines (App.tsx) + 99 test lines (App.test.tsx). Not applicable to core module protection. Approach: Scope is exactly right — one conditional block in the fold handler, with the guard on the precise invariant (empty chat vs. own session). Error handling is best-effort with a swallowed catch, which is appropriate here. The two tests directly validate both branches of the guard. No scope creep, no drive-by changes. Clean. Moving on to code review. 🔍 中文说明感谢贡献! 模板完整 ✓ — 所有必需章节齐全,含双语说明。 问题:已观测到的回归,证据充分。从 Session Overview 或 方向:对齐。这是 Web Shell 分屏折叠行为的定向修复,纯客户端 UX 问题。 规模:13 行生产代码(App.tsx)+ 99 行测试代码(App.test.tsx)。不涉及核心模块保护。 方案:范围完全正确 — 折叠处理函数中一个条件块,守卫放在精确的不变量上(空 chat vs. 有自己的会话)。错误处理是尽力而为的 catch 吞掉,在这里是合适的。两个测试直接验证了守卫的两个分支。没有范围蔓延,没有顺手改动。干净。 进入代码审查 🔍 — Qwen Code · qwen3.7-max Reviewed at |
🖼️ web-shell visual previewRendered against a mock daemon (no real backend): the PR base vs this PR head Screenshots · before / afterFull-resolution recordings (.webm) are attached to the workflow run. — Qwen Code · web-shell visuals |
Code ReviewIndependent proposal: In the Comparison with the diff: The PR's approach is exactly what I would have done. The guard on No critical blockers. No AGENTS.md violations. No scope creep. Reuse check: TestingThis is a browser viewport-resize behavior (React Full test suite (with fix): Controlled revert (fix removed, same test): The controlled revert confirms the test is meaningful — without the one-block fix, 中文说明代码审查独立方案: 在 与 diff 的对比: PR 的方案和我想的完全一样。 无关键阻塞。无 AGENTS.md 违反。无范围蔓延。 复用检查: 测试这是浏览器视口缩放行为(React 完整测试套件(含修复):105/105 通过 ✅ — Qwen Code · qwen3.7-max Reviewed at |
|
Confidence: 5/5 — Clean across every stage; would merge without hesitation. This is exactly the kind of PR the gate is designed to let through quickly: a focused, minimal fix to a real observed regression, with precise guards, meaningful tests, and clear reasoning about why the previous attempts failed and why this reconciliation works. The core insight — re-point to the first pane only when the chat is empty ( 13 production lines, 99 test lines, 2 files. Nothing to cut, nothing to add. 中文说明置信度:5/5 — 每个阶段都干净;毫不犹豫地合并。 这正是门禁设计来快速放行的那种 PR:对一个已观测到的真实回归的精准、最小修复,有精确的守卫、有意义的测试,以及对为什么之前尝试失败、为什么这次调和有效的清晰推理。 核心洞察——仅在 chat 为空时( 13 行生产代码,99 行测试代码,2 个文件。没有什么要砍的,也没有什么要加的。 — Qwen Code · qwen3.7-max Reviewed at |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
LGTM, looks ready to ship. ✅
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. |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Reviewed. Not reviewed: chunk 1 — no agent reported covering these; nobody read them.
— qwen3.7-max via Qwen Code /review

What this PR does
Restores the behavior where narrowing the browser past the large-screen breakpoint — which folds the split view back to a single chat — lands on the split's first (leftmost) pane instead of an empty "new chat", when the chat being folded to has no session of its own. A chat that already has its own session is left untouched, exactly as before.
Why it's needed
When the viewport shrinks below the large-screen breakpoint, the split view auto-folds to the single chat. If the split was entered straight from the Session Overview or a
?split=a,blink, the underlying chat has no session of its own, so a bare fold strands the user on an empty "new chat" instead of the sessions they were just looking at.This exact fallback was implemented once and then deliberately superseded during #6746: re-pointing the chat to the first pane on fold wiped the git branch and changed the session+URL of a chat that did have a session, so that PR shipped "fold without touching the connection" — which reintroduced the empty-new-chat symptom. The right reconciliation is to re-point only when the chat is empty (
!currentSessionId): an empty chat has no git branch or session/URL to preserve, so it fixes the empty-new-chat case without regressing the branch-preservation case that motivated dropping it.Reviewer Test Plan
How to verify
?split=a,blink in a fresh tab) so the underlying single chat has no session of its own.Automated (from
packages/web-shell):npm testis green (105 files / 1672 tests). Two new tests inApp.test.tsxdrive the realAppcomponent: one assertsloadSession(firstPane)fires when the folded chat is empty, the other asserts it does NOT fire when the chat already has a session. Controlled revert (below) shows the empty-chat test fails onmainand passes with the fix.npm run typecheck/lint/format:checkclean.Evidence (Before & After)
Controlled revert — same test, same
Appcomponent, only the one-block fix toggled. Without it the shrink never re-attaches a session (stranding a blank new-chat); with it the folded chat lands on the split's first pane, while the guard leaves a chat that already has its own session untouched:Tested on
Environment (optional)
Local:
npm test/npm run typecheck/npm run lint/npm run format:checkinpackages/web-shell, plus a controlled-revert experiment (git stash push App.tsx→ run the empty-chat test → it fails →git stash pop→ it passes).Risk & Scope
loadSessionfailure (e.g. a non-primary-workspace first pane the single connection can't own) simply leaves the empty chat, i.e. today's behavior.!currentSessionIdguard and covered by a dedicated test, but only in the standalone (uncontrolled) split path.Linked Issues
N/A — reported regression: the narrow-fold shows a new-session window instead of the leftmost pane.
中文说明
这个 PR 做了什么
恢复这样的行为:当浏览器拉窄、越过大屏断点导致分屏折叠回单个 chat 时,如果被折叠到的那个 chat 自己没有会话,就落到分屏的第一个(最左侧)pane,而不是一个空的「新建会话」。如果那个 chat 本来就有自己的会话,则保持不变,和之前完全一样。
为什么需要
当视口缩小到大屏断点以下时,分屏会自动折叠回单个 chat。如果分屏是从 Session Overview 或
?split=a,b链接进来的,底层 chat 并没有自己的会话,于是折叠后就把用户丢在一个空的「新建会话」上,而不是他们刚才在看的会话。这个 fallback 其实实现过一次,但在 #6746 里被有意替换掉了:折叠时把 chat 重新指向第一个 pane 会抹掉一个「本来有会话」的 chat 的 git branch、并改变它掉回去的 session+URL,所以那个 PR 最终改成了「折叠时不动连接」——空会话的症状也就回来了。正确的调和方式是:仅当 chat 为空时(
!currentSessionId)才重新指向第一个 pane——空 chat 没有 git branch、也没有要保留的 session/URL,因此既修好空会话的情况,又不回归当初促使去掉它的「保留 branch」情况。复现验证方式
?split=a,b链接),使底层单 chat 没有自己的会话。自动化(在
packages/web-shell下):npm test全绿(105 文件 / 1672 测试)。App.test.tsx里新增两个测试,驱动真实App组件:一个断言空 chat 折叠时会触发loadSession(firstPane),另一个断言 chat 已有会话时不会触发。下方的「controlled revert」显示空会话测试在main上失败、加了修复后通过。typecheck/lint/format:check均干净。证据(Before & After)
Controlled revert——同一个测试、同一个
App组件,只切换这一处修复。没有它时,缩窄从不重新挂上会话(把用户丢在空白新 chat);有了它,折叠后的 chat 落到分屏第一个 pane,而守卫让「已有自己会话」的 chat 保持不动(上图)。风险与范围
loadSession失败(例如单连接无法拥有的非主 workspace 第一个 pane)就保持空 chat,即今天的行为。!currentSessionId守卫保住了它,并有专门测试覆盖,但仅在独立分屏路径上。关联 Issue
N/A —— 回归上报:窄屏折叠显示了新建会话窗口,而不是最左侧 pane。