dsh-sidenote 0.8.3 → 0.8.4

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/lib/client.js CHANGED
@@ -330,7 +330,18 @@ window.__ModuleLoader__.load({
330
330
  /** Internal conversation DOM seams, validated by the pinned host journeys.
331
331
  * Main message markers and composer seats have no public replacement API. */
332
332
  const MAIN_ASSISTANT = "[data-chat-flow-kind=\"assistant-step\"]";
333
- const NON_QUOTE = "button, [role=\"button\"], input, textarea, select, [contenteditable=\"true\"], [data-code-block-banner], [data-variant=\"think\"], [data-sidenote-no-quote]";
333
+ const NON_QUOTE = "button, [role=\"button\"], input, textarea, select, [contenteditable=\"true\"], [data-code-block-banner], [data-variant=\"think\"], [data-chat-group-part=\"reasoning\"], [data-sidenote-no-quote]";
334
+ const PROCESS_UI = "[data-variant=\"think\"], [data-chat-group-part=\"reasoning\"], [data-chat-flow-kind=\"tool-call\"], [data-chat-flow-kind=\"tool-result\"], [data-chat-flow-kind=\"turn-process\"], [data-context-kind=\"thinking\"], [data-context-kind=\"tool\"]";
335
+ /** Collapsed process rows can remain mounted, including during their animation. */
336
+ function quoteNodeVisible(node) {
337
+ for (let element = elementOf(node); element !== null; element = element.parentElement) {
338
+ if (element.matches("[hidden], [data-turn-process-hidden], [data-turn-process-inline]")) return false;
339
+ if (element instanceof HTMLDetailsElement && !element.open && !element.querySelector(":scope > summary")?.contains(node)) return false;
340
+ const style = getComputedStyle(element);
341
+ if (style.display === "none" || style.visibility === "hidden") return false;
342
+ }
343
+ return true;
344
+ }
334
345
  function nativeAssistantBodies(root = document) {
335
346
  return [...root.querySelectorAll(MAIN_ASSISTANT)];
336
347
  }
@@ -349,6 +360,7 @@ window.__ModuleLoader__.load({
349
360
  view: conversation ?? document.body,
350
361
  anchor,
351
362
  key: anchor.dataset.chatAnchorKey,
363
+ turn: body.closest("[data-chat-turn]")?.dataset.chatTurn,
352
364
  sessionId,
353
365
  surface: "main",
354
366
  streaming: body.hasAttribute("data-streaming") || body.querySelector("[data-streaming]") !== null
@@ -659,7 +671,8 @@ window.__ModuleLoader__.load({
659
671
  const inbox = inboxFaceOf(binding);
660
672
  const nodes = () => chat.getLegacy()?.nodes ?? [];
661
673
  const maxSeq = Math.max(-1, ...nodes().map((node) => node.seq ?? -1));
662
- const queueIds = new Set(queuedMessages(binding, inbox).map((message) => message.messageId ?? message.id));
674
+ const queuedBefore = queuedMessages(binding, inbox);
675
+ const queueIds = new Set(queuedBefore.map((message) => message.messageId ?? message.id));
663
676
  const previousError = binding.session.getSnapshot().promptError;
664
677
  const previousNoticeSeq = input.notices?.getSnapshot()?.seq ?? -1;
665
678
  const off = [];
@@ -695,7 +708,10 @@ window.__ModuleLoader__.load({
695
708
  };
696
709
  const check = () => {
697
710
  if (!submitted || settled) return;
698
- if (nodes().some((node) => (node.kind === "user" || node.kind === "steering") && (node.seq ?? -1) > maxSeq && matches(node)) || queuedMessages(binding, inbox).some((message) => {
711
+ if (nodes().some((node) => {
712
+ const id = node.messageId ?? node.id;
713
+ return (node.kind === "user" || node.kind === "steering") && (node.seq ?? -1) > maxSeq && matches(node) && (id === void 0 ? !queuedBefore.some(matches) : !queueIds.has(id));
714
+ }) || queuedMessages(binding, inbox).some((message) => {
699
715
  const id = message.messageId ?? message.id;
700
716
  return id !== void 0 && !queueIds.has(id) && matches(message);
701
717
  })) {
@@ -750,7 +766,7 @@ window.__ModuleLoader__.load({
750
766
  let dockRegistered = false;
751
767
  let report = {
752
768
  versions: {
753
- sidenote: "0.8.3",
769
+ sidenote: "0.8.4",
754
770
  dsh: "unknown",
755
771
  betterSidebar: null
756
772
  },
@@ -1404,13 +1420,13 @@ window.__ModuleLoader__.load({
1404
1420
  document.head.appendChild(tag);
1405
1421
  }
1406
1422
  var diagnostics_module_css_default = {
1407
- "trigger": "tGe0LW_trigger",
1423
+ "versions": "tGe0LW_versions",
1408
1424
  "header": "tGe0LW_header",
1409
- "list": "tGe0LW_list",
1410
- "description": "tGe0LW_description",
1411
1425
  "panel": "tGe0LW_panel",
1412
- "versions": "tGe0LW_versions",
1413
- "actions": "tGe0LW_actions"
1426
+ "trigger": "tGe0LW_trigger",
1427
+ "actions": "tGe0LW_actions",
1428
+ "list": "tGe0LW_list",
1429
+ "description": "tGe0LW_description"
1414
1430
  };
1415
1431
  //#endregion
1416
1432
  //#region src/client/diagnostics/index.tsx
@@ -1718,9 +1734,13 @@ window.__ModuleLoader__.load({
1718
1734
  if (typeof a.text !== "string" || typeof a.anchorText !== "string") return null;
1719
1735
  if (typeof a.occurrence !== "number") return null;
1720
1736
  if (a.state !== "active" && a.state !== "sent") return null;
1737
+ if (a.anchors !== void 0) {
1738
+ if (!Array.isArray(a.anchors) || a.anchors.length < 2) return null;
1739
+ for (const anchor of a.anchors) if (!anchor || typeof anchor.anchorKey !== "string" || !anchor.anchorKey || typeof anchor.anchorText !== "string" || !anchor.anchorText.trim() || !Number.isSafeInteger(anchor.occurrence) || anchor.occurrence < 0) return null;
1740
+ }
1721
1741
  return {
1722
1742
  id: a.id,
1723
- number: a.number,
1743
+ number: a.state === "sent" && typeof a.localNumber === "number" ? a.localNumber : a.number,
1724
1744
  sessionId: a.sessionId,
1725
1745
  sourceSessionId: typeof a.sourceSessionId === "string" && a.sourceSessionId !== "" ? a.sourceSessionId : void 0,
1726
1746
  sourceSurface: a.sourceSurface === "main" || a.sourceSurface === "side" ? a.sourceSurface : void 0,
@@ -1728,6 +1748,7 @@ window.__ModuleLoader__.load({
1728
1748
  text: a.text,
1729
1749
  anchorText: a.anchorText,
1730
1750
  occurrence: a.occurrence,
1751
+ ...a.anchors ? { anchors: a.anchors } : {},
1731
1752
  note: typeof a.note === "string" ? a.note : "",
1732
1753
  state: a.state,
1733
1754
  createdAt: typeof a.createdAt === "number" ? a.createdAt : 0
@@ -1737,11 +1758,24 @@ window.__ModuleLoader__.load({
1737
1758
  let annotations = [];
1738
1759
  let nextId = 1;
1739
1760
  let version = 0;
1740
- /** Per-session next display number. */
1741
- const counters = /* @__PURE__ */ new Map();
1742
1761
  const listeners = /* @__PURE__ */ new Set();
1743
1762
  const submissions = /* @__PURE__ */ new Map();
1744
1763
  const idleSubmission = { pending: false };
1764
+ const countActiveOf = (sessionId) => annotations.reduce((n, a) => n + (a.sessionId === sessionId && a.state === "active" ? 1 : 0), 0);
1765
+ /** 只重排目标会话的待发送编号,已发送编号保持冻结。 */
1766
+ const renumberActive = (sessionId) => {
1767
+ const numbers = /* @__PURE__ */ new Map();
1768
+ annotations.filter((a) => a.sessionId === sessionId && a.state === "active").sort((left, right) => left.id - right.id).forEach((annotation, index) => {
1769
+ numbers.set(annotation.id, index + 1);
1770
+ });
1771
+ annotations = annotations.map((a) => {
1772
+ const number = numbers.get(a.id);
1773
+ return number === void 0 || a.number === number ? a : {
1774
+ ...a,
1775
+ number
1776
+ };
1777
+ });
1778
+ };
1745
1779
  let store = storage ?? null;
1746
1780
  if (storage === void 0) try {
1747
1781
  store = globalThis.localStorage ?? null;
@@ -1772,7 +1806,6 @@ window.__ModuleLoader__.load({
1772
1806
  const revived = reviveStored(item);
1773
1807
  if (revived === null) continue;
1774
1808
  annotations.push(revived);
1775
- counters.set(revived.sessionId, Math.max(counters.get(revived.sessionId) ?? 0, revived.number));
1776
1809
  nextId = Math.max(nextId, revived.id + 1);
1777
1810
  }
1778
1811
  }
@@ -1805,6 +1838,7 @@ window.__ModuleLoader__.load({
1805
1838
  version += 1;
1806
1839
  for (const fn of [...listeners]) fn();
1807
1840
  };
1841
+ for (const sessionId of new Set(annotations.map((a) => a.sessionId))) renumberActive(sessionId);
1808
1842
  const replace = (id, patch) => {
1809
1843
  const target = annotations.find((a) => a.id === id);
1810
1844
  annotations = annotations.map((a) => a.id === id ? {
@@ -1829,8 +1863,7 @@ window.__ModuleLoader__.load({
1829
1863
  };
1830
1864
  },
1831
1865
  add(draft) {
1832
- const number = (counters.get(draft.sessionId) ?? 0) + 1;
1833
- counters.set(draft.sessionId, number);
1866
+ const number = countActiveOf(draft.sessionId) + 1;
1834
1867
  const annotation = {
1835
1868
  id: nextId,
1836
1869
  number,
@@ -1841,6 +1874,7 @@ window.__ModuleLoader__.load({
1841
1874
  text: draft.text,
1842
1875
  anchorText: draft.anchorText,
1843
1876
  occurrence: draft.occurrence,
1877
+ ...draft.anchors ? { anchors: draft.anchors } : {},
1844
1878
  note: draft.note,
1845
1879
  state: "active",
1846
1880
  createdAt: now()
@@ -1859,10 +1893,11 @@ window.__ModuleLoader__.load({
1859
1893
  const target = annotations.find((a) => a.id === id);
1860
1894
  if (target === void 0) return;
1861
1895
  annotations = annotations.filter((a) => a.id !== id);
1896
+ renumberActive(target.sessionId);
1862
1897
  persistTouched([target.sessionId]);
1863
1898
  notify();
1864
1899
  },
1865
- markSent(ids) {
1900
+ markSent(ids, frozenNumbers) {
1866
1901
  if (ids.length === 0) return;
1867
1902
  const idSet = new Set(ids);
1868
1903
  if (!annotations.some((a) => idSet.has(a.id) && a.state === "active")) return;
@@ -1872,9 +1907,11 @@ window.__ModuleLoader__.load({
1872
1907
  touched.add(a.sessionId);
1873
1908
  return {
1874
1909
  ...a,
1875
- state: "sent"
1910
+ state: "sent",
1911
+ number: frozenNumbers?.get(a.id) ?? a.number
1876
1912
  };
1877
1913
  });
1914
+ for (const sessionId of touched) renumberActive(sessionId);
1878
1915
  persistTouched([...touched]);
1879
1916
  notify();
1880
1917
  },
@@ -1888,7 +1925,7 @@ window.__ModuleLoader__.load({
1888
1925
  return annotations.filter((a) => a.sessionId === sessionId && a.state === "active");
1889
1926
  },
1890
1927
  countActive(sessionId) {
1891
- return annotations.reduce((n, a) => n + (a.sessionId === sessionId && a.state === "active" ? 1 : 0), 0);
1928
+ return countActiveOf(sessionId);
1892
1929
  },
1893
1930
  sessions() {
1894
1931
  return [...new Set(annotations.map((a) => a.sessionId))];
@@ -2238,7 +2275,7 @@ window.__ModuleLoader__.load({
2238
2275
  }
2239
2276
  /** 协议头识别(zh/en 都认):整行匹配。 */
2240
2277
  const PROTOCOL_HEADER_RE = /^(?:我批注了以下 (\d+) 处内容:|I annotated (\d+) passage\(s\) of the conversation above:)$/;
2241
- const ANNOTATION_RE = /<annotation id="(\d+)">\s*<quote>([\s\S]*?)<\/quote>\s*(?:<note>([\s\S]*?)<\/note>\s*)?<\/annotation>/y;
2278
+ const ANNOTATION_RE = /<annotation id="(\d+)"(?: local="(\d+)")?(?: scope="[^"]*")?>\s*<quote>([\s\S]*?)<\/quote>\s*(?:<note>([\s\S]*?)<\/note>\s*)?<\/annotation>/y;
2242
2279
  const REFLOW_RE = /<reflow\s+source="([^"]*)"(?:\s+reason="([^"]*)")?\s*>([\s\S]*?)<\/reflow>/y;
2243
2280
  /**
2244
2281
  * 气泡 tooltip / 面板留痕 chip 的展示拍平:剥掉 <问>/<答> 协议标签只留内容行
@@ -2276,14 +2313,16 @@ window.__ModuleLoader__.load({
2276
2313
  const headerMatch = /^([^\n]*)\n/.exec(rest);
2277
2314
  if (headerMatch !== null && PROTOCOL_HEADER_RE.test(headerMatch[1] ?? "")) {
2278
2315
  let cursor = pos + headerMatch[0].length;
2316
+ const legacyGuidance = /^Annotation ids stay stable within (?:their session scope|this conversation)\.[^\n]*\n/.exec(text.slice(cursor));
2317
+ if (legacyGuidance) cursor += legacyGuidance[0].length;
2279
2318
  for (;;) {
2280
2319
  ANNOTATION_RE.lastIndex = cursor;
2281
2320
  const m = ANNOTATION_RE.exec(text);
2282
2321
  if (m === null) break;
2283
2322
  annotations.push({
2284
- id: Number(m[1]),
2285
- quote: m[2] ?? "",
2286
- note: (m[3] ?? "").trim()
2323
+ id: Number(m[2] ?? m[1]),
2324
+ quote: m[3] ?? "",
2325
+ note: (m[4] ?? "").trim()
2287
2326
  });
2288
2327
  cursor = skipBlank(text, ANNOTATION_RE.lastIndex);
2289
2328
  }
@@ -2309,33 +2348,33 @@ window.__ModuleLoader__.load({
2309
2348
  document.head.appendChild(tag);
2310
2349
  }
2311
2350
  var annotations_module_css_default = {
2312
- "chipDismiss": "SVnebq_chipDismiss",
2313
- "userContextHost": "SVnebq_userContextHost",
2314
- "contextAppear": "SVnebq_contextAppear",
2351
+ "contextNumber": "SVnebq_contextNumber",
2352
+ "contextActions": "SVnebq_contextActions",
2353
+ "chipClearAll": "SVnebq_chipClearAll",
2315
2354
  "sentChips": "SVnebq_sentChips",
2316
- "contextSource": "SVnebq_contextSource",
2317
- "sentChevronOpen": "SVnebq_sentChevronOpen",
2318
- "contextPanel": "SVnebq_contextPanel",
2319
- "contextContent": "SVnebq_contextContent",
2355
+ "contextText": "SVnebq_contextText",
2356
+ "chipWrap": "SVnebq_chipWrap",
2357
+ "contextFooter": "SVnebq_contextFooter",
2358
+ "userContextHost": "SVnebq_userContextHost",
2359
+ "chip": "SVnebq_chip",
2320
2360
  "sentChipGroup": "SVnebq_sentChipGroup",
2321
- "chipMain": "SVnebq_chipMain",
2322
2361
  "sentChevron": "SVnebq_sentChevron",
2323
- "contextActions": "SVnebq_contextActions",
2324
- "contextAction": "SVnebq_contextAction",
2325
- "contextNumber": "SVnebq_contextNumber",
2326
- "storageNotice": "SVnebq_storageNotice",
2327
- "sentChip": "SVnebq_sentChip",
2328
2362
  "sentPreview": "SVnebq_sentPreview",
2329
- "contextItem": "SVnebq_contextItem",
2330
- "contextFooter": "SVnebq_contextFooter",
2331
- "contextLabel": "SVnebq_contextLabel",
2332
2363
  "contextList": "SVnebq_contextList",
2333
- "chipWrap": "SVnebq_chipWrap",
2334
2364
  "chipDismissShade": "SVnebq_chipDismissShade",
2335
- "chip": "SVnebq_chip",
2336
- "contextText": "SVnebq_contextText",
2337
- "chipClearAll": "SVnebq_chipClearAll",
2338
- "contextEditor": "SVnebq_contextEditor"
2365
+ "chipDismiss": "SVnebq_chipDismiss",
2366
+ "sentChip": "SVnebq_sentChip",
2367
+ "storageNotice": "SVnebq_storageNotice",
2368
+ "contextLabel": "SVnebq_contextLabel",
2369
+ "contextAppear": "SVnebq_contextAppear",
2370
+ "contextPanel": "SVnebq_contextPanel",
2371
+ "contextContent": "SVnebq_contextContent",
2372
+ "contextAction": "SVnebq_contextAction",
2373
+ "contextEditor": "SVnebq_contextEditor",
2374
+ "chipMain": "SVnebq_chipMain",
2375
+ "contextSource": "SVnebq_contextSource",
2376
+ "sentChevronOpen": "SVnebq_sentChevronOpen",
2377
+ "contextItem": "SVnebq_contextItem"
2339
2378
  };
2340
2379
  //#endregion
2341
2380
  //#region src/client/annotations/NativeUserContext.tsx
@@ -2809,38 +2848,38 @@ window.__ModuleLoader__.load({
2809
2848
  document.head.appendChild(tag);
2810
2849
  }
2811
2850
  var sidechat_module_css_default = {
2812
- "progress": "_5KEEsG_progress",
2813
2851
  "contextColumn": "_5KEEsG_contextColumn",
2814
- "emptyTitle": "_5KEEsG_emptyTitle",
2815
- "contextUserRow": "_5KEEsG_contextUserRow",
2816
2852
  "progressDetail": "_5KEEsG_progressDetail",
2817
- "contextChevronOpen": "_5KEEsG_contextChevronOpen",
2818
- "errorRow": "_5KEEsG_errorRow",
2853
+ "progressPreview": "_5KEEsG_progressPreview",
2854
+ "headerSide": "_5KEEsG_headerSide",
2855
+ "emptyTitle": "_5KEEsG_emptyTitle",
2819
2856
  "progressContent": "_5KEEsG_progressContent",
2820
- "reflowAction": "_5KEEsG_reflowAction",
2821
- "nativeAction": "_5KEEsG_nativeAction",
2857
+ "nativeToolbar": "_5KEEsG_nativeToolbar",
2858
+ "contextUserRow": "_5KEEsG_contextUserRow",
2859
+ "progress": "_5KEEsG_progress",
2860
+ "chatView": "_5KEEsG_chatView",
2861
+ "contextChevron": "_5KEEsG_contextChevron",
2862
+ "nativeConversation": "_5KEEsG_nativeConversation",
2863
+ "contextBubble": "_5KEEsG_contextBubble",
2864
+ "nativeHost": "_5KEEsG_nativeHost",
2865
+ "progressProcess": "_5KEEsG_progressProcess",
2866
+ "emptyIcon": "_5KEEsG_emptyIcon",
2822
2867
  "root": "_5KEEsG_root",
2823
- "progressLatest": "_5KEEsG_progressLatest",
2824
2868
  "emptyText": "_5KEEsG_emptyText",
2825
2869
  "contextImage": "_5KEEsG_contextImage",
2826
- "progressEntries": "_5KEEsG_progressEntries",
2827
- "nativeConversation": "_5KEEsG_nativeConversation",
2828
- "nativeHost": "_5KEEsG_nativeHost",
2829
- "chatView": "_5KEEsG_chatView",
2830
- "contextBubble": "_5KEEsG_contextBubble",
2831
2870
  "nativeToolbarActions": "_5KEEsG_nativeToolbarActions",
2832
- "historyToggle": "_5KEEsG_historyToggle",
2871
+ "progressEntries": "_5KEEsG_progressEntries",
2833
2872
  "mainContext": "_5KEEsG_mainContext",
2834
- "stateScreen": "_5KEEsG_stateScreen",
2873
+ "contextChevronOpen": "_5KEEsG_contextChevronOpen",
2874
+ "progressEntry": "_5KEEsG_progressEntry",
2835
2875
  "stateDetail": "_5KEEsG_stateDetail",
2836
- "progressPreview": "_5KEEsG_progressPreview",
2837
- "nativeToolbar": "_5KEEsG_nativeToolbar",
2838
- "contextChevron": "_5KEEsG_contextChevron",
2876
+ "progressLatest": "_5KEEsG_progressLatest",
2877
+ "nativeAction": "_5KEEsG_nativeAction",
2878
+ "errorRow": "_5KEEsG_errorRow",
2839
2879
  "contextHint": "_5KEEsG_contextHint",
2840
- "progressProcess": "_5KEEsG_progressProcess",
2841
- "headerSide": "_5KEEsG_headerSide",
2842
- "progressEntry": "_5KEEsG_progressEntry",
2843
- "emptyIcon": "_5KEEsG_emptyIcon"
2880
+ "historyToggle": "_5KEEsG_historyToggle",
2881
+ "stateScreen": "_5KEEsG_stateScreen",
2882
+ "reflowAction": "_5KEEsG_reflowAction"
2844
2883
  };
2845
2884
  //#endregion
2846
2885
  //#region src/client/sidechat/ContextProgress.tsx
@@ -2914,6 +2953,7 @@ window.__ModuleLoader__.load({
2914
2953
  "data-sidenote-quote-body": entry.messageKey ? "" : void 0,
2915
2954
  "data-sidenote-message-role": entry.messageKey ? "assistant" : void 0,
2916
2955
  "data-sidenote-message-key": entry.messageKey,
2956
+ "data-sidenote-turn": entry.turn === void 0 ? void 0 : `history:${entry.turn}`,
2917
2957
  "data-sidenote-streaming": live && unfinished ? "true" : "false",
2918
2958
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.MarkdownText, {
2919
2959
  text: entry.text,
@@ -3492,7 +3532,10 @@ ${body}
3492
3532
  return {
3493
3533
  ...entry,
3494
3534
  key,
3495
- ...node.kind === "assistant" && entry.kind === "reply" ? { messageKey: `a:${node.seq}` } : {},
3535
+ ...node.kind === "assistant" && entry.kind === "reply" ? {
3536
+ messageKey: `a:${node.seq}`,
3537
+ turn: node.turn
3538
+ } : {},
3496
3539
  ...node.kind !== "user" && node.kind !== "steering" && images.length && index === entries.length - 1 ? { images } : {}
3497
3540
  };
3498
3541
  });
@@ -6218,7 +6261,7 @@ ${body}
6218
6261
  /** Explicit side bodies are the sole exception to the legacy sidebar exclusion. */
6219
6262
  function quoteSourceOf(node, currentSessionId) {
6220
6263
  const element = elementOf(node);
6221
- if (element?.closest("button, [role=\"button\"], input, textarea, select, [contenteditable=\"true\"], [data-code-block-banner], [data-variant=\"think\"], [data-sidenote-no-quote]") !== null) return null;
6264
+ if (!element || element.closest(`button, [role="button"], input, textarea, select, [contenteditable="true"], [data-code-block-banner], [data-variant="think"], [data-chat-group-part="reasoning"], [data-sidenote-no-quote], [data-variant="think"], [data-chat-group-part="reasoning"], [data-chat-flow-kind="tool-call"], [data-chat-flow-kind="tool-result"], [data-chat-flow-kind="turn-process"], [data-context-kind="thinking"], [data-context-kind="tool"]`) !== null) return null;
6222
6265
  const body = element?.closest("[data-sidenote-quote-body]");
6223
6266
  if (body !== void 0 && body !== null) {
6224
6267
  const anchor = body.closest("[data-sidenote-message-key][data-sidenote-message-role=\"assistant\"]");
@@ -6232,6 +6275,7 @@ ${body}
6232
6275
  sessionId,
6233
6276
  surface: "side",
6234
6277
  key: anchor.dataset.sidenoteMessageKey,
6278
+ turn: body.dataset.sidenoteTurn ?? body.closest("[data-chat-turn]")?.dataset.chatTurn,
6235
6279
  streaming: body.dataset.sidenoteStreaming === "true"
6236
6280
  };
6237
6281
  }
@@ -6249,19 +6293,57 @@ ${body}
6249
6293
  return source.key;
6250
6294
  }
6251
6295
  function sourceBodies(sessionId, surface, anchorKey, currentSessionId) {
6252
- if (surface === "main") return mainAnchorBodies(sessionId, anchorKey, currentSessionId);
6253
- return [...document.querySelectorAll("[data-sidenote-quote-body]"), ...nativeAssistantBodies()].filter((body) => {
6296
+ return (surface === "main" ? mainAnchorBodies(sessionId, anchorKey, currentSessionId) : [...document.querySelectorAll("[data-sidenote-quote-body]"), ...nativeAssistantBodies().filter((body) => !body.querySelector("[data-sidenote-quote-body]"))]).filter((body) => {
6254
6297
  const source = quoteSourceOf(body, currentSessionId);
6255
- return source?.surface === "side" && source.sessionId === sessionId && sourceKey(source) === anchorKey;
6298
+ return source?.surface === surface && source.sessionId === sessionId && sourceKey(source) === anchorKey;
6256
6299
  });
6257
6300
  }
6301
+ /** Admit the complete visible selection, never silently drop process content. */
6302
+ function quoteSourcesOfRange(range, currentSessionId) {
6303
+ const root = range.commonAncestorContainer;
6304
+ const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT);
6305
+ const sources = [];
6306
+ const seen = /* @__PURE__ */ new Set();
6307
+ for (let node = root.nodeType === Node.TEXT_NODE ? root : walker.nextNode(); node; node = walker.nextNode()) {
6308
+ if (!range.intersectsNode(node) || !quoteNodeVisible(node)) continue;
6309
+ const start = node === range.startContainer ? range.startOffset : 0;
6310
+ const end = node === range.endContainer ? range.endOffset : node.textContent.length;
6311
+ if (node.textContent.slice(start, end).trim() === "") continue;
6312
+ const element = elementOf(node);
6313
+ if (element.closest("[data-variant=\"think\"], [data-chat-group-part=\"reasoning\"], [data-chat-flow-kind=\"tool-call\"], [data-chat-flow-kind=\"tool-result\"], [data-chat-flow-kind=\"turn-process\"], [data-context-kind=\"thinking\"], [data-context-kind=\"tool\"]")) return null;
6314
+ if (element.closest("button, [role=\"button\"], input, textarea, select, [contenteditable=\"true\"], [data-code-block-banner], [data-variant=\"think\"], [data-chat-group-part=\"reasoning\"], [data-sidenote-no-quote]")) {
6315
+ const body = element.closest(`[data-sidenote-quote-body], ${MAIN_ASSISTANT}`);
6316
+ if (body && quoteSourceOf(body, currentSessionId)) continue;
6317
+ return null;
6318
+ }
6319
+ const source = quoteSourceOf(node, currentSessionId);
6320
+ if (!source || source.streaming) return null;
6321
+ const first = sources[0];
6322
+ if (first && (source.view !== first.view || source.sessionId !== first.sessionId || source.surface !== first.surface)) return null;
6323
+ if (seen.has(source.body)) continue;
6324
+ if (first && (!first.turn || source.turn !== first.turn || !first.key || !source.key)) return null;
6325
+ sources.push(source);
6326
+ seen.add(source.body);
6327
+ }
6328
+ return sources.length ? sources : null;
6329
+ }
6330
+ /** Trim each stored fragment to the actual selected text, excluding surrounding UI. */
6331
+ function quoteRangeOf(body, selection) {
6332
+ const nodes = quoteTextNodes(body).filter((node) => selection.intersectsNode(node) && (node !== selection.startContainer || selection.startOffset < node.length) && (node !== selection.endContainer || selection.endOffset > 0));
6333
+ const first = nodes[0], last = nodes.at(-1);
6334
+ if (!first || !last) return null;
6335
+ const range = document.createRange();
6336
+ range.setStart(first, first === selection.startContainer ? selection.startOffset : 0);
6337
+ range.setEnd(last, last === selection.endContainer ? selection.endOffset : last.length);
6338
+ return range;
6339
+ }
6258
6340
  /** The same text-node projection is used at capture and when rebuilding anchors.
6259
6341
  * Browser selections contain block newlines; anchor offsets deliberately do not. */
6260
6342
  function quoteTextNodes(body) {
6261
6343
  const walker = document.createTreeWalker(body, NodeFilter.SHOW_TEXT);
6262
6344
  const nodes = [];
6263
6345
  for (let node = walker.nextNode(); node; node = walker.nextNode()) {
6264
- if (node.parentElement?.closest("button, [role=\"button\"], input, textarea, select, [contenteditable=\"true\"], [data-code-block-banner], [data-variant=\"think\"], [data-sidenote-no-quote]") !== null) continue;
6346
+ if (node.parentElement?.closest(`button, [role="button"], input, textarea, select, [contenteditable="true"], [data-code-block-banner], [data-variant="think"], [data-chat-group-part="reasoning"], [data-sidenote-no-quote], [data-variant="think"], [data-chat-group-part="reasoning"], [data-chat-flow-kind="tool-call"], [data-chat-flow-kind="tool-result"], [data-chat-flow-kind="turn-process"], [data-context-kind="thinking"], [data-context-kind="tool"]`) !== null || !quoteNodeVisible(node)) continue;
6265
6347
  if (node.parentElement !== null && getComputedStyle(node.parentElement).userSelect === "none") continue;
6266
6348
  nodes.push(node);
6267
6349
  }
@@ -6278,7 +6360,7 @@ ${body}
6278
6360
  function selectedQuoteText(range, browserText, anchorText) {
6279
6361
  if (browserText.replace(/\s/g, "") === anchorText.replace(/\s/g, "")) return browserText;
6280
6362
  const fragment = range.cloneContents();
6281
- for (const element of fragment.querySelectorAll(NON_QUOTE)) element.remove();
6363
+ for (const element of fragment.querySelectorAll(`${NON_QUOTE}, ${PROCESS_UI}, [hidden], [data-turn-process-hidden], [data-turn-process-inline]`)) element.remove();
6282
6364
  const textOf = (node) => {
6283
6365
  if (node.nodeType === Node.TEXT_NODE) return node.textContent ?? "";
6284
6366
  if (node instanceof Element && node.tagName === "BR") return "\n";
@@ -6302,11 +6384,11 @@ ${body}
6302
6384
  * toolbar through a small external-store controller. Mirrors the
6303
6385
  * production-proven dsh-sidebar-qa contract, narrowed to assistant messages:
6304
6386
  *
6305
- * - both selection endpoints must sit in the SAME `[data-chat-flow-kind=
6306
- * "assistant-step"]` message node (跨消息划选不弹);
6387
+ * - completed reply bodies in one view and Turn may be selected together;
6388
+ * - selected visible process/tool UI invalidates the whole selection;
6307
6389
  * - `[data-streaming]` messages (still streaming) are excluded;
6308
6390
  * - explicit side assistant bodies are admitted; other sidebar/plugin UI is
6309
- * excluded; both endpoints belong to the same message and source view;
6391
+ * excluded; every selected reply belongs to the same source view;
6310
6392
  * - quote text is kept complete, while anchor offsets use one DOM projection.
6311
6393
  *
6312
6394
  * These are DSH-internal DOM attributes rather than a public contract — an
@@ -6338,24 +6420,38 @@ ${body}
6338
6420
  const sel = window.getSelection();
6339
6421
  if (sel === null || sel.isCollapsed || sel.rangeCount === 0) return null;
6340
6422
  const range = sel.getRangeAt(0);
6341
- const source = quoteSourceOf(range.startContainer, currentSessionId);
6342
- const end = quoteSourceOf(range.endContainer, currentSessionId);
6343
- if (source === null || end === null || source.body !== end.body || source.view !== end.view || source.streaming) return null;
6344
- const anchorText = projectedRangeText(source.body, range);
6423
+ const sources = quoteSourcesOfRange(range, currentSessionId);
6424
+ if (!sources) return null;
6425
+ const ranges = sources.map((source) => quoteRangeOf(source.body, range));
6426
+ if (ranges.some((fragment) => fragment === null)) return null;
6427
+ const fragments = ranges;
6428
+ const anchors = sources.map((source, index) => {
6429
+ const anchorText = projectedRangeText(source.body, fragments[index]);
6430
+ return {
6431
+ anchorKey: sourceKey(source),
6432
+ anchorText,
6433
+ occurrence: occurrenceOf(source.body, fragments[index], anchorText)
6434
+ };
6435
+ });
6436
+ const source = sources[0];
6437
+ const anchorText = anchors.map((anchor) => anchor.anchorText).join("");
6345
6438
  if (anchorText.trim() === "") return null;
6346
- const text = selectedQuoteText(range, sel.toString(), anchorText);
6439
+ const browserText = sel.toString();
6440
+ const text = browserText.replace(/\s/g, "") === anchorText.replace(/\s/g, "") ? browserText : fragments.map((fragment, index) => selectedQuoteText(fragment, fragment.toString(), anchors[index].anchorText)).join("\n\n");
6347
6441
  const rect = range.getBoundingClientRect();
6348
6442
  return {
6349
6443
  text,
6350
6444
  anchorText,
6351
6445
  anchorKey: sourceKey(source),
6352
- occurrence: occurrenceOf(source.body, range, anchorText),
6446
+ occurrence: anchors[0].occurrence,
6447
+ ...anchors.length > 1 ? { anchors } : {},
6353
6448
  rect: {
6354
6449
  left: rect.left,
6355
6450
  top: rect.top,
6356
6451
  width: rect.width
6357
6452
  },
6358
6453
  range: range.cloneRange(),
6454
+ ranges: fragments,
6359
6455
  sessionId: source.sessionId,
6360
6456
  source
6361
6457
  };
@@ -6379,7 +6475,7 @@ ${body}
6379
6475
  } catch {
6380
6476
  next = null;
6381
6477
  }
6382
- if (!(selection === null !== (next === null) || selection !== null && next !== null && (selection.anchorText !== next.anchorText || selection.anchorKey !== next.anchorKey || selection.sessionId !== next.sessionId || selection.source.view !== next.source.view || selection.source.surface !== next.source.surface || selection.rect.top !== next.rect.top || selection.rect.left !== next.rect.left || selection.rect.width !== next.rect.width))) return;
6478
+ if (!(selection === null !== (next === null) || selection !== null && next !== null && (selection.anchorText !== next.anchorText || selection.anchorKey !== next.anchorKey || selection.occurrence !== next.occurrence || JSON.stringify(selection.anchors) !== JSON.stringify(next.anchors) || selection.sessionId !== next.sessionId || selection.source.view !== next.source.view || selection.source.surface !== next.source.surface || selection.rect.top !== next.rect.top || selection.rect.left !== next.rect.left || selection.rect.width !== next.rect.width))) return;
6383
6479
  selection = next;
6384
6480
  notify();
6385
6481
  };
@@ -6458,31 +6554,41 @@ ${body}
6458
6554
  range.setEnd(endNode, endOffset);
6459
6555
  return range;
6460
6556
  }
6461
- function resolveRange(annotation, cache, currentSessionId = "") {
6557
+ function resolveRanges(annotation, cache, currentSessionId = "") {
6462
6558
  try {
6463
6559
  const sessionId = annotation.sourceSessionId ?? annotation.sessionId;
6464
6560
  const surface = annotation.sourceSurface ?? "main";
6465
- const valid = (range, view) => {
6561
+ const anchors = annotation.anchors ?? [annotation];
6562
+ const valid = (ranges, view) => ranges.length === anchors.length && ranges.every((range, index) => {
6466
6563
  if (!range.startContainer.isConnected || !range.endContainer.isConnected) return false;
6467
- const source = quoteSourceOf(range.startContainer, currentSessionId);
6468
- const end = quoteSourceOf(range.endContainer, currentSessionId);
6469
- return source !== null && end?.body === source.body && source.view === view && source.sessionId === sessionId && source.surface === surface && sourceKey(source) === annotation.anchorKey && !source.streaming && projectedRangeText(source.body, range) === annotation.anchorText;
6470
- };
6564
+ const sources = quoteSourcesOfRange(range, currentSessionId);
6565
+ const source = sources?.[0];
6566
+ const anchor = anchors[index];
6567
+ return sources?.length === 1 && source !== void 0 && source.view === view && source.sessionId === sessionId && source.surface === surface && sourceKey(source) === anchor.anchorKey && projectedRangeText(source.body, range) === anchor.anchorText;
6568
+ });
6471
6569
  const cached = cache.get(annotation.id);
6472
- if (cached !== void 0 && valid(cached.range, cached.view)) return cached.range;
6570
+ if (cached !== void 0 && valid(cached.ranges, cached.view)) return cached.ranges;
6473
6571
  cache.delete(annotation.id);
6474
- if (annotation.anchorKey === void 0 || annotation.anchorText === "") return null;
6475
- const bodies = sourceBodies(sessionId, surface, annotation.anchorKey, currentSessionId).filter((body) => body.getClientRects().length > 0);
6476
- if (bodies.length !== 1) return null;
6477
- const body = bodies[0];
6478
- const source = quoteSourceOf(body, currentSessionId);
6479
- if (source === null || source.streaming) return null;
6480
- const range = rangeOfOccurrence(body, annotation.anchorText, annotation.occurrence);
6481
- if (range !== null) cache.set(annotation.id, {
6482
- range,
6483
- view: source.view
6572
+ const ranges = [];
6573
+ let view;
6574
+ for (const anchor of anchors) {
6575
+ if (!anchor.anchorKey || !anchor.anchorText) return null;
6576
+ const bodies = sourceBodies(sessionId, surface, anchor.anchorKey, currentSessionId).filter((body) => body.getClientRects().length > 0);
6577
+ if (bodies.length !== 1) return null;
6578
+ const body = bodies[0];
6579
+ const source = quoteSourceOf(body, currentSessionId);
6580
+ if (!source || view && source.view !== view) return null;
6581
+ view = source.view;
6582
+ const range = rangeOfOccurrence(body, anchor.anchorText, anchor.occurrence);
6583
+ if (!range) return null;
6584
+ ranges.push(range);
6585
+ }
6586
+ if (!view || !valid(ranges, view)) return null;
6587
+ cache.set(annotation.id, {
6588
+ ranges,
6589
+ view
6484
6590
  });
6485
- return range;
6591
+ return ranges;
6486
6592
  } catch {
6487
6593
  return null;
6488
6594
  }
@@ -6568,7 +6674,7 @@ ${body}
6568
6674
  }
6569
6675
  //#endregion
6570
6676
  //#region \0dsh-css:/home/runner/work/dsh-sidenote/dsh-sidenote/src/client/annotate/annotate.module.css.mjs
6571
- const css$1 = "._SVViG_toolbar{z-index:50;background:var(--dsw-alias-bg-layer-1,#fff);border:1px solid var(--dsw-alias-border-l1,#00000014);user-select:none;border-radius:10px;flex-wrap:wrap;align-items:stretch;width:max-content;display:flex;position:fixed;overflow:hidden;transform:translate(-50%,-100%);box-shadow:0 4px 16px #0000001f}._SVViG_toolbarBelow{transform:translate(-50%)}._SVViG_toolbarButton{appearance:none;color:var(--dsw-alias-label-primary,#1f2329);cursor:pointer;white-space:nowrap;background:0 0;border:none;padding:7px 12px;font-size:13px;line-height:20px}._SVViG_toolbarButton+._SVViG_toolbarButton{border-left:1px solid var(--dsw-alias-border-l1,#00000014)}._SVViG_toolbarButton:hover{background:var(--dsw-alias-interactive-bg-hover,#0000000d)}._SVViG_toolbarButton:focus-visible{outline-offset:-3px;outline:2px solid #2f88ff}._SVViG_toolbarStatus{color:var(--dsw-alias-label-secondary,#646a73);flex-basis:100%;padding:6px 12px;font-size:12px;line-height:18px}._SVViG_badge{z-index:50;color:#fff;text-align:center;cursor:pointer;user-select:none;background:#2f88ff;border:none;border-radius:999px;width:18px;height:18px;padding:0;font-size:11px;font-weight:600;line-height:18px;position:fixed;transform:translateY(-50%);box-shadow:0 1px 4px #2f88ff73}._SVViG_badge:hover{background:#1f78f0}._SVViG_highlight{z-index:49;pointer-events:none;background:#2f88ff38;border-radius:2px;position:fixed}._SVViG_editorNew{z-index:50;background:var(--dsw-alias-bg-layer-1,#fff);border:1px solid var(--dsw-alias-border-l1,#00000014);border-radius:999px;align-items:center;gap:8px;padding:6px 8px 6px 12px;display:flex;position:fixed;box-shadow:0 6px 24px #00000024}._SVViG_editorEdit{z-index:50;background:var(--dsw-alias-bg-layer-1,#fff);border:1px solid var(--dsw-alias-border-l1,#00000014);border-radius:14px;flex-direction:column;display:flex;position:fixed;overflow:hidden;box-shadow:0 8px 28px #00000029}._SVViG_editorInput{min-width:0;color:var(--dsw-alias-label-primary,#1f2329);background:0 0;border:none;outline:none;flex:auto;font-size:13px;line-height:22px}._SVViG_editorTextarea{resize:none;color:var(--dsw-alias-label-primary,#1f2329);background:0 0;border:none;outline:none;padding:10px 12px 4px;font-family:inherit;font-size:13px;line-height:20px}._SVViG_editorInput::placeholder,._SVViG_editorTextarea::placeholder{color:var(--dsw-alias-label-caption,#8f959e)}._SVViG_editorAsk{z-index:50;background:var(--dsw-alias-bg-layer-1,#fff);border:1px solid var(--dsw-alias-border-l1,#00000014);border-radius:14px;flex-direction:column;gap:2px;padding:6px 8px 6px 12px;display:flex;position:fixed;box-shadow:0 6px 24px #00000024}._SVViG_editorAskRow{align-items:center;gap:8px;display:flex}._SVViG_editorTarget{color:var(--dsw-alias-label-caption,#8f959e);user-select:none;padding:2px 2px 0;font-size:11px;line-height:16px}._SVViG_confirmButton{color:#fff;cursor:pointer;background:#1f2329;border:none;border-radius:999px;flex:none;place-items:center;width:26px;height:26px;padding:0;display:grid}._SVViG_confirmButton:hover{background:#000}._SVViG_editorFooter{align-items:center;gap:8px;padding:6px 10px 8px;display:flex}._SVViG_editorSpacer{flex:auto}._SVViG_deleteButton{width:26px;height:26px;color:var(--dsw-alias-label-tertiary,#8f959e);cursor:pointer;background:0 0;border:none;border-radius:999px;place-items:center;padding:0;display:grid}._SVViG_deleteButton:hover{background:var(--dsw-alias-interactive-bg-hover,#0000000d);color:var(--dsw-alias-state-error-primary,#d53f3f)}._SVViG_cancelButton,._SVViG_saveButton{cursor:pointer;border:none;border-radius:999px;padding:4px 14px;font-size:13px;line-height:20px}._SVViG_cancelButton{background:var(--dsw-alias-interactive-bg-hover,#0000000d);color:var(--dsw-alias-label-primary,#1f2329)}._SVViG_saveButton{color:#fff;background:#1f2329}._SVViG_saveButton:hover{background:#000}._SVViG_editorError{color:#e5484d;white-space:nowrap;font-size:12px;position:absolute;top:calc(100% + 4px);left:12px}._SVViG_badgeSent{background:var(--dsw-alias-label-caption,#8f959e);box-shadow:none;opacity:.75}._SVViG_badgeSent:hover{background:var(--dsw-alias-label-tertiary,#6b7075)}._SVViG_sentChip{border:1px solid var(--dsw-alias-border-l1,#0000001a);background:var(--dsw-alias-interactive-bg-hover,#0000000d);color:var(--dsw-alias-label-secondary,#3f454d);cursor:default;user-select:none;border-radius:999px;align-items:center;gap:4px;margin:0 0 6px;padding:1px 10px;font-size:12px;line-height:18px;display:inline-flex}._SVViG_sentCardTitle{color:var(--dsw-alias-label-tertiary,#8f959e);padding:2px 12px 0;font-size:12px;font-weight:600}._SVViG_sentCardQuote{color:var(--dsw-alias-label-secondary,#3f454d);border-left:2px solid var(--dsw-alias-border-l1,#0000001f);word-break:break-word;margin:4px 12px 0;padding:4px 12px;font-size:13px;line-height:20px}._SVViG_sentCardNote{color:var(--dsw-alias-label-primary,#1f2329);word-break:break-word;padding:4px 12px 10px;font-size:13px;line-height:20px}._SVViG_hint{z-index:50;background:var(--dsw-alias-bg-layer-1,#fff);border:1px solid var(--dsw-alias-border-l1,#00000014);max-width:320px;color:var(--dsw-alias-label-secondary,#3f454d);border-radius:12px;align-items:center;gap:10px;padding:10px 12px;font-size:12px;line-height:18px;animation:.25s ease-out _SVViG_dshSidenoteHintIn;display:flex;position:fixed;bottom:160px;right:24px;box-shadow:0 8px 28px #00000024}._SVViG_hintClose{width:20px;height:20px;color:var(--dsw-alias-label-tertiary,#8f959e);cursor:pointer;background:0 0;border:none;border-radius:999px;flex:none;place-items:center;padding:0;font-size:14px;line-height:1;display:grid}._SVViG_hintClose:hover{background:var(--dsw-alias-interactive-bg-hover,#0000000d);color:var(--dsw-alias-label-primary,#1f2329)}@keyframes _SVViG_dshSidenoteHintIn{0%{opacity:0;transform:translateY(6px)}to{opacity:1;transform:translateY(0)}}._SVViG_sentChipRow{align-items:center;gap:6px;margin:0 0 6px;display:inline-flex}._SVViG_badgeSent{background:var(--dsw-alias-bg-layer-1,#fff);color:var(--dsw-alias-label-tertiary,#8f959e);border:1.5px solid var(--dsw-alias-label-caption,#b0b5bb);box-shadow:none;opacity:1;line-height:15px}._SVViG_badgeSent:hover{background:var(--dsw-alias-interactive-bg-hover,#0000000a);color:var(--dsw-alias-label-secondary,#3f454d)}._SVViG_hint{background:color-mix(in srgb, var(--dsw-alias-bg-layer-1,#fff) 88%, transparent);backdrop-filter:blur(8px);bottom:130px}";
6677
+ const css$1 = "._SVViG_toolbar{z-index:50;background:var(--dsw-alias-bg-layer-1,#fff);border:1px solid var(--dsw-alias-border-l1,#00000014);user-select:none;border-radius:10px;flex-wrap:wrap;align-items:stretch;width:max-content;display:flex;position:fixed;overflow:hidden;transform:translate(-50%,-100%);box-shadow:0 4px 16px #0000001f}._SVViG_toolbarBelow{transform:translate(-50%)}._SVViG_toolbarButton{appearance:none;color:var(--dsw-alias-label-primary,#1f2329);cursor:pointer;white-space:nowrap;background:0 0;border:none;padding:7px 12px;font-size:13px;line-height:20px}._SVViG_toolbarButton+._SVViG_toolbarButton{border-left:1px solid var(--dsw-alias-border-l1,#00000014)}._SVViG_toolbarButton:hover{background:var(--dsw-alias-interactive-bg-hover,#0000000d)}._SVViG_toolbarButton:focus-visible{outline-offset:-3px;outline:2px solid #2f88ff}._SVViG_toolbarStatus{color:var(--dsw-alias-label-secondary,#646a73);flex-basis:100%;padding:6px 12px;font-size:12px;line-height:18px}._SVViG_badge{z-index:50;color:#fff;text-align:center;cursor:pointer;user-select:none;background:#2f88ff;border:none;border-radius:999px;width:18px;height:18px;padding:0;font-size:11px;font-weight:600;line-height:18px;position:fixed;transform:translateY(-50%);box-shadow:0 1px 4px #2f88ff73}._SVViG_badge:hover{background:#1f78f0}._SVViG_highlight{z-index:49;pointer-events:none;background:#2f88ff38;border-radius:2px;position:fixed}._SVViG_editorNew{z-index:50;background:var(--dsw-alias-bg-layer-1,#fff);border:1px solid var(--dsw-alias-border-l1,#00000014);border-radius:18px;align-items:center;gap:8px;padding:6px 8px 6px 12px;display:flex;position:fixed;box-shadow:0 6px 24px #00000024}._SVViG_editorEdit{z-index:50;background:var(--dsw-alias-bg-layer-1,#fff);border:1px solid var(--dsw-alias-border-l1,#00000014);border-radius:14px;flex-direction:column;display:flex;position:fixed;overflow:hidden;box-shadow:0 8px 28px #00000029}._SVViG_editorInput{resize:none;min-width:0;min-height:21px;max-height:110px;color:var(--dsw-alias-label-primary,#1f2329);background:0 0;border:none;outline:none;flex:auto;padding:0;font-family:inherit;font-size:13px;line-height:22px;overflow-y:auto}._SVViG_editorTextarea{resize:none;color:var(--dsw-alias-label-primary,#1f2329);background:0 0;border:none;outline:none;padding:10px 12px 4px;font-family:inherit;font-size:13px;line-height:20px}._SVViG_editorInput::placeholder,._SVViG_editorTextarea::placeholder{color:var(--dsw-alias-label-caption,#8f959e)}._SVViG_editorAsk{z-index:50;background:var(--dsw-alias-bg-layer-1,#fff);border:1px solid var(--dsw-alias-border-l1,#00000014);border-radius:14px;flex-direction:column;gap:2px;padding:6px 8px 6px 12px;display:flex;position:fixed;box-shadow:0 6px 24px #00000024}._SVViG_editorAskRow{align-items:center;gap:8px;display:flex}._SVViG_editorTarget{color:var(--dsw-alias-label-caption,#8f959e);user-select:none;padding:2px 2px 0;font-size:11px;line-height:16px}._SVViG_confirmButton{color:#fff;cursor:pointer;background:#1f2329;border:none;border-radius:999px;flex:none;place-items:center;width:26px;height:26px;padding:0;display:grid}._SVViG_confirmButton:hover{background:#000}._SVViG_editorFooter{align-items:center;gap:8px;padding:6px 10px 8px;display:flex}._SVViG_editorSpacer{flex:auto}._SVViG_deleteButton{width:26px;height:26px;color:var(--dsw-alias-label-tertiary,#8f959e);cursor:pointer;background:0 0;border:none;border-radius:999px;place-items:center;padding:0;display:grid}._SVViG_deleteButton:hover{background:var(--dsw-alias-interactive-bg-hover,#0000000d);color:var(--dsw-alias-state-error-primary,#d53f3f)}._SVViG_cancelButton,._SVViG_saveButton{cursor:pointer;border:none;border-radius:999px;padding:4px 14px;font-size:13px;line-height:20px}._SVViG_cancelButton{background:var(--dsw-alias-interactive-bg-hover,#0000000d);color:var(--dsw-alias-label-primary,#1f2329)}._SVViG_saveButton{color:#fff;background:#1f2329}._SVViG_saveButton:hover{background:#000}._SVViG_editorError{color:#e5484d;white-space:normal;overflow-wrap:anywhere;padding:2px 2px 0;font-size:12px}._SVViG_badgeSent{background:var(--dsw-alias-label-caption,#8f959e);box-shadow:none;opacity:.75}._SVViG_badgeSent:hover{background:var(--dsw-alias-label-tertiary,#6b7075)}._SVViG_sentChip{border:1px solid var(--dsw-alias-border-l1,#0000001a);background:var(--dsw-alias-interactive-bg-hover,#0000000d);color:var(--dsw-alias-label-secondary,#3f454d);cursor:default;user-select:none;border-radius:999px;align-items:center;gap:4px;margin:0 0 6px;padding:1px 10px;font-size:12px;line-height:18px;display:inline-flex}._SVViG_sentCardTitle{color:var(--dsw-alias-label-tertiary,#8f959e);padding:2px 12px 0;font-size:12px;font-weight:600}._SVViG_sentCardQuote{color:var(--dsw-alias-label-secondary,#3f454d);border-left:2px solid var(--dsw-alias-border-l1,#0000001f);word-break:break-word;margin:4px 12px 0;padding:4px 12px;font-size:13px;line-height:20px}._SVViG_sentCardNote{color:var(--dsw-alias-label-primary,#1f2329);word-break:break-word;padding:4px 12px 10px;font-size:13px;line-height:20px}._SVViG_hint{z-index:50;background:var(--dsw-alias-bg-layer-1,#fff);border:1px solid var(--dsw-alias-border-l1,#00000014);max-width:320px;color:var(--dsw-alias-label-secondary,#3f454d);border-radius:12px;align-items:center;gap:10px;padding:10px 12px;font-size:12px;line-height:18px;animation:.25s ease-out _SVViG_dshSidenoteHintIn;display:flex;position:fixed;bottom:160px;right:24px;box-shadow:0 8px 28px #00000024}._SVViG_hintClose{width:20px;height:20px;color:var(--dsw-alias-label-tertiary,#8f959e);cursor:pointer;background:0 0;border:none;border-radius:999px;flex:none;place-items:center;padding:0;font-size:14px;line-height:1;display:grid}._SVViG_hintClose:hover{background:var(--dsw-alias-interactive-bg-hover,#0000000d);color:var(--dsw-alias-label-primary,#1f2329)}@keyframes _SVViG_dshSidenoteHintIn{0%{opacity:0;transform:translateY(6px)}to{opacity:1;transform:translateY(0)}}._SVViG_sentChipRow{align-items:center;gap:6px;margin:0 0 6px;display:inline-flex}._SVViG_badgeSent{background:var(--dsw-alias-bg-layer-1,#fff);color:var(--dsw-alias-label-tertiary,#8f959e);border:1.5px solid var(--dsw-alias-label-caption,#b0b5bb);box-shadow:none;opacity:1;line-height:15px}._SVViG_badgeSent:hover{background:var(--dsw-alias-interactive-bg-hover,#0000000a);color:var(--dsw-alias-label-secondary,#3f454d)}._SVViG_hint{background:color-mix(in srgb, var(--dsw-alias-bg-layer-1,#fff) 88%, transparent);backdrop-filter:blur(8px);bottom:130px}";
6572
6678
  const tagId$1 = "dsh-sidenote/annotate.module.css";
6573
6679
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$1) + "]") === null) {
6574
6680
  const tag = document.createElement("style");
@@ -6578,35 +6684,35 @@ ${body}
6578
6684
  document.head.appendChild(tag);
6579
6685
  }
6580
6686
  var annotate_module_css_default = {
6581
- "confirmButton": "_SVViG_confirmButton",
6687
+ "saveButton": "_SVViG_saveButton",
6582
6688
  "hintClose": "_SVViG_hintClose",
6583
- "dshSidenoteHintIn": "_SVViG_dshSidenoteHintIn",
6584
- "toolbarButton": "_SVViG_toolbarButton",
6585
- "deleteButton": "_SVViG_deleteButton",
6586
- "editorAsk": "_SVViG_editorAsk",
6587
- "badge": "_SVViG_badge",
6588
6689
  "badgeSent": "_SVViG_badgeSent",
6589
- "toolbarBelow": "_SVViG_toolbarBelow",
6590
- "saveButton": "_SVViG_saveButton",
6591
- "editorTextarea": "_SVViG_editorTextarea",
6690
+ "deleteButton": "_SVViG_deleteButton",
6691
+ "highlight": "_SVViG_highlight",
6692
+ "confirmButton": "_SVViG_confirmButton",
6693
+ "editorFooter": "_SVViG_editorFooter",
6694
+ "editorAskRow": "_SVViG_editorAskRow",
6695
+ "sentCardNote": "_SVViG_sentCardNote",
6592
6696
  "cancelButton": "_SVViG_cancelButton",
6697
+ "toolbarBelow": "_SVViG_toolbarBelow",
6593
6698
  "sentChipRow": "_SVViG_sentChipRow",
6594
- "editorTarget": "_SVViG_editorTarget",
6595
- "editorAskRow": "_SVViG_editorAskRow",
6596
- "highlight": "_SVViG_highlight",
6597
6699
  "editorInput": "_SVViG_editorInput",
6598
6700
  "toolbar": "_SVViG_toolbar",
6701
+ "editorAsk": "_SVViG_editorAsk",
6599
6702
  "editorError": "_SVViG_editorError",
6600
- "editorNew": "_SVViG_editorNew",
6601
- "sentCardNote": "_SVViG_sentCardNote",
6602
- "toolbarStatus": "_SVViG_toolbarStatus",
6703
+ "sentCardTitle": "_SVViG_sentCardTitle",
6704
+ "editorTextarea": "_SVViG_editorTextarea",
6705
+ "dshSidenoteHintIn": "_SVViG_dshSidenoteHintIn",
6706
+ "editorTarget": "_SVViG_editorTarget",
6707
+ "sentChip": "_SVViG_sentChip",
6603
6708
  "sentCardQuote": "_SVViG_sentCardQuote",
6604
- "hint": "_SVViG_hint",
6605
- "editorFooter": "_SVViG_editorFooter",
6606
6709
  "editorEdit": "_SVViG_editorEdit",
6607
6710
  "editorSpacer": "_SVViG_editorSpacer",
6608
- "sentChip": "_SVViG_sentChip",
6609
- "sentCardTitle": "_SVViG_sentCardTitle"
6711
+ "hint": "_SVViG_hint",
6712
+ "toolbarStatus": "_SVViG_toolbarStatus",
6713
+ "editorNew": "_SVViG_editorNew",
6714
+ "badge": "_SVViG_badge",
6715
+ "toolbarButton": "_SVViG_toolbarButton"
6610
6716
  };
6611
6717
  //#endregion
6612
6718
  //#region src/client/annotate/hint.tsx
@@ -6791,6 +6897,17 @@ ${body}
6791
6897
  * so the selection survives until the click commits) and failure-safe (an
6792
6898
  * error boundary around the tree; layout math is wrapped in anchor.ts).
6793
6899
  */
6900
+ /** 两种新建注解入口共用紧凑的多行输入高度。 */
6901
+ function useAutoGrowingNote(note) {
6902
+ const input = (0, react.useRef)(null);
6903
+ (0, react.useLayoutEffect)(() => {
6904
+ const element = input.current;
6905
+ if (!element) return;
6906
+ element.style.height = "auto";
6907
+ element.style.height = `${Math.min(element.scrollHeight, 110)}px`;
6908
+ }, [note]);
6909
+ return input;
6910
+ }
6794
6911
  /** Badge anchor of a freshly captured selection (editor placement on create). */
6795
6912
  function selectionBadgePoint(snapshot) {
6796
6913
  const anchor = badgeAnchorOf(snapshot.range);
@@ -6880,11 +6997,11 @@ ${body}
6880
6997
  text: snapshot.text,
6881
6998
  anchorText: snapshot.anchorText,
6882
6999
  occurrence: snapshot.occurrence,
7000
+ anchors: snapshot.anchors,
6883
7001
  note: ""
6884
7002
  });
6885
- const cached = snapshot.range.cloneRange();
6886
7003
  rangeCache.current.set(annotation.id, {
6887
- range: cached,
7004
+ ranges: snapshot.ranges.map((range) => range.cloneRange()),
6888
7005
  view: snapshot.source.view
6889
7006
  });
6890
7007
  setEditor({
@@ -6928,10 +7045,11 @@ ${body}
6928
7045
  text: snapshot.text,
6929
7046
  anchorText: snapshot.anchorText,
6930
7047
  occurrence: snapshot.occurrence,
7048
+ anchors: snapshot.anchors,
6931
7049
  note
6932
7050
  });
6933
7051
  rangeCache.current.set(annotation.id, {
6934
- range: snapshot.range.cloneRange(),
7052
+ ranges: snapshot.ranges.map((range) => range.cloneRange()),
6935
7053
  view: snapshot.source.view
6936
7054
  });
6937
7055
  setSideDraft((current) => current === sideDraft ? null : current);
@@ -6995,7 +7113,7 @@ ${body}
6995
7113
  onSave: (note) => {
6996
7114
  store.setNote(editingAnnotation.id, note);
6997
7115
  closeEditor();
6998
- const range = rangeCache.current.get(editingAnnotation.id)?.range;
7116
+ const range = rangeCache.current.get(editingAnnotation.id)?.ranges[0];
6999
7117
  const source = range === void 0 ? null : quoteSourceOf(range.startContainer, currentSessionId);
7000
7118
  if (source !== null) requestAnimationFrame(() => {
7001
7119
  if (document.activeElement === document.body) focusSourceComposer(source);
@@ -7114,6 +7232,7 @@ ${body}
7114
7232
  function SideChatNoteEditor(props) {
7115
7233
  useLocaleTick();
7116
7234
  const [note, setNote] = (0, react.useState)("");
7235
+ const noteInputRef = useAutoGrowingNote(note);
7117
7236
  const [failed, setFailed] = (0, react.useState)(false);
7118
7237
  const [pending, setPending] = (0, react.useState)(false);
7119
7238
  const rootRef = (0, react.useRef)(null);
@@ -7192,7 +7311,9 @@ ${body}
7192
7311
  style: {
7193
7312
  left,
7194
7313
  top,
7195
- width
7314
+ width,
7315
+ maxHeight: (props.bounds?.bottom ?? window.innerHeight) - top - 8,
7316
+ overflowY: "auto"
7196
7317
  },
7197
7318
  children: [
7198
7319
  props.targetTitle !== void 0 && props.targetTitle !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
@@ -7201,7 +7322,9 @@ ${body}
7201
7322
  }),
7202
7323
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7203
7324
  className: annotate_module_css_default.editorAskRow,
7204
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
7325
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
7326
+ ref: noteInputRef,
7327
+ rows: 1,
7205
7328
  className: annotate_module_css_default.editorInput,
7206
7329
  value: note,
7207
7330
  placeholder: t("sideNotePlaceholder"),
@@ -7214,7 +7337,7 @@ ${body}
7214
7337
  setFailed(false);
7215
7338
  },
7216
7339
  onKeyDown: (event) => {
7217
- if (event.key !== "Enter") return;
7340
+ if (event.key !== "Enter" || event.shiftKey) return;
7218
7341
  if (event.nativeEvent.isComposing || event.nativeEvent.keyCode === 229) return;
7219
7342
  event.preventDefault();
7220
7343
  save();
@@ -7252,12 +7375,13 @@ ${body}
7252
7375
  const [hoveredId, setHoveredId] = (0, react.useState)(null);
7253
7376
  const highlightId = props.editingId ?? hoveredId;
7254
7377
  for (const annotation of annotations) {
7255
- const range = resolveRange(annotation, props.cache, props.sessionId);
7256
- if (range === null) continue;
7378
+ const ranges = resolveRanges(annotation, props.cache, props.sessionId);
7379
+ if (ranges === null) continue;
7380
+ const range = ranges[0];
7257
7381
  const source = quoteSourceOf(range.startContainer, props.sessionId);
7258
- if (source === null) continue;
7382
+ if (source === null || source.sessionId !== annotation.sessionId) continue;
7259
7383
  const clip = sourceClip(source);
7260
- if (annotation.id === highlightId) highlight = highlightRectsOf(range).map((rect) => {
7384
+ if (annotation.id === highlightId) highlight = ranges.flatMap(highlightRectsOf).map((rect) => {
7261
7385
  const left = Math.max(rect.left, clip.left), top = Math.max(rect.top, clip.top);
7262
7386
  return {
7263
7387
  left,
@@ -7325,6 +7449,7 @@ ${body}
7325
7449
  const rootRef = (0, react.useRef)(null);
7326
7450
  const noteRef = (0, react.useRef)(note);
7327
7451
  noteRef.current = note;
7452
+ const noteInputRef = useAutoGrowingNote(note);
7328
7453
  (0, react.useEffect)(() => {
7329
7454
  const onKeyDown = (event) => {
7330
7455
  if (event.key === "Escape") {
@@ -7359,7 +7484,9 @@ ${body}
7359
7484
  style: {
7360
7485
  left,
7361
7486
  top,
7362
- width
7487
+ width,
7488
+ maxHeight: (props.bounds?.bottom ?? window.innerHeight) - top - 8,
7489
+ overflowY: "auto"
7363
7490
  },
7364
7491
  children: [props.targetTitle !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7365
7492
  className: annotate_module_css_default.editorTarget,
@@ -7367,16 +7494,18 @@ ${body}
7367
7494
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7368
7495
  className: annotate_module_css_default.editorAskRow,
7369
7496
  style: { flex: 1 },
7370
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
7497
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
7498
+ ref: noteInputRef,
7371
7499
  className: annotate_module_css_default.editorInput,
7372
7500
  value: note,
7373
7501
  placeholder: t("notePlaceholder"),
7502
+ rows: 1,
7374
7503
  autoFocus: true,
7375
7504
  onChange: (event) => {
7376
7505
  setNote(event.target.value);
7377
7506
  },
7378
7507
  onKeyDown: (event) => {
7379
- if (event.key !== "Enter") return;
7508
+ if (event.key !== "Enter" || event.shiftKey) return;
7380
7509
  if (event.nativeEvent.isComposing || event.nativeEvent.keyCode === 229) return;
7381
7510
  event.preventDefault();
7382
7511
  save();
@@ -7810,6 +7939,7 @@ ${body}
7810
7939
  cancelInputRollback(input);
7811
7940
  const parts = [...options.leadingBlocks ?? []];
7812
7941
  if (annotations.length > 0) parts.push(buildProtocolBlock(annotations));
7942
+ const wireNumbers = new Map(annotations.map((annotation) => [annotation.id, annotation.number]));
7813
7943
  const block = parts.join("\n\n");
7814
7944
  if (block === "") return false;
7815
7945
  const draftBeforeWait = input.state.getSnapshot();
@@ -7831,7 +7961,10 @@ ${body}
7831
7961
  }
7832
7962
  receipt.submit(options.mode);
7833
7963
  await receipt.outcome;
7834
- store.markSent(annotations.filter((annotation) => store.get(annotation.id) === annotation).map((annotation) => annotation.id));
7964
+ store.markSent(annotations.filter((annotation) => {
7965
+ const current = store.get(annotation.id);
7966
+ return current !== void 0 && current.state === "active" && (current === annotation || current.note === annotation.note && current.text === annotation.text);
7967
+ }).map((annotation) => annotation.id), wireNumbers);
7835
7968
  return true;
7836
7969
  } catch (error) {
7837
7970
  rollbackInputPrefix(input, prefix, staged ? () => {
@@ -8267,15 +8400,15 @@ ${body}
8267
8400
  document.head.appendChild(tag);
8268
8401
  }
8269
8402
  var settings_module_css_default = {
8270
- "page": "_2xJwMG_page",
8271
8403
  "description": "_2xJwMG_description",
8272
8404
  "error": "_2xJwMG_error",
8273
- "status": "_2xJwMG_status",
8274
- "row": "_2xJwMG_row",
8275
8405
  "hint": "_2xJwMG_hint",
8276
8406
  "label": "_2xJwMG_label",
8277
8407
  "title": "_2xJwMG_title",
8278
- "copy": "_2xJwMG_copy"
8408
+ "copy": "_2xJwMG_copy",
8409
+ "status": "_2xJwMG_status",
8410
+ "row": "_2xJwMG_row",
8411
+ "page": "_2xJwMG_page"
8279
8412
  };
8280
8413
  //#endregion
8281
8414
  //#region src/client/settings.tsx
@@ -8365,6 +8498,41 @@ ${body}
8365
8498
  }, ({ size }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconListPenOutline16, { size })));
8366
8499
  }
8367
8500
  //#endregion
8501
+ //#region src/client/host/replyDefaults.ts
8502
+ function initializeFinalReplies(sidenote, chat) {
8503
+ let stopped = false;
8504
+ let attempted = false;
8505
+ const initialize = () => {
8506
+ if (stopped || attempted) return;
8507
+ const own = sidenote.getSnapshot(), target = chat.getSnapshot();
8508
+ if (own.status !== "ready" || target.status !== "ready" || !own.writable || !target.writable || !own.value || !target.value || own.revision === void 0 || target.revision === void 0) return;
8509
+ if (own.value.finalRepliesDefaultAttempted) return;
8510
+ attempted = true;
8511
+ (async () => {
8512
+ if (!await sidenote.mutate([{
8513
+ op: "set",
8514
+ path: ["finalRepliesDefaultAttempted"],
8515
+ value: true
8516
+ }], own.revision) || stopped) return;
8517
+ if (target.value.foldedReplies !== "final") await chat.mutate([{
8518
+ op: "set",
8519
+ path: ["foldedReplies"],
8520
+ value: "final"
8521
+ }], target.revision);
8522
+ })().catch(() => {
8523
+ console.warn("[dsh-sidenote] 最终回答显示偏好初始化未完成,保留当前设置。");
8524
+ });
8525
+ };
8526
+ const unsubscribeOwn = sidenote.subscribe(initialize);
8527
+ const unsubscribeChat = chat.subscribe(initialize);
8528
+ initialize();
8529
+ return () => {
8530
+ stopped = true;
8531
+ unsubscribeOwn();
8532
+ unsubscribeChat();
8533
+ };
8534
+ }
8535
+ //#endregion
8368
8536
  //#region src/client/index.tsx
8369
8537
  const inject = [
8370
8538
  "sessions",
@@ -8387,10 +8555,13 @@ ${body}
8387
8555
  sweepOrphanedSideChatMeta();
8388
8556
  const reflow = createReflowStore();
8389
8557
  const annotations = createAnnotationStore();
8390
- const preferences = createSidenotePreferences(ctx.get("configForms").get("side-chat"));
8558
+ const forms = ctx.get("configForms");
8559
+ const form = forms.get("side-chat");
8560
+ const preferences = createSidenotePreferences(form);
8391
8561
  ctx.effect(() => () => {
8392
8562
  preferences.dispose();
8393
8563
  }, "dsh-sidenote: preferences");
8564
+ ctx.effect(() => initializeFinalReplies(form, forms.get("ui-chat")), "dsh-sidenote: final replies default");
8394
8565
  registerSidenoteSettings(ctx, preferences);
8395
8566
  registerSideChat(ctx, reflow, annotations);
8396
8567
  registerAnnotations(ctx, reflow, annotations, preferences);