← 返回列表
未验证
一句话简介:DSHDeepSeek HarnessWeb 纯客户端插件,为对话输入框composer提供…
尚未跑自动兼容性验证,可查看页面内的依赖与入口分析。 · 最近上游提交 2026/9/15 · 已提供中文文档
综合分
29.8
GitHub 分
29.8
用户评分
—
★ Stars
0
周下载量
—
安装插件(需先安装 dsh CLI 引擎:npm install -g @deepseek-ai/dsh)
dsh plugin --profile web add chendefine/dsh-composer-markdown该插件未发布到 npm,走 GitHub 源安装(pnpm 若拦截 prepare 脚本,按其提示在 pnpm-workspace.yaml 的 allowBuilds 中放行后重跑)
数据截至 2026/9/18(元数据每日更新 · 实装验证按队列轮转,单条结论的验证时间见上方)
用户评分
还没有人投票,来当第一个
订阅周报,不错过优质插件更新
每周一封 · 高评分插件 + 新用户活动
README
dsh-composer-markdown
English · npm · GitHub
npm license node CI stars
一句话简介:DSH(DeepSeek Harness)Web 纯客户端插件,为对话输入框(composer)提供 Markdown 编辑增强 —— 列表自动续行与序号规整、行内代码样式(反引号渲染后隐藏)、代码块围栏自动闭合与原子化交互。所有编辑手势做在 Shift+Enter 上;Enter 保持 DSH 原生「直接发送」语义,从不拦截。除「有序列表序号规整」外一切只作用于编辑态视觉与按键,发送文本始终保持字面 Markdown、逐字节保真。
- 平台:web(DSH Web GUI,dsh plugin --profile web)
- 形态:纯客户端插件 —— host 半为空操作载体,零运行时依赖,可在「设置 → 插件」中随时开关
- 测试:475 个断言全部通过(零依赖测试器:产物新鲜度 + 纯逻辑单测 + bundle 形状断言 + 可选宿主契约冒烟)
- 许可:MIT
目录
- 用途与适用场景
- 功能一览
- 安装
- 使用
- 技术架构
- 兼容性
- 已知限制
- 开发须知与规范
用途与适用场景
DSH 的 composer 是一个 Lexical 纯文本编辑器,原生只提供「Enter 发送 / Shift+Enter 软换行」——写 Markdown 全靠手敲字面字符。本插件把输入框的 Markdown 编辑体验补齐到成熟聊天产品的水平:
- 在输入框里写结构化内容:列表、步骤、代码片段、代码块,不用离开键盘去点工具栏;
- 让字面 Markdown 变得可读:• 圆点标记、等宽序号、行内代码底色、成对的 - 保住「纯文本」的契约:一切增强都是编辑态视觉层,发出的就是你敲的字面 Markdown,逐字节一致 —— 模型侧、消息渲染侧、剪贴板投影完全不受影响;
- 不改变任何原生习惯:Enter 照常发送,/、@ 菜单、@xxx 引用 chip、图像粘贴、IME 中文输入全部照旧。
不适用:需要富文本(非 Markdown)编辑、或希望在发送阶段改写/美化 Markdown 语法的场景 —— 本插件刻意不做任何发送态变换。
功能一览
你输入 编辑态呈现 发送文本
─────────────────────────────────────────────────────────────────────
- 苹果 • 苹果 (圆点标记) - 苹果
⇧↵ (Shift+Enter) - ␣ (下一行自动带前缀) …
1. 第一步 1. 第一步 (等宽序号) 1. 第一步
⇧↵ 2. ␣ (自动递增,组内恒连续) …
code code (等宽+底色,反引号隐藏) code
│ ts 徽标 │ (空行)
│ ␣ (光标在此) │
└─────────────────┘
按功能域:
| 域 | 能力 |
|---|---|
| 列表 | 无序/有序续行(任意视觉行与任意嵌套深度,含软换行与多行粘贴行)、光标处截断续行、空项退出、组内序号恒连续(删行接上/隔断重起/合并累加/嵌套感知)、中间插入整体顺延、层级缩进阶梯(Tab 降级 / Shift+Tab 或前缀后 Backspace 升级,整棵子树递归联动,顶层取消列表回归普通文本;降级上限 = 父级下一级,不可无限降级)、前缀原子删除(缩进并入原子)、方向键原子跨越(含缩进)、原子内部不可停留、标记差异化渲染(• 圆点 / 等宽序号) |
| 行内代码 | 成对反引号内部文本呈行内代码样式、反引号闭合瞬间隐藏(零步进隐形,保留字体度量)、选区覆盖时显形(所选即所见) |
| 代码块 |
安装
前提
- DSH 0.1.2-alpha.x(composer 为 Lexical 纯文本编辑器 + @lexical/plain-text,lexical 0.49);
- Node.js ^22.19.0 || >=24.0.0(与 DSH 本体的支持窗口一致,包的 engines 字段与其对齐);
- 现代桌面浏览器(Chrome / Edge / Firefox / Safari)。
安装插件
包内声明了 dsh.bundle.patch(cordis.patch.yml:一条 insert 行,挂载 host 半空操作 entry,使插件出现在「设置 → 插件」中)。
本地检出(开发常用):
sh
dsh plugin --profile web add link:/absolute/path/to/dsh-composer-markdown
从 npm registry:
sh
dsh plugin --profile web add dsh-composer-markdown
从 GitHub(构建产物 client.js 随仓库提交,即使 pnpm 跳过 prepare 构建脚本插件也能正常加载;建议用 # 固定 commit,避免后续 push 改变实际运行的代码):
sh
dsh plugin --profile web add github:chendefine/dsh-composer-markdown
pnpm ≥ 10 会在用户显式放行前阻塞 git 依赖的 prepare 脚本。若希望安装时从 src/client/ 重新构建 client.js,请把 pnpm 打印的键(如 dsh-composer-markdown: true)加进 profile 的 pnpm-workspace.yaml 的 allowBuilds 下再重新 add —— 这一步等于授权该包在你机器上于安装期执行代码,请仅放行信任的源。
或经 DSH 插件市场(设置 → DSH 插件市场)—— 给仓库打上 dsh-plugin topic 即被自动收录。
安装后重启 dsh web 服务并刷新浏览器页面生效。本地 link 方式安装后,修改 src/client/ 源码并执行 node scripts/build.mjs 重新生成 client.js,开发态下 HMR 会自动热载重新生成的产物(无需刷新页面;若 HMR 未运行则刷新一次)。
开关与卸载
- 开关:设置 → 插件 → composer-markdown,禁用即恢复全部原生行为(刷新后生效);
- 卸载:dsh plugin --profile web remove dsh-composer-markdown。
禁用/卸载时自动清理:document 监听器、注入的样式标签,并清除编辑器内所有代码样式标志位(草稿文本内容不变)。
使用
所有编辑手势在 Shift+Enter 上;Enter = 原生发送,从不拦截;未命中任何场景的 Shift+Enter = 原生软换行。Tab / Shift+Tab 仅当光标行是列表项时被认领(层级阶梯),其余场景保持原生(焦点移动等)。普通 Backspace / Delete / 方向键仅在恰好命中下述边界时被认领,其余保持原生逐字符/逐字符移动。
列表
| 操作 | 触发 | 行为 |
|---|---|---|
| 无序/有序续行 | 行首 - / * / N. (任意缩进深度)后输入内容,按 Shift+Enter | 下方新行自动带 {indent}- / {n+1}. 前缀(嵌套层级原样保留),光标停在前缀后;段落首行、软换行行、多行粘贴行任意视觉行均生效 |
| 行中间截断 | 光标在列表项内容中间按 Shift+Enter | 从光标处截断:光标后的内容移到新 item、接在新前缀之后(字节保真);光标在行首/前缀内部/行尾时维持「下方新增整行」 |
| 中间插入顺延 | 在有序组中间 Shift+Enter 插入新项 | 新行拿到 n+1,其下同组所有成员依次 +1(1. 2. 3. 在 1 与 2 之间插入 → 1. 2. 3. 4.),整组保持连续且不重复;插入与顺延同属一个撤销步 |
| 序号规整(不变式) | 任意改动之后(删除/插入行、空项退出、粘贴、撤销/重做) | 每个有序组(连续有序行、同缩进、围栏外)恒为 1. … n. 从首成员连续编号:删行接上;组被普通行/空行/无序行隔断 → 后一组从 1. 重起;隔断行被删、两组相连 → 合并累加;更深缩进只挂起外层组(嵌套子列表不打断外层,外层缩进回归时续接原计数),嵌套组自身独立规整。有意取舍:组不能保留非 1 起始号或手动跳号——手改序号立即回弹(连续性即契约) |
| 空项退出 | 只有前缀的空列表项上按 Shift+Enter | 移除前缀,保留空段(软行上保留空行)—— 连按两次即可干净退出列表;光标留在清空后的本行(v0.1.2)——空项在尾部、段中或块首行均如此,既不回上一项行尾、也不跳下一行行首 |
| 层级缩进(降级) | 光标在列表项那一行的任意一处(含内容中间)按 Tab | 该项连同下方全部更深层级(嵌套子列表、更深缩进的续行文本)每行缩进 +2 空格;同层兄弟项与其后内容不动;光标在行内相对位置随行平移;整棵子树一次按键、一个撤销步;嵌套有序组随后按不变式独立规整(如 2. 挂到上层后重排为嵌套组的 1.)。降级有上限(v2.9):任何项最多只能降到其父级(上方最近的列表项,普通行跳过、空行/围栏隔断)的下一级——已处于父级下一级的项、以及列表首项(上方无可嵌套的父级)按 Tab 为认领但不动的空操作(焦点不会因此跳出输入框),不会无限降级 |
| 层级缩进(升级/取消) | 同上位置按 Shift+Tab,或折叠光标恰在前缀原子之后(内容首)按 Backspace | 嵌套项(缩进 ≥1 空格)→ 该项及全部子层级各 −2 空格(奇数缩进按 min(2, 缩进) 取整到 0);顶层项 → 取消列表:删除整个前缀原子,只留正文成普通文本行(光标留在该行,v0.1.2),子层级仍各升一级。取消列表留下普通行会隔断有序组,其后同缩进成员按不变式从 1. 重起。长按 Tab 降一级后停在层级上限(认领但不动);长按 Shift+Tab 每次重复升一级——两键焦点都不会从列表行跳走 |
| 前缀原子删除 | 折叠光标恰在列表项行首(整个原子之前)按 Delete | 缩进 + 前缀整体一次删净(如 ␣␣1. 六字符一并消失),该行回归顶层普通文本,独立撤销步;缩进无法被逐字符选中删除 |
| 前缀方向键原子跨越 | 折叠光标贴在任意列表原子边界按 ← / → | → 在行首一次跨过整个 ␣␣1. / ␣␣- (缩进含在内)落到内容首字符左侧;← 在前缀后一次跨回行首;光标已落入原子内部(含缩进空格内,点击/纵向移动)时按方向键直接跳出 —— 键盘永不逐字符穿过缩进或前缀内部。Shift+方向键保持原生(选区仍可精确覆盖序号字符手动改号) |
| 标记差异化渲染 | 草稿中任意视觉行行首的 - / * / N. (围栏外,含空前缀) | 有序序号换用等宽字体;无序横线/星号渲染为 • (圆点 + 空格为一个整体标记单元);标记字节原样保留在草稿/复制/发送文本中;选区真正覆盖标记字符时显形原始字符 |
| 原子内部不可停留 | ↑/↓、鼠标点击等任意途径把折叠光标落入原子内部(含缩进空格之间) | 光标被移到最近的原子边界(平局取行首边界);边界本身(行首/内容首)是合法停留点;选区不受影响 |
行内代码
- 段内出现成对反引号 非空内容 ,且内容不跨行、首尾紧邻定界的字符非空白(含全角空格/NBSP)时:内部文本呈行内代码样式(等宽字体、浅底、圆角,复用 DSH 主题 token);
- 配对反引号闭合瞬间即隐藏(不可见、不占宽,仍逐字节保留在草稿与发送文本中);光标移入配对内部或紧邻两侧时保持隐藏;
- 只有选区真正覆盖到某个反引号字符时(Shift+方向键、全选等)该配对才临时显形(所选即所见),选区收起后重新隐藏;跨段落选区(Ctrl+A)按各段分别判定,段边界不漏显;
- 不满足条件的配对完全惰性(反引号保持可见,不影响后续配对); 等多重反引号定界不识别(朴素两两配对)。
代码块
| 操作 | 触发 | 行为 |
|---|---|---|
| 骨架闭合 | 光标所在行恰为 | 保留内容流 | 在已有内容的行行首输入 | 成对渲染 | 草稿中出现成对 | 一次解包 | 光标在块边界按 Backspace / Delete(内容首行行首/块后一段行首 ← 退格;块前一段行尾/内容末行行尾 → 删除键) | 一次按键去掉一对 | 退出块 | 内容末行行尾按 ↓(块为草稿末尾时自动生长一个空段) | 光标落到块下方新段,继续输入普通文本;↑/↓ 在块边界垂直移动时直接跨过标记行;标记行驱逐光标时落点为文本锚点(v0.1.3),被送回正文边缘后 ↑/↓ 依然可用——块顶/块底绝不成为键盘死角 |
| 围栏内换段 | 光标在围栏内(含闭合行)按 Shift+Enter | 插入新段(而非软换行),从光标处截断;普通 Enter 在围栏内也照常发送 |
与原生行为的共存
- Enter:保持 DSH 原生「直接发送」,本插件从不拦截(列表行上、围栏内也一样);
- Ctrl/Cmd+Enter:保持原生「加速提交」,不拦截;
- Shift+Enter(未命中场景):保持原生软换行();
- Tab / Shift+Tab(非列表行、围栏内、选区状态、带修饰键):完全保持原生(浏览器焦点移动等),从不拦截;触发菜单打开时同样让位;列表行上的长按重复与单击同样重新规划(绝不落到原生焦点移动);
- /、@ 触发菜单打开时:对 Enter 系按键完全让位;
- 中文输入法(IME):组合中的按键不触发任何本插件行为(三信号防护:isComposing / keyCode 229 / compositionend 后 10ms 窗口,与 DSH 输入机自身防护一致);
- 斜杠命令认领、@ 引用 chip、图像拖放/粘贴、busy/locked 状态、问答卡片与审批卡片均不受影响;
- 与其他 composer 插件共存:捕获阶段先到先得 —— 已被 preventDefault 的按键本插件让位;本插件消费的按键 stopImmediatePropagation。
技术架构
双端结构
DSH 插件分 host(node)半与 browser 半,本插件是极端的纯客户端形态:
┌─ host 半 (node) ──────────────────────────────────────────────┐
│ index.js 空操作载体:给 bundle 一个可导入的 entry 行, │
│ 使插件出现在「设置 → 插件」中可开关 │
└───────────────────────────────────────────────────────────────┘
┌─ browser 半 (web) ─────────────────────────────────────────────┐
│ client.js 单文件装载产物(DSH 装载器只认单文件 bundle),│
│ 由 src/client/ 15 个模块经 scripts/build.mjs │
│ 拼装生成并入库;经 │
│ window.__ModuleLoader__.load({id, factory}) │
│ 注册,内部自带 CommonJS 风格模块注册表 │
└───────────────────────────────────────────────────────────────┘
源码分层(src/client/,自上而下单向依赖)
text
constants.js 常量 + 行文法正则 + CSS 类名(样式表选择器由它拼装,杜绝漂移)
editor.js 宿主契约缝:唯一触碰宿主下划线内部
(__lexicalEditor/_nodes/_nodeMap/_selection/_compositionKey)的模块
grammar.js 行文法基底:视觉行、围栏区间 + 提交制覆盖、共享光标行解析、
选区覆盖显形规则 —— 每次读取只推导一次
doc.js 文档读取:块/叶子扁平几何 + 光标/选区映射
(committed/live 两读法)+ 空壳块查询
fence-plan.js 纯围栏投影与决策(行/块角色、软行提升、围栏对、孤儿清理、
原子键、光标归位)
code-plan.js 纯行内代码核 + 每块 span/定界投影
enter-plan.js 纯 Shift+Enter 仲裁(先围栏后列表的一棵决策树)
list-plan.js 纯列表规划(状态驱动重编号、标记字形、原子删/跨越/归位、
合并/脱离、顺延、重锚)
analysis.js 一次 analyzeDraft(texts):模型 + 全部域投影
edits.js 编辑代数:全部活节点变更的唯一所在(平坦区间切除/软行提升/
段落切分/数字改写/格式形状)
style-sheet.js CSS 文本(由类名常量拼装)+ 样式标签生命周期
present.js 呈现层:块标记(围栏类/徽标)+ 唯一的字形标记引擎
({at, class, reveal} 表)+ stripDom
gestures.js 键盘面:守卫 + 手势策略表(prepare/apply/history 三步)
+ 唯一 claim 路径
restyle.js 收敛引擎:有序 STAGES 表(规范化 → DOM 着色 → 修复 →
光标归位 → 代码/标记形状),每轮一次分析、至多一次结构提交
+ 跨扫描记忆 + 环路守卫,实例状态非模块全局
index.js 组合根:生命周期 + __internals 组装
五条贯穿性设计
所有功能不是一条条 feature 各自铺管线,而是同一组正交抽象上的行:
1. 一条行文法:视觉行切分(段落按 \n 切行,软换行 \n 与粘贴 \n 等价)、围栏配对与提交制覆盖、光标行解析只在 grammar.js 推导一次;列表/行内代码/围栏/Enter 仲裁全部消费同一模型,构造上不可能互相漂移。
2. 一次草稿分析:analyzeDraft(texts) 把围栏角色、软行提升、行内代码 span/定界、列表标记字形、重编号不变式组装为单一对象 —— 各投影看见同一些行、同一份围栏覆盖。
3. 一套编辑代数:plan(纯数据,单测覆盖)与 apply(活节点操作,edits.js 独占)彻底分离;手势策略与收敛引擎都不直接碰节点。
4. 一个呈现引擎:编辑态视觉只有两种形状 —— 块标记(围栏段落类/徽标)与字形标记(单字符叶 + 类 + 显形规则);隐藏反引号与列表标记样式共用同一引擎与同一显形谓词,新增一种字形是加一行表项,不是加一个 pass。
5. 一张表驱动的双驱动:键盘面是策略表(每个键族一条 prepare/apply/history),收敛面是阶段表(每阶段一个纯 plan 函数);新增行为是向表里加行,而不是再铺一条管线。
关键技术细节
键盘路径(为什么是 document 捕获阶段):DSH 的 Enter 命令处理器以 CRITICAL 优先级吞掉一切非 Shift Enter(提交),命令层无插入口;而 Lexical 的 Enter 派发源自根元素 keydown 监听。因此插件在 document 捕获阶段监听 keydown —— 必然先于 Lexical —— 仅在命中编辑场景时 preventDefault() + stopImmediatePropagation() 阻断原生软换行,改用 editor.update() 完成插入。普通 Enter 从不进入该路径。
隐藏字形技巧(零步进隐形):Lexical 每个文本节点渲染为独立元素。样式写入先把反引号/标记字符两侧切开(splitText)并把该单字符叶子标记为 unmergeable(Lexical 归一化会把同格式相邻叶子合并回去,只有这个 detail 位能保住隔离),随后的 DOM 扫描用 editor.getElementByKey() 给这些叶子挂类:等宽字体 + letter-spacing: -1ch 恰好抵消字符步进、color: transparent 隐去墨迹 —— 保留真实字体度量。不能用 display: none(无盒子,浏览器无法在其旁锚定光标),也不能用 font-size: 0(光标高度取自其锚定文本节点的字体,会得到零高度不可见光标)。无序圆点由 ::after 伪元素渲染(排在零步进横线之后,行首光标因此渲染在整个 • 单元之前)。
围栏成对提交制:只有闭合的围栏才渲染;未闭合区间在覆盖判定中只占标记行自身 —— 行中键入
软行提升(任意行开块):围栏文法以段落为单位,而软换行是段内 、多行粘贴把字面 \n 拼进文本节点。restyle 第 1 阶段把与围栏相邻的软行边界提升为真正的段落边界(文本投影恒等,草稿/发送文本逐字节不变),使
收敛引擎:restyle 由编辑器 update 监听器驱动,每轮读一次文档、跑一遍阶段表、至多提交一次结构更新;提交再次触发监听器,下一轮发现无差异即停(写只在有差异时发生,幂等收敛)。自触发写入限速(滚动窗口 16 次/秒)兜底环路;IME 组合中直接让路。格式/样式写入带 history-merge 标签,不污染撤销栈;结构性手势(插入+顺延、合并/脱离+改号)是离散撤销步,一次 Ctrl+Z 整体还原。
宿主契约与安全降级:lexical 并非模块表的共享模块,插件也不能打包第二份 lexical(双副本会分裂模块态与节点类)。所有节点操作都通过宿主编辑器实例完成:editor._nodes 取真实节点类、editorState._nodeMap/_selection 读取与定位、全部使用宿主节点自身的方法(insertAfter/append/select/splitText/spliceText/setFormat)。这些内部访问点与 __lexicalEditor 同属事实契约,集中在 editor.js;升级破坏契约时插件安全降级为无操作(console.warn 一次)。
发送保真:除「有序列表序号规整」(它本身就把序号改写为连续后的字面值,所见即所发)外,其余各条线都不触碰文本内容 —— 发送文本 = composer 的 clipboard 投影 = 逐文本节点 getTextContent(),样式标志位与 DOM 类不影响序列化结果。
兼容性
- DSH 0.1.2-alpha.x(composer 为 Lexical 纯文本编辑器 + @lexical/plain-text,lexical 0.49);
- 现代桌面浏览器(Chrome / Edge / Firefox / Safari);
- 与其他 composer 插件共存:捕获阶段先到先得(见「与原生行为的共存」)。
已知限制
- 列表标记样式与反引号隐藏均为编辑态视觉层:字符本身仍在草稿/复制/发送文本中;选区覆盖时恢复原始字样与字宽。无序圆点与原横线的字宽不完全一致(圆点用等宽字体 ::after),极端窄列下可能比原 - 略宽,属可接受的排版差异。
- 纵向归位有短暂瞬态:↑/↓ 落入标记内部后,驱逐发生在随后的 restyle 扫描(微任务级),极端情况下可能见到一帧内部位置;←/→ 按键期仲裁无此瞬态。
- 代码块语言标识不可直接编辑(标记行不可达):修改语言 = 一次 Backspace 解包(保留正文)后重敲围栏。
- 未闭合的 - / x - 有序列表规整按现状重写一切非连续序号:手动跳号/非 1 起始会回弹;更深缩进的行只挂起外层组(空行、围栏、缩进不深于本组的普通行/无序行仍断组);同一组超过 9 位序号(≥10 亿行)不适用。
- 层级阶梯的子树边界按「更深缩进」判定:空行、围栏区域、缩进不深于该项的行都终止子树(与规整分组的边界语义一致);空行后的深层缩进行不算前项的子层级,不会被联动平移。
- 降级上限(v2.9):父级 = 上方最近的列表项行(普通文本行跳过不算,空行/围栏区域隔断查找);项最多处于父级下一级(父级缩进 +2)。已达上限的项与列表首项按 Tab 是「认领但不动」的空操作——不会无限降级,也不会因此让焦点跳出输入框;要再深一级,须先让其父级(或上方兄弟)先降级。升级方向无上限(0 级为自然下限)。
- 取消列表(顶层升级)会隔断有序组:留下的普通行使下方同缩进成员按不变式从 1. 重起——这是既定编号契约的直接后果,非 bug。v2.8 起该手势取代了旧的「有序项退格合并进上一行」行为。
- 缩进/前缀原子的「不可选中、不可删除」为稳态键盘体验:折叠光标无法进入原子内部(方向键跨越、越界驱逐、行首 Delete 整体删除);拖拽/Ctrl+A 等范围选区仍可覆盖原子字符(与序号手动改号的「所选即所见」规则一致),此时原生删除可用。
- 行内代码内容若恰为 lexicon 中的 /name、@name 文本引用 token,样式可能被 chip 着色覆盖(文本内容不受影响)。
- 引用 chip(@xxx)内部含换行时无法在其上提升软行(chip 为原子节点);这类行的围栏判定退化为整段判定。
- 完全无显形的行内代码方案需 chip 节点(技术文档路线 B,留作后续演进)。
开发须知与规范
构建与测试
sh
node scripts/build.mjs # 由 src/client/ 重新生成 client.js
node scripts/build.mjs --check # 仅校验 client.js 是否与源码同步
node tests/run-tests.mjs # 产物新鲜度 + 纯逻辑单测 + bundle 形状断言
DSH_CHECKOUT=/path/to/dsh node tests/run-tests.mjs # 追加宿主契约冒烟(grep 断言)
npm run build / npm test 为等价快捷方式。手工验收清单见 tests/e2e-recipe.md(可用 playwright-cli 驱动,键盘事件需是真实 keydown)。设计与证据文档:仓库根 dsh-composer-markdown-view-tech.md(需求、证据索引、已否决方案)。
源码规范
- 不要直接改 client.js:它是 scripts/build.mjs 生成的产物且入库,测试会校验新鲜度;改 src/client/ 后重新构建并提交再生的产物。DSH 装载器把每个插件的 ./client 导出作为单个 加载(无多文件插件支持),这是存在构建步骤的唯一原因。
- 模块约定:每个 src/client/.js 文件是一个 CommonJS 风格的模块体(接收 module, exports, require,require('./xxx') 解析到 bundle 内部注册表);构建时按 MODULES 表顺序原样拼接,不做转译或重排版。零运行时依赖 —— 不得引入任何外部包。
- 绝不 import/bundle lexical:lexical 不是模块表的共享模块,打包第二份会分裂模块态与节点类。一切节点操作经宿主编辑器实例(editor.js 是唯一契约缝);新增宿主内部访问点时加进 editor.js 并保持「破坏契约 → warn 一次 → 无操作」的降级。
- 扩展方式是向表里加行:新按键手势 → 在 gestures.js 的策略表加一条 {keys, guard, prepare, apply, history};新收敛阶段 → 在 restyle.js 的 STAGES 加一个纯 plan 函数;新字形样式 → 在 present.js 的字形标记表加一行 {at, class, reveal}。不铺新管线。
- plan/apply 分离:规划器必须是当前草稿的纯函数(无跨扫描记忆,围栏孤儿清理的配对记忆除外),经 __internals 暴露给零浏览器测试器单测;活节点变更只写在 edits.js。
- 收敛纪律:写只在有差异时发生;每轮至多一次结构提交;样式/光标写入带 history-merge,结构性手势用离散撤销步;尊重 MAX_WRITES_PER_SECOND 环路守卫与 IME 让路。
- 样式复用 DSH 主题 token(--ds-font-family-code、--dsw-alias-markdown-inline-code、--dsw-alias-markdown-code-block),选择器由 constants.js 的类名常量拼装,禁止硬编码颜色或让 CSS 与类名漂移。
许可
MIT(见 LICENSE)。扫码进群