dsh-edit-turn 0.2.8 → 0.2.10
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 +16 -2
- package/lib/client.js +73 -6
- package/lib/index.js +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -205,7 +205,8 @@ ln -s /path/to/dsh-install/node_modules ./node_modules
|
|
|
205
205
|
|
|
206
206
|
记录里有 `state` 且 `replies` 大于 0 → 数据到了客户端,问题在渲染;**一条记录都没有** → 客户端半部没跑起来(看浏览器控制台)。
|
|
207
207
|
|
|
208
|
-
4.
|
|
208
|
+
4. **「某个会话里完全看不到编辑入口,控制台里 `/dsh-edit-turn/state` 返回 404」**:这个会话**正被另一个 DSH 实例使用**(例如桌面版开着它,网页版就读不到;反之亦然)。同一会话同一时间只能被一个实例持有,这是宿主的行为,不是插件的问题——在那个实例里用,或先在另一个实例里放开它。`verify:ui` 遇到这种情况会注记并跳过该项,不会误报。
|
|
209
|
+
5. **「历史加载失败 / 某个会话读不出来」**(`session-not-found`,控制台里出现 `SessionFormatError`):这可能是 0.2.0–0.2.3 的回复编辑留下的坏日志。用仓库里的修复工具扫描并用官方加载器验证、备份后截断:
|
|
209
210
|
|
|
210
211
|
```sh
|
|
211
212
|
node tools/repair-session.mjs ~/.dsh/sessions/<项目目录> --dry-run
|
|
@@ -218,6 +219,18 @@ ln -s /path/to/dsh-install/node_modules ./node_modules
|
|
|
218
219
|
|
|
219
220
|
### 更新日志
|
|
220
221
|
|
|
222
|
+
**0.2.10** —— 修「编辑器浮层留在设置页上」。
|
|
223
|
+
|
|
224
|
+
- **修复:会话视图卸载后编辑器没有跟着走**。编辑器挂在 `body` 的独立浮层上(这是为了躲开宿主重绘),而没有任何框架会替它收尾:切到设置页时对话视图(连同注册在输入框卡上的条目)卸载了,编辑器却留在浮层里**飘在设置页上**。现在条目卸载时清掉自己的编辑器与参考,空浮层也一并移除。
|
|
225
|
+
- 卸载判定不能"一有 cleanup 就清":这个 effect 在每次视图变化时都会先 cleanup 再重跑,硬清会把编辑器反复拆掉(焦点/草稿语义都会乱)。现在是**延迟清理**——下一次 effect 重跑会取消它,只有真正的卸载(不再重跑)才让它落地。
|
|
226
|
+
- 新增用例「会话视图卸载后编辑器跟着消失(浮层也不留)」+ 2 条静态检查;110 单测 / 80 契约 / 静态 / verify:live / verify:ui 全绿。
|
|
227
|
+
|
|
228
|
+
**0.2.9** —— 修「同一条消息出现两个一样的气泡」。
|
|
229
|
+
|
|
230
|
+
- **修复:一行两个替代气泡,两个来源都堵掉**。①气泡此前只认「行的直系子节点 / 紧邻前一个兄弟」,一旦被宿主挪开就认不出,下一趟又种一个;现在气泡带**行的 key 标记**(`data-dshet-revision-for`),挪到哪都找得回来,且每行只允许一个(多余的按标记清除)。②更隐蔽的一个:MutationObserver 的回调可能**晚于它所属的那次渲染**,带着**过期视图**(`hidden` 为空)跑一遍,把已折叠的行重新展开——原气泡就回到了替代气泡旁边;现在这趟 pass 从 `controller.getSnapshot()` 现取当前视图,过期视图再也不可能把行展开。
|
|
231
|
+
- 测试桩补上真实 DOM 语义(`appendChild`/`insertBefore` 会先把节点从旧父节点摘除)——正是这条语义缺失让"同一个对象同时挂两处"在桩里被当成正常,掩盖了第一类问题。
|
|
232
|
+
- 新增用例「被挪走的替代气泡会被复用而不是再种一个」+ 3 条静态检查;109 单测 / 80 契约 / 静态 / verify:live / verify:ui 全绿。
|
|
233
|
+
|
|
221
234
|
**0.2.8** —— 安装元数据:peer 接受 DSH `0.2.0`。
|
|
222
235
|
|
|
223
236
|
- **修复:在 DSH 0.2.0 上会被 peer 检查拒装**。此前 peer 范围只列到 `^0.1.5-alpha.1`,`0.2.0` 不在其中;现在两个 peer(`@deepseek-ai/dsh-settings`、`@deepseek-ai/dsh-tools`)都追加 `|| ^0.2.0-rc.1`(`^0.2.0-rc.1` 同时覆盖 `0.2.0` 与后续 `0.2.x`)。**纯安装元数据,运行时代码零变化**;`npm run verify:profile`(真实 profile 安装 + 启动 + 路由)全绿。
|
|
@@ -712,7 +725,8 @@ Separate "the plugin never loaded" from "it loaded but nothing rendered" - the t
|
|
|
712
725
|
```
|
|
713
726
|
|
|
714
727
|
Mind the two kinds of dependency: **peers** provided by the host (`dsh-tools`, `dsh-settings`) are resolved by the DSH loader and need no install; **regular dependencies** (like `schemastery`) must resolve on their own - the loader will not find them for you.
|
|
715
|
-
3. **"
|
|
728
|
+
3. **"One session shows no edit entries at all while `/dsh-edit-turn/state` answers 404 in its console"**: that session is being **held by another DSH instance** (the desktop app has it open, or vice versa). A session can only belong to one instance at a time - host behaviour, not a plugin problem. Use it from that instance, or release it in the other one first. `verify:ui` notes and skips this case instead of failing.
|
|
729
|
+
4. **"The history fails to load / one session is unreadable"** (`session-not-found`,
|
|
716
730
|
with `SessionFormatError` in the console): this can be a log broken by the reply
|
|
717
731
|
edits of 0.2.0-0.2.3. The repository ships a repair tool that scans a sessions
|
|
718
732
|
directory, validates every log through the official loader, and - after a
|
package/lib/client.js
CHANGED
|
@@ -30,7 +30,7 @@ window.__ModuleLoader__.load({
|
|
|
30
30
|
const ROUTE_PREFIX = '/dsh-edit-turn'
|
|
31
31
|
|
|
32
32
|
/** Keep in sync with package.json and lib/index.js. */
|
|
33
|
-
const PLUGIN_VERSION = '0.2.
|
|
33
|
+
const PLUGIN_VERSION = '0.2.10'
|
|
34
34
|
|
|
35
35
|
// --- copy -----------------------------------------------------------------
|
|
36
36
|
|
|
@@ -264,6 +264,7 @@ window.__ModuleLoader__.load({
|
|
|
264
264
|
this.editorRow = null
|
|
265
265
|
this.editorFocus = false
|
|
266
266
|
this.editorDraft = ''
|
|
267
|
+
this.clearTimer = null
|
|
267
268
|
this.confirming = false
|
|
268
269
|
this.noticeTimer = null
|
|
269
270
|
this.view = Object.freeze({
|
|
@@ -548,6 +549,9 @@ window.__ModuleLoader__.load({
|
|
|
548
549
|
}
|
|
549
550
|
|
|
550
551
|
dispose() {
|
|
552
|
+
if (this.clearTimer !== null) window.clearTimeout(this.clearTimer)
|
|
553
|
+
this.clearTimer = null
|
|
554
|
+
clearEditor()
|
|
551
555
|
this.listeners.clear()
|
|
552
556
|
if (this.noticeTimer !== null) window.clearTimeout(this.noticeTimer)
|
|
553
557
|
this.noticeTimer = null
|
|
@@ -667,14 +671,33 @@ window.__ModuleLoader__.load({
|
|
|
667
671
|
if (existing !== null) existing.remove()
|
|
668
672
|
return true
|
|
669
673
|
}
|
|
674
|
+
// Only one bubble per row, ever: drop any other bubble that claims it
|
|
675
|
+
// (the host can shunt a bubble away from where it was planted, and the
|
|
676
|
+
// next pass used to plant a second one next to the stray).
|
|
677
|
+
{
|
|
678
|
+
const key = row.getAttribute('data-chat-flow-key')
|
|
679
|
+
if (typeof key === 'string' && key !== '') {
|
|
680
|
+
for (const candidate of document.querySelectorAll('.dshet-revision')) {
|
|
681
|
+
if (candidate !== existing && candidate.dataset !== undefined && candidate.dataset[REVISION_FOR] === key) {
|
|
682
|
+
candidate.remove()
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
}
|
|
670
687
|
const entry = view.editable.get(headRevision(view.revisions.get(revisionSeq), view.revisions))
|
|
671
688
|
const text = entry && typeof entry.text === 'string' ? entry.text : ''
|
|
672
689
|
if (text === '') {
|
|
673
690
|
if (existing !== null) existing.remove()
|
|
674
691
|
return node.kind !== 'user'
|
|
675
692
|
}
|
|
676
|
-
const bubble = existing === null ? cloneUserBubble() : existing
|
|
693
|
+
const bubble = existing === null ? cloneUserBubble(row) : existing
|
|
677
694
|
if (existing === null) bubble.dataset.dshetRevision = '1'
|
|
695
|
+
{
|
|
696
|
+
const key = row.getAttribute('data-chat-flow-key')
|
|
697
|
+
if (typeof key === 'string' && key !== '' && bubble.dataset[REVISION_FOR] !== key) {
|
|
698
|
+
bubble.dataset[REVISION_FOR] = key
|
|
699
|
+
}
|
|
700
|
+
}
|
|
678
701
|
placeRevisionBubble(row, bubble)
|
|
679
702
|
if (bubble.textContent !== text) bubble.textContent = text
|
|
680
703
|
// The edit entry goes where every other action of this row lives: the
|
|
@@ -703,11 +726,19 @@ window.__ModuleLoader__.load({
|
|
|
703
726
|
}
|
|
704
727
|
|
|
705
728
|
// A copy of a live user bubble element, classes and all.
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
729
|
+
// The key of the row a bubble stands in for. It is what makes a stray
|
|
730
|
+
// bubble recognisable after the host's DOM has shifted around it: an
|
|
731
|
+
// unlabelled bubble that is no longer the row's direct child or its
|
|
732
|
+
// immediate previous sibling used to be forgotten, and the next pass
|
|
733
|
+
// planted a second one - two identical bubbles on screen.
|
|
734
|
+
const REVISION_FOR = 'dshetRevisionFor'
|
|
735
|
+
function cloneUserBubble(row) {
|
|
709
736
|
const bubble = document.createElement('div')
|
|
710
737
|
bubble.className = 'dshet-revision'
|
|
738
|
+
if (row !== null && row !== undefined) {
|
|
739
|
+
const key = row.getAttribute('data-chat-flow-key')
|
|
740
|
+
if (typeof key === 'string' && key !== '') bubble.dataset[REVISION_FOR] = key
|
|
741
|
+
}
|
|
711
742
|
return bubble
|
|
712
743
|
}
|
|
713
744
|
|
|
@@ -725,6 +756,15 @@ window.__ModuleLoader__.load({
|
|
|
725
756
|
if (previous !== null && previous.classList && previous.classList.contains('dshet-revision')) {
|
|
726
757
|
return previous
|
|
727
758
|
}
|
|
759
|
+
// Anywhere else the host may have shunted it: the bubble carries the row
|
|
760
|
+
// key, so it is found again instead of duplicated. Filtered in place
|
|
761
|
+
// rather than with an attribute selector, which keeps the stubs honest.
|
|
762
|
+
const key = row.getAttribute('data-chat-flow-key')
|
|
763
|
+
if (typeof key === 'string' && key !== '') {
|
|
764
|
+
for (const bubble of document.querySelectorAll('.dshet-revision')) {
|
|
765
|
+
if (bubble.dataset !== undefined && bubble.dataset[REVISION_FOR] === key) return bubble
|
|
766
|
+
}
|
|
767
|
+
}
|
|
728
768
|
return null
|
|
729
769
|
}
|
|
730
770
|
|
|
@@ -967,6 +1007,12 @@ window.__ModuleLoader__.load({
|
|
|
967
1007
|
function clearEditor() {
|
|
968
1008
|
const elements = document.querySelectorAll('.dshet-editor')
|
|
969
1009
|
for (const element of elements) element.remove()
|
|
1010
|
+
// An empty layer has no business staying on the page: the editor is
|
|
1011
|
+
// hosted on the body, where nothing else clears up after it.
|
|
1012
|
+
if (editorLayer !== null && editorLayer.isConnected === true && editorLayer.children.length === 0) {
|
|
1013
|
+
editorLayer.remove()
|
|
1014
|
+
editorLayer = null
|
|
1015
|
+
}
|
|
970
1016
|
}
|
|
971
1017
|
|
|
972
1018
|
// The layer the editor is hosted on. It is created once, hangs off the
|
|
@@ -1416,7 +1462,11 @@ window.__ModuleLoader__.load({
|
|
|
1416
1462
|
let scheduled = false
|
|
1417
1463
|
const run = () => {
|
|
1418
1464
|
scheduled = false
|
|
1419
|
-
|
|
1465
|
+
// Always the CURRENT view: this callback can outlive the render that
|
|
1466
|
+
// created it, and a pass carrying an older view un-hid a collapsed
|
|
1467
|
+
// row - the original bubble reappeared beside the revision bubble.
|
|
1468
|
+
const live = typeof controller.getSnapshot === 'function' ? controller.getSnapshot() : view
|
|
1469
|
+
applyDom(snapshot, live, controller, t)
|
|
1420
1470
|
}
|
|
1421
1471
|
run()
|
|
1422
1472
|
const observer = new MutationObserver(() => {
|
|
@@ -1426,8 +1476,25 @@ window.__ModuleLoader__.load({
|
|
|
1426
1476
|
else window.setTimeout(run, 16)
|
|
1427
1477
|
})
|
|
1428
1478
|
observer.observe(document.body, { childList: true, subtree: true })
|
|
1479
|
+
// This effect re-runs whenever the view changes, and its cleanup runs
|
|
1480
|
+
// first every time; only a REAL unmount means the conversation view
|
|
1481
|
+
// went away (switching to Settings, say) and the editor has to go with
|
|
1482
|
+
// it - left behind, it floated over whatever came next. So the clear is
|
|
1483
|
+
// deferred, and the next run of this effect cancels it.
|
|
1484
|
+
if (controller.clearTimer !== null) {
|
|
1485
|
+
window.clearTimeout(controller.clearTimer)
|
|
1486
|
+
controller.clearTimer = null
|
|
1487
|
+
}
|
|
1429
1488
|
return () => {
|
|
1430
1489
|
observer.disconnect()
|
|
1490
|
+
if (controller.clearTimer !== null) window.clearTimeout(controller.clearTimer)
|
|
1491
|
+
controller.clearTimer = window.setTimeout(() => {
|
|
1492
|
+
controller.clearTimer = null
|
|
1493
|
+
clearEditor()
|
|
1494
|
+
controller.editorBox = null
|
|
1495
|
+
controller.editorRow = null
|
|
1496
|
+
controller.editorElement = null
|
|
1497
|
+
}, 250)
|
|
1431
1498
|
}
|
|
1432
1499
|
}, [snapshot, view, controller, t])
|
|
1433
1500
|
|
package/lib/index.js
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
// admission path. It resumes the Session when cold and starts exactly one
|
|
23
23
|
// new turn from the revised text.
|
|
24
24
|
//
|
|
25
|
-
// The silent replacement carrier is an EMPTY `developer/message` (0.2.
|
|
25
|
+
// The silent replacement carrier is an EMPTY `developer/message` (0.2.10;
|
|
26
26
|
// before that an empty `system/message`, which no reader accepts - see the
|
|
27
27
|
// README). Empty content projects to no model message, so the rollback adds
|
|
28
28
|
// nothing the model can see and the derived context after an edit is exactly
|
|
@@ -60,7 +60,7 @@ export const PLUGIN_ID = 'dsh-edit-turn'
|
|
|
60
60
|
export const name = PLUGIN_ID
|
|
61
61
|
|
|
62
62
|
/** Keep in sync with package.json and lib/client.js. */
|
|
63
|
-
export const PLUGIN_VERSION = '0.2.
|
|
63
|
+
export const PLUGIN_VERSION = '0.2.10'
|
|
64
64
|
|
|
65
65
|
// A ring of the last few requests, answering "did the browser half even ask, and
|
|
66
66
|
// 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.2.
|
|
3
|
+
"version": "0.2.10",
|
|
4
4
|
"description": "Edit any user turn in DeepSeek Harness and re-run it: an inline editor on the message row rolls the conversation back to just before that turn through the official surface-replace contract, then re-prompts the agent with the revised text. The append-only session log is never rewritten.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|