# 设计文档:GitHub 提示块的渲染方案 面向维护者。用户可见的行为见 [README.md](../README.md),开发与发版流程见 [CONTRIBUTING.md](../CONTRIBUTING.md)。 ## 1. 目标与约束 - 目标:DSH 回复里的 GitHub 提示块(`> [!NOTE]` 等五种)显示成 GitHub 的提示框,且随主题与界面语言变化。 - 约束一:**只改显示**。会话记录、事件流、导出的 Markdown 必须保持原文,因此不能在产生消息的环节改文本。 - 约束二:**不能覆盖宿主的对话渲染**。整节点覆盖会连带重写复制按钮、图片、流式渲染与折叠逻辑,等于把宿主行为复制一份。 - 约束三:插件不能因为一个提示块而弄坏界面。Client 插件入口失败会让整个 Web boot 失败(宿主的 `web boot: N entries did not activate`),所以 `apply` 必须不抛异常。 ## 2. 宿主现状(DSH 0.2.1-rc.5) - Markdown 渲染器是宿主内置的 mdast 渲染器:节点类型在固定的 `switch` 里渲染,`blockquote` → `
{children}
`,`paragraph` → `

{children}

`。 - 没有可注册的块级渲染扩展点,也没有「助手正文」槽位。`conversation.chat.node` 的 `assistant-step` 是**整节点**渲染器槽位(`replaceRisk: shadows-shipped-ui`),替换它要自己重写助手消息行。 - Markdown 容器的子节点之间由渲染器插入 `\n` 文本节点(用于保持行内空白),因此「第一个元素子节点」是 `:first-of-type` 而不是 `:first-child`。 - `break` 节点渲染成 `
` 加一个 `\n` 文本节点。 所以:**唯一不需要复制宿主行为的落点是渲染后的 DOM**。 ## 3. 方案取舍 | 方案 | 结果 | | --- | --- | | 注册 Markdown 块级渲染器 | 宿主没有这个扩展点 | | 替换 `assistant-step` 渲染器 | 要复制助手消息的全部宿主行为,宿主升级即碎 | | 装饰渲染后的 `blockquote` | 只碰属性、文本与一份样式表;最坏退化为普通引用块 | 选择第三条,并把侵入面压到最小:**不插入、不移动 React 节点**。 ## 4. DOM 契约 装饰依赖以下结构,全部来自 §2 的实测输出: ```html
"\n"

"[!NOTE]"
"\n" "另外顺带修好了……"

"\n"
``` | 依赖点 | 用法 | | --- | --- | | `blockquote` 的首个 `

` | 只处理这种形状;没有段落就不动 | | 该段落 `firstChild` 是以 `[!TYPE]` 开头的文本节点 | 识别的唯一依据;大小写不敏感,允许前后空格与制表符 | | 标记后紧跟 `
` | 该换行是空行,标记到 `data-dsh-gfm-alert-lead` 后由 CSS 隐藏 | | 段落其余文本 | 原样保留,不再改写 | 任何一条不成立,插件就不动这个块——宁可退化成普通引用块,也不猜。 ## 5. 装饰算法 1. 扫描:`apply` 时对 `document.body` 做一次全量扫描;此后由 `MutationObserver`(`childList`、`subtree`、`characterData`)增量处理新增子树与文本变化。 2. 识别:取 `blockquote` 首个 `

` 的 `firstChild`,用 `^[ \t]*\[!(NOTE|TIP|IMPORTANT|WARNING|CAUTION)\][ \t]*` 匹配。 3. 写入: - `data-dsh-gfm-alert=""`:样式表的类型钩子; - `data-dsh-gfm-alert-label="<本地化标题>"`:由 `content: attr()` 画成标题; - 若标记后紧跟 `
` 且文本已空,给段落加 `data-dsh-gfm-alert-lead`。 4. 剥离:把标记从文本节点值里切掉,其余文本不动。 5. 幂等:第二次跑时匹配不到标记,只做标题同步(语言切换走 `locale/change` → 重新写 `label` 属性)。 6. 流式:宿主重新渲染可能把标记写回文本节点;`characterData` 记录会立刻再走一遍 3–4 步,不产生可见闪烁。 `MutationObserver` 也会收到插件自己剥离文本触发的回调,因此步骤 3–5 必须无副作用地重复执行。 ## 6. 样式方案 - 提示框:容器规则给 `blockquote[data-dsh-gfm-alert]` 设圆角、内边距、底色与左侧 3px 色条。宿主对 `blockquote` 的默认规则是 `. blockquote`(0,1,1),本插件的属性选择器优先级更高,不依赖样式表注入顺序。 - 标题:`blockquote[data-dsh-gfm-alert]::before`,`content: attr(data-dsh-gfm-alert-label)`,加粗、主题色。 - 图标:`blockquote[data-dsh-gfm-alert] > p:first-of-type::before`,绝对定位到标题行左侧,`mask-image` 使用按类型内联的 SVG data URI(`encodeURIComponent` 生成),`background-color` 取强调色,因此图标颜色跟随主题。 - 颜色:全部来自 `--dsw-alias-*`(brand / state-success / state-warn / state-error / bg-layer-1 / border-l1 / label-primary)。 | 类型 | 强调色 | | --- | --- | | note | `--dsw-alias-brand-primary` | | tip | `--dsw-alias-state-success-primary` | | important | `color-mix(in oklab, brand 45%, state-error)`,不支持混色时回退到纯 `state-error` | | warning | `--dsw-alias-state-warn-primary` | | caution | `--dsw-alias-state-error-primary` | 底色是强调色与 `--dsw-alias-bg-layer-1` 的 7% 混色,浅色/深色主题下都由宿主 token 决定。 ## 7. 生命周期 - `apply(ctx)` 在 `ctx.effect` 内完成:注册 `gfm-alerts` 本地化词典(`en` / `zh`)、注入 `