@aragon-agent/cli 0.6.9 → 0.6.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.
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * The selection controller (tui-selection-and-scroll-follow §4.4). NON-REACT: it
3
- * owns the mouse lifecycle, the 16 ms drag coalescer, the `decorate` hook and the
4
- * copy-on-release, and it reaches the React tree only through the two callbacks
5
- * `App` registers on mount.
3
+ * owns the mouse lifecycle, the 16 ms drag coalescer, the `decorate` hook and
4
+ * the released-selection state Ctrl+C consumes, and it reaches the React tree
5
+ * only through the callbacks `App` registers on mount.
6
6
  *
7
7
  * WHY IT IS NOT A COMPONENT (D-6). A React re-render per motion report goes
8
8
  * through the render governor and the whole component tree; the frame pipeline
@@ -57,6 +57,10 @@ export function createSelectionController(options) {
57
57
  let enabled = true;
58
58
  let sel = null;
59
59
  let dragging = false;
60
+ /** A released, non-empty selection is waiting for Ctrl+C (4.2.1). */
61
+ let settled = false;
62
+ /** Mirror of the last `hold` emitted, so exits can be conditional. */
63
+ let holdActive = false;
60
64
  let pendingFocus = null;
61
65
  let flushTimer = null;
62
66
  let holdWatchdog = null;
@@ -64,6 +68,7 @@ export function createSelectionController(options) {
64
68
  /** Guards `decorate` against being re-entered from inside a repaint. */
65
69
  let painting = false;
66
70
  const emitHold = (hold) => {
71
+ holdActive = hold;
67
72
  for (const listener of [...holdListeners])
68
73
  listener(hold);
69
74
  };
@@ -121,15 +126,18 @@ export function createSelectionController(options) {
121
126
  // Nothing to undo: no timers, no repaint, no redraw. This is what keeps
122
127
  // `onInvalidate` — which fires on every resize and every foreign write —
123
128
  // free for a session that has never selected anything.
124
- if (sel === null && !dragging && pendingFocus === null)
129
+ if (sel === null && !dragging && pendingFocus === null && !settled)
125
130
  return;
126
- const wasDragging = dragging;
127
131
  sel = null;
128
132
  dragging = false;
133
+ settled = false;
129
134
  pendingFocus = null;
130
135
  clearFlushTimer();
131
136
  clearWatchdog();
132
- if (wasDragging)
137
+ // `holdActive`, not `wasDragging`: since tui-shift-enter-copy-queue a
138
+ // RELEASED selection also holds the viewport (4.2.3), so this is the
139
+ // exit for two states, not one.
140
+ if (holdActive)
133
141
  emitHold(false);
134
142
  paint(false);
135
143
  };
@@ -150,26 +158,38 @@ export function createSelectionController(options) {
150
158
  paint(true);
151
159
  };
152
160
  const finishSelection = () => {
153
- if (!sel)
161
+ if (!sel) {
162
+ emitHold(false);
154
163
  return;
164
+ }
155
165
  const normal = normalize(sel);
156
166
  if (isEmpty(normal)) {
157
- // A plain click. It must never leave a one-cell highlight behind, and it
158
- // must never clobber the clipboard.
167
+ // A plain click. It must never leave a one-cell highlight behind, and
168
+ // it must never clobber the clipboard.
159
169
  sel = null;
170
+ settled = false;
171
+ emitHold(false);
160
172
  paint(false);
161
173
  return;
162
174
  }
163
- const text = selectedText(mirror.plain, normal);
164
- if (text.length === 0) {
175
+ if (selectedText(mirror.plain, normal).length === 0) {
176
+ // Geometry without content (blank cells): nothing honest to promise a
177
+ // later Ctrl+C, so this is the same no-op a click is.
165
178
  sel = null;
179
+ settled = false;
180
+ emitHold(false);
166
181
  paint(false);
167
182
  return;
168
183
  }
169
- const via = options.copy(text);
170
- options.onCopied(via, text.split('\n').length, text.length);
171
- // The highlight STAYS after a release: it is the receipt for what was taken,
172
- // and it goes away on the next thing that moves the rows (I-9).
184
+ // RELEASED AND NON-EMPTY: the highlight STAYS and so does the hold
185
+ // (4.2.1 / 4.2.3). The highlight is the promise of what Ctrl+C will
186
+ // copy -- not a receipt for a copy that already happened -- and the
187
+ // hold keeps the rows under it frozen until that promise resolves,
188
+ // which is what lets "what you see is what you get" survive a
189
+ // streaming run. The watchdog is NOT re-armed: the release already
190
+ // happened, and the frozen state has explicit exits (Ctrl+C, any other
191
+ // key, resize, overlay, /mouse off).
192
+ settled = true;
173
193
  paint(true);
174
194
  };
175
195
  const onMouse = (event) => {
@@ -222,7 +242,9 @@ export function createSelectionController(options) {
222
242
  clearFlushTimer();
223
243
  clearWatchdog();
224
244
  dragging = false;
225
- emitHold(false);
245
+ // `finishSelection` decides the hold: a non-empty release KEEPS it
246
+ // (4.2.3) so the pending selection cannot be pushed off screen; every
247
+ // empty path releases it there.
226
248
  finishSelection();
227
249
  };
228
250
  const unsubscribe = options.source.subscribe(onMouse);
@@ -258,6 +280,32 @@ export function createSelectionController(options) {
258
280
  holdListeners.add(listener);
259
281
  return () => holdListeners.delete(listener);
260
282
  },
283
+ hasPendingSelection() {
284
+ return settled && sel !== null;
285
+ },
286
+ takeSelection() {
287
+ if (!settled || sel === null)
288
+ return null;
289
+ const normal = normalize(sel);
290
+ const text = selectedText(mirror.plain, normal);
291
+ // CONSUMED BEFORE THE CALLER COPIES (4.3 / R7). The copy writes OSC
292
+ // 52 through `writeForeign`; the differ treats that as a foreign
293
+ // write and invalidates, and `onInvalidate` clears this controller on
294
+ // the next macrotask. Clearing first makes that callback a no-op;
295
+ // copying first would let it erase the held viewport state a tick
296
+ // after the copy -- the "copy flashes the screen" regression.
297
+ sel = null;
298
+ settled = false;
299
+ pendingFocus = null;
300
+ clearFlushTimer();
301
+ clearWatchdog();
302
+ if (holdActive)
303
+ emitHold(false);
304
+ paint(false);
305
+ if (text.length === 0)
306
+ return null;
307
+ return { text, lines: text.split('\n').length };
308
+ },
261
309
  clear,
262
310
  setEnabled(next) {
263
311
  if (enabled === next)
@@ -270,6 +318,10 @@ export function createSelectionController(options) {
270
318
  if (disposed)
271
319
  return;
272
320
  disposed = true;
321
+ // 4.2.3: a pending selection holds the viewport; teardown must let go
322
+ // of it explicitly, exactly like `clear()` does.
323
+ if (holdActive)
324
+ emitHold(false);
273
325
  unsubscribe();
274
326
  clearFlushTimer();
275
327
  clearWatchdog();
@@ -22,8 +22,12 @@ import type { ViewAction } from '../agent/reducer.js';
22
22
  *
23
23
  * `1` = the first text that names DRAG-SELECT and `/mouse`. `0` = the 0.6.2 text
24
24
  * that taught the Shift bypass, recorded back when this was a boolean.
25
+ * `2` = the Ctrl+C-to-copy text (tui-shift-enter-copy-queue 4.4): releasing
26
+ * no longer copies, and every user who learned the old gesture -- which is
27
+ * every user who has a version recorded at all -- has to be told the new
28
+ * one, once.
25
29
  */
26
- export declare const MOUSE_NOTICE_VERSION = 1;
30
+ export declare const MOUSE_NOTICE_VERSION = 2;
27
31
  /**
28
32
  * The revision of the text below that a user must have seen for this notice to
29
33
  * stay quiet (`state.json::vtInputNoticeVersion`, `0` when absent).
@@ -26,8 +26,12 @@ import { getLogger } from '../logging/logger.js';
26
26
  *
27
27
  * `1` = the first text that names DRAG-SELECT and `/mouse`. `0` = the 0.6.2 text
28
28
  * that taught the Shift bypass, recorded back when this was a boolean.
29
+ * `2` = the Ctrl+C-to-copy text (tui-shift-enter-copy-queue 4.4): releasing
30
+ * no longer copies, and every user who learned the old gesture -- which is
31
+ * every user who has a version recorded at all -- has to be told the new
32
+ * one, once.
29
33
  */
30
- export const MOUSE_NOTICE_VERSION = 1;
34
+ export const MOUSE_NOTICE_VERSION = 2;
31
35
  /**
32
36
  * The one-shot mouse notice (tui-selection-and-scroll-follow §5.2 row 29).
33
37
  *
@@ -47,8 +51,8 @@ function mouseNoticeText(selectEnabled) {
47
51
  return ('Mouse wheel scrolls the transcript. Drag-select is off (mouseSelect); ' +
48
52
  '/mouse off hands the mouse back to your terminal for this session.');
49
53
  }
50
- return ('Mouse wheel scrolls the transcript, and dragging selects text - releasing ' +
51
- 'copies it. /mouse off hands the mouse back to your terminal for this session.');
54
+ return ('Mouse wheel scrolls the transcript; drag to select, then Ctrl+C to copy. ' +
55
+ '/mouse off hands the mouse back to your terminal for this session.');
52
56
  }
53
57
  /**
54
58
  * The §5.1 notice (tui-render-performance). Shown once per run, and only when
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aragon-agent/cli",
3
- "version": "0.6.9",
3
+ "version": "0.6.10",
4
4
  "description": "AragonAgent CLI — a Claude-Code / Codex-style interactive terminal UI (TUI) on top of the @aragon-agent/core engine. Installable and runnable via npm / npx.",
5
5
  "license": "MIT",
6
6
  "author": "AragonAgent contributors",
@@ -56,7 +56,7 @@
56
56
  "prepublishOnly": "npm run build"
57
57
  },
58
58
  "dependencies": {
59
- "@aragon-agent/core": "^0.2.21",
59
+ "@aragon-agent/core": "^0.2.22",
60
60
  "chalk": "^5.6.2",
61
61
  "cli-highlight": "^2.0.0",
62
62
  "commander": "^12.1.0",