dsh-quote-followup 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 CHANGED
@@ -23,6 +23,7 @@ A Web-only [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) p
23
23
  - Follow the active DSH locale for the floating action and serialized quote frame.
24
24
  - Attach the conversation turn number to the serialized frame when both ends of the selection belong to the same DSH chat row. Selections spanning messages keep the excerpt but omit ambiguous role/turn provenance.
25
25
  - Quote multiple excerpts; on send, the plugin codec expands each chip into a model-readable Markdown blockquote.
26
+ - Resolve the active session through the DSH 0.1.7 UI session adapter, while retaining the older session-list selection path; never paste an unscoped quote when no session binding is available.
26
27
  - Fall back to a plain-text quote when the host lacks native chip support.
27
28
  - Show a localized notice if the composer is missing or unavailable, restore the selection, and let you retry quoting the original text.
28
29
 
@@ -58,7 +59,7 @@ Add `dsh-quote-followup` to the Web profile's `dsh.profile.bundles`, then restar
58
59
  npm test
59
60
  ```
60
61
 
61
- The regression harness covers compact native quote chips, locale switching, single-row versus cross-message provenance, missing/locked composer feedback and retry, repeated quoting, spacing after an existing draft, codec serialization, the Firefox text fallback, and current-client takeover of stale singleton/button state after a hot swap.
62
+ The regression harness covers modern and legacy session selection, absent selection/binding, compact native quote chips, locale switching, single-row versus cross-message provenance, missing/locked composer feedback and retry, repeated quoting, spacing after an existing draft, codec serialization, the Firefox text fallback, and current-client takeover of stale singleton/button state after a hot swap.
62
63
 
63
64
  An opt-in [assembled DSH Web regression](https://github.com/tr1v3r/dsh-quote-followup/blob/master/test/integration/README.md) runs the plugin through a pinned, built DSH checkout with Chromium. It covers existing drafts, repeated quoting, undo/redo, session isolation, prompt serialization, and Host-driven unavailable input.
64
65
 
package/README.zh.md CHANGED
@@ -23,6 +23,7 @@
23
23
  - 浮动按钮和发送时的引用框架跟随 DSH 当前语言。
24
24
  - 选区两端属于同一 DSH 聊天行时,序列化引用附带对话轮次编号;跨消息选区保留摘录,但不标注归属不明的角色与轮次。
25
25
  - 可连续引用多段内容;发送时由插件 codec 将各 chip 展开为模型可读的 Markdown 引用块。
26
+ - 通过 DSH 0.1.7 的 UI 会话适配器确定当前会话,同时兼容旧版会话列表的选中态;未取得会话 binding 时不会把引用作为无会话归属的文本插入。
26
27
  - 旧版 DSH 缺少原生 chip 能力时,自动降级为纯文本引用。
27
28
  - 输入框缺失或暂不可编辑时显示本地化提示,恢复原选区,便于重试引用。
28
29
 
@@ -58,7 +59,7 @@ dsh plugin --profile web add dsh-quote-followup
58
59
  npm test
59
60
  ```
60
61
 
61
- 回归测试覆盖精简的原生引用 chip、中英文 locale 切换、同消息与跨消息的来源判定、输入框缺失/不可编辑时的提示与重试、连续引用、已有草稿后的间距、codec 序列化、Firefox 文本降级路径,以及热替换后新版 client 接管旧按钮/单例状态。
62
+ 回归测试覆盖新版与旧版会话选中态、无选中会话或 binding、精简的原生引用 chip、中英文 locale 切换、同消息与跨消息的来源判定、输入框缺失/不可编辑时的提示与重试、连续引用、已有草稿后的间距、codec 序列化、Firefox 文本降级路径,以及热替换后新版 client 接管旧按钮/单例状态。
62
63
 
