修复芯片内容显示时会直接消失的问题 - #65
Conversation
宿主 layout mutation 误杀了正在观看的面板
|
补充一份独立验证,供维护者参考。 我在本机(DSH Desktop 2.0.11 / 内核 0.1.5-rc.2 / Windows 11)复现并修复了同一个问题,结论与本 PR 的两条修复一致:
我用 jsdom 把真实的 本 PR 的 另外两条我单独开了 PR(与本 PR 正交、可各自独立合并):
如果本 PR 想要回归用例,我可以把上面那套 jsdom harness 整理成一个 PR 提上来——它跑在上游代码上能稳定复现本问题(4 条 e2e 中 3 条失败),在修复后全绿。 |
add 批注内容浮窗增加删除按钮 fix 批注无法发送 fix 进入按钮显示超出
fix 回复芯片正则条件放宽
|
已合入 main,感谢 @fengbinmov 的高质量工作——卡死自激修复、0.1.7 会话识别、半透明浮层与编号图层这套改动是一次完整的适配,CHANGELOG 已随 PR 收录你的条目。 我已在本地跑通 接下来我们会在其上直接接手继续维护(改动会推到各贡献者分支、保留原署名):
如对本 PR 中的产品决策(半透明默认、编辑窗删除按钮、芯片匹配放宽)有保留意见,欢迎继续在这里讨论。 |
Merges the uiSession.adapter.current read face (omdsh-dev#65) with the localStorage / retainedBy fallback chain and the noSession toast + pending-retry UX (omdsh-dev#68). Session-switch detection now layers the uiSession source subscription, the legacy list subscription, and a 1s poll. Verified against dsh-v0.2.0-rc.2 kernel sources; npm run check passes 33/33.
`tipLayer` 是长期存活的 body 级单例(只在插件销毁时 remove),而气泡标签
(attachBubbleTag)与回复芯片(makeReplyChip)会被反复重建 —— 消息重渲染、语言
切换、切会话回来都会让它们换一代。改动前每个面板都在自己的 mouseenter 里往
`tipLayer` 追加一对监听器,却从不移除:
tag.addEventListener('mouseleave', bubbleHide)
tipLayer.addEventListener('mouseenter', bubbleKeep) // ← 每代 +1
tipLayer.addEventListener('mouseleave', bubbleHide) // ← 每代 +1
回复芯片同理(keep / hide)。于是每次重建净增两个闭包,它们持有已废弃的 grace
定时器与已被移出 DOM 的触发元素;关闭决策不再由「当前面板」决定,而是由历史上
任意一代的定时器决定 —— 这既让面板的关闭时机变得不可预测,也随会话变长持续泄漏。
改动:
- 共享容器上只注册两处固定监听器 sharedTipMouseEnter / sharedTipMouseLeave;
- 新增 `tipActiveHide` 指针:由面板在 mouseenter 时登记 `{ keep, hide }`,
共享监听器按它派发;
- 新增 releaseActiveTip():面板真正关闭时解除登记(三处宽限到点的关闭都调用),
避免残留钩子仍被派发;
- 移除三类面板中各自的 tipLayer.addEventListener。
对外行为保持不变:指针进入面板仍会取消关闭,离开仍会按各自的 250ms 宽限关闭。
回归测试 test/shared-listeners.test.mjs(8 条):结构断言「tipLayer 上恰好两处
监听器」+ 把真实的 sharedTipMouseEnter / sharedTipMouseLeave / releaseActiveTip
抽到 vm 沙箱里按行为验证派发目标、面板换代、无登记空操作、解除登记后不再派发。
同一套用例跑上游原版 client.js:8/8 失败;本分支 8/8 通过。
注:本 PR 与 #65、以及「跨间隙保活」那个 PR 均正交。#65 引入归属模型解决「宿主
layout mutation 用 updateChip 抹掉面板」与「定时器跨面板误杀」;本 PR 只处理
「监听器挂在共享容器上、随面板重建累积」这一条。
The listener-leak fix (#67) and the tipOwner attributed-clear mechanism (#65) are orthogonal: tipActiveHide removes the per-rebuild listener accumulation on the shared tipLayer, tipOwner stops unattributed clears from killing a visible panel. All clearTip/presentTip ownership pairs kept; releaseActiveTip fires on every real close path.
面板定位用 r.bottom + 6 / r.top - h - 6,与触发元素之间必然留 6px 间隙。鼠标从 触发元素移向面板时会先触发 mouseleave,进入「谁都不在」的空档;当前唯一阻止关闭的 是固定 250ms 宽限(官方 HoverCard 的 pointer-grace 思路)。于是: - 指针停在间隙里把 250ms 走满 → 面板消失,想点「删除」按钮常常点不中; - 触摸板细调或系统卡顿更容易命中; - 面板被放到上方时(top < 8 的分支)间隙还更大。 改动: - 用 pointermove(capture)持续记录实时指针坐标; - 新增 pointerInsideRect / pointerWithinTipLayer / shouldKeepTipOpen:宽限到点时 复查指针是否仍在「面板 ∪ 触发元素」的并集内(容差 10px,覆盖 6px 间隙); - scheduleHide / hide / bubbleHide 接收触发元素,统一用 shouldKeepTipOpen 判定; - teardown 移除 pointermove 监听。 为什么必须用实时坐标:mouseleave 事件里的 clientX/clientY 是离开那一刻的位置, 250ms 后再拿它判断会读到过期值,等于没判。 回归测试 test/pointer-gap.test.mjs(7 条):把 client.js 里真实的 pointerInsideRect / pointerWithinTipLayer / shouldKeepTipOpen / scheduleHide 抽到 vm 沙箱里按行为验证 —— 覆盖指针停在触发元素上 / 停在 6px 间隙里 / 移进面板 / 真正远离(仍会正常关闭)/ 宽限期内移回 / cancelHide / 生命周期注册。 注:本 PR 与 #65 正交。#65 解决「宿主 layout mutation 触发 updateChip 抹掉面板」 和「250ms 定时器跨面板误杀」;本 PR 解决「指针跨间隙时被固定宽限关掉」。两者互不 依赖,可各自独立合并。
fix 芯片在显示内容后会直接消失导致无法正常观看(当回话页面更新时updateChip会将其抹掉)
fix 芯片的 250ms 延迟 hide 会误杀另一个芯片刚显示的面板(快速从 A 滑到 B 时)