Qwen Code WebShell 移动端空会话 Composer 底部锚定:760px 断点布局修复与 E2E 验证
【免费下载链接】qwen-codeAn open-source AI coding agent that lives in your terminal.项目地址: https://gitcode.com/GitHub_Trending/qw/qwen-code
导读
本文围绕 Qwen Code WebShell 在窄视口(≤760px)下的空会话(new-chat)布局展开,聚焦「移动端 composer 底部锚定(mobile composer-bottom)」这一布局路径:介绍断点激活条件、mobileWelcomeFooterMiddle等宿主 Props 的语义、CSS 修复方案(绝对定位 footer 对齐聊天面板底部),并梳理仓库中对应的 Chromium E2E 测试矩阵与可复现命令。读完本文,你将掌握该布局的触发条件、修复前后差异、各变体(有无欢迎页脚、自定义页脚)的渲染行为,以及如何在本地用 Playwright 复现与验证这一缺陷。
场景:窄视口下空会话的 Composer 布局
在 WebShell 中,当用户尚未打开任何会话(空会话态)时,聊天面板会渲染欢迎头(welcome header)与欢迎脚(welcome footer),并竖直居中显示。在桌面宽度下,composer 作为聊天面板的常规 flex 子项参与流式排版;但在移动端窄视口下,WebShell 切换为mobile composer-bottom 布局:composer 需要「贴住」聊天面板底部,而不是悬浮在内容流的某个位置。
原设计文档(2026-08-05-webshell-mobile-composer-bottom.md)给出的复现场景是:
在无会话状态下打开 composer-layout 测试挂载(harness),启用
mobileWelcomeFooterMiddle以及欢迎头/欢迎脚渲染器;在760px 视口宽度下,空聊天面板使用 mobile composer-bottom 布局;随后把宽度调整到 761px 再调回 760px。
这一场景同时涵盖了布局的激活、断点切换与恢复三个维度,是验证该路径是否健壮的最小用例。
修复前的缺陷:419px 的页脚到面板底部间距
在 CSS 修复之前,内部 Chromium 挂载在760×900视口下实测到:composer 页脚底部(footer bottom)与聊天面板底部(chat pane bottom)之间存在419px 的间距。
这意味着窄屏用户打开 WebShell 时,输入框并没有稳稳地贴在屏幕/面板底部,而是悬浮在面板中部偏下的位置——视觉上「悬空」,与底部工具栏、安全区之间出现大段空白,体验割裂。
缺陷的根源在于:空会话态为了竖直居中欢迎内容,聊天面板使用了居中布局,而移动断点下的 composer 仍以流式元素参与排版,没有获得「底部锚定」的定位上下文。修复的目标非常明确:在 ≤760px 时让 composer 页脚绝对定位并贴住聊天面板底部。
断点与锚定机制:760px 的 CSS 实现
移动布局的开关是媒体查询@media (max-width: 760px),实现在 packages/web-shell/client/App.module.css 中。核心规则如下:
@media (max-width: 760px) { .appChatEmpty .chatPaneWithMobileComposerBottom { position: relative; overflow: hidden; } .appChatEmpty .chatViewWithMobileComposerBottom .footer { position: absolute; right: 0; bottom: 0; left: 0; margin-top: 0; } }关键点逐一拆解:
position: relative建立定位上下文:聊天面板(.chatPaneWithMobileComposerBottom)在空会话态下成为包含块,同时overflow: hidden防止绝对定位的页脚溢出面板。position: absolute; bottom: 0完成锚定:composer 页脚(.footer)被从文档流中取出,直接贴住面板底部,right/left: 0保证横向铺满。- 断点即开关:在 761px 及以上,该规则不生效,页脚回到流式布局(
position: relative);宽度回调到 760px 时,底部锚定自动恢复——这正是文档场景中「761px→760px 往返」要验证的行为。
为保证空会话态下该定位生效,CSS 还配套了两条前置规则(App.module.css):
.appChatEmpty .chatViewWrap { flex: 0 0 auto; overflow: visible; }注释解释了原因:空会话态通过.appChatEmpty .chatPane { justify-content: center }实现欢迎内容竖直居中,为此聊天视图包裹层必须收缩到内容尺寸(flex: 0 0 auto)而不是填满面板,居中定位才能成立;页脚则脱离该包裹层,独立锚定到面板底部。
触发布局的宿主 Props
mobile composer-bottom 布局并非默认启用,而是由宿主(宿主应用通过 Props 定制 WebShell)显式声明。三个关键 Props 定义在 packages/web-shell/client/App.tsx:
| Props | 类型 | 作用 |
|---|---|---|
renderWelcomeHeader | WelcomeHeaderRenderer | 空会话态欢迎头的自定义渲染器(接收 version、cwd、model、mode 等参数) |
renderWelcomeFooter | WelcomeFooterRenderer | 空会话态显示在 composer 下方的自定义渲染器 |
mobileWelcomeFooterMiddle | boolean | 为true时,在移动端空会话态把renderWelcomeFooter渲染到「欢迎头与 composer 之间」,移动 CSS 重排依赖该开关 |
三者的联动关系是:mobileWelcomeFooterMiddle要求同时提供renderWelcomeFooter,否则移动端 CSS 重排不会生效(见 App.tsx 中的 JSDoc 注释)。
组合变体:display: contents与页脚排序
当同时存在欢迎脚与自定义页脚(renderFooter)时,移动断点下的渲染顺序需要显式控制。CSS 中通过display: contents与order实现(App.module.css):
.appChatEmpty .chatViewWithWelcomeMiddle .footerWithCustomFooter { display: contents; } .appChatEmpty .chatViewWithWelcomeMiddle .footerWithCustomFooter .composer { order: 2; width: min(100%, var(--chat-shell-width)); margin: 0 auto; padding-right: 20px; padding-left: 20px; } .appChatEmpty .chatViewWithWelcomeMiddle .mobileWelcomeFooterMiddle { display: flex; flex: 0 0 auto; align-items: flex-start; justify-content: center; width: min(100%, var(--chat-shell-width)); margin: 0 auto; padding-right: 20px; padding-left: 20px; } .appChatEmpty .chatViewWithWelcomeMiddle .customFooter { order: 1; display: flex; flex: 0 0 auto; ... }含义拆解:
display: contents:自定义页脚变体下,页脚容器自身不产生盒模型,其子元素(composer、欢迎脚、自定义页脚)直接参与父级 flex 排布,这保证了绝对定位的底部面板(如状态面板)仍能以上方内容为包含块正确锚定——对应 CSS 中针对.chatViewWithCustomFooter的position: relative兜底规则(App.module.css)。order控制纵向顺序:自定义页脚(order: 1)在欢迎脚之上,composer(order: 2)沉底,欢迎头则保持在最上方——对应文档场景中「welcome header 保持在 composer 上方」的检查项。- 居中与宽度约束:composer 与欢迎脚都受
--chat-shell-width约束并水平居中,与桌面端视觉一致。
与移动输入后端的配合
移动 composer-bottom 布局同样适用于触摸设备上的 textarea 输入后端:当宿主以mobile-chromium项目(Pixel 7 模拟:touch、coarse pointer、无 hover)运行时,composer 渲染为纯 textarea 而非 CodeMirror。移动端空会话锚定在 textarea 后端同样有专门的 E2E 覆盖(见下文测试矩阵),保证窄屏触控设备的锚定与输入可用性不因输入组件切换而退化。
Chromium E2E 验证矩阵
仓库围绕该布局提供了一组 Playwright 测试,是文档中「可执行基线」的实体。主测试文件为 packages/web-shell/client/e2e/web-shell.smoke.spec.ts,共 4 个相关用例:
| 测试用例 | 关注点 |
|---|---|
anchors the empty mobile composer to the chat pane across the breakpoint @smoke(L1602) | 760px 锚定 → 761px 断点停用 → 调回 760px 恢复锚定,并验证 composer 可键入 |
anchors the empty mobile composer without a welcome footer @smoke(L1649) | 无欢迎脚(renderWelcomeFooter缺省)时仍正确锚定 |
keeps the bottom status panel visible in the custom footer mobile welcome variant @smoke(L1669) | 自定义页脚变体中,底部状态面板保持在聊天面板范围内 |
anchors the empty mobile composer with a custom footer but no welcome footer @smoke(L1705) | 自定义页脚 + 无欢迎脚的组合变体 |
另外,textarea 后端锚定由 packages/web-shell/client/e2e/web-shell.composer.mobile.spec.ts 中的anchors the empty mobile composer with the textarea backend覆盖(在mobile-chromium项目下运行,触摸点按输入并断言 textarea 值)。
锚定的量化断言
expectEmptyMobileComposerAnchored(packages/web-shell/client/e2e/utils/emptyMobileComposer.ts)把「锚定正确」翻译为一组可判定的几何与样式断言:
expect(layout.footerPosition).toBe('absolute'); expect(Math.abs(layout.footerBottom - layout.chatPaneBottom)).toBeLessThanOrEqual(1); expect(layout.chatViewPosition).toBe('static'); expect(layout.chatViewIsPaneFlexItem).toBe(true); expect(layout.chatViewZIndex).toBe('auto'); expect(layout.footerAnchoredToChatPane).toBe(true);要点:
- 页脚底与面板底偏差 ≤1px:这是「贴住底部」的量化标准,419px 的缺陷在该断言下会直接失败;
- 页脚
offsetParent必须是聊天面板:验证position: relative包含块关系确实建立; - 聊天视图
position: static、z-index: auto:确保欢迎内容不被浮层干扰,处于正常层叠上下文。
复现与运行命令
文档给出了可直接执行的复现命令(--grep精确匹配锚定用例):
npx playwright test --config packages/web-shell/playwright.config.ts packages/web-shell/client/e2e/web-shell.smoke.spec.ts --grep 'anchors the empty mobile composer'该命令同时覆盖across the breakpoint与without a welcome footer两个用例。修复后上游聚焦运行结果为3 passed(含另一处锚定相关用例)。移动触控后端用例则通过mobile-chromiumproject 运行,可配合 Playwright 的--project参数执行。
测试挂载(harness)如何构造场景
场景由 packages/web-shell/client/e2e/composer-layout-harness.tsx 提供:通过 URL 查询参数emptyMobileWelcome=true进入空欢迎态,并注入mobileWelcomeFooterMiddle: true、renderWelcomeHeader、renderWelcomeFooter(welcomeFooter=false时省略后者),以及customFooter=true时注入renderFooter与bottomStatusItems。这印证了上文 Props 语义——布局完全由宿主 Props 驱动,因此同一 harness 可枚举出「有/无欢迎脚 × 有/无自定义页脚」的组合矩阵。
关于 CLI 基线不适用性的说明
文档特别指出:对该 host-only 布局路径,针对全局qwenCLI 的基线 dry-run不适用——CLI 不渲染 WebShell DOM,也不暴露mobileWelcomeFooterMiddle、renderWelcomeHeader、renderWelcomeFooter这些宿主 Props,因此无法触发该布局。可执行的基线就是上文基于真实 Chromium 的 composer-layout 挂载:它在 CSS 修复前以「页脚到面板底部 419px 间距」复现了缺陷。这一说明对读者有实用价值:验证该路径无需也无法通过 CLI 完成,请直接跑 Playwright 用例。
修复效果小结
综合设计文档与仓库源码,本次修复达成的可验证目标包括:
- 底部锚定:≤760px 时 composer 页脚以绝对定位贴住聊天面板底部(偏差 ≤1px 的量化断言);
- 欢迎内容层级正确:欢迎头位于 composer 上方,欢迎脚(
mobileWelcomeFooterMiddle模式下)位于欢迎头与 composer 之间,不遮挡前景、不捕获指针事件; - 断点往返稳定:761px 时移动绝对定位规则停用,回调 760px 后底部锚定自动恢复;
- 输入可用:锚定状态下 composer 可正常键入、触控设备使用 textarea 后端、草稿可清空而不提交;
- 组合变体覆盖:有无欢迎脚、有无自定义页脚、有无状态面板的矩阵均有用例保障,且
display: contents变体的测量限制(无盒模型、断言必须拒绝而非误报)在测试中被显式处理(web-shell.smoke.spec.ts)。
对需要定制 WebShell 空会话欢迎区的宿主开发者而言,本文涉及的三个 Props 与 760px 断点规则构成了完整的接入契约;对维护者而言,上述 Playwright 用例则是该布局路径的回归防线,可随时在本地以文档中的命令复现验证。
【免费下载链接】qwen-codeAn open-source AI coding agent that lives in your terminal.项目地址: https://gitcode.com/GitHub_Trending/qw/qwen-code
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考