@timurproko/a1 0.2.7-dev.1255 → 0.2.7-dev.1264

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.
Files changed (78) hide show
  1. package/dist/app/session-shell/prompt-submission.d.ts +1 -1
  2. package/dist/app/session-shell/prompt-submission.js +9 -9
  3. package/dist/app/session-shell/resource-labels.d.ts +2 -0
  4. package/dist/app/session-shell/resource-labels.js +158 -0
  5. package/dist/app/session-shell/session-prompt-history.d.ts +29 -0
  6. package/dist/app/session-shell/session-prompt-history.js +80 -0
  7. package/dist/app/session-shell/session-selectors.d.ts +46 -0
  8. package/dist/app/session-shell/session-selectors.js +269 -0
  9. package/dist/app/session-shell/session-shell-root.d.ts +6 -28
  10. package/dist/app/session-shell/session-shell-root.js +26 -220
  11. package/dist/app/session-shell/session-shell.d.ts +3 -23
  12. package/dist/app/session-shell/session-shell.js +54 -365
  13. package/dist/app/session-shell/session-viewport-controller.d.ts +2 -10
  14. package/dist/app/session-shell/session-viewport-controller.js +40 -318
  15. package/dist/app/session-shell/settings-bindings.d.ts +1 -1
  16. package/dist/app/session-shell/settings-bindings.js +2 -2
  17. package/dist/app/session-shell/viewport-pointer.d.ts +83 -0
  18. package/dist/app/session-shell/viewport-pointer.js +361 -0
  19. package/dist/composition/owned-ui.js +63 -59
  20. package/dist/contracts/owned-ui/index.d.ts +1 -1
  21. package/dist/contracts/owned-ui/index.js +1 -1
  22. package/dist/contracts/owned-ui/validation.d.ts +1 -11
  23. package/dist/contracts/owned-ui/validation.js +8 -104
  24. package/dist/contracts/presentation/index.d.ts +1 -1
  25. package/dist/contracts/presentation/terminal.d.ts +0 -6
  26. package/dist/features/owned-ui/settings-app.js +13 -208
  27. package/dist/features/owned-ui/settings-model-dialog.d.ts +41 -0
  28. package/dist/features/owned-ui/settings-model-dialog.js +194 -0
  29. package/dist/foundation/release/automatic-update.js +1 -8
  30. package/dist/foundation/release/dependency-layer.js +3 -3
  31. package/dist/foundation/release/latest-release.js +1 -1
  32. package/dist/foundation/release/managed-path.d.ts +10 -0
  33. package/dist/foundation/release/managed-path.js +26 -0
  34. package/dist/foundation/release/release-gc.js +6 -8
  35. package/dist/foundation/release/restart-certification.js +3 -4
  36. package/dist/foundation/release/update-activation.js +1 -1
  37. package/dist/foundation/release/update-execution-lease.js +1 -7
  38. package/dist/foundation/release/update-installer.d.ts +45 -0
  39. package/dist/foundation/release/update-installer.js +57 -0
  40. package/dist/foundation/release/update-recovery.js +2 -9
  41. package/dist/foundation/release/update.d.ts +4 -23
  42. package/dist/foundation/release/update.js +160 -179
  43. package/dist/integrations/pi/components/bracketed-paste.d.ts +33 -0
  44. package/dist/integrations/pi/components/bracketed-paste.js +123 -0
  45. package/dist/integrations/pi/components/owned-editor-ux.js +46 -140
  46. package/dist/integrations/pi/engine/adapter.d.ts +1 -8
  47. package/dist/integrations/pi/engine/adapter.js +6 -49
  48. package/dist/integrations/pi/engine/host.js +1 -1
  49. package/dist/integrations/pi/engine/repository-metadata.d.ts +49 -0
  50. package/dist/integrations/pi/engine/repository-metadata.js +189 -0
  51. package/dist/integrations/pi/engine/session-runtime.d.ts +6 -12
  52. package/dist/integrations/pi/engine/session-runtime.js +21 -178
  53. package/dist/integrations/pi/engine/view-state.d.ts +21 -2
  54. package/dist/integrations/pi/engine/view-state.js +129 -1
  55. package/dist/integrations/pi/engine/workflow-runner.d.ts +0 -1
  56. package/dist/integrations/pi/engine/workflow-runner.js +254 -365
  57. package/dist/integrations/pi/engine/workflow-share.d.ts +9 -0
  58. package/dist/integrations/pi/engine/workflow-share.js +168 -0
  59. package/dist/integrations/pi/engine/workflow-support.d.ts +0 -14
  60. package/dist/integrations/pi/engine/workflow-support.js +0 -47
  61. package/dist/integrations/pi/tui-runtime/adapter.d.ts +9 -19
  62. package/dist/integrations/pi/tui-runtime/adapter.js +35 -144
  63. package/dist/integrations/pi/tui-runtime/damage-aware-terminal.d.ts +4 -4
  64. package/dist/integrations/pi/tui-runtime/index.d.ts +0 -1
  65. package/dist/integrations/pi/tui-runtime/overlay-geometry.d.ts +2 -2
  66. package/dist/integrations/pi/tui-runtime/presentation-adapter.d.ts +2 -2
  67. package/dist/ui/components/transcript-viewport-frame.d.ts +120 -0
  68. package/dist/ui/components/transcript-viewport-frame.js +314 -0
  69. package/dist/ui/components/transcript-viewport-types.d.ts +147 -0
  70. package/dist/ui/components/transcript-viewport.d.ts +2 -147
  71. package/dist/ui/components/transcript-viewport.js +87 -315
  72. package/package.json +3 -3
  73. package/dist/integrations/pi/engine/diagnostics-state.d.ts +0 -9
  74. package/dist/integrations/pi/engine/diagnostics-state.js +0 -34
  75. package/dist/integrations/pi/engine/usage-view.d.ts +0 -4
  76. package/dist/integrations/pi/engine/usage-view.js +0 -56
  77. package/dist/integrations/pi/tui-runtime/contracts.d.ts +0 -22
  78. /package/dist/{integrations/pi/tui-runtime/contracts.js → ui/components/transcript-viewport-types.js} +0 -0
