dsh-md-linebreak — 正文里的换行不再被合并
它解决的问题
Web 客户端把 Markdown 交给 MarkdownText 渲染,而 CommonMark 的软换行(正文里
单独一个换行)在渲染树里只是一个文本节点里的 \n:
case "text": return node.value
文本节点里的换行在 HTML 里是可折叠空白,所以源码写了多行的段落会被画成一行:
第一行
第二行 → 显示为「第一行 第二行」
DSH 对用户自己发的消息是保留换行的(气泡用 white-space: pre-wrap),只有渲染出来
的 Markdown 会丢——这个插件补的就是这一处。
它做了什么
宿主半边在每次采集注入表时(HTTP 服务每次响应 index.html,以及 host 启动时那一次采集)
往表里推一行 style(webserver/index-inject),规则是:
:is([class*='_markdown_'], [data-dsh-part='message-body']) :is(p, td, th) { white-space: pre-line }
:is([class*='_markdown_'], [data-dsh-part='message-body']) li:not(:has(> :is(p, ul, ol, pre, blockquote, div, table, hr, h1, h2, h3, h4, h5, h6))) { white-space: pre-line }
pre-line就是全部修复:换行按换行显示,其余空白(空格、制表符)照常折叠, 长行仍然在容器边缘自动折行。等价于 GFM 的breaks: true。 如果你想让被换行的源码连缩进也逐字保留,把pre-line换成pre-wrap即可 (就在lib/index.js的STYLE里,改一处)。- 代码不动:
pre/code有自己的white-space策略;列表项只在内容是行内 时才命中规则——渲染器会在容器的块级子节点之间插入装饰性换行文本节点,pre-line会把那些画成空行,所以带嵌套列表 / 代码块 / 段落的列表项被排除在外。 - 两个锚点:
[class*="_markdown_"]是MarkdownText的根类(CSS Modules 会写成_markdown_<hash>_<line>,稳定的部分是本地名),[data-dsh-part="message-body"]是皮肤中心语义属性契约里「助手消息正文」的官方锚点(启用皮肤时才存在)。任一命中即可。
为什么走 index 注入表
那张表有两个消费者:webserver 每次响应 index.html 时把行渲染进 <head>;Desktop 壳
在启动 payload 里收到同一批行。一行同时覆盖两种载体。因此:
- 不写入 harness home,也不修改已安装的应用文件(
app.asar一个字节都不动); - 升级 DSH 不会把它冲掉,规则失效也只会表现成「换行又被合并」;
- 卸掉这个 bundle,下一次 host 启动(用浏览器访问 HTTP 端口的话则是刷新)后就没有这条规则了。
怎么生效 / 怎么验证
- 重启 DSH(桌面端必须重启,原因见下)。
- 回到会话里看一条正文写了多行的回复:每一行各自占一行。
- 校验行确实被推入注入表(不需要浏览器):
其中一项用真实 cordis 派发 + 官方$node tools\dsh-env\run-mdlinebreak.mjsrenderIndexInjections渲染真实的dist/index.html, 断言这条<style>落在<head>里、__DSH_BOOT_READY__未被扰动。 cordis_inspect_query(platform: host, provider: Config, method: listConfigs, input: {name: '@workspace/dsh-md-linebreak'})应能看到include:md-linebreak这一行,且状态不是inactive。
为什么桌面端要重启
注入表有两个消费者:HTTP 服务的每次 index 响应,以及桌面壳的启动 payload。浏览器直接访问
http://127.0.0.1:19387 时,刷新即可(每次响应重新收集);桌面端的窗口加载的是
dsh-app://app/(应用自有协议,Electron main 从 app.asar 里的同一份 dist 提供),注入行不是
从 index.html 里读的,而是窗口启动时经 IPC 取「host ready 那一刻采集的那一份」
(dsh-desktop-host/lib/index.js 的 ready 消息 → lib/main.js 的
ipcMain.handle(DESKTOP_IPC.boot))。新增的行因此只在下一次 host 启动时进入窗口,
刷新页面拿到的是同一份 payload。
怎么关掉
设置 → 插件里禁用这个 bundle,然后重启(或刷新,见上)。
彻底移除:plugin_manager(remove_bundle, target: "@workspace/dsh-md-linebreak")。
安装与维护
# 安装(会写入 profile 的 node_modules 与 dsh.profile.bundles)
plugin_manager(install_bundle, target: "file:C:\Users\wangjiezhe\Documents\deepseek-harness\default-workspace\dsh-md-linebreak")
# 改完源码后同步到已安装副本(pnpm 是 COPY,不是 junction)
pwsh -File dsh-md-linebreak\sync-installed.ps1
# 校验:逐文件 SHA256 + 关键标记
$node tools\dsh-env\verify-installed.mjs
dsh-md-linebreak/
package.json dsh.bundle.patch 指向 cordis.patch.yml
cordis.patch.yml 插入一行 id: md-linebreak
lib/index.js 规则本体 + 一行 style 注入(全部逻辑都在这一个文件里)
sync-installed.ps1 按内容哈希同步到已安装副本
README.md 这份文档
已知边界
- 换行只在正文层面恢复:代码块本来就不折叠;表格单元格在 GFM 里本来就不能跨行 (规则对它没有副作用)。
- 规则是 CSS,锚点依赖上游的类名 / 语义属性。上游若改名,表现是规则静默失效
(不会画错),此时更新
ANCHOR常量即可。 - 这条规则作用于所有
MarkdownText表面:聊天正文、思考过程(compact 变体)、 右侧文档预览。它们共用同一个渲染器,所以行为一致。
No comments yet. Be the first to write one.