dsh-edit-turn 0.2.5 → 0.2.7

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 CHANGED
@@ -218,6 +218,14 @@ ln -s /path/to/dsh-install/node_modules ./node_modules
218
218
 
219
219
  ### 更新日志
220
220
 
221
+ **0.2.7** —— 修「同一条消息改写第二次后,气泡和笔一起消失」。
222
+
223
+ - **修复:替代链只解析了一跳**。同一条消息改写两次会落两条替换(`8 → 19`,再 `19 → 23`);转录行始终代表 `8`,客户端只取 `revisions.get(8) = 19` 就去可编辑集合里找条目——而 19 已被第二次改写折叠、条目已删,于是气泡和笔一起消失(`保存一次笔就没了`)。现在 `headRevision()` 沿链取到头(23)再取条目,气泡文本、笔的目标、编辑器锚定三处都按链头解析。新增用例「同一条消息改写两次,气泡与笔仍在(且编辑的是链头)」+ 1 条静态检查;真机复现(现场链 `8 → 19 → 87`):重载后气泡仍显示「回复2」、点笔打开的编辑器预填「回复2」。
224
+
225
+ **0.2.6** —— 修「改写过的提示词,再点它的笔没反应」。
226
+
227
+ - **修复:折叠改写行上的铅笔是死的**。改写后的提示词由一个替换事件承载,而平台**不为替换事件渲染行**——于是"哪一行覆盖被编辑的 seq"永远匹配不上,编辑器根本不渲染,点击无声无息(无报错)。现在"覆盖"也认**该行的替换指向**(`revisions.get(行seq) === 编辑seq`),并且编辑器自 0.2.3 起挂在独立浮层上,折叠行承载它没有任何问题。新增用例「改写过的提示词,点笔仍能打开编辑器(预填替代文本)」+ 1 条静态检查;真机复现:重载后点击折叠行上的笔,编辑器打开并预填「回复2」。
228
+
221
229
  **0.2.5** —— 修「每保存一次,模型的回答往下掉一格」。
222
230
 
223
231
  - **修复:被回滚的「轮次尾巴」没有隐藏,堆成了一列空条**。回复编辑会留下旧轮次的尾巴(用时/用量/动作条)——它不是消息行,属于它的消息已经被回滚,尾巴上没有任何可操作的东西。此前我们按"消息行"的规则给它保留了动作条(那条规则本是为了让被回滚的用户消息还能复制/删除),于是**每保存一次就多一条空条**,把对话一格格顶下去。现在按 `data-chat-flow-kind="turn-tail"` 识别:尾巴整条隐藏(并清理旧版本给它打过的 `data-dshet-keep-actions`),消息行照旧保留动作条。新增用例「被回滚的轮次尾巴整条消失,而不是叠一条空条」+ 2 条静态检查;`verify:ui` 的现场断言改为三条事实——轮次尾巴整条隐藏、没有动作条的行整条隐藏、有动作条的消息行必须保住动作条(真机 6 条孤儿尾巴全部按新规则消失)。
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.5'
33
+ const PLUGIN_VERSION = '0.2.7'
34
34
 
35
35
  // --- copy -----------------------------------------------------------------
36
36
 
@@ -667,7 +667,7 @@ window.__ModuleLoader__.load({
667
667
  if (existing !== null) existing.remove()
668
668
  return true
669
669
  }
670
- const entry = view.editable.get(view.revisions.get(revisionSeq))
670
+ const entry = view.editable.get(headRevision(view.revisions.get(revisionSeq), view.revisions))
671
671
  const text = entry && typeof entry.text === 'string' ? entry.text : ''
672
672
  if (text === '') {
673
673
  if (existing !== null) existing.remove()
@@ -728,6 +728,25 @@ window.__ModuleLoader__.load({
728
728
  return null
729
729
  }
730
730
 
731
+ // The message a chain of rewrites ends at.
732
+ //
733
+ // Rewriting the same message twice lands two replacements: 8 -> 19 for the
734
+ // first, 19 -> 23 for the second. The transcript row still stands for 8, so
735
+ // resolving a single hop lands on 19 - a shadowed seq with no entry left -
736
+ // and the bubble and its pencil silently disappeared after the second save.
737
+ // The chain has to be followed to the head (23).
738
+ function headRevision(seq, revisions) {
739
+ let current = seq
740
+ const seen = new Set([seq])
741
+ for (let hop = 0; hop < 64; hop += 1) {
742
+ const next = revisions.get(current)
743
+ if (typeof next !== 'number' || seen.has(next)) return current
744
+ current = next
745
+ seen.add(current)
746
+ }
747
+ return current
748
+ }
749
+
731
750
  // Plant the bubble where the message it replaces used to be: inside the row,
732
751
  // ahead of the node that carries the action bar, so the rewritten prompt reads
733
752
  // above the timestamp and the copy button - the order the platform draws a
@@ -1252,12 +1271,18 @@ window.__ModuleLoader__.load({
1252
1271
  // The editor anchors to whichever row covers the seq being edited, not to
1253
1272
  // the row that owns the entry: a reply's entry now lives in the turn-tail
1254
1273
  // action strip, while its editor still belongs under the reply text.
1255
- if (
1256
- !hidden &&
1257
- view.editing !== null &&
1258
- typeof view.editing.seq === 'number' &&
1259
- seqsFor(node).includes(view.editing.seq)
1260
- ) {
1274
+ //
1275
+ // A rewritten prompt is covered by no row at all: the revision is a
1276
+ // replacement event and the platform draws no row for those, so its seq
1277
+ // appears in no node. The row that STANDS for it - the collapsed one
1278
+ // whose pencil was just pressed - covers it through its revision
1279
+ // mapping, and the editor is hosted on the layer anyway, so rendering it
1280
+ // under a collapsed row costs nothing and works.
1281
+ const editing = view.editing
1282
+ const coversEditing = editing !== null && typeof editing.seq === 'number' &&
1283
+ (seqsFor(node).includes(editing.seq) ||
1284
+ seqsFor(node).some((seq) => headRevision(seq, view.revisions) === editing.seq))
1285
+ if (coversEditing && (!hidden || row.style.display !== 'none')) {
1261
1286
  renderEditor(row, controller, view, t)
1262
1287
  }
1263
1288
  }
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.5;
25
+ // The silent replacement carrier is an EMPTY `developer/message` (0.2.7;
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.5'
63
+ export const PLUGIN_VERSION = '0.2.7'
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.5",
3
+ "version": "0.2.7",
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",