# better-float 实现计划(v2 · 依据 dsh 设计报告修正) > 本文件已依据 **`dsh-element-scout-design.md`**(dsh 架构设计报告,含 4 轮追问修订与已核实的平台事实)对原计划做系统性修正。 > 原计划保留为 **`better-float-implementation-plan.orig.md`**,勿删,便于对照。 > 后续实现在本文件夹(`G:\Code\fork\dsh-better-float`)进行。 --- ## 0. 修正摘要(先看这里) 原计划的技术底座(Portal 锚点 + postMessage 事件桥接 + `window.open`)在**通用 Web**语境下成立,但落到 **dsh / Electron** 后有若干处会导致「能跑起来但关键功能不工作」。下表按严重度排列。 | # | 原计划写法 | 问题 | 修正 | 级别 | |---|---|---|---|---| | **C1** | 事件:新窗口捕获 → `postMessage` → 原窗口在 Portal 锚点上重建并 `dispatch` 冒到上层 | **X 自身的 React `onClick` 永远不会触发。** React 18 委托挂在 root container,按 `event.target` 沿 fiber 树定位 handler;此时原窗口只剩一个空锚点 A,React 只能找到 A 及其祖先,**X 自己的 props 不在路径上**(props 存在 X 的 `__reactProps$*`,而 X 已在新窗口)。更糟的是:X 的真身在新窗口被 React 持续更新 → 「渲染在新窗口、事件在原窗口」两条世界线分裂 | 改为**就地反射(in-place reflection)**:在新窗口直接读取目标节点链上的 `__reactProps$*`,按 `composedPath` 走捕获+冒泡两轮直接调用,配最小合成事件 shim。**同文档浮动则完全不需要任何透传**(委托仍在同一个 root container 内) | **P0** | | **C2** | 完全没有处理「父节点失去子节点」 | 搬迁后 React 卸载/重排/条件渲染会调 `P.removeChild(X)`,而 X 已不是 P 的子节点 → 抛 `NotFoundError`,**整棵子树崩溃** | 必须实现 `installStructuralStandIn(P, X, A)`:在 **P 的实例自有属性**上(遮蔽原型,非改原型)拦截 `removeChild` / `insertBefore` / `appendChild` / `replaceChild`,把 X 映射为 A | **P0** | | **C3** | `window.open('', '_blank')` + `window.opener` | dsh 主窗口 `apps/desktop/src/main.ts:235-237` 的 `setWindowOpenHandler` 对**所有** URL 返回 `{action:'deny'}`(http/https 直接外部浏览器打开)→ `window.open` **拿不到 WindowProxy**。且 `window.open('')` 得到 about:blank(空来源),会直接切断同源 | 新增主进程 popout 通道创建窗口;若坚持 live 跨窗,则**仅对内部 URL 放行** `dsh-app://app/?dsh-popout=`,以换取 `window.open` 的返回句柄 | **P0** | | **C4** | 「设同一 `affinity` 即可跨窗 `adoptNode`」 | **共享 renderer 进程是必要不充分条件。** 跨窗传递 DOM 节点还必须先拿到对方的**对象引用**(WindowProxy / opener / parent)。没有引用,即便同进程也无从把节点交给对方(`postMessage` 的结构化克隆明确排除 DOM 节点) | 见 C3:`affinity` **与**「放行内部 `window.open` 拿到句柄」二者缺一不可。(此项同时修正了我方设计文档 §11 的遗漏) | **P0** | | **C5** | 无代价地给主窗口加 `affinity` | 给主窗口设 `webPreferences.affinity` 是一次**全局行为变更**:所有 popout 与主应用共享同一 renderer 进程,**popout 崩溃会连带拖垮主应用** | v1 **默认不给主窗口加 affinity** → 跨窗默认走 **snapshot / clone 档**;live 跨窗降级为 experimental,需单独评估崩溃隔离(spike #S5) | **P1** | | **C6** | `skeleton.id = 'parent'`(骨架复制 id) | 同文档浮动会造成**重复 id**,违反 HTML 唯一约束,影响 `getElementById` / `