@@ -1,11 +1,7 @@
1
1
  import { samePointerSurfaces } from "../../contracts/presentation/index.js";
2
2
  import { TranscriptViewport } from "../../ui/components/transcript-viewport.js";
3
- import { readVisibleHyperlinks } from "../../ui/components/visible-hyperlinks.js";
4
- import { routeMouseInput } from "../../ui/components/mouse.js";
5
- import { scrollForTrackPage, scrollForThumbRow, scrollbarSelectionRows, scrollbarWheelRows } from "../../ui/components/scrollbar.js";
6
- function withinTailControl(region, column, row) {
7
- return row >= region.rowStart && row <= region.rowEnd && column >= region.columnStart && column <= region.columnEnd;
8
- }
3
+ import { scrollbarSelectionRows } from "../../ui/components/scrollbar.js";
4
+ import { ViewportPointerGestures, withinTailControl } from "./viewport-pointer.js";
9
5
  /**
10
6
  * Owns the stateful interaction policy of the bare-A1 session viewport. The
11
7
  * shell root supplies semantic rows and dock geometry; this controller owns
@@ -26,27 +22,16 @@ export class SessionViewportController {
26
22
  scrollbarStyle: "thin",
27
23
  scrollbarSpeed: "normal",
28
24
  };
29
- #dragGrabOffset = null;
30
25
  #inputSurface;
31
26
  #inputSurfacePending = false;
32
27
  #overlays = [];
33
- #pointerOwner;
34
- // Invariant: a right-click editor gesture remains owned by the dock.
35
- #dockPointerSuppressed = false;
36
- // Invariant: a gesture begun on an explicit control remains owned by that control.
37
- #tailPointerSuppressed = false;
38
- // Invariant: editor selection remains true only while its left button is held.
39
- #editorPointerSelecting = false;
40
- // Invariant: no-drag editor presses replay on release; distinct motion promotes frame selection.
41
- #pendingEditorClick;
42
28
  // Invariant: pointer routing uses the most recently composed editor rows.
43
29
  #editorPointerFrame;
44
30
  // Invariant: one clickable region in the viewport tail, located by the most recent composition.
45
31
  #tailControl;
46
32
  #selectionAutoScrollTimer;
47
33
  #selectionAutoScrollPointer;
48
- #pointerPosition;
49
- #hoveredHyperlinkKey;
34
+ #gestures;
50
35
  #lastRequestedSelectionRevision = -1;
51
36
  #presentationRevision = 0;
52
37
  #copyOnSelect = true;
@@ -62,12 +47,32 @@ export class SessionViewportController {
62
47
  this.#requestHyperlinkCleanup = options.requestHyperlinkCleanup ?? (() => { });
63
48
  this.#pasteDiagnostics = options.pasteDiagnostics;
64
49
  this.#nextPasteDiagnosticRequest = options.nextPasteDiagnosticRequest ?? (() => --this.#pasteDiagnosticRequest);
50
+ this.#gestures = new ViewportPointerGestures({
51
+ viewport: this.#viewport,
52
+ editor: this.#editor,
53
+ config: () => this.#config,
54
+ copyOnSelect: () => this.#copyOnSelect,
55
+ inputGeometryReady: () => this.inputGeometryReady,
56
+ modalAt: (column, row) => this.#modalAt(column, row),
57
+ editorFrame: () => this.#editorPointerFrame,
58
+ tailControl: () => this.#tailControl,
59
+ clearTailControl: () => { this.#tailControl = undefined; },
60
+ requestRender: () => { this.#requestRender(); },
61
+ requestHyperlinkCleanup: rows => { this.#requestHyperlinkCleanup(rows); },
62
+ updateSelectionAutoScroll: (column, row, viewportHeight, frameHeight) => { this.#updateSelectionAutoScroll(column, row, viewportHeight, frameHeight); },
63
+ stopSelectionAutoScroll: () => { this.#stopSelectionAutoScroll(); },
64
+ pasteFromPointer: () => {
65
+ const request = this.#nextPasteDiagnosticRequest();
66
+ if (this.#editor.pasteClipboard())
67
+ this.#emitPasteRoute(request, "pointer-admitted");
68
+ },
69
+ });
65
70
  }
66
71
  get config() {
67
72
  return this.#config;
68
73
  }
69
74
  get editorPointerSelecting() {
70
- return this.#editorPointerSelecting;
75
+ return this.#gestures.editorSelecting;
71
76
  }
72
77
  get transcriptPointerSelecting() {
73
78
  return this.#viewport.selectionActive;
@@ -83,7 +88,7 @@ export class SessionViewportController {
83
88
  }
84
89
  /** Latest reported coordinates; exposed without rendering for frame provenance and evidence. */
