dsh-quote-annotate-pro 0.3.0 → 0.3.1

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 CHANGED
@@ -2,6 +2,17 @@
2
2
 
3
3
  本项目的所有重要变更都记在这里。版本号遵循 [SemVer](https://semver.org/lang/zh-CN/)。
4
4
 
5
+ ## 0.3.1 — 引用管线懒重试:页面刷新后不再退化成纯文本
6
+
7
+ - **根因**:页面刷新时插件 `apply` 早于宿主 `inputTriggers` 服务就绪,`ctx.get('inputTriggers')` 拿到空值后
8
+ `pipelineAvailable` 被永久置为 `false` → 之后每次批注插入都走「纯文本回退」,输入框里出现 `【批注 1 …】` 裸文本块
9
+ (实测证据:localStorage 草稿记录为 `{"draft":{"text":"【批注 1 · …】\n钟)\n\n批注:1…","references":[]}}`,即没有 chip)
10
+ - **修复**:引用源注册改为幂等懒加载 `ensureQuoteSource()`,服务未就绪时按 500ms × 20 次(≈10s)自动重试,
11
+ 每次插入前也会先试一次;重试成功即恢复正常 chip 插入,无需用户刷新页面或重启
12
+ - **可观测性**:`insert:draft` 诊断补充 `draftLen` / `pipeline`;`insert:fallback-plaintext` 补充 `draftLen` / `bodyLen` / `pipeline`;
13
+ 编辑器提示按状态区分(`null` = 正在初始化引用管线,`false` = 回退为纯文本)
14
+ - 自检照旧:`npm test`(8 组纯函数断言)、`node --check`、`check-manifest` 全绿
15
+
5
16
  ## 0.3.0 — 编号批注 + 回复逐条对照
6
17
 
7
18
  - **批注自动编号**:同一条消息里每次插入批注都会编号(`批注 1`、`批注 2`…,删掉中间某条不会重号,见 `nextAnnotationNo()`);
package/README.en.md CHANGED
@@ -104,6 +104,7 @@ Official surfaces used: `slots` (`shell.overlay`, `conversation.chat.turnTail`,
104
104
 
105
105
  ```sh
106
106
  npm run check # manifest self-check (package.json / patch / bundle registration id)
107
+ npm test # pure-function self-check for the client bundle (numbering, block parsing, snippets — no browser/network)
107
108
  npm run diag # local diagnostics sink (http://127.0.0.1:47821)
108
109
  ```
109
110
 
package/lib/client.js CHANGED
@@ -215,6 +215,8 @@ window.__ModuleLoader__.load({
215
215
  let currentSessionId = null
216
216
  let refSeq = 0
217
217
  let pipelineAvailable = null // null=未知 false=不可用 true=可用
218
+ // 引用 source 的注册函数(懒加载 + 重试):服务晚就绪、HMR 重载后仍能恢复 chip 插入
219
+ let ensureQuoteSource = null
218
220
  // ref -> { sessionId, anchorKey, body, label } 用于点击 chip 跳回原文 / 悬停显示原文
219
221
  const refTargets = new Map()
220
222
  // ref -> { body, clipboardText } 提交时经 codec 序列化
@@ -910,6 +912,8 @@ window.__ModuleLoader__.load({
910
912
  refCodecs.delete(oldest)
911
913
  refTargets.delete(oldest)
912
914
  }
915
+ // 懒重试:若 apply 时 inputTriggers 还没就绪,插入前再试一次(常见于"刚启动就点批注")
916
+ try { if (typeof ensureQuoteSource === 'function') ensureQuoteSource() } catch (e) { /* ignore */ }
913
917
  // 事件派发目标:优先用 sessions.scope()(社区插件在本版本已验证的路径),退回 binding.ctx
914
918
  let emitCtx = binding.ctx
915
919
  try {
@@ -929,10 +933,13 @@ window.__ModuleLoader__.load({
929
933
  try {
930
934
  const snap2 = input.state.getSnapshot()
931
935
  const occ2 = (snap2 && snap2.occurrences) || []
936
+ const draft2 = String((snap2 && snap2.draft) || '')
932
937
  report('insert:draft', {
933
- draft: String((snap2 && snap2.draft) || '').replace(/\s+/g, ' ').slice(0, 120),
938
+ draft: draft2.replace(/\s+/g, ' ').slice(0, 400),
939
+ draftLen: draft2.length,
934
940
  occ: occ2.length,
935
941
  occLabels: occ2.map((o) => (o && o.label) || '').join('|').slice(0, 80),
942
+ pipeline: pipelineAvailable,
936
943
  })
937
944
  } catch (e) { /* ignore */ }
938
945
  report('insert:chip', { ref, anchorKey: item.anchorKey || null, header, chars: item.text.length })
@@ -950,7 +957,9 @@ window.__ModuleLoader__.load({
950
957
  refTargets.delete(ref)
951
958
  refCodecs.delete(ref)
952
959
  input.setDraft(base === '' ? quoteBody : base + '\n\n' + quoteBody)
953
- report('insert:fallback-plaintext', { ref, header })
960
+ let draftLen = 0
961
+ try { draftLen = String((input.state.getSnapshot() || {}).draft || '').length } catch (e) { /* ignore */ }
962
+ report('insert:fallback-plaintext', { ref, header, draftLen, bodyLen: quoteBody.length, pipeline: pipelineAvailable })
954
963
  setStatus({ ok: false, text: 'chip 插入未生效,已回退为纯文本引用' })
955
964
  editorStore.close()
956
965
  } catch (e) {
@@ -1183,9 +1192,21 @@ window.__ModuleLoader__.load({
1183
1192
  available: pipelineAvailable,
1184
1193
  hasInsertReference: !!(inputTriggers && typeof inputTriggers.insertReference === 'function'),
1185
1194
  })
1186
- if (pipelineAvailable) {
1195
+ // 注册机:幂等、可重试。宿主服务可能晚于插件就绪(或 HMR 重载后旧注册仍在),
1196
+ // 一次性判定会让整页退化成"纯文本回退",因此这里允许后续再次调用。
1197
+ let quoteSourceRegistered = false
1198
+ ensureQuoteSource = () => {
1199
+ if (quoteSourceRegistered) return true
1200
+ let triggers = inputTriggers
1201
+ if (!triggers) {
1202
+ try { triggers = ctx.get('inputTriggers') } catch (e) { triggers = undefined }
1203
+ }
1204
+ if (!triggers || typeof triggers.registerSource !== 'function') {
1205
+ pipelineAvailable = false
1206
+ return false
1207
+ }
1187
1208
  try {
1188
- ctx.effect(() => inputTriggers.registerSource({
1209
+ ctx.effect(() => triggers.registerSource({
1189
1210
  trigger: '@',
1190
1211
  name: 'quote-ref',
1191
1212
  order: 100,
@@ -1196,12 +1217,42 @@ window.__ModuleLoader__.load({
1196
1217
  serialize: (ref) => Promise.resolve(refCodecs.get(ref) ? refCodecs.get(ref).body : ''),
1197
1218
  },
1198
1219
  }), 'dsh-quote-annotate-pro: quote-ref source')
1220
+ quoteSourceRegistered = true
1221
+ pipelineAvailable = true
1199
1222
  report('register:quote-ref-source', 'ok')
1223
+ return true
1200
1224
  } catch (e) {
1225
+ const msg = String((e && e.stack) || (e && e.message) || e)
1226
+ // 已注册(HMR 重载后宿主保留旧注册)不算失败:序列化仍会路由到当前模块的 codec
1227
+ if (/already|duplicate|exist|注册/i.test(msg)) {
1228
+ quoteSourceRegistered = true
1229
+ pipelineAvailable = true
1230
+ report('register:quote-ref-source', 'already')
1231
+ return true
1232
+ }
1201
1233
  pipelineAvailable = false
1202
- report('register:quote-ref-source', 'throw ' + String((e && e.stack) || (e && e.message) || e))
1234
+ report('register:quote-ref-source', 'throw ' + msg)
1235
+ return false
1203
1236
  }
1204
1237
  }
1238
+ if (!ensureQuoteSource()) {
1239
+ // 退避重试:最多 20 次、间隔 500ms(约 10s),把启动时序造成的退化救回来
1240
+ try {
1241
+ ctx.effect(() => {
1242
+ let tries = 0
1243
+ let timer = null
1244
+ const tick = () => {
1245
+ timer = null
1246
+ if (quoteSourceRegistered || tries >= 20) return
1247
+ tries += 1
1248
+ if (!ensureQuoteSource()) timer = setTimeout(tick, 500)
1249
+ }
1250
+ timer = setTimeout(tick, 500)
1251
+ report('register:quote-ref-source', 'retry-scheduled')
1252
+ return () => { if (timer !== null) clearTimeout(timer) }
1253
+ }, 'dsh-quote-annotate-pro: quote-ref retry')
1254
+ } catch (e) { /* ignore */ }
1255
+ }
1205
1256
  // 全局监听(选区气泡 / chip 点击跳转 / chip 悬停提示;ctx.effect 保证卸载时全部清理)
1206
1257
  try {
1207
1258
  ctx.effect(() => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-quote-annotate-pro",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "划词批注与引用锚点:划词 → 批注(自动编号)→ 引用;引用块在输入框和已发送消息里始终可见(最多 3 行、位置行隐藏),点一下跳回原文并高亮;模型按「Annotation N:」逐条回应,回复里的标签可悬浮看原文与评语。纯客户端,锚点存本地 localStorage。",
5
5
  "keywords": ["dsh", "dsh-plugin", "deepseek-harness", "quote", "annotate", "selection", "reference", "anchor"],
6
6
  "license": "MIT",