@brimveyn/aimux 1.12.0 → 1.12.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brimveyn/aimux",
3
- "version": "1.12.0",
3
+ "version": "1.12.1",
4
4
  "description": "A terminal multiplexer for AI CLIs. Run Claude, Codex, OpenCode side-by-side with tabbed navigation, split panes, and persistent sessions.",
5
5
  "keywords": [
6
6
  "ai",
@@ -0,0 +1,21 @@
1
+ // On multi-click selection mouseUp opentui's finishSelection() fires the
2
+ // 'selection' event *after* our handleTerminalMouseUp has already written
3
+ // the clipboard from drag.capturedLines (anchored at click time). The event
4
+ // handler would then call computeStreamSelectedText against the *current*
5
+ // tab.viewport.lines and overwrite our write with a value that can diverge
6
+ // by a few rows when output landed between mouseDown and mouseUp.
7
+ //
8
+ // To avoid that, handleTerminalMouseUp pings the guard right after it
9
+ // copies; handleSelection consults the guard and skips its own copy for a
10
+ // short window.
11
+
12
+ const SUPPRESS_WINDOW_MS = 100
13
+ let lastMultiClickWriteAt = 0
14
+
15
+ export function recordMultiClickClipboardWrite(): void {
16
+ lastMultiClickWriteAt = Date.now()
17
+ }
18
+
19
+ export function shouldSuppressSelectionCopy(): boolean {
20
+ return Date.now() - lastMultiClickWriteAt < SUPPRESS_WINDOW_MS
21
+ }
@@ -19,6 +19,7 @@ import {
19
19
  type MultiClickMode,
20
20
  resolveClickSelection,
21
21
  } from './click-selection-resolver'
22
+ import { recordMultiClickClipboardWrite } from './multi-click-clipboard-guard'
22
23
  import { requestRenderUpTree } from './render-invalidation'
23
24
  import {
24
25
  type AnchoredRatioDragState,
@@ -540,6 +541,7 @@ export function useMouseHandlers({
540
541
  const text = extractStreamText(lines, anchorIdx, anchorCol, focusIdx, drag.focusCol)
541
542
  if (text.length > 0) {
542
543
  copyToSystemClipboard(text)
544
+ recordMultiClickClipboardWrite()
543
545
  }
544
546
 
545
547
  multiClickDragRef.current = null
@@ -8,6 +8,7 @@ import type { AppAction, FocusMode, TabSession } from '../state/types'
8
8
  import { INPUT_DEBUG_LOG_PATH, logInputDebug } from '../debug/input-log'
9
9
  import { createRawInputHandler } from '../input/raw-input-handler'
10
10
  import { copyToSystemClipboard } from '../platform/clipboard'
11
+ import { shouldSuppressSelectionCopy } from './multi-click-clipboard-guard'
11
12
  import { writePasteToTab, writeToTab } from './pty-write'
12
13
  import { type OtuiSelection, resolveSelectionClipboardText } from './selection-clipboard'
13
14
  import { applyViewportObservation, type ViewportObservation } from './selection-scroll'
@@ -118,6 +119,16 @@ export function useRendererBindings({
118
119
  return
119
120
  }
120
121
 
122
+ // After a multi-click drag, handleTerminalMouseUp has just copied the
123
+ // authoritative text built from drag.capturedLines (anchored at click
124
+ // time). opentui's finishSelection() then re-fires this 'selection'
125
+ // event, but the recomputed text can drift by a few rows when output
126
+ // landed between mouseDown and mouseUp. Skip the redundant write.
127
+ if (shouldSuppressSelectionCopy()) {
128
+ logInputDebug('app.selection.suppressed', { textLength: selectedText.length })
129
+ return
130
+ }
131
+
121
132
  renderer.copyToClipboardOSC52(selectedText)
122
133
  copyToSystemClipboard(selectedText)
123
134
  }