Files
deepseek-harness/.agents/notes/implemented/bug-fix/2026-08-06-onboarding-step-owned-takeover-chrome.zh.md
imccyu 8d4164452a fix(ui): drop stale react-dom deps and address onboarding review
knip: ui-settings no longer imports react-dom (the portal moved into
OnboardingSurface), so the react-dom peer/dev dependencies and
@types/react-dom go away with the usage.

Review follow-ups: the Agent Note's Testing section now records the
held-join reload regression scenario this PR adds (both languages,
pairing re-recorded); the e2e hold gates EVERY settings.describe issued
before release instead of only the first, so a future boot-time join
consumer cannot silently collapse the widened window; the sampler's
persistence across later navigations is documented and the vacuity
wording softened to what the hold actually buys (timing independence).
2026-08-06 21:08:20 +08:00

4.4 KiB
Raw Blame History

Agent Note首次使用引导的接管界面框架移入步骤自身

状态:已实现

English | 中文

问题

设置外壳在 settings.onboarding 有已注册且本地未完成的步骤时就立即挂出首次使用引导的接管界面框架——portal 到 body 的浮层,带不透明的 --dsw-alias-bg-layer-1 展示层、模糊遮罩,并把 #root 置为 inert。而每个步骤都要先加载私有事实才能判定自己是否需要出场WelcomeNotice经其设置 join 读取确认位DeepSeekOnboardingDialog经 Models join 读取凭据就绪状态),判定期间渲染 null。渲染 null 无法抑制界面框架,因为不透明展示层是外壳画在 slot outlet 外面的,不属于步骤。

于是每次在 hero空白或无会话状态下刷新页面会话列表一变 ready 就弹出整屏不透明层——亮色主题下是白色——并阻断全部交互,时长恰好等于一次凭据/设置 RPC 往返;之后已配置好的步骤自我完成,图层消失。用户看到的就是每次刷新在 workspace/会话列表落地的瞬间闪一下白屏。

决定

接管界面框架属于步骤,不属于外壳。新增零 cordis 原语 OnboardingSurfaceui-primitives渲染 portal 到 body 的浮层遮罩展示层——CSS 类名与几何从 SettingsRoot.module.css 逐字迁移——并在自身挂载生命周期内保持 #rootinert。两个步骤组件只把各自的可见分支包进该原语;既有的 null 分支由此在构造上不绘制、不阻塞任何内容,因为界面框架已是同一次渲染决策的一部分。

SettingsRoot 的协调器原样保留(有序账本投影、每次挂载一个步骤、本地完成集合、stepIdcompleteopenSection currency但对当选步骤裸渲染——不再有 portal、展示层和 inert 效果。settings.onboarding 的 slot 契约现在写明:注册方持有外层包裹,且在私有事实未决时必须渲染 null

曾考虑的替代方案

条件注册(账本即有内容信号)。 私有 join 解析出「需要介入」后才注册条目。架构上干净(在 commit point 发布但改动更大join 的加载必须从对话框上移到各插件的 apply注册销毁在两个包里都变成响应式接线。对本缺陷而言过重否决。

settings.onboarding 改成 chain 并把完成集合外置为 store。 composer takeover 的版型做过原型后回退。selector 只能判定 owner props私有就绪事实仍然只能在组件内部解析——chain 买来的是当前两个步骤并不需要的路由通用性,代价却是跨三个包的契约变更。

在渲染点探测 slot 输出为空。 renderSlot 无条件返回 outlet 元素owner 无法据步骤的 null 分支;探测已渲染 DOM 是否为空需要先提交再撤回的手法,其动态翻转会失去 paint 前的保证。

后果

步骤已挂载但尚未判定期间,应用保持可见且可交互:判定窗口内 #root 不再是 inert(此前是在不透明图层背后被置灰)。对真正未配置的用户,接管层比从前晚一个 join 往返出现——但一出现就带着内容,而不是先露出空白展示层再填充。

未来若有步骤注册后不把可见内容包进 OnboardingSurface会无遮罩地裸渲染在应用之上slot 契约的 JSDoc 已把包裹写为注册方的义务。

测试

packages/client/ui-primitives/tests/onboarding-surface.spec.tsx 钉住原语行为:内容外的 body portal、遮罩展示层类名存在、#rootinert 恰好持续挂载生命周期,以及无 #root 的组合。packages/client/ui-settings/tests/settings-root.spec.tsx 钉住反转后的外壳契约:已挂载步骤什么都不渲染时,无接管界面框架、无 inert。apps/web/tests/onboarding-deepseek-config.e2e.ts 新增本缺陷的整装回归钉:已配置世界刷新页面,同时在浏览器网络边界扣住所有 settings.describe 响应——把步骤的判定窗口从 loopback 下不可见拉宽到数百毫秒,这正是断言保持非空洞的关键——页内 8ms 采样器证明接管界面框架从未挂载、#root 从未变为 inert。该文件的既有场景与步骤 specui-settings-generalui-models)原样通过——样式表逐字迁移,遮罩选择器与几何钉子得以幸存。