Skip to content

fix: 批注高亮与编号不再压住会话顶栏 (#62) - #63

Merged
taekchef merged 1 commit into
omdsh-dev:mainfrom
PerryLink:fix/header-band-clip-62
Sep 30, 2026
Merged

taekchef merged 1 commit into
omdsh-dev:mainfrom
PerryLink:fix/header-band-clip-62

Conversation

@PerryLink

@PerryLink PerryLink commented Sep 21, 2026 •

Copy link
Copy Markdown

修复 #62。

根因

标记层是 body 级的 position: fixed 图层(client.js:912,z-index: 900,编号胶囊 940)。改动前
renderMarkers() 只在这个图层上挖了一个 evenodd 洞 —— 输入框那个(#50 的修法)—— 所以输入框以外的一切,
包括会话顶栏横带,都仍然可绘制。顶栏是文档流里的普通元素,渲染在会话滚动体之上
(ConversationMainPanel.tsx:45 先渲染 conversation.session.header 槽位,再渲染内容),因此被批注的
原文一旦滚到顶栏之下,高亮矩形与编号胶囊仍按视口坐标绘制,直接压在顶栏上。

修法

沿用挖洞方案,再为顶栏横带挖第二个洞,位置由宿主钩子 [data-conversation-scroll] 推导(滚动体,渲染于
packages/client/ui-conversation/src/client/skeleton/ConversationContent.tsx:189):

  1. 新洞覆盖会话列中「视口上沿 → 滚动体上沿」这段横带;
  2. chipTop 的下限从「视口上沿 4px」抬到「滚动体上沿以下 4px」,使本会落进洞里的胶囊被推到洞以下,
    而不是被整块裁掉。

这个洞有意只取 scrollBody.left..scrollBody.right,而不是 #62 建议的整幅宽度:#57 的侧边栏
文件预览(ui-sidebar-documentpreview,[data-document-markdown] 与 [data-code-preview] 所在处)
在会话列之外,整幅宽度的洞会把它们的标记一并裁掉。

其余不变。若 [data-conversation-scroll] 不存在或宽度为 0,markerViewport() 返回 null,裁剪路径
完全退回原先只有输入框的多边形。

宿主钩子的稳定性

[data-conversation-scroll] 不是新钩子:它由 2026-07-29 的 a4602b959e("feat: optimize chat page
scroll area")引入,是 dsh-v0.1.0-rc.7 直到 dsh-v0.1.6-alpha.2 的祖先,即本插件支持的整个区间内
都存在。宿主自己的客户端测试就在用它 —— ui-conversation/tests/skeleton.client.spec.tsx:484、
ui-chat/tests/chat-view.client.spec.tsx:2688、
ui-conversation/tests/assembly-surfaces.client.spec.tsx:145。

对我自己在 #62 里那条评论的更正:我在那里把引入提交写成了 e62587c163,不对。git grep -c 在
a4602b959e^ 上匹配 0 个文件、在 a4602b959e 上匹配 13 个;而 e62587c163(2026-09-17)只是改了
该属性的一处既有用法,甚至不在 dsh-v0.1.0-rc.7 的区间里。那条评论的结论(钩子存在、稳定、早于支持
区间)不受影响,错的只是归属。

回归重点与取舍

① 顶栏下沿是结构上精确的,不是近似。 .root 是 flex-direction: column,顶栏槽位是它的首个子节点,
紧跟着 .body > .scrollBody;.header 是 flex: none 且没有任何 margin,.body 是 flex: 1 且没有
上外边距/上内边距,.scrollBody 只有 margin-right: 2px(水平)。⇒ [data-conversation-scroll] 的
getBoundingClientRect().top 就是顶栏下沿,直接可用。

② 为什么从滚动体推算,而不是用 [data-conversation-header-corner]。 那个属性确实存在
(packages/client/ui-conversation/src/client/skeleton/ConversationSession.tsx:137,一个 headerCorner
容器),但它标的是顶栏内部的角落容器,不是顶栏根节点;顶栏根节点只有 CSS Modules 哈希类名,宿主对外
给的是槽位名(conversation.session.header 等)而非 DOM 属性。所以从滚动体推算更稳。

③ #50 的输入框洞不会被覆盖。 新 clipPath 是把两个洞拼进同一个 polygon(evenodd, …),不是替换;
test/layout-overlay.test.mjs 里有一条专门的回归用例断言输入框洞与顶栏洞同时存在
(输入框挖洞(issue #50)保持原样,两个洞互不影响)。侧栏文档预览不被误裁同理有专门断言。

本 PR 不做的事:不改动标记层的挂载位置(把它挂进会话滚动容器会牵动滚动、裁剪与坐标语义,评审面过大,
应另开一条)。

验证

  • node --test test/*.test.mjs —— 25 passed, 0 failed(Windows 11 / Node v22.22.3)。
  • test/layout-overlay.test.mjs 新增 4 个用例,沿用 placeAbove 那个用例既有的「抽取源码再 eval」写法。
    它们用桩 DOM 驱动真实的 renderMarkers(),断言的是插件实际安装的 clipPath,而不是某个辅助函数
    是否存在。

反证 —— 把新断言对着未打补丁的 client.js 跑:

✖ renderMarkers 的 clipPath 挖掉会话顶栏横带,高亮不再压住顶栏(issue #62)
  AssertionError [ERR_ASSERTION]: header hole missing from
  polygon(evenodd, 0 0, 100% 0, 100% 100%, 0 100%, 0 0, 120px 600px, 820px 600px, 820px 700px, 120px 700px, 120px 600px)
ℹ tests 7  ℹ pass 5  ℹ fail 2

打印出来的那个多边形就是缺陷本身:只有一个洞(输入框),顶栏横带整条可绘制。

本机验证(真实浏览器)

上一版这一段写的是「这台机器没有浏览器」——那是我写错了。 本机装了
Chrome 153.0.8010.52 / Edge 148 / Firefox 156;test/overlay-browser.mjs 也不是
「只断言 CSS.supports(...)」,它已经用 document.elementFromPoint() 验过输入框洞真的挡得住命中。
真正的原因是下面第 3 条:那个脚本在 Windows 上跑不起来。现在三条都补跑了。

1. 插件自带的浏览器 E2E —— 本机通过

test/overlay-browser.mjs,Chrome 153.0.8010.52(Windows 11,channel: 'chrome')+ Playwright 1.62.1:

PASS: 工具条下方定位、标题栏拖动、边界、缩放、保存、裁剪、重新编辑、取消、无页面错误

⚠️ 但它不能区分本 PR:同一个脚本在未打补丁的 client.js 上同样 PASS —— 它只覆盖 #50 的输入框洞。
它是「没有弄坏」的证据,不是「修好了」的证据。

2. 顶栏横带的裁剪,用真实浏览器引擎单独验(本 PR 缺的就是这一半)

真实布局夹具(顶栏 / [data-conversation-scroll] / [data-composer-card] / 会话列之外的侧边栏),
把插件实际安装的 clipPath 交给引擎,再用 document.elementFromPoint() 逐点验证裁剪是否真的生效:

探针 补丁后 未打补丁
会话列内的顶栏横带 ✅ 被裁掉(该点命中顶栏) ❌ 命中标记层 —— 缺陷复现
输入框洞(#50) ✅ 仍被裁掉 ✅
会话正文 ✅ 可绘制 —
侧边栏(与横带同 y、在会话列之外,#57) ✅ 可绘制 —

先断言该引擎确实在命中测试里遵守 clip-path(零面积裁剪的元素不可命中),否则整张表没有意义 —— 这条自检通过。

3. 顺带发现:test/overlay-browser.mjs 在 Windows 上跑不起来(与本 PR 无关,本 PR 没有改它)

它用 new URL('../client.js', import.meta.url).pathname 定位 client.js;Windows 上这得到
/D:/...,Playwright 于是去开 D:\D:\... 并报 ENOENT。改成
fileURLToPath(new URL('../client.js', import.meta.url)) 之后,上面第 1、2 条才跑得起来。
要的话我另开一条一行 PR 修它 —— 按你们的分工,我不把无关改动塞进这条。

仍未做的

没有对着真实 DSH Desktop 做端到端确认(本机没装 Desktop 2.0.13)。上面验的是裁剪语义与几何,
外加插件自身浏览器 E2E 的回归通过;宿主里的实际观感仍建议在 2.0.13 / 内核 0.1.5-rc.2 的环境上复验一遍。

另外,如果你更希望由宿主在顶栏根节点上直接暴露一个 data-conversation-header 属性,说一声,我改成
消费它,而不是从滚动体反推这条横带。

@taekchef

Copy link
Copy Markdown
Collaborator

感谢 @PerryLink ——两个关键贡献都在这次合入中落了地:

  1. 「只裁会话列」的约束:顶栏横带在侧边栏上方同样延伸,整幅裁掉会把 0.1.5+ 侧边栏文件批注一并裁掉——这个洞察正是 修复芯片内容显示时会直接消失的问题 #65(已先合入)的 _header 全局启发式所缺的保护,现在逐个头部矩形先与「会话列」求交、完全在列外的直接丢弃(b578e8d / a4beb43)。
  2. data-conversation-scroll 属性选择器:比 CSS Modules 哈希类名跨版本稳定,已对照 dsh-v0.2.0-rc.2 源码核实仍在(ui-chat/ChatView.tsx),现在它是会话列定位的首选、类名启发式只作回退。

情况说明:本 PR 基于 #65 之前的 main,而 #65 重写了同一片 renderMarkers 区域,无法直接合并;维护者接手后把你的两个核心洞见组合进了 #65 的编号图层架构(编号下限抬到会话列上沿 + 4px,仅当原文起于其下;原文已滚入顶栏之下时仍走「滚出可视区即消失」),你的原始提交 c5d81f4 已完整进入主仓历史,本 PR 已标记为 Merged,署名不变。你原有的 vm 沙箱测试适配后全部保留——现在直接驱动真实 renderMarkers 验证「侧边栏头不被裁剪」。54/54 测试全绿,CHANGELOG 已注明洞察来自你。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants