dsh-edit-turn 0.3.1 → 0.3.3
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/README.md +14 -1
- package/lib/client.js +81 -16
- package/lib/index.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -164,7 +164,7 @@ ctx.sessions.registerMessageProjection({
|
|
|
164
164
|
| 运行中的实例是否真的挂载了本插件(只读路由守卫探针,无需 token) | `npm run probe:loaded [端口]` | 通过:`/state` 返 400、`/apply` 返 405 —— 这两个状态码只有本插件会返回 |
|
|
165
165
|
| 浏览器半部到底问了什么、拿回了什么(只读诊断,含失败记录) | `GET /dsh-edit-turn/debug` | 通过:真实浏览器里确认客户端确实拿到 replies;并复现了旧 v3 会话的 session-not-found |
|
|
166
166
|
| 官方 append **与读取**契约(真实校验器,进程内) | `npm run verify:contract` | 96 项通过(每段写入后都用加载器重读整份日志):替换事件被接受、派生历史真的收缩、日志 append-only、工具结果与调用同进同退、空 developer 载体不产生模型消息、**连续两次回退都被接受**、**编辑模型回答的完整机制被接受**、**回复编辑后的尾部重放(后续提问/回答/tool 调用与结果按新 turn 号回到修正回答之后,顺序正确、日志仍可加载)**、**跨插件依赖的不变量(单节点窗口 / 同事件类型 / 全窗口 sourceEventSeqs / 每个替换都带 `editedBy` 标记 / 改写后的 `source.kind` 不是 `user` / 修正回复不带 `sourceEventSeqs`)**,以及**就地补丁:一条 ignorable 记录、surface 不变、去掉投影后原文回来、后续轮次与 tool 结果不动、第二条记录可叠加** |
|
|
167
|
-
| 纯逻辑 + 宿主集成 + 客户端 DOM 行为(真 HTTP、真校验器、桩服务、DOM 桩) | `npm test` |
|
|
167
|
+
| 纯逻辑 + 宿主集成 + 客户端 DOM 行为(真 HTTP、真校验器、桩服务、DOM 桩) | `npm test` | 213 项通过(含回复编辑的尾部重放用例:窗口/重放区间、新 turn 号与新 messageId、tool 调用与结果成对重映射、上一轮修正可再次编辑,以及与 `dsh-rerun-turn` builder 的逐字段对照;另含「文本没变 → 不写」的四条,以及块随消息一起改的 35 条:未动的块逐字节保留、删块少一块、文字位置不变、no-op 语义升级、附件服务缺失时降级且不写坏数据、自定义 `quote-card` 块的通用性、录用与拒绝、`/state` 的块描述与能力、附件字节路由、编辑器芯片/删除/添加/缩略图/降级警告) |
|
|
168
168
|
| 客户端半部静态检查(注册、i18n 完整性、样式、皮肤可读性、版本三处同步、线协议、块契约) | `npm run verify:client` | 184 项全部通过(含 12 条块契约检查:能力上报、服务现场探测、录用是唯一建块路径、录用先于 append、**两侧都不特判块类型**、块逐字节复制、文字归位、`dropped` 上报、只在无服务时警告、芯片靠 `error` 事件自证) |
|
|
169
169
|
| 实机前端产物校验(运行中的 DSH 是否在下发当前代码) | `npm run verify:live -- --token-file ~/path/to/dsh.log` | 全部通过(含**宿主节点形状锚点**) |
|
|
170
170
|
| **真实浏览器渲染冒烟**(CDP 驱动已开着的标签页,只读) | `npm run verify:ui` | 全部通过:下发的字节就是刚改的字节、无槽位崩溃留下的空占位、回复铅笔在平台动作条里、被回滚的行保住动作条、**被回滚的用户行一定显示替代文本(消息不会凭空消失)**、**替代气泡一定排在时间/复制之上(顺序与正常消息一致)**、**改写后那颗铅笔一定在还活着的动作条末尾(已在原始会话、切走的会话、切回后三处现场确认)**、**回复铅笔一定排在动作条最左(复制之前,已现场确认)**、气泡上没有 `.dshet-floating` 压着文字、切走再切回入口仍在 |
|
|
@@ -250,6 +250,19 @@ ln -s /path/to/dsh-install/node_modules ./node_modules
|
|
|
250
250
|
|
|
251
251
|
### 更新日志
|
|
252
252
|
|
|
253
|
+
**0.3.3** —— 编辑器锚定到刚按下的铅笔,位置不再跑到屏幕另一端。
|
|
254
|
+
|
|
255
|
+
- 回复的编辑按钮在 turn-tail 操作条里,而编辑器的锚点行可能是比屏幕还高的回答行;0.3.2 会把它钳到视口顶部,按钮可达但离点击处很远。现在把锚点记为按下的按钮本身:优先贴在按钮下方,放不下就贴在按钮上方,再按视口钳制。
|
|
256
|
+
- 长草稿仍由编辑器自身滚动、footer sticky,保存/取消始终可见。
|
|
257
|
+
- **测试**:`npm test` **213/213**、`npm run verify:contract` **96** 项、`npm run verify:client` **184** 项。
|
|
258
|
+
|
|
259
|
+
**0.3.2** —— 修复编辑器在长回答或页面底部时位置错误、按钮点不到的问题。
|
|
260
|
+
|
|
261
|
+
- **视口钳制**:编辑框优先放在行下方;放不下就翻到行上方;行比屏幕高、底部在视口外时钉在视口底部,不再跟着行跑到屏幕外。
|
|
262
|
+
- **自身滚动**:编辑框有 `max-height` 和 `overflow-y:auto`,保存/取消所在的 footer 用 `position:sticky` 固定,长草稿也能滚到按钮。
|
|
263
|
+
- **宽度/左边界**也按视口钳制,窄窗口不会被推出屏幕。
|
|
264
|
+
- **测试**:`npm test` **213/213**、`npm run verify:contract` **96** 项、`npm run verify:client` **184** 项。
|
|
265
|
+
|
|
253
266
|
**0.3.1** —— 修复 0.3.0 的显示与跨插件清理问题。
|
|
254
267
|
|
|
255
268
|
- **编辑后看不到变化**:`turn-process` 行和 `assistant-step` 行都覆盖同一个 seq,旧的 DOM 映射按文档顺序先拿到过程行,补丁落到没有正文的行上。现在优先 assistant 行;极短回答是裸文本节点时也有兜底。
|
package/lib/client.js
CHANGED
|
@@ -42,7 +42,7 @@ window.__ModuleLoader__.load({
|
|
|
42
42
|
const RERUN_PREFIX = '/dsh-rerun-turn'
|
|
43
43
|
|
|
44
44
|
/** Keep in sync with package.json and lib/index.js. */
|
|
45
|
-
const PLUGIN_VERSION = '0.3.
|
|
45
|
+
const PLUGIN_VERSION = '0.3.3'
|
|
46
46
|
|
|
47
47
|
// --- copy -----------------------------------------------------------------
|
|
48
48
|
|
|
@@ -262,7 +262,7 @@ window.__ModuleLoader__.load({
|
|
|
262
262
|
// positioned under its row by `placeEditor`.
|
|
263
263
|
'.dshet-layer{position:fixed;left:0;top:0;right:0;bottom:0;pointer-events:none;z-index:40}',
|
|
264
264
|
'.dshet-layer .dshet-editor{position:absolute;pointer-events:auto}',
|
|
265
|
-
'.dshet-editor{margin:0;border:1px solid var(--dshet-line);border-radius:16px;background:var(--dshet-panel);backdrop-filter:blur(18px) saturate(1.2);box-shadow:var(--dshet-shadow);color:var(--dshet-ink);transition:border-color .12s}',
|
|
265
|
+
'.dshet-editor{display:flex;flex-direction:column;max-height:calc(100vh - 16px);overflow-y:auto;overscroll-behavior:contain;margin:0;border:1px solid var(--dshet-line);border-radius:16px;background:var(--dshet-panel);backdrop-filter:blur(18px) saturate(1.2);box-shadow:var(--dshet-shadow);color:var(--dshet-ink);transition:border-color .12s}',
|
|
266
266
|
'.dshet-editor:focus-within{border-color:var(--dsw-alias-button-primary-fill,var(--dshet-accent))}',
|
|
267
267
|
'.dshet-editor textarea{display:block;width:100%;min-height:47px;max-height:46vh;overflow-y:auto;resize:none;box-sizing:border-box;padding:11px 14px 0;border:0;background:transparent;color:inherit;font:inherit;font-size:var(--dsh-content-font-size,14px);line-height:calc(22px + var(--dsh-content-font-delta,0px));outline:none}',
|
|
268
268
|
'.dshet-editor textarea::placeholder{color:var(--dshet-ink-dim)}',
|
|
@@ -290,7 +290,7 @@ window.__ModuleLoader__.load({
|
|
|
290
290
|
'.dshet-note{color:var(--dshet-ink-dim)}',
|
|
291
291
|
'.dshet-warn{color:var(--dsw-alias-state-warning-primary,#a2650a)}',
|
|
292
292
|
'.dshet-error{color:var(--dsw-alias-state-error-primary,#c93a31)}',
|
|
293
|
-
'.dshet-footer{display:flex;align-items:center;justify-content:flex-end;gap:8px;padding:4px 10px 10px}',
|
|
293
|
+
'.dshet-footer{position:sticky;bottom:0;z-index:1;display:flex;align-items:center;justify-content:flex-end;gap:8px;padding:4px 10px 10px;background:var(--dshet-panel);backdrop-filter:blur(18px) saturate(1.2)}',
|
|
294
294
|
'.dshet-btn{padding:6px 16px;border:1px solid var(--dshet-line);border-radius:999px;background:var(--dshet-field);color:var(--dshet-ink);font:inherit;font-size:13px;line-height:20px;cursor:pointer;transition:background-color .12s,border-color .12s}',
|
|
295
295
|
'.dshet-btn:hover:not(:disabled){background:var(--dshet-hover)}',
|
|
296
296
|
'.dshet-btn:focus-visible{outline:2px solid var(--dsw-alias-button-primary-fill,var(--dshet-accent));outline-offset:2px}',
|
|
@@ -364,6 +364,7 @@ window.__ModuleLoader__.load({
|
|
|
364
364
|
this.editorElement = null
|
|
365
365
|
this.editorBox = null
|
|
366
366
|
this.editorRow = null
|
|
367
|
+
this.editorAnchor = null
|
|
367
368
|
this.editorFocus = false
|
|
368
369
|
this.editorDraft = ''
|
|
369
370
|
// The block list being edited: what the carrier will be rebuilt from.
|
|
@@ -563,6 +564,9 @@ window.__ModuleLoader__.load({
|
|
|
563
564
|
}
|
|
564
565
|
: target
|
|
565
566
|
this.editor = normalized
|
|
567
|
+
this.editorAnchor = target.anchor !== undefined && target.anchor !== null && target.anchor.isConnected === true
|
|
568
|
+
? target.anchor
|
|
569
|
+
: null
|
|
566
570
|
this.editorBlocks = replyBlocks
|
|
567
571
|
this.editorBlockIndex = chosen === undefined ? null : chosen.index
|
|
568
572
|
this.editorBlockKind = chosen === undefined ? null : chosen.kind
|
|
@@ -644,6 +648,7 @@ window.__ModuleLoader__.load({
|
|
|
644
648
|
this.editorElement = null
|
|
645
649
|
this.editorBox = null
|
|
646
650
|
this.editorRow = null
|
|
651
|
+
this.editorAnchor = null
|
|
647
652
|
this.editorFocus = false
|
|
648
653
|
this.editor = null
|
|
649
654
|
this.editorDraft = ''
|
|
@@ -1197,9 +1202,9 @@ window.__ModuleLoader__.load({
|
|
|
1197
1202
|
* at all and reads exactly like a dead pencil.
|
|
1198
1203
|
*/
|
|
1199
1204
|
function wireAction(button) {
|
|
1200
|
-
pressable(button, () => {
|
|
1205
|
+
pressable(button, (event) => {
|
|
1201
1206
|
const activate = button[ACTIVATE]
|
|
1202
|
-
if (typeof activate === 'function') activate()
|
|
1207
|
+
if (typeof activate === 'function') activate(event)
|
|
1203
1208
|
})
|
|
1204
1209
|
}
|
|
1205
1210
|
|
|
@@ -1815,7 +1820,14 @@ window.__ModuleLoader__.load({
|
|
|
1815
1820
|
} else if (button.dataset !== undefined && button.dataset.dshetFallback !== undefined) {
|
|
1816
1821
|
delete button.dataset.dshetFallback
|
|
1817
1822
|
}
|
|
1818
|
-
button[ACTIVATE] = () => controller.open(
|
|
1823
|
+
button[ACTIVATE] = (event) => controller.open({
|
|
1824
|
+
...entry.target,
|
|
1825
|
+
// The editor belongs beside the pencil the user just pressed, not at the
|
|
1826
|
+
// top of a row that may be taller than the screen.
|
|
1827
|
+
anchor: event !== undefined && event !== null && event.currentTarget !== undefined
|
|
1828
|
+
? event.currentTarget
|
|
1829
|
+
: button,
|
|
1830
|
+
})
|
|
1819
1831
|
liveHosts.add(host)
|
|
1820
1832
|
if (button.getAttribute('aria-label') !== label) {
|
|
1821
1833
|
button.setAttribute('aria-label', label)
|
|
@@ -1945,13 +1957,57 @@ window.__ModuleLoader__.load({
|
|
|
1945
1957
|
// Put the box under the row it belongs to: same width, just below its lower
|
|
1946
1958
|
// edge. Called on render and whenever the page scrolls or resizes, since
|
|
1947
1959
|
// the layer is fixed to the viewport.
|
|
1948
|
-
function
|
|
1960
|
+
function viewportSize() {
|
|
1961
|
+
const win = typeof window === 'undefined' ? undefined : window
|
|
1962
|
+
const doc = typeof document === 'undefined' ? undefined : document
|
|
1963
|
+
const width = (win !== undefined && win.innerWidth) ||
|
|
1964
|
+
(doc !== undefined && doc.documentElement && doc.documentElement.clientWidth) || 0
|
|
1965
|
+
const height = (win !== undefined && win.innerHeight) ||
|
|
1966
|
+
(doc !== undefined && doc.documentElement && doc.documentElement.clientHeight) || 0
|
|
1967
|
+
return { width, height }
|
|
1968
|
+
}
|
|
1969
|
+
|
|
1970
|
+
// Put the box under the row it belongs to: same width, just below its lower
|
|
1971
|
+
// edge. The layer is fixed to the viewport, so a row near the bottom (or one
|
|
1972
|
+
// taller than the screen) used to push the footer - and its save button -
|
|
1973
|
+
// past the bottom edge where nothing could reach it. Flip above the row when
|
|
1974
|
+
// there is room, otherwise pin the box to the bottom of the viewport and let
|
|
1975
|
+
// its own scrollbar carry the content; the footer stays sticky either way.
|
|
1976
|
+
function placeEditor(row, box, anchor = null) {
|
|
1949
1977
|
const layer = ensureEditorLayer()
|
|
1950
1978
|
if (box.parentElement !== layer) layer.appendChild(box)
|
|
1951
1979
|
const rect = row.getBoundingClientRect()
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1980
|
+
let anchorRect = null
|
|
1981
|
+
if (anchor !== null && anchor !== undefined && anchor.isConnected === true && typeof anchor.getBoundingClientRect === 'function') {
|
|
1982
|
+
const candidate = anchor.getBoundingClientRect()
|
|
1983
|
+
if (candidate.width > 0 && candidate.height > 0) anchorRect = candidate
|
|
1984
|
+
}
|
|
1985
|
+
const vertical = anchorRect === null ? rect : anchorRect
|
|
1986
|
+
const viewport = viewportSize()
|
|
1987
|
+
const margin = 8
|
|
1988
|
+
const width = viewport.width > 0
|
|
1989
|
+
? Math.max(1, Math.min(Math.max(200, rect.width || 0), viewport.width - margin * 2))
|
|
1990
|
+
: Math.max(200, rect.width || 0)
|
|
1991
|
+
box.style.width = `${Math.round(width)}px`
|
|
1992
|
+
const measured = box.getBoundingClientRect()
|
|
1993
|
+
const height = (measured && measured.height) || box.offsetHeight || 0
|
|
1994
|
+
let top = vertical.bottom + 6
|
|
1995
|
+
if (viewport.height > 0 && height > 0) {
|
|
1996
|
+
const maxTop = Math.max(margin, viewport.height - height - margin)
|
|
1997
|
+
if (top + height > viewport.height - margin) {
|
|
1998
|
+
const above = vertical.top - height - 6
|
|
1999
|
+
top = above >= margin ? above : maxTop
|
|
2000
|
+
}
|
|
2001
|
+
// A row taller than the screen has its bottom above the viewport after
|
|
2002
|
+
// the user scrolls to its action bar; without this clamp the box follows
|
|
2003
|
+
// it off-screen and the buttons become unreachable again.
|
|
2004
|
+
top = Math.max(margin, Math.min(top, maxTop))
|
|
2005
|
+
}
|
|
2006
|
+
let left = rect.left
|
|
2007
|
+
if (viewport.width > 0 && left + width > viewport.width - margin) left = viewport.width - width - margin
|
|
2008
|
+
if (left < margin) left = margin
|
|
2009
|
+
box.style.left = `${Math.round(left)}px`
|
|
2010
|
+
box.style.top = `${Math.round(top)}px`
|
|
1955
2011
|
}
|
|
1956
2012
|
|
|
1957
2013
|
// Grow the textarea to its content, the way a normal input box behaves.
|
|
@@ -1981,7 +2037,7 @@ window.__ModuleLoader__.load({
|
|
|
1981
2037
|
pressed = true
|
|
1982
2038
|
event.preventDefault()
|
|
1983
2039
|
event.stopPropagation()
|
|
1984
|
-
action()
|
|
2040
|
+
action(event)
|
|
1985
2041
|
}
|
|
1986
2042
|
button.onclick = (event) => {
|
|
1987
2043
|
event.preventDefault()
|
|
@@ -1990,7 +2046,7 @@ window.__ModuleLoader__.load({
|
|
|
1990
2046
|
pressed = false
|
|
1991
2047
|
return
|
|
1992
2048
|
}
|
|
1993
|
-
action()
|
|
2049
|
+
action(event)
|
|
1994
2050
|
}
|
|
1995
2051
|
}
|
|
1996
2052
|
|
|
@@ -2232,7 +2288,7 @@ window.__ModuleLoader__.load({
|
|
|
2232
2288
|
? controller.editorBox
|
|
2233
2289
|
: null
|
|
2234
2290
|
if (existing && existing.dataset.dshetFor === marker) {
|
|
2235
|
-
placeEditor(row, existing)
|
|
2291
|
+
placeEditor(row, existing, controller.editorAnchor)
|
|
2236
2292
|
return
|
|
2237
2293
|
}
|
|
2238
2294
|
|
|
@@ -2297,8 +2353,16 @@ window.__ModuleLoader__.load({
|
|
|
2297
2353
|
// "eats" pinyin.
|
|
2298
2354
|
if (event.isComposing === true) return
|
|
2299
2355
|
fitEditorHeight(area)
|
|
2356
|
+
const row = controller.editorRow
|
|
2357
|
+
const box = controller.editorBox
|
|
2358
|
+
if (row !== null && box !== null && box.isConnected === true) placeEditor(row, box, controller.editorAnchor)
|
|
2359
|
+
})
|
|
2360
|
+
area.addEventListener('compositionend', () => {
|
|
2361
|
+
fitEditorHeight(area)
|
|
2362
|
+
const row = controller.editorRow
|
|
2363
|
+
const box = controller.editorBox
|
|
2364
|
+
if (row !== null && box !== null && box.isConnected === true) placeEditor(row, box, controller.editorAnchor)
|
|
2300
2365
|
})
|
|
2301
|
-
area.addEventListener('compositionend', () => fitEditorHeight(area))
|
|
2302
2366
|
area.addEventListener('focus', () => {
|
|
2303
2367
|
controller.editorFocus = true
|
|
2304
2368
|
})
|
|
@@ -2408,11 +2472,11 @@ window.__ModuleLoader__.load({
|
|
|
2408
2472
|
footer.appendChild(submit)
|
|
2409
2473
|
box.appendChild(footer)
|
|
2410
2474
|
|
|
2411
|
-
placeEditor(row, box)
|
|
2412
2475
|
controller.editorBox = box
|
|
2413
2476
|
controller.editorRow = row
|
|
2414
2477
|
controller.editorElement = area
|
|
2415
2478
|
fitEditorHeight(area)
|
|
2479
|
+
placeEditor(row, box, controller.editorAnchor)
|
|
2416
2480
|
if (!view.confirming && !view.pending && (fresh || wasFocused)) {
|
|
2417
2481
|
try {
|
|
2418
2482
|
area.focus()
|
|
@@ -2886,6 +2950,7 @@ window.__ModuleLoader__.load({
|
|
|
2886
2950
|
event.preventDefault()
|
|
2887
2951
|
event.stopPropagation()
|
|
2888
2952
|
controller.open({
|
|
2953
|
+
anchor: event.currentTarget,
|
|
2889
2954
|
seq: entry.seq,
|
|
2890
2955
|
mode: 'reply',
|
|
2891
2956
|
turn: entry.turn,
|
|
@@ -2939,7 +3004,7 @@ window.__ModuleLoader__.load({
|
|
|
2939
3004
|
const keepPlaced = () => {
|
|
2940
3005
|
const row = controller.editorRow
|
|
2941
3006
|
const box = controller.editorBox
|
|
2942
|
-
if (row !== null && box !== null && box.isConnected === true) placeEditor(row, box)
|
|
3007
|
+
if (row !== null && box !== null && box.isConnected === true) placeEditor(row, box, controller.editorAnchor)
|
|
2943
3008
|
}
|
|
2944
3009
|
document.addEventListener('pointerdown', onPointerDown, true)
|
|
2945
3010
|
// Scroll events do not bubble; capture reaches the inner scroller too.
|
package/lib/index.js
CHANGED
|
@@ -80,7 +80,7 @@ export const PLUGIN_ID = 'dsh-edit-turn'
|
|
|
80
80
|
export const name = PLUGIN_ID
|
|
81
81
|
|
|
82
82
|
/** Keep in sync with package.json and lib/client.js. */
|
|
83
|
-
export const PLUGIN_VERSION = '0.3.
|
|
83
|
+
export const PLUGIN_VERSION = '0.3.3'
|
|
84
84
|
|
|
85
85
|
// A ring of the last few requests, answering "did the browser half even ask, and
|
|
86
86
|
// what did it get?" without needing its console. Read-only diagnostics: the
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-edit-turn",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.3",
|
|
4
4
|
"description": "Edit any message in DeepSeek Harness in place: a prompt is replaced where it stands; a model reply block (answer text or reasoning) is patched through a plugin record and a message projection, so the turn stays where it is, later turns are untouched and the log grows by one small record. Falls back to the replay splice on hosts without the projection API. The append-only session log is never rewritten.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|