85
90
  get pointerPosition() {
86
- return this.#pointerPosition;
91
+ return this.#gestures.position;
87
92
  }
88
93
  setCopyOnSelect(enabled) {
89
94
  this.#copyOnSelect = enabled;
@@ -100,7 +105,7 @@ export class SessionViewportController {
100
105
  }
101
106
  /** Whether the pointer currently rests on the tail control; drives its hover paint. */
102
107
  tailControlHovered(target) {
103
- const pointer = this.#pointerPosition;
108
+ const pointer = this.#gestures.position;
104
109
  return target !== undefined && pointer !== undefined && withinTailControl(target, pointer.column, pointer.row);
105
110
  }
106
111
  /** Replacement input changed before its new bounds have been painted. */
@@ -143,7 +148,7 @@ export class SessionViewportController {
143
148
  })));
144
149
  const frame = this.#viewport.compose({
145
150
  ...input,
146
- ...(this.#pointerPosition === undefined ? {} : { pointerPosition: this.#pointerPosition }),
151
+ ...(this.#gestures.position === undefined ? {} : { pointerPosition: this.#gestures.position }),
147
152
  ...(coveredCells.length === 0 ? {} : { coveredCells }),
148
153
  });
149
154
  // Concurrency: if selection changed during composition, this frame truthfully carries
@@ -153,16 +158,7 @@ export class SessionViewportController {
153
158
  this.#lastRequestedSelectionRevision = this.#viewport.selectionRevision;
154
159
  this.#requestRender();
155
160
  }
156
- if (this.#pointerPosition !== undefined && !this.#viewport.selectionActive && !this.#editorPointerSelecting) {
157
- const next = this.#hyperlinkKeyAt(frame, this.#pointerPosition.column, this.#pointerPosition.row);
158
- if (this.#hoveredHyperlinkKey !== undefined && next !== this.#hoveredHyperlinkKey) {
159
- // Platform: Windows Terminal can leave its solid native-hover underline behind
160
- // when a following viewport moves the link away from a stationary pointer.
161
- this.#requestHyperlinkCleanup([this.#pointerPosition.row]);
162
- this.#requestRender();
163
- }
164
- this.#hoveredHyperlinkKey = next;
165
- }
161
+ this.#gestures.refreshHover(frame);
166
162
  return frame;
167
163
  }
