feat(web-shell): add composer footer renderer - #7856
Conversation
|
✅ Qwen Triage finished — CI landed green on ✅ Qwen Triage 已完成 —— |
|
Thanks for the PR! Template looks good ✓ Problem: This is a feature addition, not a bug fix — the "problem" is the absence of a composer footer customization hook. The motivation is clearly stated: embedded WebShell hosts need a placement for contextual content next to the composer, and the existing header hook doesn't cover that position. No linked issue, but the PR description is self-contained and the need is concrete. Direction: Well-aligned. WebShell already has a family of composer customization hooks ( Size: Not applicable — no core paths touched. All changes are in Approach: The scope feels right. Every edit is needed: a new type alias, a new interface field, the export, and the wiring in both the primary composer (App.tsx) and split-view panes (ChatPane.tsx). No unrelated changes, no drive-by refactors. One deliberate difference from the header hook worth noting: the footer is wired into ChatPane (split-view) as well as App, while the header only lives in App — the PR description calls this out explicitly, and the tests cover both surfaces. Moving on to code review. 🔍 中文说明感谢贡献! 模板完整 ✓ 问题:这是一个功能新增,而非 bug 修复——"问题"是缺少输入框 footer 自定义渲染钩子。动机描述清晰:嵌入 WebShell 的宿主需要在输入框旁放置上下文内容,现有的 header 钩子不覆盖该位置。没有关联 issue,但 PR 描述自洽,需求具体。 方向:高度对齐。WebShell 已有一组输入框自定义钩子( 规模:不适用——未触及核心路径。所有改动在 方案:范围合理。每处改动都是必要的:新类型别名、新接口字段、导出、以及在主输入框(App.tsx)和分屏面板(ChatPane.tsx)中的接线。没有无关改动,没有顺手重构。一个与 header 钩子的有意差异值得注意:footer 同时接入了 ChatPane(分屏)和 App,而 header 只在 App 中——PR 描述明确说明了这一点,测试也覆盖了两个场景。 进入代码审查 🔍 — Qwen Code · qwen3.8-max-preview Reviewed at |
Code ReviewIndependent proposal: I'd add a Comparison: The PR matches this exactly — no simpler path missed, no extra scope. A few observations:
No critical blockers. No convention violations. TestingThis is an unattended CI run — no local tmux testing. The PR adds an opt-in extension point with no default UI change, so real-scenario TUI testing is N/A. CI signal for Final CI results for
One row per check name (latest run); skipped checks omitted; failures sort first. / 每个检查名一行(取最新一次运行),省略 skipped,失败项排在最前。 The ubuntu unit suite and web-shell visual capture are still running. macOS/Windows tests are skipped (fork PR authorization). No failures so far. The finalize workflow will update this table when CI settles. Not verified: Windows and Linux rendering (author tested macOS only). Host-specific content and styling are intentionally out of scope. 中文说明代码审查独立方案: 我会添加一个 对比: PR 与上述方案完全一致——没有遗漏更简路径,没有额外范围。几点观察:
无关键阻塞项。无规范违反。 测试这是无人值守 CI 运行——无本地 tmux 测试。PR 新增的是按需启用的扩展点,默认 UI 无变化,真实场景 TUI 测试不适用。
未验证:Windows 和 Linux 渲染(作者仅在 macOS 上测试)。宿主特有内容和样式有意不在范围内。 — Qwen Code · qwen3.8-max-preview Reviewed at |
|
Confidence: 5/5 — clean, minimal feature addition that follows the existing customization-hook pattern exactly; would merge without hesitation. This is what a good extension-point PR looks like: 33 production lines, no new abstractions, no scope creep. The Nothing to second-guess. The independent proposal and the diff are the same shape. CI is still running (ubuntu unit suite + web-shell visual capture); approval deferred until it lands green on 中文说明置信度:5/5 —— 干净、最小化的功能新增,完全遵循现有的自定义钩子模式;毫不犹豫地合并。 这是一个优秀的扩展点 PR 应有的样子:33 行生产代码,无新抽象,无范围蔓延。 没有什么需要再斟酌的。独立方案和 diff 形态一致。CI 仍在运行(ubuntu 单元测试 + web-shell 视觉捕获);批准推迟到 — 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 4 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 /resolve |
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 resolved the merge conflicts and pushed the branch update. Merge Conflict Resolution — PR #7856Root causePR #7856 (
Only #7754 produced a conflict. Both PRs added new Textual or semanticPurely textual. The two sides added imports from completely different modules with no overlapping symbols:
Both sets of types are used later in the file — import type { WebShellComposerToolbarRenderInfo } from './customization';
import type {
VoiceStatusRevision,
VoiceWorkspaceTarget,
} from './voice/voice-workspace-target';What is load-bearingNothing beyond import ordering. The two type sets are independent — no type from one is used to parameterise the other, and no test exercises both features simultaneously. Prettier/ESLint import ordering may reorder them, but no runtime or type-check behaviour depends on the order. What I could not verifyThis task runs no build, typecheck, or tests. The merge brought in substantial changes from #7762 (submitted prompt provenance — new hook event types, config fields, and telemetry) and #7754 (voice workspace targeting — new props threaded through 中文说明根因PR #7856( 文本冲突还是语义冲突纯文本冲突。两边导入的类型来自完全不同的模块,没有符号重叠:
两组类型在文件后续均有使用,解决方案保留了双方导入。 关键依赖无。两组类型相互独立,没有交叉引用,导入顺序不影响运行或类型检查行为。 无法验证的内容本次操作未执行构建、类型检查或测试。#7762(submitted prompt provenance)和 #7754(语音工作区目标)带来的大量改动在 |
ace6265 to
4b1a645
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)为单个提交。 |
doudouOUC
left a comment
There was a problem hiding this comment.
[Critical] ChatPane.test.tsx ~line 487: Pre-existing voice-owner test uses inline root?.render() without WebShellCustomizationProvider wrapper, causing context mismatch and toBe() reference-equality failure (confirmed by test execution)
— qwen3.7-max via Qwen Code /review
|
@qwen-code /takeover |
|
🤝 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 冲突,直到移除标签或达到轮次上限。移除 |
|
🤖 Addressed the latest review feedback (round 1/100). What changed, and what I pushed back on: · 已处理最新评审反馈(第 1/100 轮)。改动内容与我反驳保留之处如下: Autofix review round summary (PR #7856)All four review findings were triaged against the current Findings
Conflict notes
Verification
中文说明Autofix 审查轮次总结(PR #7856)所有四条审查意见均已对照当前 各项意见
冲突说明
验证
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/模型 |
wenshao
left a comment
There was a problem hiding this comment.
中文说明
— qwen3.8-max-preview 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
|
🤖 Reviewed the latest feedback — no changes needed. Why, point by point: · 已审阅最新反馈——无需改动。逐点说明原因如下: No action requiredThis round's feedback contains no actionable items:
No inline comments, issue-level comments, failed checks, or still-red checks were reported. No code changes are needed. 中文说明无需操作本轮反馈中没有可操作的项目:
没有内联评论、Issue 级别评论、失败的检查或持续红色的检查。无需进行代码更改。 Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。 🧠 Handled by Qwen Code · model/模型 |
doudouOUC
left a comment
There was a problem hiding this comment.
No issues found. LGTM! ✅
中文说明
未发现问题。LGTM!✅
— qwen3.7-max via Qwen Code /review
|
@qwen-code /verify |
|
Sandboxed verification: merge-ready (agent verdict) - workflow run Ran the PR in an isolated, token-free container: A/B against the base build, mock-free harness assertions, targeted gates. Advisory evidence for human reviewers — not a review, an approval, or a CI check. 沙箱验证在隔离、无凭证的容器中执行了该 PR 的代码(与 base 构建 A/B 对照、无 mock harness 断言、定向门禁)。仅作为评审证据,不构成评审、批准或 CI 检查。 Scripted assertions: 2345 passed · 0 failed · 2345 total Verification report (report.md)Harness scripts and raw logs are in the workflow run artifacts (7-day retention). — Qwen Code · sandboxed verification |
|
🤖 Reviewed the latest feedback — no changes needed. Why, point by point: · 已审阅最新反馈——无需改动。逐点说明原因如下: Autofix review round — no action takenNo code changes were made this round. There is no actionable review feedback on PR #7856:
The report's remaining notes are all explicitly informational and require no code change:
Since no finding names a real code defect and there are no unresolved review threads, no commit is warranted. Adding changes here would be an unrequested drive-by. 中文说明Autofix 评审轮次 —— 未采取任何操作本轮未做任何代码改动。PR #7856 没有可处理的评审反馈:
该报告其余的说明均为信息性,明确无需任何代码改动:
由于没有任何发现指出真实的代码缺陷,也没有未解决的评审线程,因此没有提交改动的必要。在此处添加改动将属于未被请求的顺手修改(drive-by)。 Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。 🧠 Handled by Qwen Code · model/模型 |
|
@qwen-code /verify |
|
Sandboxed verification: fail - workflow run The PR could not be built because Install/build log— Qwen Code · sandboxed verification |
Local verification report — real build + real-render E2EVerified as maintainer in an isolated worktree at head 1. Reviewer test plan — all steps reproduced
On the full-suite run: Author's formatting note confirmed accurate. Trial merge with current 2. Mutation matrix — the new tests are not vacuousI applied 17 block-anchored mutations to the two production call sites and re-ran the PR's own tests. 17/17 killed.
Every prop in the contract and both placements are genuinely pinned. Note #8: wrapping the footer in a wrapper element is itself a caught regression, so the "no wrapper element" shape is deliberate and locked.
3. Real-render verification (the tests all mock
|
| 步骤 | 命令 | 结果 |
|---|---|---|
| 定向测试 | vitest run client/App.test.tsx client/components/ChatPane.test.tsx |
✅ 283 通过(2 个文件) |
| WebShell 全量测试 | vitest run --config vitest.config.ts |
✅ 2331 通过 / 141 个文件 |
| 包构建 | npm run build --workspace @qwen-code/web-shell |
✅ 构建通过;dist/index.js 中包含 renderComposerFooter |
| 类型检查 | tsc -p tsconfig.json --noEmit |
✅ 0 错误 |
| Lint | eslint packages/web-shell |
✅ 干净 |
| Prettier(本 PR 修改的 6 个文件) | prettier --check |
✅ 干净 |
关于全量测试: 如果在 npm run build 之前跑全量测试,client/build-artifact.test.ts(10 个用例)会失败——它读取 ../dist/index.js。先构建再跑即全绿。这不是 PR 的问题,只是执行顺序的前置条件。
作者关于格式化的说明经核实准确。 GitModePopover.module.css 和 client/index.html 确实无法通过 prettier --check,但它们与 base 逐字节相同(两侧 sha 均为 12caed4f / 61be3de2),且本 PR 未修改它们。属于既有的基线欠账。
与当前 main(923e5ab)试合并 —— 本 PR 落后 8 个提交,且 App.tsx 在 main 上有变动。合并无冲突,footer 的位置完好保留;合并后的树上:2340 通过 / 143 个文件,定向测试 284 通过。
2. 变异矩阵 —— 新增测试并非空转
我对两处生产调用点施加了 17 个按代码块锚定的变异,并重跑本 PR 自带的测试。17/17 全部被杀死。
| # | 变异 | App | Pane |
|---|---|---|---|
| 1 | footer 移到输入框上方(顺序对调) | ✅ | ✅ |
| 2 | footer 提到输入框容器之外 | ✅ | ✅ |
| 3 | disabled 硬编码为 false |
✅ | ✅ |
| 4 | isRunning 硬编码为 false |
✅ | ✅ |
| 5 | currentMode 硬编码 |
✅ | ✅ |
| 6 | currentModel 硬编码 |
✅ | ✅ |
| 7 | sessionName 硬编码为 undefined |
✅ | ✅ |
| 8 | footer 外面套一层 <div> |
✅ | — |
| 9 | 完全移除 footer 渲染 | ✅ | ✅ |
契约中的每个 prop 以及两处位置都被真正钉住了。注意第 8 条:给 footer 套一层容器元素本身就会被测试捕获,说明「不加包裹元素」是刻意设计并且被锁定的。
⚠️ 复现时的方法论提醒:朴素的str.replace("<28 个空格>disabled={isDisabled}", ...)会悄悄命中 header 代码块,因为 header 那 30 个空格的缩进包含了 28 个空格的锚点作为子串。我第一轮就因此报出了 2 个假幸存者。上表来自带唯一性断言的按块锚定改写。
3. 真实渲染验证(PR 的测试全部 mock 了 ChatEditor)
两个测试文件都把 ChatEditor 桩成了 <div data-web-shell-composer>,因此顺序断言是针对 mock 做出的。真实的 ChatEditor 根节点确实带这个属性(ChatEditor.tsx:1863),所以这个标记是忠实的——但我仍然用 Playwright 配合 mock daemon,把宿主的 renderComposerFooter 注入 StandaloneApp,针对真实组件 + 真实 CSS 重新验证了一遍。
在真实 DOM 中实测(而非从 mock 断言):
MAIN footerIsNextSiblingOfComposer=true sameParent=true composerBottom=854 < footerTop=868
CONTROL footerFound=false composer.nextElementSibling=null
SPLIT pane[0] nextSibling=true sameParent=true 817 -> 831
pane[1] nextSibling=true sameParent=true 817 -> 831
(截图见英文部分:未传入 vs 传入对比、分屏双面板实时渲染。)
4. 观察(均不阻塞)
(a) disabled 在不同的输入框插槽里含义不同 —— 值得后续跟进。
三个钩子共享完全相同的 WebShellComposerToolbarRenderInfo 类型,但在主聊天视图中拿到的值并不一样:
// App.tsx —— ChatEditor 实际拿到的
disabled={isDisabled || isStartingNewSessionSuggestion || interactionBlocked || approvalOverlayActive}
// App.tsx —— header 以及本 PR 的 footer 拿到的
disabled={isDisabled} // 只有第一项renderComposerToolbarStart/End/Right 渲染在 ChatEditor 内部,因此能看到完整的四项状态;而 renderComposerHeader 和 renderComposerFooter 只能看到 isDisabled。实测:输入 ? 打开快捷键对话框(dialogOpen → interactionBlocked),此时输入框仍然可见:
基线 footer=false header=false toolbarRight=false
对话框打开 footer=false header=false toolbarRight=true <-- 同一类型,答案不同
也就是说,宿主若想实现「输入框禁用时把我的 footer 内容置灰」,在分屏面板中是对的,在主聊天视图中则是错的。这是从 renderComposerHeader 继承下来的既有行为,并非本 PR 引入的回归——而且在 ChatPane 中 footer 的 disabled={approvalActive} 与 ChatEditor 收到的值完全一致,分屏这一侧是对齐的(这正是 4b1a645 这个提交修掉的问题)。把 App 侧的 header + footer 对齐到 ChatEditor 的完整表达式,适合作为独立 PR,不应阻塞本 PR。
(b) PR 描述中的契约已过时。 正文写「渲染器接收与现有 header 钩子一致的输入框状态契约(sessionId 和 inputDisabled)」。实际契约是 WebShellComposerToolbarRenderInfo = { disabled, isRunning, currentMode, currentModel, sessionName? }——既没有 sessionId,也没有 inputDisabled。代码是对的,也确实与 header 钩子一致,只是这段文字写错了。建议修正正文,以免误导宿主接入方。
(c) 分屏视图中渲染器被实例化 3 次,而非 2 次。 App 的主聊天视图在分屏背后保持挂载但隐藏,因此会渲染出第三个实例(实测:hiddenAncestor=_chatViewWrap_ _chatViewHidden_,矩形 0×0,session=(none))。可见的只有 2 个。这是聊天视图保持挂载所致,对现有的 header 钩子同样成立——但它也让 PR 自己提到的「每个输入框实例都会调用该渲染器」这条风险更具体了:宿主如果在渲染器里做了任何带副作用的事(埋点、portal、订阅),也会为一个不可见的面板触发一次。
(d) 没有内置间距。 header 钩子外面包了 <div className={styles.composerHeader}>,带 margin-bottom: 8px;footer 则是裸渲染。宿主需要自己提供上边距——我在上面的验证工装里就得手动加一个才能看到间隙。根据变异矩阵第 8 条这是刻意设计,只是值得写进文档。
建议
合并。 33 行生产代码,没有新抽象,完全遵循既有钩子模式;测试经实测并非空转,真实渲染行为与 mock 断言在主输入框和分屏输入框上都一致。(b) 只需改一行描述;(a) 是一个先于本 PR 就存在的、值得跟进的后续项。
doudouOUC
left a comment
There was a problem hiding this comment.
Reviewed — no blockers. Suggestions are inline.
中文说明
已审查——无阻断问题。 建议见行内评论。
— qwen3.7-max via Qwen Code /review
| {CustomComposerFooter && ( | ||
| <CustomComposerFooter | ||
| disabled={isDisabled} | ||
| isRunning={streamingState !== 'idle'} |
There was a problem hiding this comment.
[Suggestion] CustomComposerFooter is rendered without a shell-owned wrapper div, while CustomComposerHeader (line 8459) is wrapped in <div className={styles.composerHeader}> which provides margin-bottom: 8px. This creates a spacing asymmetry — hosts providing both renderers see 8px above the composer (shell-provided) but 0px below it. — Concrete cost: every host consumer must independently discover and add margin-top to their footer renderer to achieve symmetric spacing.
| {CustomComposerFooter && ( | |
| <CustomComposerFooter | |
| disabled={isDisabled} | |
| isRunning={streamingState !== 'idle'} | |
| {CustomComposerFooter && ( | |
| <div className={styles.composerFooter}> | |
| <CustomComposerFooter | |
| disabled={isDisabled} | |
| isRunning={streamingState !== 'idle'} | |
| currentMode={currentMode} | |
| currentModel={currentModel} | |
| sessionName={sessionDisplayName} | |
| /> | |
| </div> | |
| )} |
中文说明
[Suggestion] CustomComposerFooter 渲染时没有 shell 提供的包裹 div,而 CustomComposerHeader(第 8459 行)被包裹在 <div className={styles.composerHeader}> 中(提供 margin-bottom: 8px)。这导致间距不对称——同时提供两个渲染器的宿主会看到输入框上方有 8px 间距(shell 提供),但下方为 0px。—— 具体成本:每个宿主消费者都需要自行发现并在 footer 渲染器中添加 margin-top 以实现对称间距。
— qwen3.7-max via Qwen Code /review
…wenLM#7884) Run 30319209722 posted `Sandboxed verification: fail` on PR QwenLM#7856 with "The PR could not be built ... treated as a PR failure verdict rather than an infrastructure failure." The PR changed six source files. The install died here: Error: spawnSync .../web-templates/node_modules/esbuild/bin/esbuild ETXTBSY at validateBinaryVersion (esbuild/install.js:99:28) ETXTBSY is npm writing a dependency's binary and that package's own install script exec'ing it before the write is closed — a race, and one the PR had no part in. The comment accused its author anyway. Both sandbox lanes now retry `npm ci` once. `npm ci` removes node_modules before installing, so the second attempt cannot inherit the half-written file; the transient class is simply absorbed instead of being classified. Retrying rather than classifying is the point. The obvious fix — match ETXTBSY in the log and downgrade to infra-error — would read text the PR's own lifecycle scripts can print, which is exactly the forgeable signal the verdict logic was rewritten to stop consulting: it let a PR launder its own deterministic breakage into "infrastructure, please re-run". A retry consults nothing. A genuinely broken tree fails twice and still earns `fail`, and the second attempt is only ever paid on a path that is already failing. The build is deliberately not retried: a compile error is deterministic, so a second run would only double the cost of an honest failure. Because the install now gets two chances, the sentence that blames the PR says so — "failed twice in a row". PREPARE_ATTEMPTS is initialised rather than defaulted at the point of use, or an inherited value would claim a single-shot `npm run build` had failed twice, which is the same false accusation pointed the other way. Mutation-verified 5/5: an unbounded retry, no retry at all, a retry on the build, dropping the initialisation, and reverting the wording each turn at least one test red. The retry itself is covered behaviourally — the real step text runs against a stubbed `runuser`/`npm`, so the tests count actual install attempts instead of asserting that a loop exists, which would pass on both a loop that never retries and one that never stops. Co-authored-by: wenshao <wenshao@example.com>
|
Released in v0.21.1. |



What this PR does
Adds an optional
renderComposerFootercustomization hook to WebShell. Hosts can render contextual content immediately after the composer in both the primary chat and split-view panes. The renderer receives the same composer state contract used by the existing header hook (sessionIdandinputDisabled), and omitting the hook or returningnulladds no DOM.Why it's needed
Embedded WebShell hosts sometimes need to place notices, guidance, or other host-owned content next to the composer. A generic render hook provides that placement without moving host-specific wording, styling, or policy into WebShell.
Reviewer Test Plan
How to verify
npm run test --workspace=packages/web-shell -- client/App.test.tsx client/components/ChatPane.test.tsxand confirm all focused tests pass.npm run test --workspace=packages/web-shelland confirm the full WebShell suite passes.npm run build --workspace=packages/web-shelland confirm the package builds.ChatEditorin the primary and split-pane composers, receives the pane-specific state, and produces no extra DOM when omitted or when the renderer returnsnull.Local results: focused tests passed (264/264), the full WebShell suite passed (138 files, 2268 tests), and the WebShell build passed.
npm run verify --workspace=packages/web-shellpassed linting but stopped on formatting in two unchanged baseline files (packages/web-shell/client/components/GitModePopover.module.cssandpackages/web-shell/client/index.html). Rootnpm run preflightpassed clean, install, format, lint, build, and typecheck, thentest:cifailed in unrelated CLI tests; a focused rerun left four failures inpackages/cli/src/commands/extensions/list.test.ts(local Chinese locale versus an English assertion) andpackages/cli/src/ui/auth/AuthDialog.test.tsx(three interaction timing/selection assertions).Evidence (Before & After)
N/A — this PR adds an opt-in WebShell extension point and does not change the default UI. Host content and styling are intentionally outside this PR.
Tested on
Environment (optional)
Local macOS development environment with the repository workspace dependencies installed through
npm ci.Risk & Scope
null.Linked Issues
N/A
中文说明
本 PR 做了什么
为 WebShell 新增可选的
renderComposerFooter自定义渲染钩子。宿主可以在主聊天和分屏面板的输入框后方紧邻位置渲染上下文内容。渲染器接收与现有 header 钩子一致的输入框状态契约(sessionId和inputDisabled);未传入该钩子或渲染器返回null时不会增加任何 DOM。为什么需要它
嵌入 WebShell 的宿主有时需要在输入框附近展示提示、引导或其他由宿主管理的内容。通用渲染钩子可以提供这一挂载位置,同时避免把宿主特有的文案、样式或策略放进 WebShell。
Reviewer 测试计划
如何验证
npm run test --workspace=packages/web-shell -- client/App.test.tsx client/components/ChatPane.test.tsx,确认所有定向测试通过。npm run test --workspace=packages/web-shell,确认 WebShell 全量测试通过。npm run build --workspace=packages/web-shell,确认包构建通过。ChatEditor之后,能够收到对应面板的状态,并且未传入渲染器或渲染器返回null时不会产生额外 DOM。本地结果:定向测试通过(264/264),WebShell 全量测试通过(138 个文件、2268 个测试),WebShell 构建通过。
npm run verify --workspace=packages/web-shell的 lint 已通过,但被两个未修改的基线格式文件拦截(packages/web-shell/client/components/GitModePopover.module.css和packages/web-shell/client/index.html)。根目录npm run preflight的清理、安装、格式化、lint、构建和类型检查均通过,随后test:ci在与本 PR 无关的 CLI 测试中失败;定向复跑后剩余 4 个失败,分别位于packages/cli/src/commands/extensions/list.test.ts(本机中文 locale 与英文断言不一致)和packages/cli/src/ui/auth/AuthDialog.test.tsx(3 个交互时序/选择断言)。证据(修改前与修改后)
不适用——本 PR 新增的是按需启用的 WebShell 扩展点,不会改变默认 UI。宿主内容和样式有意不包含在本 PR 中。
测试平台
环境(可选)
本地 macOS 开发环境,仓库工作区依赖通过
npm ci安装。风险与范围
null时,现有消费者的渲染行为与之前完全一致。关联 Issue
不适用