dsh-composer-live 0.2.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,102 @@
1
+ # Changelog
2
+
3
+ > 更新日志以中文记录。Changelog entries are written in Chinese.
4
+
5
+ ## 0.2.11(2026-09-27)
6
+
7
+ - **README 配图**:双语 README 的截图段落补齐三张实拍(docs/screenshots/)——实时渲染全景(行内格式/任务列表/引用块/链接)、代码块(语法高亮/语言标签/等宽)、选区浮动格式条。新增 `e2e/capture-shots.mjs` 截图脚本(复用 E2E 链路:独立 Chrome + CDP + cookie,注入演示草稿后按输入卡元素截取,画面不含侧栏)。
8
+ - 文档与发布基建:双语 README(英文主 + 中文完整版)、docs/architecture.md 架构深读、e2e/README.md、MIT LICENSE 修正版权行、package.json 补 repository 元数据;e2e/run-e2e.mjs 硬编码路径参数化(DSH_CL_PORT / DSH_CL_E2E_PROFILE / DSH_CL_DST_PKG 环境变量,默认值不变)。
9
+
10
+ ## 0.2.10(2026-09-27)
11
+
12
+ **输入体验四件套(用户选定方向全做):**
13
+
14
+ - **Ctrl+B / Ctrl+I / Ctrl+E 快捷格式**:选中即包裹加粗/斜体/行内代码,光标态插空标记(与工具栏按钮同通道 applyWrapAction)。拦截同时挡掉浏览器对 contenteditable 的默认 `execCommand('bold')`——那会插真 `<b>` 富文本标签进编辑器(隐性 bug 顺带修复)。
15
+ - **选区跨行 Tab/Shift+Tab 批量缩进**:整块每行行首 ±2 空格(空行跳过),缩进后选区恢复为新块(连按 Tab 逐级缩进)。实现坑:**有选区时直接跑 insertInline 的 beforeinput 序列与 Lexical 内部 selection 状态不同步(E2E 实测首段丢失)**——整块替换必须走「先合成删除选区 → commit → 插入新块 → 恢复选区」三步链。
16
+ - **任务列表勾选样式**:`- [ ] 待办` 渲染成淡胶囊方块、`- [x] 已办` 绿底+行内容划线(line-through paint-only 不改 advance;三字符标记整体进胶囊,字符流不变光标对齐不受影响)。
17
+ - **表格快捷补全**:首尾包夹的表格首行(`|列A|列B|`,至少 2 列)行尾 Enter/Shift+Enter 自动补 `|---|---|` 分隔行+空行,光标落空行;下一行已是分隔行不重复补。实现坑:`indexOf("\n", 行尾)` 从行尾搜会把行尾的 \n 自己找到(slice 起点大于终点得空串)——须从 行尾+1 搜。
18
+
19
+ 单测 173→188(+15),E2E 49→53 场景(Q6-Q9:快捷键包裹/表格补全/任务样式/选区逐级缩进全链)全绿。
20
+
21
+ ## 0.2.9(2026-09-27)
22
+
23
+ - **Tab 缩进体系(此前 Tab 在输入框内完全不可用——浏览器默认把焦点切出输入框)**:Tab 全境拦截不再跳焦点。①代码块内=光标处插两空格(既有);②光标在列表/引用「标记头」内或行首空白区=**行首插两空格**(嵌套列表——列表续项后光标恰在标记后,Tab 即缩进成子列表);③行中间=光标处插两空格;④**Shift+Tab=行首反缩进**(删最多 2 个空白,嵌套列表升回去;无缩进时只吞按键防跳焦点)。实现坑:反缩进/标记头判定必须用「完整行」——光标前缀在光标处于行首时是空串(单测当场抓住)。E2E 新增 Q4/Q5 键盘流场景(合成 Tab keydown + 投影断言:续项→Tab 嵌套→打字→Shift+Tab 反缩进全链)。11 项新增单测(173 总)、49 场景全绿。另:deploy.mjs 探活窗口 40s→60s(冷启动偶尔超 40s)。
24
+
25
+ ## 0.2.8(2026-09-25)
26
+
27
+ - **工具栏 sticky 化(用户三报「工具栏现在是透明的」的根治重构)**:v0.2.7 的让位方案让工具栏下移到编辑器顶后,暴露出更深的两个问题——①滚动时编辑器 padding 让位随内容滚走,absolute 工具栏(钉输入卡不随滚动)叠上正文且无背景(用户看到的「透明」);②占位逻辑与附件栏/滚动互相纠缠。v0.2.8 重构:**工具栏挂进滚动容器(grow 内、overlay 前)+ `position:sticky;top:0`**——永远钉在滚动视口顶,内容从它下方滚过永不重叠;附件栏在滚动容器外(实测 railInScroll=false),scroll 顶=附件栏底,**天然让位**(v0.2.7 的让位函数/变量整体删除);新增不透明背景(暗 rgba(28,30,36,.95)/浅白,分隔线并入 border-bottom);占位从「编辑器 padding 44px」改为「sticky 流内 38px + padding 6px」,总高不变;box-sizing:border-box 保证含边框精确 38px。**挂载 bug 修复**:首版改造后工具栏消失——appendChild 被移除而 grow 块用 querySelector 找未进 DOM 的元素,改 `card.__dshClToolbar` 引用传递 + 搬入逻辑移出 `if (!ov)` 块。
28
+ - **E2E INV-7 判定修复(测试基建)**:D1-cjk-300(中文软折行场景)在 v0.2.8 稳定误报——采样 offset 178 的字符恰落在软折行点,collapsed caret 在折行点产**两个等价 rect**(上一视觉行尾 + 本行首),原判定恒取 rr[0](行尾)与行首期望比较必差一行。实时字符 rect 与测量记录完全一致(caret 无错,判定取错 rect)。修法:全部候选 rect 任一匹配即通过。v0.2.5→v0.2.8 的 1px 布局微调让折行点恰好挪到采样点,纯属踩中运气。
29
+ - 验证:滚动场景工具栏钉视口顶(卡内 y=80 恒定、内容滚 1200px 不叠)+ 有/无附件几何(让位/回位)+ computed 样式(sticky/白底/规则齐)+ 视觉确认(实心工具条、正文从下方经过)+ 单测 162 / E2E 47 全过。
30
+
31
+ ## 0.2.7(2026-09-25)
32
+
33
+ - **图片附件在场时格式工具栏盖在缩略图上(用户二报「问题依旧」的真正主 bug)**:官方附件栏(粘贴/拖入图片后的缩略图行)渲染在输入卡顶部 y=10-74、编辑器上方(官方自身布局正确);本插件常驻工具栏 absolute top:0 也在卡顶——附件在场时 B/«/链接等按钮直接悬浮盖在图片上(用户截图实锤)。修复 `offsetToolbarForAttachments`(挂 render 主循环):检测「编辑器外的 img」→ 向上找全宽祖先取附件栏底边(几何法,不依赖官方 hash 类名,升级免疫)→ CSS 变量 `--dsh-cl-bar-top` 驱动工具栏下移到附件栏底 +6px。编辑器 44px padding-top 占位无需变(工具栏贴编辑器顶后底边距内容顶仍有 6px 呼吸);附件移除变量清零回位。实测:有附件 toolbarTop 0→80 与附件栏零重叠、无附件回 0 变量未设;单测 162 / E2E 47 全过。
34
+
35
+ ## 0.2.6(2026-09-25)
36
+
37
+ - **图片附件在场时 @// 候选菜单悬空溢出(官方定位 bug 兜底修复,用户报「标签溢出了,而且颜色也不对」)**:官方 input-trigger 候选菜单向上展开时锚定「输入组件容器顶」——输入框带图片附件(附件栏占编辑器上方 66px+)时锚点不随之下移,菜单悬空 84px、整体浮出输入卡 300px+ 盖在消息流上(白卡片悬在打字区上方,菜单内文件名条目全露在输入框外)。三组对照实测定责(e2e/diag-menu-pos.mjs):无图 gap=12px 正常 / 有图 gap=84px / 抵消插件 padding-top 不变 → 官方自身问题非本插件引入。兜底 `fixTriggerMenuPos`(挂 render 主循环):菜单底边与编辑器顶间隙 >30px 时用 **transform:translateY** 平移贴回编辑器上沿——实测官方菜单是 absolute + **bottom:4px 锚定**,bottom 锚定下 margin-top 零位移(首版 marginTop 方案实测不动、且 gap 不收敛导致叠加正反馈失控累积到 7704px,已废弃),top 会与 bottom 冲突拉伸高度,translateY 与官方无 transform 的定位零冲突且 rect 实测含 transform 天然幂等收敛(修正 72px 一轮到位)。600px 上限防不可控叠加;只管与输入卡横向重叠且向上展开的菜单。回归:diag-menu-pos.mjs B 组 gap 84→12px(与无图基线一致)。附:官方图片附件胶囊(64×64 缩略图)无文件名标签,用户说的「标签」=菜单里的文件名条目。
38
+ - 一次性诊断脚本四件(e2e/diag-image / diag-at / diag-longname / diag-img-menu,CDF 直连复刻「粘贴图片/@ 引用」真实管线并 dump DOM 几何)留档可复用;其中 diag-menu-pos.mjs 兼作本修复的回归验证(B 组 gap 应 ≤ ~12px)。
39
+
40
+ ## 0.2.5(2026-09-25)
41
+
42
+ - **引用自动续项 / 空项退出**(与列表续项同款交互,用户需求):引用行(`> ` 开头,`>text` 无空格也认)行尾按 Enter/Shift+Enter 自动续「> 」前缀(缩进保留);空引用项上按 → 删除标记退出引用,行原地变普通行(**两次 Shift+Enter 结束引用**:第一次续出空引用行、第二次退出)。光标不在行尾放行官方;引用行内的列表标记(`> - 项`)按引用续(引用语境优先)。9 项新增单测(162 总)+ E2E 新增 Q 组键盘流场景(合成 Enter keydown 走插件捕获层 + 投影断言 op,顺带回归列表续项)。
43
+
44
+ ## 0.2.4(2026-09-25)
45
+
46
+ **E2E 自动化测试体系建成 + 长文本五处真 bug 修复(全部由测试矩阵定位)。**
47
+
48
+ 新增 `e2e/`:一条命令(`node e2e/run-e2e.mjs --with-deploy`)在真浏览器里跑 44 个场景 × 10 条不变量——六种注入通道复刻真实输入形态(字面 \n/字面 \r\n/逐行 br/insertParagraph/合成 paste/刷新恢复多 p),逐字符对比「渲染段内字符真实 rect vs 编辑器字符 rect」(INV-3)、行距均匀性(INV-6,多余空白探测器)、caret 采样对齐(INV-7)等。详细设计见 CLAUDE.md「E2E 测试体系」节。
49
+
50
+ 测试暴露并修复的 bug(此前 25/44 场景失败):
51
+
52
+ - **渲染文字整体偏低 3px**:段定位用了「字形盒顶」,但段内字符随段自身行盒排版(字形顶 = 段top + half-leading(行高27/字形21 → 3px))。历史验证测的是「段定位值 vs 测量值」(定义上恒等),从未测过真实视觉位置。修法:段 top 减 half-leading。
53
+ - **CRLF 粘贴「多余空白/行错乱」(用户头号问题)**:段 text 里的 `\r` 经 `innerHTML` 被浏览器规范化成 `\n`(HTML 解析规则),pre-wrap 段内凭空多出真换行。修法:段 text 剔除 `\r`(视觉零宽,与编辑器换行语义一致)+ escapeHtml 补 `&#13;` 实体通道。
54
+ - **行内代码文字恒低 1px**:cd/img 胶囊 `padding:1px 5px` 横向有负 margin 补偿、垂直没有——绝对定位段的内容下移 padding-top。修法:`margin:-1px -5px` 四向补偿。
55
+ - **大段粘贴形态下代码块等宽完全失效**:MONO 打点循环的行号推进只认 br/para 锚,字面 \n 单文本锚跨全部行使 lineIdx 恒 0;且单锚跨 N 行时 DOM 无法按行换字体。修法:回退 v0.1.x「整框等宽」语义(文档含已闭合代码块 → 编辑器整框 + 渲染全段同步切等宽合成栈)。
56
+ - **Maple Mono NF CN 漏剔**:合成栈未滤名含 NF CN 的字体(自带中文 1.2em 字形),代码块中文 advance 两侧不一致。修法:CJK 关键词清单补 "nf cn"(与 8123 旧版度量保护一致)。
57
+
58
+ ## 0.2.3(2026-09-16)
59
+
60
+ - **长文本粘贴「一片空白」真正根因修复**:粘贴的大段文本在编辑器里是「单个文本节点内含字面
61
+ 」(官方 paste 管线的 insertText 不把
62
+ 转成换行元素)——渲染侧行列表按
63
+ 切成了 N 行,但字符测量的行号推进只认 DOM 换行元素(br/段落边界),字面
64
+ 不推进——1669 个字符全被记到第 0 行,而第 0 行的文本只有 40 个字,后续字符取字越界返回空串 → 40 个渲染段只有第 1 段有字、其余全空 → 可见区一片空白(刷新后正常是因为草稿恢复走 setDraft 产多段落结构,不经此路径)。修法:测量的行号推进同样认文本节点内的字面
65
+ (与 split("
66
+ ") 的行边界一致,off 用负偏移补偿保持行内偏移语义)。
67
+
68
+ ## 0.2.2(2026-09-14)
69
+
70
+ - **大段粘贴「一片空白」修复(三层防御)**:①锚表对未知元素(B/I/STRONG/DIV 等富文本粘贴产物)从「忽略内容」改为继续下钻收集文字——之前粘贴的富文本结构整个不进锚表,渲染层空转 + 透明化已生效 = 一片空白(文字在编辑器里透明不可见,发送时却在);②锚表漏收检测兜底——编辑器有文字但投影文本全空时自动回退官方显示(不透明化);③渲染抛错兜底——测量/解析异常时同样回退官方显示,另加 measureAnchorChars 逐锚容错(批量编辑中间态单个节点测量失败跳过该锚,下一轮自愈)。
71
+
72
+ ## 0.2.1(2026-09-13)
73
+
74
+ - **列表自动续项 / 空项退出**:光标在列表行尾按 Enter/Shift+Enter 自动续项——无序列表保持符号(- * +)、有序列表递增编号(1. → 2.、3) → 4))、缩进保留;空列表项(只有标记没内容)上按 → 删除标记退出列表,行原地变普通行(两次 Shift+Enter 即结束列表:第一次续出空项、第二次退出)。光标不在行尾放行官方(行内普通换行)。坑:退出删除不能走 execCommand("delete")——无 user activation 时静默 no-op(CDP/特殊环境不可靠),改合成 beforeinput deleteContentBackward(Lexical 不校验激活)。15 项新增单测(152 总)。
75
+
76
+ ## 0.2.0(2026-09-13)
77
+
78
+ **架构级根治:字符锚定渲染。** 渲染层不再自己排版——逐字符测量编辑器的真实坐标(Range rect),渲染字符绝对定位画在同一坐标上。字体/字重/断行/行结构/等宽打点时序/任何度量维度怎么变,渲染物理跟随编辑器——漂移/抖动/错位从架构上不存在(历史 7 轮对齐修复的根因是「两套排版引擎各自计算」,现在只剩一套)。
79
+
80
+ - 渲染管线:锚表 → 等宽打点(提前,布局定型后再测量)→ 块级背景聚合(代码块/引用段整块一个矩形——容器内容宽、首行字符顶到末行字符底,垂直无缝右缘齐平;语言标签 CSS right 定位)→ 逐字符 Range rect 测量 → 按「同样式+同视觉行+横向连续」合并成定位段 → 绝对定位输出;代码块行背景/引用背景按行字符包围盒画背景块,语言标签定位到围栏行右侧
81
+ - 首行动态对齐偏移机制退役(字符坐标系天然吸收全局偏移);围栏字符从「隐藏+光标行显示」改为统一淡化显示(消灭切换闪烁);窗口 resize 重渲染监听
82
+ - 验证:行内格式/代码块/块后行/软换行(2 视觉行)全部偏差 ≤0.02px;真键盘连续打字 358 帧逐帧 delta=0.00;caret 与渲染字符 diffX=0;展开/选中/工具栏回归全过
83
+
84
+ ## 0.1.1(2026-09-13)
85
+
86
+ 五轮实测修复(层叠与对齐体系成型,137 项单测):
87
+
88
+ - **代码块内光标不可见**:编辑器设 `position:relative;z-index:1` 提到渲染层(z:0)之上——光标/选区随编辑器内容自然盖在渲染文字上;`caret-color` 显式设色(透明化后 `caret-color:auto` 在部分 Chromium 跟随 text-fill 画透明光标)。中间踩过「渲染层压 z-index:-1」的反例:负 z 掉到输入框背景之下,渲染文字整体不可见。
89
+ - **击键抖动**:首行对齐偏移改增量算法(测量差 + 当前偏移 = 绝对目标,一步收敛)——「差值直接赋值」会在两个值间振荡,基准漂移大时振幅肉眼可见。
90
+ - **选中重影**:`::selection{color:transparent}` 防止 Chromium 选中时用反色把透明文字重绘成白字(双影)。
91
+ - **选中无色**:::selection 规则一旦匹配,未声明的属性取初始值——必须同时显式写 `background`(品牌蓝半透明,明暗两套)。
92
+ - **行内格式光标漂移/打字抖动(字符级对齐)**:标记字符(`**`、`` ` ``、`~~`、`[]()`)保留占位只淡化显示(`.dsh-cl-mk`),粗体/标题改 `-webkit-text-stroke` 伪粗(paint-only 零 advance 变化)——渲染行与源文字符流逐字符等宽,光标在格式区间精确贴齐(实测偏差 0~0.1px)。
93
+ - **展开键失效**:展开态补 `height:70vh`(只有 max-height 时高度仍由内容决定,内容少时点了纹丝不动)。
94
+
95
+ ## 0.1.0(2026-09-12)
96
+
97
+ 初版(129 项单测 + 8124 实测 E2E 全过):
98
+
99
+ - markdown 实时渲染(透明编辑器 + overlay 渲染层,源文原样保留)
100
+ - 代码块体验:未闭合围栏不渲染、Shift+Enter 自动补全封闭块、↓ 跳出、Tab 缩进、语法高亮、语言标签、行级等宽
101
+ - 常驻格式工具栏 + 选区浮动格式条
102
+ - 70vh 展开 / 大段粘贴自动包代码块 / Esc 分发 / @ 引用胶囊兼容
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 dsh-composer-live contributors (zeusxx)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,179 @@
1
+ # dsh-composer-live
2
+
3
+ **Live Markdown rendering and typing enhancements for the DeepSeek Harness (DSH) web composer.**
4
+
5
+ [English](./README.md) · [简体中文](./README.zh-CN.md)
6
+
7
+ ![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)
8
+ ![DSH](https://img.shields.io/badge/DSH-%3E%3D%200.1.5--rc.2-4c6ef5)
9
+ ![Tests](https://img.shields.io/badge/unit%20tests-188%20passing-2ea44f)
10
+
11
+ dsh-composer-live turns the DSH Web input box into an Open-WebUI-style live editor: you type Markdown, it renders as you type — bold, italic, code blocks with syntax highlighting, lists, quotes, tables — while the draft itself stays plain Markdown source, so what you send is exactly what you typed.
12
+
13
+ It is a **pure browser-side plugin**: zero runtime dependencies, no service injection, no patches to official code. It layers on top of the official 0.1.5 Lexical composer without touching what DSH already does natively.
14
+
15
+ > Looking for the DSH **0.1.x** (textarea composer) version? That's [`dsh-composer-md`](#faq), the predecessor of this plugin — discontinued.
16
+
17
+ ## Screenshots
18
+
19
+ ### Live markdown rendering
20
+
21
+ ![Live markdown rendering in the composer](docs/screenshots/overview.png)
22
+
23
+ ### Code blocks
24
+
25
+ ![Code block with syntax highlighting and language label](docs/screenshots/code-block.png)
26
+
27
+ ### Selection toolbar
28
+
29
+ ![Floating format bar above the selection](docs/screenshots/selection-bar.png)
30
+
31
+ ## Features
32
+
33
+ ### Live Markdown rendering
34
+
35
+ - Inline styles render as you type: `**bold**`, `*italic*`, `` `code` `` (brand-blue capsule), `~~strikethrough~~`, `[links](url)` (whitelisted protocols), `![images](url)` capsules.
36
+ - **Character-perfect caret alignment.** Marker characters (`**`, `` ` ``, `~~`, `[]()`) are dimmed but keep their exact width, and bold/headings use paint-only faux-bold — so the rendered line stays character-for-character aligned with the invisible editor text. The caret lands precisely everywhere, including inside formatted spans, with zero drift while typing.
37
+ - Task list items: `- [ ]` / `- [x]` render as checkbox capsules; completed items get struck through.
38
+ - The draft always remains plain Markdown source. Rendering is purely visual — the message you send is the source text you typed.
39
+
40
+ ### Code blocks
41
+
42
+ - Typing ``` shows plain text until the fence **closes** — no half-rendered blocks while you are still typing.
43
+ - **Shift+Enter on an open fence** auto-completes a closed block (blank line + closing fence + trailing line) and puts the caret inside, ready to code.
44
+ - ↓ on the last content line of a closed block jumps below the block.
45
+ - Zero-dependency regex syntax highlighting: js/ts/py/sh/json/yml/css/html, auto-detected from content when unlabeled — Chinese prose is never misdetected as code.
46
+ - Language label on the fence line; GitHub-dark / GitHub-light block themes; fence characters dimmed.
47
+ - Monospace: while a closed code block is present, the whole composer (editor + overlay) switches to a monospace stack synced with the official code font. CJK characters deliberately stay on the UI font so line wrapping never diverges between the two layers.
48
+
49
+ ### Lists, quotes, tables
50
+
51
+ - **Enter** at the end of a list line continues it: `-`/`*`/`+` markers kept, ordered markers increment (`1.` → `2.`, `3)` → `4)`), indentation kept. **Enter on an empty item exits the list** — two Shift+Enter presses end a list.
52
+ - `> ` quote lines continue the same way (`>text` without a space counts too); Enter on an empty quote item exits.
53
+ - **Tab / Shift+Tab** indent / outdent by 2 spaces — single line or whole selection block. Tab with the caret in a list marker or leading whitespace nests the item. Tab is fully intercepted: it never moves focus out of the composer.
54
+ - A table header row (`|a|b|`, ≥ 2 columns) followed by **Enter** completes the `|---|---|` separator row plus a blank line, caret on the blank line.
55
+
56
+ ### Toolbars and shortcuts
57
+
58
+ - **Sticky top toolbar**: B / italic / code / link / list / code block / expand. Wraps the selection, or inserts empty markers at the caret; B lights up when the caret sits inside `**…**`. The bar is pinned to the top of the scroll area, so it never overlaps content or image attachments.
59
+ - **Selection floating format bar** (Open WebUI style): select text and B/italic/code/link appear above the selection.
60
+ - **Ctrl+B / Ctrl+I / Ctrl+E** for bold / italic / inline code — which also blocks the browser's native contenteditable bold that would silently insert real `<b>` tags into the draft.
61
+
62
+ ### Paste handling
63
+
64
+ - Large pastes (> 8 lines or > 4 KB) that look like code or logs are **automatically wrapped in a code fence** — one undo step, with a toast hint. Chinese prose is not misdetected; no wrapping when the caret is already inside a code block or the paste itself contains fences.
65
+
66
+ ### Esc dispatch, themes, compatibility
67
+
68
+ - **Esc** does the right thing in context: open candidate menus → official handler; expanded composer → collapse; generation in progress → stop generating.
69
+ - One-key **70vh expand** for long-form writing.
70
+ - Light/dark theme aware; IME-safe (composition input never flashes); rendering is rAF-batched and incremental.
71
+ - **@ mention chips** stay official and editable. Paragraphs containing chips gracefully degrade to official rendering (chip widths cannot be replicated; zero misalignment wins). Text decorations are unaffected.
72
+ - Also fixes two official positioning glitches when image attachments are present: the trigger menu floating far above the composer, and toolbars overlapping thumbnails.
73
+
74
+ ### Not duplicated from DSH 0.1.5
75
+
76
+ The official composer already provides — and this plugin deliberately does not reimplement: `/` command menu, `@` file mentions, paste-image/file to attachments, Enter-to-send / Shift+Enter newline, IME protection, draft persistence.
77
+
78
+ ## Quick reference
79
+
80
+ | Input | Context | Action |
81
+ | --- | --- | --- |
82
+ | Enter | plain line | send (official) |
83
+ | Shift+Enter | plain line | line break (official) |
84
+ | Enter / Shift+Enter | end of list / quote line | continue the marker (ordered increments) |
85
+ | Enter / Shift+Enter | empty list / quote item | remove marker, exit the list / quote |
86
+ | Enter / Shift+Enter | unclosed fence line | close the block; caret lands inside |
87
+ | Enter / Shift+Enter | table header row (≥ 2 cols) | complete separator row + blank line |
88
+ | Tab | inside a code block | insert 2 spaces at caret |
89
+ | Tab | list/quote marker or leading whitespace | indent line (nested list) |
90
+ | Tab | anywhere else | insert 2 spaces at caret |
91
+ | Tab / Shift+Tab | multi-line selection | indent / outdent the whole block |
92
+ | Shift+Tab | any line | outdent up to 2 spaces (absorbs the key when none) |
93
+ | Ctrl+B / Ctrl+I / Ctrl+E | anywhere | wrap selection bold / italic / code, or insert empty markers |
94
+ | ↓ | last content line of a closed block | jump below the block |
95
+ | Esc | menus open → official · expanded → collapse · generating → stop | context dispatch |
96
+
97
+ ## Requirements
98
+
99
+ - **DSH ≥ 0.1.5-rc.2**, `web` profile — the plugin targets the 0.1.5 Lexical contenteditable composer.
100
+ - A **Chromium-based browser** (Chrome, Edge, …): the transparency technique relies on `-webkit-text-fill-color`.
101
+ - OS-independent — it is pure DOM/CSS with no native dependencies.
102
+
103
+ ## Installation
104
+
105
+ ```bash
106
+ # from npm (recommended)
107
+ dsh plugin --profile web add dsh-composer-live
108
+
109
+ # or from a local checkout (file: installs are physically copied)
110
+ dsh plugin --profile web add "file:/path/to/dsh-composer-live"
111
+ ```
112
+
113
+ **Restart the web instance after installing.** Plugins load from a startup snapshot; refreshing the page alone can leave old and new bundles coexisting.
114
+
115
+ Running an isolated DSH instance? Point `DSH_HOME` at it so the plugin installs there instead of the default `~/.dsh`:
116
+
117
+ ```bash
118
+ DSH_HOME=/path/to/your/.dsh dsh plugin --profile web add dsh-composer-live
119
+ ```
120
+
121
+ ## Usage
122
+
123
+ Everything is on by default — install, restart, and type Markdown in the composer.
124
+
125
+ Typical flows:
126
+
127
+ - **Code block**: type ```` ```js ````, press Shift+Enter — a closed block appears with the caret inside. Code with live highlighting; Tab indents two spaces; press ↓ on the last content line to exit below the block.
128
+ - **List**: type `- item`, press Shift+Enter to continue items; press Shift+Enter on an empty item to end the list. Tab nests, Shift+Tab un-nests.
129
+ - **Long text**: hit the expand button (or work as usual) — the composer grows to 70vh for long-form writing; Esc collapses it.
130
+ - **Paste a log**: a big chunk of code or log text becomes a fenced code block automatically; Ctrl+Z if you don't want that.
131
+
132
+ ## How it works
133
+
134
+ The official composer is a Lexical contenteditable editor. This plugin makes its text invisible with `-webkit-text-fill-color: transparent` (the `color` property is kept, so the caret and official decorations keep working) and paints its own rendering layer — with every rendered character positioned at the editor's real text coordinates, measured per-character from Range rects.
135
+
136
+ Because the overlay never does its own layout, alignment bugs (drift, jitter, misplacement) cannot exist by construction: measured deviation is ≤ 0.02px across scenarios, 0.00 per keystroke frame.
137
+
138
+ The plugin injects no official services. It reads the editor DOM directly and derives a "projection text" (chips → U+FFFC, `<br>`/paragraph boundaries → newlines), which keeps it immune to client API churn; DOM anchoring uses stable `data-` attributes instead of hashed CSS-module class names. Draft edits (list continuation, fence completion, indenting…) go through Lexical-native channels — synthetic `beforeinput` events that preserve the undo stack — never direct DOM mutation.
139
+
140
+ See [docs/architecture.md](./docs/architecture.md) for the full deep dive.
141
+
142
+ ## Known limitations
143
+
144
+ - Paragraphs containing **@ chips** render as plain official text (chip widths cannot be replicated; zero misalignment wins). Text-ref decorations are unaffected.
145
+ - Block visuals (headings, quotes, lists, table markers) are paint-only — color, weight, background. Never font size, line height, or indentation; the source layout is preserved so line metrics never change.
146
+ - Whole-composer monospace applies whenever a closed code block is present (not per-line — a literal-`\n` single-text-node draft makes per-line font switching physically impossible).
147
+ - Fence-completion caret placement waits 80 ms for Lexical's async commit; on very slow machines the caret may occasionally land slightly late.
148
+ - ↓ jump-out only triggers on the last content line of a **closed** block; an unclosed fence falls through to the official handler.
149
+ - "Stop generating" is matched by aria-label (English and Chinese enumerated); if upstream renames the label, use the button directly.
150
+ - `web` profile only; Chromium-family browsers only.
151
+
152
+ ## Development
153
+
154
+ ```bash
155
+ node test-live.cjs # 188 unit tests (pure functions, no browser needed)
156
+ node e2e/run-e2e.mjs --with-deploy # 53-scenario × 10-invariant E2E suite against a live instance
157
+ ```
158
+
159
+ The E2E suite replays six real input channels (literal `\n` / literal `\r\n` / line-by-line br / insertParagraph / synthetic paste / refresh-restore multi-paragraph) and cross-checks an independent projection against the plugin's own, character by character. See [e2e/README.md](./e2e/README.md).
160
+
161
+ Version discipline: every change bumps `package.json` and gets a [CHANGELOG.md](./CHANGELOG.md) entry.
162
+
163
+ ## FAQ
164
+
165
+ **Why Chromium-only?**
166
+ The transparency trick (`-webkit-text-fill-color`) and several caret behaviors are Chromium-specific. Firefox/Safari would need a different approach.
167
+
168
+ **Does it change what I send?**
169
+ No. The draft is always plain Markdown source; rendering is purely visual.
170
+
171
+ **Is per-character measurement slow?**
172
+ It is batched per render frame and merged into a few absolutely-positioned spans. The overlay is `pointer-events: none` and out of flow, so it causes no reflow of the editor. Measured per-keystroke delta is 0; no perceptible input latency.
173
+
174
+ **Will a DSH update break it?**
175
+ Zero service injection plus `data-`-attribute anchoring make it resilient to routine updates (class-name hash churn, client API refactors). When the official composer *architecture itself* changes — as 0.1.1 → 0.1.5 did, textarea → Lexical — the plugin needs a port. This plugin *is* that port: `dsh-composer-md` was its 0.1.x predecessor and is now discontinued.
176
+
177
+ ## License
178
+
179
+ [MIT](./LICENSE)
@@ -0,0 +1,179 @@
1
+ # dsh-composer-live
2
+
3
+ **为 DeepSeek Harness(DSH)Web 输入框带来 Markdown 实时渲染与输入体验增强。**
4
+
5
+ [English](./README.md) · [简体中文](./README.zh-CN.md)
6
+
7
+ ![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)
8
+ ![DSH](https://img.shields.io/badge/DSH-%3E%3D%200.1.5--rc.2-4c6ef5)
9
+ ![Tests](https://img.shields.io/badge/unit%20tests-188%20passing-2ea44f)
10
+
11
+ dsh-composer-live 把 DSH Web 的输入框变成 Open WebUI 风格的实时编辑器:你打 Markdown,它边打边渲染——加粗、斜体、带语法高亮的代码块、列表、引用、表格——而草稿本身始终是纯 Markdown 源文本,发送出去的就是你输入的原文。
12
+
13
+ 它是一个**纯浏览器端插件**:零运行时依赖、不注入任何官方服务、不改官方源码。它在官方 0.1.5 Lexical 输入框之上叠加增强,不重复 DSH 已原生提供的功能。
14
+
15
+ > 找 DSH **0.1.x**(textarea 架构输入框)的版本?那是 [`dsh-composer-md`](#faq)——本插件的前身,已停止维护。
16
+
17
+ ## 截图
18
+
19
+ ### Markdown 实时渲染
20
+
21
+ ![输入框实时渲染效果](docs/screenshots/overview.png)
22
+
23
+ ### 代码块
24
+
25
+ ![代码块语法高亮与语言标签](docs/screenshots/code-block.png)
26
+
27
+ ### 选区浮动格式条
28
+
29
+ ![选区上方浮现的浮动格式条](docs/screenshots/selection-bar.png)
30
+
31
+ ## 功能
32
+
33
+ ### Markdown 实时渲染
34
+
35
+ - 行内样式边打边生效:`**加粗**`、`*斜体*`、`` `行内代码` ``(品牌蓝胶囊)、`~~删除线~~`、`[链接](url)`(白名单协议)、`![图片](url)` 胶囊。
36
+ - **光标字符级精确对齐**。标记字符(`**`、`` ` ``、`~~`、`[]()`)淡化显示但保留精确宽度,粗体/标题用 paint-only 伪粗——渲染行与编辑器透明文字逐字符完全对齐,光标落在行内任何位置(包括格式区间内部)都精确贴齐,打字零漂移。
37
+ - 任务列表项:`- [ ]` / `- [x]` 渲染成勾选胶囊;已完成项内容划线。
38
+ - 草稿始终是纯 Markdown 源文本。渲染纯视觉——发送的消息就是你输入的源文本。
39
+
40
+ ### 代码块
41
+
42
+ - 输入 ``` 时按普通文本显示,直到围栏**闭合**——不会出现打一半就被渲染的残块。
43
+ - **未闭合围栏行上按 Shift+Enter** 自动补全封闭块(块内空行 + 闭合围栏 + 块后空行),光标落回块内,直接开写。
44
+ - 已闭合块最后一个内容行按 ↓ 直接跳出,落到块后的普通行。
45
+ - 零依赖正则语法高亮:js/ts/py/sh/json/yml/css/html,未标语言按内容自动推断——中文长文不会误判成代码。
46
+ - 围栏行右上角语言标签;GitHub-dark / GitHub-light 两套块主题;围栏字符淡化。
47
+ - 等宽:草稿含已闭合代码块期间,整个输入框(编辑器 + 渲染层)同步切换等宽字体栈(与官方代码字体同源)。中文字符刻意保持界面字体,两侧换行点永不发散。
48
+
49
+ ### 列表、引用、表格
50
+
51
+ - 列表行尾按 **Enter** 自动续项:无序列表保持符号(`-` `*` `+`)、有序列表递增编号(`1.` → `2.`、`3)` → `4)`)、缩进保留。**空列表项上按 Enter 退出列表**——两次 Shift+Enter 结束一个列表。
52
+ - `> ` 引用行同样续项(`>text` 无空格也算);空引用项上按 Enter 退出。
53
+ - **Tab / Shift+Tab** 缩进 / 反缩进两空格——单行或整块选区。光标在列表标记或行首空白处按 Tab 即嵌套子列表。Tab 全境拦截:焦点永远不会被切出输入框。
54
+ - 表格首行(`|列A|列B|`,至少 2 列)行尾按 **Enter** 自动补 `|---|---|` 分隔行加一个空行,光标落空行。
55
+
56
+ ### 工具栏与快捷键
57
+
58
+ - **常驻顶部工具栏**:加粗 / 斜体 / 行内代码 / 链接 / 列表 / 代码块 / 展开。选中文字时包裹、未选中插入空标记;光标落在 `**…**` 内时加粗键自动点亮。工具栏钉在滚动区顶部,永不与正文或图片附件重叠。
59
+ - **选区浮动格式条**(Open WebUI 形态):选中一段文字,加粗/斜体/代码/链接四键浮现在选区上方。
60
+ - **Ctrl+B / Ctrl+I / Ctrl+E** 快捷加粗 / 斜体 / 行内代码——同时拦掉浏览器对 contenteditable 的原生加粗(那会悄悄往草稿里插入真正的 `<b>` 标签)。
61
+
62
+ ### 粘贴处理
63
+
64
+ - 大段粘贴(超过 8 行或 4KB)且内容像代码/日志时,**自动用代码围栏包起来**——单一撤销单元,配 toast 轻提示。中文长文不误判;光标已在代码块内或内容本身含围栏时不包。
65
+
66
+ ### Esc 分发、主题、兼容
67
+
68
+ - **Esc** 按语境做对的事:官方候选菜单开着 → 归官方处理;展开态 → 收起;AI 生成中 → 停止生成。
69
+ - 一键**展开 70vh** 长文写作模式。
70
+ - 明暗主题自适应;输入法安全(拼音组字不闪烁);渲染按 rAF 合帧、增量进行。
71
+ - **@ 引用胶囊**保持官方原样、可编辑。含胶囊的段落优雅降级为官方显示(胶囊宽度无法复刻,零错位优先)。热词文本装饰不受影响。
72
+ - 顺带修复官方在图片附件在场时的两个定位问题:候选菜单悬空浮出输入框、工具栏盖住缩略图。
73
+
74
+ ### 不与 DSH 0.1.5 重复
75
+
76
+ 官方输入框已原生提供、本插件刻意不重做的:`/` 命令菜单、`@` 文件引用、粘贴图片/文件转附件、Enter 发送 / Shift+Enter 换行、输入法保护、草稿持久化。
77
+
78
+ ## 速查表
79
+
80
+ | 按键 | 语境 | 行为 |
81
+ | --- | --- | --- |
82
+ | Enter | 普通行 | 发送(官方) |
83
+ | Shift+Enter | 普通行 | 换行(官方) |
84
+ | Enter / Shift+Enter | 列表 / 引用行尾 | 续标记(有序递增编号) |
85
+ | Enter / Shift+Enter | 空列表 / 空引用项 | 删标记,退出列表 / 引用 |
86
+ | Enter / Shift+Enter | 未闭合围栏行 | 封闭代码块;光标落块内 |
87
+ | Enter / Shift+Enter | 表格首行(≥ 2 列) | 补分隔行 + 空行 |
88
+ | Tab | 代码块内 | 光标处插 2 空格 |
89
+ | Tab | 列表/引用标记头或行首空白 | 行首缩进(嵌套列表) |
90
+ | Tab | 其他位置 | 光标处插 2 空格 |
91
+ | Tab / Shift+Tab | 多行选区 | 整块缩进 / 反缩进 |
92
+ | Shift+Tab | 任意行 | 行首反缩进最多 2 空格(无缩进时只吞按键) |
93
+ | Ctrl+B / Ctrl+I / Ctrl+E | 任意 | 包裹选中加粗 / 斜体 / 代码,或插入空标记 |
94
+ | ↓ | 已闭合块最后一个内容行 | 跳到块后 |
95
+ | Esc | 菜单开着→官方 · 展开态→收起 · 生成中→停止 | 按语境分发 |
96
+
97
+ ## 环境要求
98
+
99
+ - **DSH ≥ 0.1.5-rc.2**,`web` profile——本插件面向 0.1.5 的 Lexical contenteditable 输入框。
100
+ - **Chromium 系浏览器**(Chrome、Edge 等):透明化技术依赖 `-webkit-text-fill-color`。
101
+ - 与操作系统无关——纯 DOM/CSS,无原生依赖。
102
+
103
+ ## 安装
104
+
105
+ ```bash
106
+ # 从 npm 安装(推荐)
107
+ dsh plugin --profile web add dsh-composer-live
108
+
109
+ # 或从本地目录安装(file: 安装为物理拷贝)
110
+ dsh plugin --profile web add "file:/path/to/dsh-composer-live"
111
+ ```
112
+
113
+ **装完必须重启 web 实例。** 插件按启动时快照加载,光刷新页面会新旧 bundle 并存。
114
+
115
+ 跑的是隔离实例?用 `DSH_HOME` 指向它,插件就装进那个实例(否则装进默认的 `~/.dsh`):
116
+
117
+ ```bash
118
+ DSH_HOME=/path/to/your/.dsh dsh plugin --profile web add dsh-composer-live
119
+ ```
120
+
121
+ ## 使用
122
+
123
+ 装上即全量生效——安装、重启、直接在输入框里打 Markdown。
124
+
125
+ 典型工作流:
126
+
127
+ - **代码块**:输入 ```` ```js ````,按 Shift+Enter——封闭块出现、光标在块内。写代码实时高亮;Tab 缩进两空格;最后一个内容行按 ↓ 跳出块后。
128
+ - **列表**:输入 `- 项目`,Shift+Enter 逐项续写;空项上再按一次 Shift+Enter 结束列表。Tab 嵌套,Shift+Tab 升回。
129
+ - **长文写作**:点展开键——输入框拉到 70vh;Esc 收起。
130
+ - **贴日志**:一大段代码或日志自动变成代码块;不想要就 Ctrl+Z。
131
+
132
+ ## 工作原理
133
+
134
+ 官方输入框是一个 Lexical contenteditable 编辑器。本插件用 `-webkit-text-fill-color: transparent` 把它的文字变透明(`color` 属性保留,所以光标和官方装饰照常工作),再绘制自己的渲染层——每个渲染字符都定位在编辑器文字的真实坐标上(逐字符测量 Range rect)。
135
+
136
+ 渲染层从不自己排版,所以对齐类问题(漂移、抖动、错位)在架构上不存在:实测各场景偏差 ≤ 0.02px,连续打字逐帧偏差 0.00。
137
+
138
+ 插件不注入任何官方服务。它直接读编辑器 DOM 推导「投影文本」(胶囊 → U+FFFC,`<br>`/段落边界 → 换行),对官方 client API 变化免疫;DOM 锚定用稳定的 `data-` 属性而非哈希过的 CSS Modules 类名。改动草稿的操作(列表续项、围栏补全、缩进……)走 Lexical 原生通道——保撤销栈的合成 `beforeinput` 事件——绝不直接改 DOM。
139
+
140
+ 完整深读见 [docs/architecture.md](./docs/architecture.md)。
141
+
142
+ ## 已知限制
143
+
144
+ - 含 **@ 胶囊**的段落按官方纯文本显示(胶囊宽度无法复刻,零错位优先)。热词文本装饰不受影响。
145
+ - 块级视觉(标题、引用、列表、表格标记)只上色/加粗/加背景——不改字号、行高、缩进;源文布局保留,行度量永不变化。
146
+ - 只要草稿含已闭合代码块,整个输入框切等宽(不是逐行——字面 `\n` 单文本节点的草稿形态让逐行换字体物理不可行)。
147
+ - 围栏补全的光标落位等 80ms(Lexical 异步提交);极慢机器上光标可能偶发落位偏后。
148
+ - ↓ 跳出仅在**已闭合**块最后一个内容行触发;未闭合围栏走官方默认行为。
149
+ - 「停止生成」按 aria-label 双语枚举匹配;官方改文案时请直接点按钮。
150
+ - 仅 `web` profile;仅 Chromium 系浏览器。
151
+
152
+ ## 开发
153
+
154
+ ```bash
155
+ node test-live.cjs # 188 项单测(纯函数,无需浏览器)
156
+ node e2e/run-e2e.mjs --with-deploy # 53 场景 × 10 不变量的 E2E 套件(对着运行中的实例测)
157
+ ```
158
+
159
+ E2E 套件复刻六种真实输入通道(字面 `\n` / 字面 `\r\n` / 逐行 br / insertParagraph / 合成 paste / 刷新恢复多段落),用一份独立投影与插件自身的投影逐字符交叉验证。见 [e2e/README.md](./e2e/README.md)。
160
+
161
+ 版本纪律:每次改动 bump `package.json` 并在 [CHANGELOG.md](./CHANGELOG.md) 记一笔。
162
+
163
+ ## FAQ
164
+
165
+ **为什么只支持 Chromium 系?**
166
+ 透明化手法(`-webkit-text-fill-color`)和若干光标行为是 Chromium 特有的。Firefox/Safari 需要另一套方案。
167
+
168
+ **会改变我发送的内容吗?**
169
+ 不会。草稿始终是纯 Markdown 源文本,渲染纯视觉。
170
+
171
+ **逐字符测量不慢吗?**
172
+ 按渲染帧合批,合并成少量绝对定位 span。渲染层 `pointer-events: none` 且脱离文档流,不引起编辑器重排。实测逐击键偏差为 0,无可感知输入延迟。
173
+
174
+ **DSH 升级会弄坏它吗?**
175
+ 零服务注入 + `data-` 属性锚定让它对日常升级(类名哈希变化、client API 重构)免疫。当官方输入框**架构本身**变化时——比如 0.1.1 → 0.1.5 从 textarea 换成 Lexical——插件需要重写。本插件正是那次重写的产物:`dsh-composer-md` 是它的 0.1.x 前身,现已停止维护。
176
+
177
+ ## 许可证
178
+
179
+ [MIT](./LICENSE)