63
64
  可选的[真实 DSH Web 集成回归](https://github.com/tr1v3r/dsh-quote-followup/blob/master/test/integration/README.md)通过固定版本、已构建的 DSH 和 Chromium 加载插件,验证已有草稿、连续引用、撤销重做、会话隔离、发送序列化及 Host 驱动的不可写输入状态。
64
65
 
package/lib/client.js CHANGED
@@ -8,7 +8,7 @@
8
8
  * state and zero host-side privileges.
9
9
  *
10
10
  * Pure DOM (no React). The injected host services are DSH's
11
- * `inputTriggers` codec registry, `locale`, and `sessions`. Without the
11
+ * `inputTriggers` codec registry, `locale`, `uiSession`, and `sessions`. Without the
12
12
  * optional codec, quotes degrade to plain text; without locale, copy is English.
13
13
  * Session scoping is needed for native reference insertion. Mounted by the
14
14
  * web client runtime through the package's `dsh.client` declaration
@@ -37,7 +37,7 @@ window.__ModuleLoader__.load({
37
37
  ];
38
38
  const BUTTON_ID = "dsh-quote-followup-btn";
39
39
  const BUTTON_VERSION_ATTR = "data-dsh-quote-followup-version";
40
- const CLIENT_VERSION = "0.2.8";
40
+ const CLIENT_VERSION = "0.2.10";
41
41
  /** Shared <style> element; versioned like the button so hot-swaps replace it. */
42
42
  const STYLE_ID = "dsh-quote-followup-style";
43
43
  const STYLE_VERSION_ATTR = "data-dsh-quote-followup-style-version";
@@ -222,17 +222,51 @@ window.__ModuleLoader__.load({
222
222
  }
223
223
  return null;
224
224
  };
225
+ /** The UI adapter owns selection in 0.1.7; older hosts expose list.current. */
226
+ const selectedSessionId = (ctx, sessions) => {
227
+ let uiSession;
228
+ try {
229
+ uiSession = ctx?.get?.("uiSession");
230
+ } catch {
231
+ // Older hosts do not provide the UI session adapter.
232
+ }
233
+ const current = uiSession?.adapter?.current;
234
+ if (typeof current?.getSnapshot === "function") {
235
+ try {
236
+ return current.getSnapshot()?.key;
237
+ } catch {
238
+ return undefined;
239
+ }
240
+ }
241
+ return sessions?.list?.getSnapshot?.()?.current;
242
+ };
225
243
  /**
226
244
  * The per-session input facade owns chip creation, editor transactions and
227
245
  * phase/revision guards. Append at its published draft end (detect-text
228
246
  * offsets differ from clipboard-text lengths when a draft contains chips).
229
247
  */
230
- const appendQuoteChip = (ctx, element, payload, t) => {
248
+ const selectedSessionScope = (ctx) => {
231
249
  const sessions = ctx?.get?.("sessions");
232
- const sessionId = sessions?.list?.getSnapshot?.()?.current;
233
- const scope = sessionId === undefined ? null : sessions?.scope?.(sessionId);
234
- const conversation = scope?.get?.("conversation");
235
- if (scope === null || scope === undefined || typeof conversation?.input?.for !== "function")
250
+ if (sessions === null || sessions === undefined)
251
+ return null;
252
+ const sessionId = selectedSessionId(ctx, sessions);
253
+ // No selected session is not a missing composer capability. Never paste
254
+ // unscoped text into a composer that may belong to another session.
255
+ if (sessionId === undefined || sessionId === null)
256
+ return null;
257
+ // A binding is borrowed only for this synchronous edit; never retain it
258
+ // across a promise or a subsequent click after its generation may release.
259
+ // DSH 0.1.7 returns a SessionBinding wrapper whose scoped Cordis context
260
+ // lives at `.ctx`; older hosts returned that context from sessions.scope().
261
+ if (typeof sessions.binding === "function") {
262
+ const binding = sessions.binding(sessionId);
263
+ return binding?.ctx ?? null;
264
+ }
265
+ return sessions.scope?.(sessionId);
266
+ };
267
+ const appendQuoteChip = (scope, element, payload, t) => {
268
+ const conversation = scope.get?.("conversation");
269
+ if (typeof conversation?.input?.for !== "function")
236
270
  return false;
237
271
  let input;
238
272
  try {
@@ -263,8 +297,14 @@ window.__ModuleLoader__.load({
263
297
  const inserted = input.insertReference(quoteInsert(payload, t), {
264
298
  start: at, end: at, draftRev: current.draftRev
265
299
  });
266
- if (inserted)
267
- element.focus();
300
+ if (inserted) {
301
+ // DOM focus can move a Lexical caret to the start. The session
302
+ // facade restores the selection left after the inserted chip.
303
+ if (typeof input.focus === "function")
304
+ input.focus();
305
+ else
306
+ element.focus();
307
+ }
268
308
  return inserted ? true : null;
269
309
  } catch (error) {
270
310
  console.warn("[dsh-quote-followup] scoped reference insertion failed", error);
@@ -583,7 +623,10 @@ window.__ModuleLoader__.load({
583
623
  document.getSelection()?.removeAllRanges();
584
624
  const payload = quotePayload(quote.text, quote.role, quote.turn);
585
625
  try {
586
- const inserted = quoteSourceReady ? appendQuoteChip(clientContext, composer, payload, currentT) : false;
626
+ const scope = selectedSessionScope(clientContext);
627
+ const inserted = scope === null || scope === undefined
628
+ ? null
629
+ : quoteSourceReady ? appendQuoteChip(scope, composer, payload, currentT) : false;
587
630
  if (inserted === false)
588
631
  appendToComposer(composer, quoteFrame(payload, currentT));
589
632
  if (inserted === null) {
@@ -698,7 +741,7 @@ window.__ModuleLoader__.load({
698
741
  ctx.effect(() => cleanup, "dsh-quote-followup: selection quote ui");
699
742
  }
700
743
  //#endregion
701
- exports.inject = ["inputTriggers", "locale", "sessions"];
744
+ exports.inject = ["inputTriggers", "locale", "sessions", "uiSession"];
702
745
  exports.apply = apply;
703
746
  return module.exports;
704
747
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-quote-followup",
3
- "version": "0.2.8",
3
+ "version": "0.2.10",
4
4
  "description": "Quote selected Web conversation text into the DeepSeek Harness composer for targeted follow-up turns.",
5
5
  "keywords": [
6
6
  "dsh",