168
164
  composeDockOnly(dockRows, width, height, copyableDockRows = []) {
@@ -196,32 +192,18 @@ export class SessionViewportController {
196
192
  this.#presentationRevision += 1;
197
193
  this.#clearActivityTimer();
198
194
  this.#stopSelectionAutoScroll();
199
- this.#editorPointerSelecting = false;
200
- this.#pendingEditorClick = undefined;
195
+ this.#gestures.clear();
201
196
  this.#editorPointerFrame = undefined;
202
- this.#pointerPosition = undefined;
203
- this.#hoveredHyperlinkKey = undefined;
204
197
  this.#viewport.reset();
205
- this.#pointerOwner = undefined;
206
- this.#dragGrabOffset = null;
207
- this.#dockPointerSuppressed = false;
208
- this.#tailPointerSuppressed = false;
209
198
  }
210
199
  clearPointerState() {
211
200
  this.#presentationRevision += 1;
212
201
  this.#clearActivityTimer();
213
- this.#dragGrabOffset = null;
214
- this.#dockPointerSuppressed = false;
215
- this.#tailPointerSuppressed = false;
216
- this.#editorPointerSelecting = false;
217
- this.#pendingEditorClick = undefined;
202
+ this.#gestures.clear();
218
203
  this.#editorPointerFrame = undefined;
219
- this.#pointerPosition = undefined;
220
- this.#hoveredHyperlinkKey = undefined;
221
204
  this.#stopSelectionAutoScroll();
222
205
  this.#viewport.clearSelection();
223
206
  this.#viewport.clearTransient();
224
- this.#pointerOwner = undefined;
225
207
  }
226
208
  #emitPasteRoute(request, phase) {
227
209
  try {
@@ -230,15 +212,15 @@ export class SessionViewportController {
230
212
  catch { /* Invariant: payload-free diagnostics cannot interfere with input routing. */ }
231
213
  }
232
214
  #cancelGesture() {
233
- const held = this.#pointerOwner !== undefined || this.#viewport.selectionActive || this.#editorPointerSelecting;
215
+ const held = this.#gestures.held;
234
216
  const repaint = this.#viewport.hasSelection || held;
235
- if (!repaint && this.#pointerPosition === undefined && this.#hoveredHyperlinkKey === undefined)
217
+ if (!repaint && this.#gestures.idle)
236
218
  return;
237
219
  const editorFrame = this.#editorPointerFrame;
238
220
  this.clearPointerState();
239
221
  this.#editorPointerFrame = editorFrame;
240
222
  if (held)
241
- this.#pointerOwner = "drain";
223
+ this.#gestures.drain();
242
224
  if (repaint)
243
225
  this.#requestRender();
244
226
  }
@@ -291,16 +273,8 @@ export class SessionViewportController {
291
273
  if (data === "\u0018" && editorActive && !this.#viewport.hasSelection && !this.#editor.hasSelection()) {
292
274
  return { data: "", consumed: true, copyLastMessage: true };
293
275
  }
294
- if (this.#viewport.frame === null) {
295
- // Invariant: a not-yet-painted A1 screen is not Pi's selection surface.
296
- return routeMouseInput(data, event => {
297
- if (event.kind === "press" && event.button === 0)
298
- this.#tailPointerSuppressed = true;
299
- if (event.kind === "release")
300
- this.#tailPointerSuppressed = false;
301
- return event.kind === "motion" || event.kind === "release" || (event.kind === "press" && event.button === 0);
302
- });
303
- }
276
+ if (this.#viewport.frame === null)
277
+ return this.#gestures.routeUnpainted(data);
304
278
  if ((data === "\u0003" || data === "\u0018") && this.#viewport.hasSelection) {
305
279
  // Invariant: the newest frame selection wins over an older editor selection.
306
280
  const copySelection = this.#viewport.captureSelectedText();
@@ -325,259 +299,14 @@ export class SessionViewportController {
325
299
  if (!data.includes("\u001b[<") && data !== "\u0003" && this.#viewport.clearSelection()) {
326
300
  this.#requestRender();
327
301
  }
328
- const frame = this.#viewport.frame;
329
- let repaint = false;
330
- let forceRepaint = false;
331
- let activity = false;
332
- let completedCopy;
333
- const routed = routeMouseInput(data, (event, report) => {
334
- const hits = frame.hits;
335
- const overModal = this.#modalAt(event.column, event.row);
336
- const wheel = event.kind === "wheel-up" || event.kind === "wheel-down";
337
- // Invariant: latch each gesture to one surface.
338
- if (this.#pointerOwner === "drain" || !this.inputGeometryReady) {
339
- if (event.kind === "release")
340
- this.#pointerOwner = undefined;
341
- else if (event.kind === "press")
342
- this.#pointerOwner = "drain";
343
- return true;
344
- }
345
- const owner = !wheel && this.#pointerOwner !== undefined ? this.#pointerOwner : overModal;
346
- if (owner !== undefined && owner !== "viewport") {
347
- if (event.kind === "press")
348
- this.#pointerOwner = owner;
349
- if (event.kind === "release")
350
- this.#pointerOwner = undefined;
351
- if (event.kind === "press" && this.#viewport.clearSelection())
352
- repaint = true;
353
- this.#viewport.setRailHovered(false);
354
- this.#viewport.setStickyHovered(false);
355
- this.#pointerPosition = undefined;
356
- owner.component.handleInput?.(report);
357
- repaint = true;
358
- return true;
359
- }
360
- if (event.kind === "press")
361
- this.#pointerOwner = "viewport";
362
- if (event.kind === "release")
363
- this.#pointerOwner = undefined;
364
- const overRail = overModal === undefined && hits.rail !== null
365
- && event.column === hits.rail.column
366
- && event.row >= hits.rail.rowStart
367
- && event.row < hits.rail.rowStart + hits.rail.trackHeight;
368
- const overSticky = overModal === undefined && hits.sticky !== null && event.row === hits.sticky.row && event.column <= hits.sticky.width;
369
- const overBottom = overModal === undefined && hits.bottom !== null && event.row === hits.bottom.row
370
- && event.column >= hits.bottom.columnStart && event.column <= hits.bottom.columnEnd;
371
- const tailControl = this.#tailControl;
372
- const overTailControl = overModal === undefined && tailControl !== undefined
373
- && withinTailControl(tailControl, event.column, event.row);
374
- const previousPointer = this.#pointerPosition;
375
- const wasOverBottom = hits.bottom !== null && previousPointer !== undefined
376
- && previousPointer.row === hits.bottom.row
377
- && previousPointer.column >= hits.bottom.columnStart && previousPointer.column <= hits.bottom.columnEnd;
378
- // Invariant: every report updates the pointer position.
379
- this.#pointerPosition = { column: event.column, row: event.row };
380
- repaint ||= wasOverBottom !== overBottom;
381
- const wasOverTailControl = tailControl !== undefined && previousPointer !== undefined
382
- && withinTailControl(tailControl, previousPointer.column, previousPointer.row);
383
- repaint ||= wasOverTailControl !== overTailControl;
384
- if (!this.#viewport.selectionActive && !this.#editorPointerSelecting) {
385
- const nextHyperlink = this.#hyperlinkKeyAt(frame, event.column, event.row);
386
- if (this.#hoveredHyperlinkKey !== undefined && nextHyperlink !== this.#hoveredHyperlinkKey) {
387
- // Platform: overwrite Windows Terminal's cached native-hover underline exactly once.
388
- this.#requestHyperlinkCleanup(previousPointer === undefined ? undefined : [previousPointer.row]);
389
- repaint = true;
390
- }
391
- this.#hoveredHyperlinkKey = nextHyperlink;
392
- }
393
- if (event.kind === "motion") {
394
- this.#viewport.setRailHovered(overRail);
395
- this.#viewport.setStickyHovered(overSticky);
396
- repaint = true;
397
- if (editorActive && this.#editorPointerSelecting && this.#editorPointerFrame !== undefined) {
398
- this.#editor.handlePointer({
399
- kind: "motion",
400
- button: event.button,
401
- column: event.column,
402
- row: event.row - this.#editorPointerFrame.rowStart + 1,
403
- });
404
- return true;
405
- }
406
- if (this.#dockPointerSuppressed || this.#tailPointerSuppressed)
407
- return true;
408
- const pendingEditorClick = this.#pendingEditorClick;
409
- if (pendingEditorClick !== undefined) {
410
- if (event.column === pendingEditorClick.column && event.row === pendingEditorClick.row)
411
- return true;
412
- this.#pendingEditorClick = undefined;
413
- const editorFrame = editorActive ? this.#editorPointerFrame : undefined;
414
- if (editorFrame !== undefined && this.#editor.handlePointer({ kind: "press", button: 0, column: pendingEditorClick.column, row: pendingEditorClick.row - editorFrame.rowStart + 1 })) {
415
- this.#editorPointerSelecting = true;
416
- this.#editor.handlePointer({ kind: "motion", button: event.button, column: event.column, row: event.row - editorFrame.rowStart + 1 });
417
- return true;
418
- }
419
- this.#viewport.pressSelection(pendingEditorClick.column, pendingEditorClick.row, now, { continueMultiClick: false });
420
- this.#viewport.extendSelection(event.column, event.row, now, false);
421
- this.#updateSelectionAutoScroll(event.column, event.row, hits.viewportHeight, frame.rows.length);
422
- activity = true;
423
- return true;
424
- }
425
- if (this.#viewport.selectionActive) {
426
- this.#viewport.extendSelection(event.column, event.row, now, false);
427
- this.#updateSelectionAutoScroll(event.column, event.row, hits.viewportHeight, frame.rows.length);
428
- activity = true;
429
- return true;
430
- }
431
- if (this.#dragGrabOffset !== null && hits.rail !== null) {
432
- const target = scrollForThumbRow(hits.rail.geometry, event.row - hits.rail.rowStart - this.#dragGrabOffset);
433
- this.#viewport.scrollTo(target, now);
434
- activity = true;
435
- return true;
436
- }
437
- return overRail || overSticky || overBottom || overTailControl;
438
- }
439
- if (event.kind === "wheel-up" || event.kind === "wheel-down") {
440
- if (!allowWheel || event.row < 1 || event.row > hits.viewportHeight)
441
- return false;
442
- const distance = scrollbarWheelRows(this.#config.scrollbarSpeed);
443
- this.#viewport.scrollBy(event.kind === "wheel-up" ? -distance : distance, now);
444
- activity = true;
445
- repaint = true;
446
- forceRepaint = true;
447
- return true;
448
- }
449
- if (event.kind === "press") {
450
- const editorFrame = editorActive ? this.#editorPointerFrame : undefined;
451
- if (event.button === 2 && editorFrame !== undefined
452
- && event.row >= editorFrame.rowStart && event.row <= editorFrame.rowEnd) {
453
- this.#stopSelectionAutoScroll();
454
- if (this.#viewport.clearSelection())
455
- repaint = true;
456
- const request = this.#nextPasteDiagnosticRequest();
457
- if (this.#editor.pasteClipboard())
458
- this.#emitPasteRoute(request, "pointer-admitted");
459
- this.#dockPointerSuppressed = true;
460
- repaint = true;
461
- return true;
462
- }
463
- if (event.button !== 0)
464
- return false;
465
- this.#dockPointerSuppressed = false;
466
- this.#tailPointerSuppressed = false;
467
- this.#pendingEditorClick = undefined;
468
- this.#stopSelectionAutoScroll();
469
- if (this.#viewport.clearSelection())
470
- repaint = true;
471
- if (overTailControl) {
472
- this.#tailControl = undefined;
473
- tailControl.activate();
474
- this.#tailPointerSuppressed = true;
475
- repaint = true;
476
- return true;
477
- }
478
- if (overBottom) {
479
- this.#viewport.scrollToEnd(now);
480
- this.#tailPointerSuppressed = true;
481
- activity = true;
482
- repaint = true;
483
- return true;
484
- }
485
- if (overSticky && hits.sticky !== null) {
486
- this.#viewport.scrollTo(hits.sticky.target, now);
487
- this.#tailPointerSuppressed = true;
488
- activity = true;
489
- repaint = true;
490
- return true;
491
- }
492
- if (overRail && hits.rail !== null) {
493
- const trackRow = event.row - hits.rail.rowStart;
494
- const geometry = hits.rail.geometry;
495
- if (trackRow >= geometry.thumbTop && trackRow < geometry.thumbTop + geometry.thumbHeight) {
496
- this.#dragGrabOffset = trackRow - geometry.thumbTop;
497
- this.#viewport.setRailDragging(true);
498
- }
499
- else {
500
- this.#viewport.scrollTo(scrollForTrackPage(geometry, trackRow, frame.scrollTop, hits.viewportHeight), now);
501
- this.#tailPointerSuppressed = true;
502
- }
503
- activity = true;
504
- repaint = true;
505
- return true;
506
- }
507
- if (event.row >= 1 && event.row <= frame.rows.length && event.column <= frame.descriptor.width) {
508
- if (editorFrame !== undefined && event.row >= editorFrame.rowStart && event.row <= editorFrame.rowEnd) {
509
- // Invariant: no-drag clicks remain editor-owned; first motion latches an editor drag.
510
- this.#pendingEditorClick = { column: event.column, row: event.row };
511
- }
512
- else {
513
- this.#viewport.pressSelection(event.column, event.row, now);
514
- }
515
- repaint = true;
516
- return true;
517
- }
518
- this.#tailPointerSuppressed = true;
519
- return true;
520
- }
521
- if (event.kind === "release") {
522
- if (editorActive && this.#editorPointerSelecting && this.#editorPointerFrame !== undefined) {
523
- const wasEditorSelecting = this.#editorPointerSelecting;
524
- this.#editor.handlePointer({
525
- kind: "release",
526
- button: event.button,
527
- column: event.column,
528
- row: event.row - this.#editorPointerFrame.rowStart + 1,
529
- });
530
- this.#editorPointerSelecting = false;
531
- forceRepaint ||= wasEditorSelecting;
532
- repaint = true;
533
- return true;
534
- }
535
- const pendingEditorClick = this.#pendingEditorClick;
536
- if (pendingEditorClick !== undefined) {
537
- this.#pendingEditorClick = undefined;
538
- if (this.#editorPointerFrame !== undefined) {
539
- const editorRow = pendingEditorClick.row - this.#editorPointerFrame.rowStart + 1;
540
- this.#editor.handlePointer({ kind: "press", button: 0, column: pendingEditorClick.column, row: editorRow });
541
- this.#editor.handlePointer({ kind: "release", button: 0, column: pendingEditorClick.column, row: editorRow });
542
- }
543
- forceRepaint = true;
544
- repaint = true;
545
- return true;
546
- }
547
- if (this.#viewport.releaseSelection()) {
548
- this.#stopSelectionAutoScroll();
549
- if (this.#copyOnSelect)
550
- completedCopy = this.#viewport.captureSelectedText() ?? undefined;
551
- // Platform: restore OSC 8 links only after the held-button selection paint has
552
- // ended, then overwrite any terminal-cached hover cells immediately.
553
- forceRepaint = true;
554
- repaint = true;
555
- return true;
556
- }
557
- if (this.#dragGrabOffset !== null) {
558
- this.#dragGrabOffset = null;
559
- this.#viewport.setRailDragging(false);
560
- repaint = true;
561
- return true;
562
- }
563
- if (this.#dockPointerSuppressed) {
564
- this.#dockPointerSuppressed = false;
565
- return true;
566
- }
567
- if (this.#tailPointerSuppressed) {
568
- this.#tailPointerSuppressed = false;
569
- return true;
570
- }
571
- }
572
- return false;
573
- });
574
- if (activity)
302
+ const { routed, outcome } = this.#gestures.route(data, this.#viewport.frame, { allowWheel, now, editorActive });
303
+ if (outcome.activity)
575
304
  this.#scheduleActivityExpiry();
576
- if (repaint) {
305
+ if (outcome.repaint) {
577
306
  this.#lastRequestedSelectionRevision = this.#viewport.selectionRevision;
578
- this.#requestRender(forceRepaint);
307
+ this.#requestRender(outcome.forceRepaint);
579
308
  }
580
- return completedCopy === undefined ? routed : { ...routed, copySelection: completedCopy };
309
+ return outcome.completedCopy === undefined ? routed : { ...routed, copySelection: outcome.completedCopy };
581
310
  }
582
311
  #modalAt(column, row) {
583
312
  const contains = (surface) => column >= surface.columnStart && column <= surface.columnEnd
@@ -585,13 +314,6 @@ export class SessionViewportController {
585
314
  const overlay = this.#overlays?.findLast(contains);
586
315
  return overlay ?? (this.#inputSurface !== undefined && contains(this.#inputSurface) ? this.#inputSurface : undefined);
587
316
  }
588
- #hyperlinkKeyAt(frame, column, row) {
589
- if (row < 1 || row > frame.rows.length || column < 1)
590
- return undefined;
591
- const range = readVisibleHyperlinks(frame.rows[row - 1] ?? "").ranges
592
- .find(candidate => column - 1 >= candidate.from && column - 1 < candidate.to);
593
- return range === undefined ? undefined : JSON.stringify([row, range]);
594
- }
595
317
  #updateSelectionAutoScroll(column, row, viewportHeight, frameHeight) {
596
318
  // Invariant: the last content row stays selectable; only the pointer below it scrolls,
597
319
  // unless no row exists below. The top row scrolls because nothing lies above it. A
@@ -4,7 +4,7 @@ import type { OwnedUiSessionShellRoot } from "./session-shell-root.js";
4
4
  /** What the session's live setting effects reach: terminal modes, root presentation, and the presenter's progress flag. */
5
5
  interface SessionSettingsTargets {
6
6
  readonly terminal: Pick<OwnedUiPresenterTerminal, "setHardwareCursor" | "setClearOnShrink" | "setWheelScrollLines">;
7
- readonly root: Pick<OwnedUiSessionShellRoot, "setEditorPaddingX" | "setAutocompleteMaxVisible" | "setOutputPad" | "setHideThinkingBlock" | "setMermaidRenderingMode" | "setImagePresentation" | "setFullscreenCopyOnSelect">;
7
+ readonly root: Pick<OwnedUiSessionShellRoot, "setEditorPaddingX" | "setAutocompleteMaxVisible" | "setOutputPad" | "setHideThinkingBlock" | "setMermaidRenderingMode" | "setImagePresentation" | "viewport">;
8
8
  setTerminalProgress(enabled: boolean): void;
9
9
  }
10
10
  /**
@@ -49,13 +49,13 @@ function settingBindings(targets, snapshot, customViewport, bare) {
49
49
  { owner: null, key: "imageWidthCells", live: () => { }, seed: presentImages },
50
50
  // Invariant: the custom viewport owns copy-on-select; the pinned comparison profile keeps Pi's wheel distance.
51
51
  customViewport
52
- ? setting("shell", "fullscreenCopyOnSelect", "Fullscreen copy-on-select setting is invalid", isBoolean, value => root.setFullscreenCopyOnSelect(value))
52
+ ? setting("shell", "fullscreenCopyOnSelect", "Fullscreen copy-on-select setting is invalid", isBoolean, value => root.viewport.setCopyOnSelect(value))
53
53
  : setting("shell", "fullscreenWheelScrollLines", "Fullscreen wheel-scroll distance is invalid", isWheelDistance, value => terminal.setWheelScrollLines(value), false),
54
54
  ...(customViewport ? [] : [{
55
55
  owner: null,
56
56
  key: "fullscreenCopyOnSelect",
57
57
  live: () => { },
58
- seed: (initial) => root.setFullscreenCopyOnSelect(initial.fullscreenCopyOnSelect),
58
+ seed: (initial) => root.viewport.setCopyOnSelect(initial.fullscreenCopyOnSelect),
59
59
  }]),
60
60
  ];
61
61
  }
@@ -0,0 +1,83 @@
1
+ import type { OwnedUiShellEditorPort, OwnedUiViewportSettings } from "../../contracts/owned-ui/index.js";
2
+ import type { TerminalPointerSurface } from "../../contracts/presentation/index.js";
3
+ import { type RoutedMouseInput } from "../../ui/components/mouse.js";
4
+ import type { SelectionCopySnapshot } from "../../ui/components/selection-copy.js";
5
+ import type { TranscriptViewport, TranscriptViewportFrame } from "../../ui/components/transcript-viewport.js";
6
+ /** One-based, inclusive terminal cells of a clickable viewport-tail control. */
7
+ export interface TailControlRegion {
8
+ readonly rowStart: number;
9
+ readonly rowEnd: number;
10
+ readonly columnStart: number;
11
+ readonly columnEnd: number;
12
+ }
13
+ export type TailControl = TailControlRegion & {
14
+ readonly activate: () => void;
15
+ };
16
+ export declare function withinTailControl(region: TailControlRegion, column: number, row: number): boolean;
17
+ export interface ViewportPointerPorts {
18
+ readonly viewport: TranscriptViewport;
19
+ readonly editor: OwnedUiShellEditorPort;
20
+ config(): OwnedUiViewportSettings;
21
+ copyOnSelect(): boolean;
22
+ /** False while replacement input or overlay bounds have not been painted yet. */
23
+ inputGeometryReady(): boolean;
24
+ /** The topmost overlay or input surface under a cell. */
25
+ modalAt(column: number, row: number): TerminalPointerSurface | undefined;
26
+ /** The editor rows of the most recent composition. */
27
+ editorFrame(): {
28
+ readonly rowStart: number;
29
+ readonly rowEnd: number;
30
+ } | undefined;
31
+ tailControl(): TailControl | undefined;
32
+ clearTailControl(): void;
33
+ requestRender(): void;
34
+ requestHyperlinkCleanup(rows?: readonly number[]): void;
35
+ updateSelectionAutoScroll(column: number, row: number, viewportHeight: number, frameHeight: number): void;
36
+ stopSelectionAutoScroll(): void;
37
+ /** A right-click on the editor pastes the clipboard. */
38
+ pasteFromPointer(): void;
39
+ }
40
+ /** How one input chunk's mouse reports changed the viewport. */
41
+ export interface ViewportPointerOutcome {
42
+ repaint: boolean;
43
+ forceRepaint: boolean;
44
+ activity: boolean;
45
+ completedCopy: SelectionCopySnapshot | undefined;
46
+ }
47
+ interface PointerCall {
48
+ readonly allowWheel: boolean;
49
+ readonly now: number;
50
+ readonly editorActive: boolean;
51
+ }
52
+ /**
53
+ * The pointer gestures of the session viewport: which surface a gesture is latched to, the
54
+ * suppression of a gesture begun on the dock or a control, the editor click that waits for drag
55
+ * or release, the rail drag offset, the pointer position and the hovered link. Each report is
56
+ * routed in the same order of checks, and its effect is reported as an outcome.
57
+ */
58
+ export declare class ViewportPointerGestures {
59
+ #private;
60
+ constructor(ports: ViewportPointerPorts);
61
+ get position(): {
62
+ readonly column: number;
63
+ readonly row: number;
64
+ } | undefined;
65
+ get editorSelecting(): boolean;
66
+ /** A gesture is latched to a surface or a selection is being dragged. */
67
+ get held(): boolean;
68
+ /** No pointer position or hovered link is remembered. */
69
+ get idle(): boolean;
70
+ /** Forget every latch. */
71
+ clear(): void;
72
+ /** Swallow the rest of a held gesture until its release. */
73
+ drain(): void;
74
+ /** Before the first paint, a left press and its motion and release belong to no surface. */
75
+ routeUnpainted(data: string): RoutedMouseInput;
76
+ route(data: string, frame: TranscriptViewportFrame, call: PointerCall): {
77
+ readonly routed: RoutedMouseInput;
78
+ readonly outcome: ViewportPointerOutcome;
79
+ };
80
+ /** A newly composed frame may move a link away from a stationary pointer. */
81
+ refreshHover(frame: TranscriptViewportFrame): void;
82
+ }
83
+ export {};