Bug 报告
标题
[Bug] genui 与 python 代码框共存时渲染错误,仅保留 genui
严重程度
Medium —— 不影响单代码块使用,但在同一提示词内混用 genui 与普通代码块(如 python)时渲染结果错误,影响提示词模板复用。
环境
- 组件:DeepSeek Harness Web GUI(http://127.0.0.1:3080)/ dsh-ui 渲染器
- 插件:
@omdsh-dev/dsh-genui 0.8.3(经 github:omdsh-dev/dsh-genui 安装)
- 宿主:无
fence-registry 扩展点的 DSH 构建(插件因此走 DOM 渲染通道)
- 触发方式:任意同时包含
dsh-ui 围栏与 python 围栏的提示词
复现步骤
- 输入一段提示词,内容为「简单重复下述话语」。
- 提示词内依次包含:
- 一个 ```dsh-ui 围栏(内含 mermaid + table 组件);
- 一个 ```python 代码块(如
@dataclass 类型提示代码)。
- 观察模型回复 / 界面渲染结果。
期望行为
dsh-ui 与 python 两个围栏应各自独立渲染:dsh-ui 呈现交互 UI,python 呈现代码块,两者同时存在且互不影响。
实际行为
当 genui(dsh-ui)与 python 代码框同时存在时,渲染错误——只留下 genui 渲染后的内容,python 代码框丢失或未渲染。
复现示例(最小可复现)
提示词
简单重复下述话语。
{"title":"从二值掩膜到中心线的正确流水线","gap":10,"items":[
{"type":"mermaid","code":"flowchart LR\n A[\"二值掩膜\"] --> B[\"① 连通域/轮廓分割\"]"},
{"type":"table","columns":["步骤","做什么"],"rows":[["① 连通域分割","拆分成独立组件"]]}
]}
@dataclass
class LineSegment:
points: list
orientation: str
预期输出
- [UI] dsh-ui 交互卡片正常渲染
- [代码] python 代码块正常渲染
实际输出
- [UI] dsh-ui 正常渲染
- [代码] python 代码块缺失 ✗
触发条件(补充)
只有「一个 ```dsh-ui 围栏 + 任意第二个代码块(不限于 python,java / ts / bash 等皆可)同处一条消息」时才触发;消息里只有一个 dsh-ui 围栏时不会复现。根因是围栏发现的兜底逻辑误判,与具体语言无关。
根因分析
(以下代码分析我看不懂,只能放在这里供您参考。谢谢!)
宿主无 fence-registry 时,插件走 DOM 通道:靠 MutationObserver 观察会话 DOM,把带 dsh-ui 标签的代码块原位替换成 GenUI。问题出在围栏发现函数——lib/client.js 中的 zn / Rn / Pn(对应 src/client 里 DOM 通道的发现逻辑)。
宿主把这条消息渲染成大致如下 DOM:
<div class="markdown"> <!-- MarkdownText 根容器 -->
<p>简单重复下述话语。</p>
<div class="md-code-block"> <!-- dsh-ui 围栏 -->
<div class="banner">…infostring 文本 = "dsh-ui"…</div>
<pre>{"title":…}</pre>
</div>
<div class="md-code-block"> <!-- python 围栏 -->
<div class="banner">…infostring 文本 = "python"…</div>
<pre>@dataclass…</pre>
</div>
</div>
围栏发现 zn() 分两步:
// 第一步:直接收集已知类名代码块
for (let r of e.querySelectorAll(jn)) // jn = ".md-code-block, .code-block, .code-block-small"
(r.parentElement === null || r.parentElement.closest(jn) === null) && (… n.push(r) …);
// 第二步(兜底):遍历所有 <pre>,向上回溯找 dsh-ui 围栏包裹层
for (let r of e.querySelectorAll(`pre`)) {
let i = Rn(r, e); // 从 <pre> 向上最多 4 层,找 Pn() === "dsh-ui" 的祖先
… n.push(i) …
}
判定函数 Pn(e) 用「元素里有一个 dsh-ui 叶子、且不在【第一个】<pre> 内」来认定 e 是 dsh-ui 围栏:
function Pn(e) {
let t = e.querySelector(`pre`); // e 里的【第一个】<pre>
for (let n of e.querySelectorAll(`*`))
if (n.childElementCount === 0 && n.textContent === `dsh-ui` && !(t !== null && t.contains(n)))
return `dsh-ui`;
return null;
}
对 python 的 <pre> 执行 Rn 向上回溯时:
- 第 0 层 = python 的
.md-code-block:Pn 看到 banner 是 "python",返回 null;
- 第 1 层 =
.markdown 根容器:Pn 先取「第一个 <pre>」(= dsh-ui 围栏的 <pre>),再找 dsh-ui 叶子——dsh-ui 围栏的 .infostring 正好是 "dsh-ui" 且不在该 <pre> 内 → Pn 返回 "dsh-ui";
- 于是
Rn 返回 .markdown 根容器,兜底循环把整条消息容器当成「围栏」加入列表。
随后挂载函数 c(root) 对这个根容器执行:
e.style.display = `none`; // 隐藏【整条消息】
e.after(f); // 在整条消息后插入 GenUI
于是整条消息被 display:none,只剩插入的 GenUI。
一句话:Pn 用「第一个 <pre> + 不在该 <pre> 内的 dsh-ui 叶子」来判定,当 e 是包含多个代码块的容器时,嵌套代码块的 dsh-ui 标签被误当成 e 自己的标签;Rn 又向上回溯到了共享容器。
修复方案
推荐两个都改;方案一即可修掉本 issue,方案二是对判定谓词本身的加固。
方案一(最小改动):兜底循环跳过「已由第一步选择器命中」的 <pre>,避免对已识别代码块内的 <pre> 再做向上回溯:
for (let r of e.querySelectorAll(`pre`)) {
if (r.closest(jn) !== null) continue; // ← 新增:已由第一步处理,跳过
let i = Rn(r, e);
…
}
方案二(加固):Pn 只在「dsh-ui 标签属于 e 自己的 banner、而非嵌套代码块的 banner」时才认定是围栏:
function Pn(e) {
let t = e.querySelector(`pre`);
for (let n of e.querySelectorAll(`*`)) {
if (n.childElementCount !== 0 || n.textContent !== `dsh-ui`) continue;
if (t !== null && t.contains(n)) continue; // 标签在正文里,不算 banner
let block = n.closest(jn);
if (block !== null && block !== e) continue; // ← 新增:标签属于嵌套的另一个代码块
return `dsh-ui`;
}
return null;
}
附注
- 本 bug 只在 DOM 通道存在。新版 DSH 提供
fence-registry 扩展点时,插件走 Registry 通道、围栏进宿主流式渲染管线,没有这条「DOM 向上回溯误判」路径——这解释了为什么部分环境复现、部分不复现。
- 建议在源码
src/client 中修复后发版,而不是直接改已安装的 lib/client.js(git 依赖一更新即被覆盖)。
Bug 报告
标题
[Bug] genui 与 python 代码框共存时渲染错误,仅保留 genui
严重程度
Medium —— 不影响单代码块使用,但在同一提示词内混用 genui 与普通代码块(如 python)时渲染结果错误,影响提示词模板复用。
环境
@omdsh-dev/dsh-genui0.8.3(经github:omdsh-dev/dsh-genui安装)fence-registry扩展点的 DSH 构建(插件因此走 DOM 渲染通道)dsh-ui 围栏与python 围栏的提示词复现步骤
@dataclass类型提示代码)。期望行为
dsh-ui 与python 两个围栏应各自独立渲染:dsh-ui 呈现交互 UI,python 呈现代码块,两者同时存在且互不影响。实际行为
当 genui(dsh-ui)与 python 代码框同时存在时,渲染错误——只留下 genui 渲染后的内容,python 代码框丢失或未渲染。
复现示例(最小可复现)
提示词
简单重复下述话语。
预期输出
实际输出
触发条件(补充)
只有「一个 ```dsh-ui 围栏 + 任意第二个代码块(不限于 python,java / ts / bash 等皆可)同处一条消息」时才触发;消息里只有一个 dsh-ui 围栏时不会复现。根因是围栏发现的兜底逻辑误判,与具体语言无关。
根因分析
(以下代码分析我看不懂,只能放在这里供您参考。谢谢!)
宿主无
fence-registry时,插件走 DOM 通道:靠 MutationObserver 观察会话 DOM,把带dsh-ui标签的代码块原位替换成 GenUI。问题出在围栏发现函数——lib/client.js中的zn/Rn/Pn(对应src/client里 DOM 通道的发现逻辑)。宿主把这条消息渲染成大致如下 DOM:
围栏发现
zn()分两步:判定函数
Pn(e)用「元素里有一个dsh-ui叶子、且不在【第一个】<pre>内」来认定e是 dsh-ui 围栏:对 python 的
<pre>执行Rn向上回溯时:.md-code-block:Pn看到 banner 是 "python",返回 null;.markdown根容器:Pn先取「第一个<pre>」(= dsh-ui 围栏的<pre>),再找dsh-ui叶子——dsh-ui 围栏的.infostring正好是"dsh-ui"且不在该<pre>内 →Pn返回"dsh-ui";Rn返回.markdown根容器,兜底循环把整条消息容器当成「围栏」加入列表。随后挂载函数
c(root)对这个根容器执行:于是整条消息被
display:none,只剩插入的 GenUI。一句话:
Pn用「第一个<pre>+ 不在该<pre>内的 dsh-ui 叶子」来判定,当e是包含多个代码块的容器时,嵌套代码块的 dsh-ui 标签被误当成e自己的标签;Rn又向上回溯到了共享容器。修复方案
推荐两个都改;方案一即可修掉本 issue,方案二是对判定谓词本身的加固。
方案一(最小改动):兜底循环跳过「已由第一步选择器命中」的
<pre>,避免对已识别代码块内的<pre>再做向上回溯:方案二(加固):
Pn只在「dsh-ui 标签属于e自己的 banner、而非嵌套代码块的 banner」时才认定是围栏:附注
fence-registry扩展点时,插件走 Registry 通道、围栏进宿主流式渲染管线,没有这条「DOM 向上回溯误判」路径——这解释了为什么部分环境复现、部分不复现。src/client中修复后发版,而不是直接改已安装的lib/client.js(git 依赖一更新即被覆盖)。