dsh-composer-predictions 1.2.3 → 1.2.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/CHANGELOG.md CHANGED
@@ -6,6 +6,17 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [1.2.4] - 2026-10-25
10
+
11
+ ### Fixed
12
+
13
+ - The ghost is anchored to the composer card that owns the overlay, found
14
+ through the overlay's own DOM ancestry. It previously measured the first
15
+ `[data-composer-card]` in the document, so with more than one composer
16
+ mounted (another panel, a transition, a second window) the suggestion could
17
+ paint over another conversation and Tab would consult the wrong card.
18
+ - Switching Sessions also clears the measured box, not just the suggestion.
19
+
9
20
  ## [1.2.3] - 2026-10-25
10
21
 
11
22
  ### Changed
package/README.md CHANGED
@@ -7,7 +7,7 @@
7
7
  AI autocomplete and ghost-text suggestions for the composer in **DeepSeek
8
8
  Harness desktop and the Web GUI**.
9
9
 
10
- **Current release: 1.2.2** · [Install or update](#install) · [中文说明](#中文说明)
10
+ **Current release: [latest on npm](https://www.npmjs.com/package/dsh-composer-predictions)** · [Install or update](#install) · [中文说明](#中文说明)
11
11
 
12
12
  - **Next message** — after the agent replies, the empty composer shows a
13
13
  streamed suggestion for your likely next message. **Tab** accepts it as an
@@ -100,12 +100,12 @@ https://raw.githubusercontent.com/Stijnus/dsh-composer-predictions/main/market-i
100
100
  `Tab` 采纳已完成的候选为可编辑草稿,不会自动发送;`Alt+↓/↑` 切换最多三个候选,
101
101
  `Esc` 关闭。设置 → 通用 → 输入预测可开关,设置保存在当前设备的浏览器中。
102
102
 
103
- 安装或更新:**插件 → 安装 → `dsh-composer-predictions@1.2.2`**,然后重启 Harness。
104
- CLI 用户可运行 `dsh plugin --profile web add dsh-composer-predictions@1.2.2`,
103
+ 安装或更新:**插件 → 安装 → `dsh-composer-predictions`**,然后重启 Harness。
104
+ CLI 用户可运行 `dsh plugin --profile web add dsh-composer-predictions`,
105
105
  把 `web` 换成自己的 profile。预测会调用会话模型并消耗 token,虽然不会出现在会话日志和 token 统计中。
106
106
 
107
107
  也可以把上方 [Install](#install) 中的市场索引添加到「插件 → 发现」。
108
- 演示、完整配置与限制见本页;[1.2.2 发布说明](https://github.com/Stijnus/dsh-composer-predictions/releases/tag/v1.2.2)。
108
+ 演示、完整配置与限制见本页;[最新发布说明](https://github.com/Stijnus/dsh-composer-predictions/releases/latest)。
109
109
 
110
110
  ## How it works
111
111
 
package/client.js CHANGED
@@ -162,10 +162,18 @@ window.__ModuleLoader__.load({
162
162
  opacity: 0.8,
163
163
  }
164
164
 
165
- /** Locate the resident composer card and its editable root. */
166
- function composerElements() {
165
+ /**
166
+ * Locate the composer card this overlay actually belongs to.
167
+ *
168
+ * The seat is rendered inside the resident composer card, so the anchor node
169
+ * is an ancestor-scoped key: a global `querySelector` would pick whichever
170
+ * card happens to be first in the document and paint that conversation's
171
+ * ghost over another one.
172
+ */
173
+ function composerElementsNear(anchor) {
167
174
  if (typeof document === 'undefined') return null
168
- const card = document.querySelector('[data-composer-card]')
175
+ if (anchor === null || anchor === undefined || !anchor.isConnected) return null
176
+ const card = anchor.closest('[data-composer-card]')
169
177
  if (card === null) return null
170
178
  const editor = card.querySelector('[contenteditable="true"]') || card.querySelector('[role="textbox"]')
171
179
  return editor === null ? null : { card, editor }
@@ -364,6 +372,8 @@ window.__ModuleLoader__.load({
364
372
  const [box, setBox] = React.useState(null)
365
373
  const [compositionEnd, setCompositionEnd] = React.useState(0)
366
374
 
375
+ const anchorRef = React.useRef(null)
376
+ const elements = () => composerElementsNear(anchorRef.current)
367
377
  const abortRef = React.useRef(null)
368
378
  const composingRef = React.useRef(false)
369
379
  const dismissedRef = React.useRef(null)
@@ -484,6 +494,7 @@ window.__ModuleLoader__.load({
484
494
  dismissedRef.current = null
485
495
  abortRef.current?.abort()
486
496
  setGhost(NO_GHOST)
497
+ setBox(null)
487
498
  return () => abortRef.current?.abort()
488
499
  }, [sessionId])
489
500
 
@@ -524,7 +535,7 @@ window.__ModuleLoader__.load({
524
535
  if (dismissedRef.current !== null && dismissedRef.current.draft === draft) return undefined
525
536
  const timer = setTimeout(() => {
526
537
  if (composingRef.current) return
527
- const found = composerElements()
538
+ const found = elements()
528
539
  if (found === null) return
529
540
  if (caretAtEnd(found.editor, draft) === null) return
530
541
  void request('continue', draft.slice(-MAX_DRAFT_CHARS))
@@ -564,7 +575,7 @@ window.__ModuleLoader__.load({
564
575
  React.useLayoutEffect(() => {
565
576
  if (ghost.mode !== 'next') return undefined
566
577
  const measure = () => {
567
- const found = composerElements()
578
+ const found = elements()
568
579
  if (found === null) {
569
580
  setBox((previous) => (previous === null ? previous : null))
570
581
  return
@@ -590,7 +601,7 @@ window.__ModuleLoader__.load({
590
601
  setBox((previous) => (sameBox(previous, next) ? previous : next))
591
602
  }
592
603
  measure()
593
- const found = composerElements()
604
+ const found = elements()
594
605
  let observer
595
606
  if (found !== null && typeof ResizeObserver === 'function') {
596
607
  observer = new ResizeObserver(measure)
@@ -610,7 +621,7 @@ window.__ModuleLoader__.load({
610
621
  let frame = 0
611
622
  const measure = () => {
612
623
  frame = 0
613
- const found = composerElements()
624
+ const found = elements()
614
625
  if (found === null) {
615
626
  setBox((previous) => (previous === null ? previous : null))
616
627
  return
@@ -661,7 +672,7 @@ window.__ModuleLoader__.load({
661
672
  measure()
662
673
  document.addEventListener('selectionchange', schedule)
663
674
  document.addEventListener('scroll', schedule, true)
664
- const found = composerElements()
675
+ const found = elements()
665
676
  window.addEventListener('resize', schedule)
666
677
  let observer
667
678
  if (found !== null && typeof ResizeObserver === 'function') {
@@ -694,7 +705,7 @@ window.__ModuleLoader__.load({
694
705
  }
695
706
  // Accepting leaves an editable draft: hand the keyboard back to the editor.
696
707
  try {
697
- const found = composerElements()
708
+ const found = elements()
698
709
  if (found !== null && !found.card.contains(document.activeElement)) found.editor.focus()
699
710
  } catch {
700
711
  // Focus is a courtesy, never a failure.
@@ -717,7 +728,7 @@ window.__ModuleLoader__.load({
717
728
  if (event.defaultPrevented) return
718
729
  // An IME candidate window owns the keyboard while composing.
719
730
  if (event.isComposing === true || event.keyCode === 229) return
720
- const found = composerElements()
731
+ const found = elements()
721
732
  if (found === null) return
722
733
  const target = typeof Node !== 'undefined' && event.target instanceof Node ? event.target : null
723
734
  const inEditor = found.editor.contains(document.activeElement)
@@ -755,7 +766,14 @@ window.__ModuleLoader__.load({
755
766
  return () => document.removeEventListener('keydown', onKeyDown, true)
756
767
  }, [visible, draft, ghost.items.length, ghost.active, ghost.mode])
757
768
 
758
- if (!visible || box === null) return null
769
+ const anchor = h('span', {
770
+ ref: anchorRef,
771
+ 'data-composer-prediction-anchor': '',
772
+ 'aria-hidden': 'true',
773
+ style: { display: 'none' },
774
+ })
775
+
776
+ if (!visible || box === null) return anchor
759
777
 
760
778
  const continuation = ghost.mode === 'continue'
761
779
  const style = continuation ? CONTINUE_GHOST_STYLE : NEXT_GHOST_STYLE
@@ -775,6 +793,7 @@ window.__ModuleLoader__.load({
775
793
  showBadge ? h('span', { style: BADGE_STYLE, title: t('more') }, `${ghost.index + 1}/${ghost.items.length}`) : null,
776
794
  showKeycap ? h('kbd', { style: KEYCAP_STYLE, title: t('accept') }, 'Tab') : null,
777
795
  ),
796
+ anchor,
778
797
  )
779
798
  }
780
799
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-composer-predictions",
3
- "version": "1.2.3",
3
+ "version": "1.2.4",
4
4
  "description": "Tab autocomplete for the DeepSeek Harness desktop and web composer: next-message ghost text, inline draft completion and keyboard candidate cycling",
5
5
  "type": "module",
6
6
  "license": "MIT",