@timurproko/a1 0.1.8-dev.157 → 0.1.8-dev.214

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 (171) hide show
  1. package/README.md +32 -0
  2. package/bin/cli.js +2 -2
  3. package/bin/sync-pi-tui-proxy.js +1 -1
  4. package/bin/ui.js +18 -3
  5. package/dist/cli/dispatch.js +3 -3
  6. package/dist/cli/packages.js +1 -1
  7. package/dist/cli/version-stats.js +3 -3
  8. package/dist/composition/owned-ui.d.ts +4 -1
  9. package/dist/composition/owned-ui.js +9 -6
  10. package/dist/composition/settings-route-host.js +6 -4
  11. package/dist/contracts/agent-engine/capability-ports.d.ts +2 -2
  12. package/dist/contracts/agent-engine/domain-validation.js +22 -4
  13. package/dist/contracts/agent-engine/domain.d.ts +17 -0
  14. package/dist/contracts/owned-ui/model.d.ts +8 -0
  15. package/dist/contracts/owned-ui/validation.js +11 -0
  16. package/dist/features/launch/development-launch.js +1 -1
  17. package/dist/features/owned-ui/customization.d.ts +2 -0
  18. package/dist/features/owned-ui/customization.js +2 -0
  19. package/dist/features/owned-ui/diagnostics.d.ts +1 -0
  20. package/dist/features/owned-ui/diagnostics.js +1 -0
  21. package/dist/features/owned-ui/index.d.ts +1 -0
  22. package/dist/features/owned-ui/index.js +1 -0
  23. package/dist/features/owned-ui/project-trust-prompt.d.ts +25 -0
  24. package/dist/features/owned-ui/project-trust-prompt.js +149 -0
  25. package/dist/features/owned-ui/settings-app.js +69 -44
  26. package/dist/features/workspace/reducer.d.ts +1 -0
  27. package/dist/features/workspace/reducer.js +1 -0
  28. package/dist/features/workspace/router.d.ts +1 -0
  29. package/dist/features/workspace/router.js +1 -0
  30. package/dist/features/workspace/store.d.ts +1 -0
  31. package/dist/features/workspace/store.js +1 -0
  32. package/dist/foundation/launch-guardian/main.js +1 -1
  33. package/dist/foundation/native-host-protocol/codec.d.ts +2 -0
  34. package/dist/foundation/native-host-protocol/codec.js +2 -0
  35. package/dist/foundation/process-containment/linux-process-inspector.d.ts +1 -0
  36. package/dist/foundation/process-containment/linux-process-inspector.js +1 -0
  37. package/dist/foundation/process-containment/native-guardian-containment.d.ts +1 -0
  38. package/dist/foundation/process-containment/native-guardian-containment.js +1 -0
  39. package/dist/foundation/process-containment/windows-process-inspector.d.ts +1 -0
  40. package/dist/foundation/process-containment/windows-process-inspector.js +1 -0
  41. package/dist/foundation/protocol/client.d.ts +1 -0
  42. package/dist/foundation/protocol/client.js +1 -0
  43. package/dist/foundation/protocol/messages.d.ts +1 -0
  44. package/dist/foundation/protocol/messages.js +1 -0
  45. package/dist/foundation/release/bootstrap.js +6 -6
  46. package/dist/foundation/release/cohort-state.d.ts +1 -0
  47. package/dist/foundation/release/cohort-state.js +1 -0
  48. package/dist/foundation/release/release-gc.js +1 -1
  49. package/dist/foundation/release/release.js +1 -1
  50. package/dist/foundation/release/update-transaction.d.ts +1 -0
  51. package/dist/foundation/release/update-transaction.js +1 -0
  52. package/dist/foundation/release/update.js +12 -12
  53. package/dist/foundation/storage/control-store.d.ts +1 -0
  54. package/dist/foundation/storage/control-store.js +1 -0
  55. package/dist/foundation/structured-agent-runtime/backpressure.d.ts +1 -0
  56. package/dist/foundation/structured-agent-runtime/backpressure.js +1 -0
  57. package/dist/foundation/structured-agent-runtime/commands.d.ts +1 -0
  58. package/dist/foundation/structured-agent-runtime/commands.js +1 -0
  59. package/dist/foundation/structured-agent-runtime/reconnection.d.ts +1 -0
  60. package/dist/foundation/structured-agent-runtime/reconnection.js +1 -0
  61. package/dist/foundation/structured-agent-runtime/state.d.ts +1 -0
  62. package/dist/foundation/structured-agent-runtime/state.js +1 -0
  63. package/dist/foundation/supervision/main.js +1 -1
  64. package/dist/foundation/supervision/server.d.ts +1 -0
  65. package/dist/foundation/supervision/server.js +4 -6
  66. package/dist/integrations/pi/components/conformance.d.ts +1 -0
  67. package/dist/integrations/pi/components/conformance.js +1 -0
  68. package/dist/integrations/pi/components/shell-components.d.ts +1 -0
  69. package/dist/integrations/pi/components/shell-components.js +1 -0
  70. package/dist/integrations/pi/components/shell-editor-autocomplete.js +10 -0
  71. package/dist/integrations/pi/components/shell-footer-status.d.ts +1 -1
  72. package/dist/integrations/pi/components/shell-footer-status.js +19 -13
  73. package/dist/integrations/pi/components/shell-presenters-info.d.ts +36 -0
  74. package/dist/integrations/pi/components/shell-presenters-info.js +78 -0
  75. package/dist/integrations/pi/components/shell-presenters-transcript.d.ts +3 -36
  76. package/dist/integrations/pi/components/shell-presenters-transcript.js +88 -128
  77. package/dist/integrations/pi/components/shell-shared-facade.d.ts +11 -1
  78. package/dist/integrations/pi/components/shell-shared-facade.js +1 -1
  79. package/dist/integrations/pi/engine/adapter.d.ts +22 -4
  80. package/dist/integrations/pi/engine/adapter.js +220 -70
  81. package/dist/integrations/pi/engine/conformance.d.ts +2 -0
  82. package/dist/integrations/pi/engine/conformance.js +2 -0
  83. package/dist/integrations/pi/engine/http-dispatcher.d.ts +4 -0
  84. package/dist/integrations/pi/engine/http-dispatcher.js +25 -0
  85. package/dist/integrations/pi/engine/index.d.ts +3 -0
  86. package/dist/integrations/pi/engine/index.js +3 -0
  87. package/dist/integrations/pi/engine/model-auth-integration.d.ts +1 -0
  88. package/dist/integrations/pi/engine/model-auth-integration.js +1 -0
  89. package/dist/integrations/pi/engine/package-integration.js +1 -1
  90. package/dist/integrations/pi/engine/project-trust-preflight.d.ts +22 -0
  91. package/dist/integrations/pi/engine/project-trust-preflight.js +50 -0
  92. package/dist/integrations/pi/engine/resource-extension-integration.d.ts +1 -0
  93. package/dist/integrations/pi/engine/resource-extension-integration.js +1 -0
  94. package/dist/integrations/pi/engine/runtime-integration.d.ts +16 -1
  95. package/dist/integrations/pi/engine/runtime-integration.js +34 -4
  96. package/dist/integrations/pi/engine/session-integration.d.ts +1 -0
  97. package/dist/integrations/pi/engine/session-integration.js +2 -1
  98. package/dist/integrations/pi/engine/settings-effects.d.ts +66 -0
  99. package/dist/integrations/pi/engine/settings-effects.js +249 -0
  100. package/dist/integrations/pi/engine/settings-integration.d.ts +14 -26
  101. package/dist/integrations/pi/engine/settings-integration.js +102 -108
  102. package/dist/integrations/pi/engine/workflow-controllers.d.ts +2 -1
  103. package/dist/integrations/pi/engine/workflow-controllers.js +1 -0
  104. package/dist/integrations/pi/session-ui/clipboard-image.d.ts +11 -0
  105. package/dist/integrations/pi/session-ui/clipboard-image.js +29 -0
  106. package/dist/integrations/pi/session-ui/index.d.ts +1 -0
  107. package/dist/integrations/pi/session-ui/index.js +1 -0
  108. package/dist/integrations/pi/session-ui/prompt-chips.js +6 -2
  109. package/dist/integrations/pi/session-ui/session-shell-root.d.ts +39 -4
  110. package/dist/integrations/pi/session-ui/session-shell-root.js +215 -80
  111. package/dist/integrations/pi/session-ui/session-shell.d.ts +10 -1
  112. package/dist/integrations/pi/session-ui/session-shell.js +256 -45
  113. package/dist/integrations/pi/session-ui/session-viewport-controller.d.ts +5 -0
  114. package/dist/integrations/pi/session-ui/session-viewport-controller.js +76 -11
  115. package/dist/integrations/pi/session-ui/stream-presentation-coalescer.d.ts +29 -0
  116. package/dist/integrations/pi/session-ui/stream-presentation-coalescer.js +79 -0
  117. package/dist/integrations/pi/session-ui/system-clipboard.d.ts +9 -0
  118. package/dist/integrations/pi/session-ui/system-clipboard.js +38 -9
  119. package/dist/integrations/pi/tui-runtime/adapter.d.ts +6 -0
  120. package/dist/integrations/pi/tui-runtime/adapter.js +166 -8
  121. package/dist/integrations/pi/tui-runtime/contracts.d.ts +25 -0
  122. package/dist/integrations/pi/tui-runtime/damage-aware-terminal.d.ts +63 -0
  123. package/dist/integrations/pi/tui-runtime/damage-aware-terminal.js +240 -0
  124. package/dist/integrations/pi/tui-runtime/index.d.ts +2 -0
  125. package/dist/integrations/pi/tui-runtime/index.js +2 -0
  126. package/dist/integrations/pi/tui-runtime/input-presentation-coordinator.d.ts +39 -0
  127. package/dist/integrations/pi/tui-runtime/input-presentation-coordinator.js +139 -0
  128. package/dist/native/darwin-arm64/manifest.json +1 -1
  129. package/dist/native/linux-x64/manifest.json +1 -1
  130. package/dist/native/win32-x64/manifest.json +2 -2
  131. package/dist/native/win32-x64/process-guardian.exe +0 -0
  132. package/dist/ui/apps/host.js +5 -9
  133. package/dist/ui/apps/registry.d.ts +1 -0
  134. package/dist/ui/apps/registry.js +1 -0
  135. package/dist/ui/components/dialog-panel.js +5 -3
  136. package/dist/ui/components/frame.d.ts +1 -0
  137. package/dist/ui/components/frame.js +1 -0
  138. package/dist/ui/components/index.d.ts +1 -0
  139. package/dist/ui/components/index.js +1 -0
  140. package/dist/ui/components/line-input.d.ts +1 -6
  141. package/dist/ui/components/line-input.js +4 -9
  142. package/dist/ui/components/list-view.d.ts +0 -6
  143. package/dist/ui/components/list-view.js +14 -7
  144. package/dist/ui/components/progress-status.d.ts +2 -0
  145. package/dist/ui/components/progress-status.js +4 -0
  146. package/dist/ui/components/scrollbar.js +1 -1
  147. package/dist/ui/components/shortcuts.d.ts +1 -0
  148. package/dist/ui/components/shortcuts.js +1 -0
  149. package/dist/ui/components/spans.d.ts +2 -5
  150. package/dist/ui/components/spans.js +28 -8
  151. package/dist/ui/components/surface.d.ts +0 -5
  152. package/dist/ui/components/surface.js +1 -5
  153. package/dist/ui/components/text-selection.d.ts +10 -1
  154. package/dist/ui/components/text-selection.js +96 -29
  155. package/dist/ui/components/text.js +3 -3
  156. package/dist/ui/components/transcript-viewport.d.ts +42 -0
  157. package/dist/ui/components/transcript-viewport.js +287 -39
  158. package/dist/ui/settings/sections.d.ts +6 -19
  159. package/dist/ui/settings/sections.js +17 -15
  160. package/dist/ui/settings/session.d.ts +8 -18
  161. package/dist/ui/settings/session.js +60 -52
  162. package/dist/ui/settings/store.d.ts +1 -0
  163. package/dist/ui/settings/store.js +2 -1
  164. package/docs/architecture/code-documentation.md +77 -0
  165. package/docs/architecture/project-structure.md +15 -1
  166. package/docs/architecture/ui-reference-provenance.md +23 -3
  167. package/docs/ci-release-runbook.md +98 -7
  168. package/docs/manual-owned-ui-checkpoint.md +28 -1
  169. package/docs/manual-terminal-colour-check.md +4 -3
  170. package/docs/repository-governance-live-acceptance.md +77 -0
  171. package/package.json +11 -3
@@ -1,4 +1,4 @@
1
- import { TranscriptViewport, routeMouseInput, scrollForTrackPage, scrollForThumbRow, scrollbarSelectionRows, scrollbarWheelRows, } from "../../../ui/components/index.js";
1
+ import { TranscriptViewport, hyperlinkTargetAtColumn, routeMouseInput, scrollForTrackPage, scrollForThumbRow, scrollbarSelectionRows, scrollbarWheelRows, } from "../../../ui/components/index.js";
2
2
  /**
3
3
  * Owns the stateful interaction policy of the bare-A1 session viewport. The
4
4
  * shell root supplies semantic rows and dock geometry; this controller owns
@@ -16,19 +16,26 @@ export class SessionViewportController {
16
16
  scrollbarSpeed: "normal",
17
17
  };
18
18
  #dragGrabOffset = null;
19
- /** A left-button sequence begun in status/editor/footer chrome is swallowed. */
19
+ // Invariant: a left-button sequence begun in dock chrome remains owned by the dock.
20
20
  #dockPointerSuppressed = false;
21
- /** True only while the editor owns a held left mouse button. */
21
+ // Invariant: editor selection remains true only while its left button is held.
22
22
  #editorPointerSelecting = false;
23
- /** Last composed terminal rows occupied by the default prompt editor. */
23
+ // Invariant: pointer routing uses the most recently composed editor rows.
24
24
  #editorPointerFrame;
25
25
  #selectionAutoScrollTimer;
26
26
  #selectionAutoScrollPointer;
27
+ #pointerPosition;
28
+ #hoveredHyperlinkKey;
29
+ #lastRequestedSelectionRevision = -1;
30
+ #presentationRevision = 0;
27
31
  #activityTimer;
28
32
  constructor(options) {
29
33
  this.#enabled = options.enabled;
30
34
  this.#editor = options.editor;
31
- this.#requestRender = options.requestRender;
35
+ this.#requestRender = force => {
36
+ this.#presentationRevision += 1;
37
+ options.requestRender(force);
38
+ };
32
39
  this.#hasEditorLinks = options.hasEditorLinks ?? (() => false);
33
40
  }
34
41
  get config() {
@@ -40,12 +47,45 @@ export class SessionViewportController {
40
47
  get transcriptPointerSelecting() {
41
48
  return this.#viewport.selectionActive;
42
49
  }
50
+ get hasSelection() {
51
+ return this.#viewport.hasSelection;
52
+ }
53
+ get selectionRevision() {
54
+ return this.#viewport.selectionRevision;
55
+ }
56
+ get presentationRevision() {
57
+ return this.#presentationRevision;
58
+ }
59
+ get frame() {
60
+ return this.#viewport.frame;
61
+ }
43
62
  setEditorPointerFrame(frame) {
44
63
  this.#editorPointerFrame = frame;
45
64
  }
46
65
  compose(input) {
47
66
  this.#viewport.setConfig(this.#config);
48
- return this.#viewport.compose(input);
67
+ const selectionRevision = this.#viewport.selectionRevision;
68
+ const frame = this.#viewport.compose(input);
69
+ // Concurrency: if selection changed during composition, this frame truthfully carries
70
+ // the older revision and exactly one ordinary follow-up render publishes the latest.
71
+ if (this.#viewport.selectionRevision !== selectionRevision
72
+ && this.#lastRequestedSelectionRevision < this.#viewport.selectionRevision) {
73
+ this.#lastRequestedSelectionRevision = this.#viewport.selectionRevision;
74
+ this.#requestRender();
75
+ }
76
+ if (this.#pointerPosition !== undefined && !this.#viewport.selectionActive && !this.#editorPointerSelecting) {
77
+ const next = this.#hyperlinkKeyAt(frame, this.#pointerPosition.column, this.#pointerPosition.row);
78
+ if (this.#hoveredHyperlinkKey !== undefined && next !== this.#hoveredHyperlinkKey) {
79
+ // Platform: Windows Terminal can leave its solid native-hover underline behind
80
+ // when a following viewport moves the link away from a stationary pointer.
81
+ this.#requestRender(true);
82
+ }
83
+ this.#hoveredHyperlinkKey = next;
84
+ }
85
+ return frame;
86
+ }
87
+ composeDockOnly(dockRows, width, height) {
88
+ return this.#viewport.composeDockOnly(dockRows, width, height);
49
89
  }
50
90
  /** Returns true when document wrapping may have changed. */
51
91
  setConfig(config) {
@@ -72,18 +112,24 @@ export class SessionViewportController {
72
112
  this.#requestRender();
73
113
  }
74
114
  reset() {
115
+ this.#presentationRevision += 1;
75
116
  this.#clearActivityTimer();
76
117
  this.#stopSelectionAutoScroll();
77
118
  this.#editorPointerSelecting = false;
78
119
  this.#editorPointerFrame = undefined;
120
+ this.#pointerPosition = undefined;
121
+ this.#hoveredHyperlinkKey = undefined;
79
122
  this.#viewport.reset();
80
123
  }
81
124
  clearPointerState() {
125
+ this.#presentationRevision += 1;
82
126
  this.#clearActivityTimer();
83
127
  this.#dragGrabOffset = null;
84
128
  this.#dockPointerSuppressed = false;
85
129
  this.#editorPointerSelecting = false;
86
130
  this.#editorPointerFrame = undefined;
131
+ this.#pointerPosition = undefined;
132
+ this.#hoveredHyperlinkKey = undefined;
87
133
  this.#stopSelectionAutoScroll();
88
134
  this.#viewport.clearSelection();
89
135
  this.#viewport.clearTransient();
@@ -91,15 +137,15 @@ export class SessionViewportController {
91
137
  handlePreInput(data, allowWheel = true, now = Date.now()) {
92
138
  if (!this.#enabled)
93
139
  return { data, consumed: false };
94
- // Pi components share one keybinding manager. Restore bare A1's aliases
140
+ // Compatibility: Pi components share one keybinding manager. Restore bare A1's aliases
95
141
  // before the focused vanilla editor handles this input.
96
142
  this.#editor.activateKeybindings();
97
- // Handle the physical paste chord at the pre-input boundary. Windows
143
+ // Platform: handle the physical paste chord at the pre-input boundary. Windows
98
144
  // terminals vary between forwarding Ctrl+V and terminal-owned bracketed paste.
99
145
  if (this.#editor.matchesTerminalKey(data, "ctrl+v") && this.#editor.pasteClipboard()) {
100
146
  return { data: "", consumed: true };
101
147
  }
102
- // URL chip deletion must overwrite terminal link cells in the same frame.
148
+ // Platform: URL chip deletion must overwrite terminal link cells in the same frame.
103
149
  if (EDITOR_LINK_DELETION_INPUTS.has(data) && this.#hasEditorLinks())
104
150
  this.#requestRender(true);
105
151
  if (this.#viewport.frame === null)
@@ -160,6 +206,16 @@ export class SessionViewportController {
160
206
  const overBottom = hits.bottom !== null && event.row === hits.bottom.row
161
207
  && event.column >= hits.bottom.columnStart && event.column <= hits.bottom.columnEnd;
162
208
  if (event.kind === "motion") {
209
+ this.#pointerPosition = { column: event.column, row: event.row };
210
+ if (!this.#viewport.selectionActive && !this.#editorPointerSelecting) {
211
+ const nextHyperlink = this.#hyperlinkKeyAt(frame, event.column, event.row);
212
+ if (this.#hoveredHyperlinkKey !== undefined && nextHyperlink !== this.#hoveredHyperlinkKey) {
213
+ // Platform: a forced redraw overwrites Windows Terminal's cached solid hover
214
+ // underline exactly once when the pointer leaves or changes links.
215
+ forceRepaint = true;
216
+ }
217
+ this.#hoveredHyperlinkKey = nextHyperlink;
218
+ }
163
219
  this.#viewport.setRailHovered(overRail);
164
220
  this.#viewport.setStickyHovered(overSticky);
165
221
  this.#viewport.setBottomHovered(overBottom);
@@ -282,7 +338,7 @@ export class SessionViewportController {
282
338
  }
283
339
  if (this.#viewport.releaseSelection()) {
284
340
  this.#stopSelectionAutoScroll();
285
- // Restore OSC 8 links only after the held-button selection paint has
341
+ // Platform: restore OSC 8 links only after the held-button selection paint has
286
342
  // ended, then overwrite any terminal-cached hover cells immediately.
287
343
  forceRepaint = true;
288
344
  repaint = true;
@@ -303,10 +359,18 @@ export class SessionViewportController {
303
359
  });
304
360
  if (activity)
305
361
  this.#scheduleActivityExpiry();
306
- if (repaint)
362
+ if (repaint) {
363
+ this.#lastRequestedSelectionRevision = this.#viewport.selectionRevision;
307
364
  this.#requestRender(forceRepaint);
365
+ }
308
366
  return routed;
309
367
  }
368
+ #hyperlinkKeyAt(frame, column, row) {
369
+ if (row < 1 || row > frame.rows.length || column < 1)
370
+ return undefined;
371
+ const target = hyperlinkTargetAtColumn(frame.rows[row - 1] ?? "", column - 1);
372
+ return target === undefined ? undefined : `${row}\u0000${target}`;
373
+ }
310
374
  #updateSelectionAutoScroll(column, row, viewportHeight) {
311
375
  const beyondEdge = row <= 1 || row > viewportHeight;
312
376
  if (!beyondEdge) {
@@ -338,6 +402,7 @@ export class SessionViewportController {
338
402
  this.#stopSelectionAutoScroll();
339
403
  return;
340
404
  }
405
+ this.#lastRequestedSelectionRevision = this.#viewport.selectionRevision;
341
406
  this.#requestRender();
342
407
  this.#scheduleActivityExpiry();
343
408
  this.#selectionAutoScrollTimer = setTimeout(() => this.#selectionAutoScrollTick(), SELECTION_AUTO_SCROLL_INTERVAL_MS);
@@ -0,0 +1,29 @@
1
+ /**
2
+ * 33 ms (30.3 fps): baseline chunks arrive every 5 ms, while this cadence
3
+ * retains immediate first paint/input and explicit final-state flushing.
4
+ */
5
+ export declare const STREAM_PRESENTATION_INTERVAL_MS = 33;
6
+ export interface StreamPresentationScheduler {
7
+ now(): number;
8
+ setTimeout(callback: () => void, delayMs: number): ReturnType<typeof setTimeout>;
9
+ clearTimeout(timer: ReturnType<typeof setTimeout>): void;
10
+ }
11
+ /** Bounds terminal presentation requests while semantic root state remains current. */
12
+ export declare class StreamPresentationCoalescer {
13
+ #private;
14
+ readonly present: () => void;
15
+ readonly intervalMs: number;
16
+ readonly scheduler: StreamPresentationScheduler;
17
+ constructor(present: () => void, intervalMs?: number, scheduler?: StreamPresentationScheduler);
18
+ get pending(): boolean;
19
+ request(): void;
20
+ /** Cancels a superseded stream frame; the immediate input/overlay/resize path renders current state. */
21
+ preempt(): void;
22
+ /** Records an immediate input/status/overlay frame and cancels an obsolete stream timer. */
23
+ noteImmediatePresentation(): void;
24
+ /** Presents current semantic state immediately regardless of pending stream work. */
25
+ presentImmediate(): void;
26
+ /** Presents the newest semantic state immediately only when a stream frame is pending. */
27
+ flush(): void;
28
+ dispose(): void;
29
+ }
@@ -0,0 +1,79 @@
1
+ /**
2
+ * 33 ms (30.3 fps): baseline chunks arrive every 5 ms, while this cadence
3
+ * retains immediate first paint/input and explicit final-state flushing.
4
+ */
5
+ export const STREAM_PRESENTATION_INTERVAL_MS = 33;
6
+ const SYSTEM_SCHEDULER = {
7
+ now: Date.now,
8
+ setTimeout: (callback, delayMs) => setTimeout(callback, delayMs),
9
+ clearTimeout: timer => clearTimeout(timer),
10
+ };
11
+ /** Bounds terminal presentation requests while semantic root state remains current. */
12
+ export class StreamPresentationCoalescer {
13
+ present;
14
+ intervalMs;
15
+ scheduler;
16
+ #timer;
17
+ #lastPresentationAt = Number.NEGATIVE_INFINITY;
18
+ #disposed = false;
19
+ constructor(present, intervalMs = STREAM_PRESENTATION_INTERVAL_MS, scheduler = SYSTEM_SCHEDULER) {
20
+ this.present = present;
21
+ this.intervalMs = intervalMs;
22
+ this.scheduler = scheduler;
23
+ if (!Number.isSafeInteger(intervalMs) || intervalMs < 1)
24
+ throw new RangeError("stream presentation interval must be positive");
25
+ }
26
+ get pending() { return this.#timer !== undefined; }
27
+ request() {
28
+ if (this.#disposed || this.#timer !== undefined)
29
+ return;
30
+ const elapsed = this.scheduler.now() - this.#lastPresentationAt;
31
+ if (elapsed >= this.intervalMs) {
32
+ this.#presentNow();
33
+ return;
34
+ }
35
+ this.#timer = this.scheduler.setTimeout(() => {
36
+ this.#timer = undefined;
37
+ this.#presentNow();
38
+ }, Math.max(0, this.intervalMs - elapsed));
39
+ }
40
+ /** Cancels a superseded stream frame; the immediate input/overlay/resize path renders current state. */
41
+ preempt() {
42
+ if (this.#timer === undefined)
43
+ return;
44
+ this.scheduler.clearTimeout(this.#timer);
45
+ this.#timer = undefined;
46
+ }
47
+ /** Records an immediate input/status/overlay frame and cancels an obsolete stream timer. */
48
+ noteImmediatePresentation() {
49
+ if (this.#disposed)
50
+ return;
51
+ this.preempt();
52
+ this.#lastPresentationAt = this.scheduler.now();
53
+ }
54
+ /** Presents current semantic state immediately regardless of pending stream work. */
55
+ presentImmediate() {
56
+ if (this.#disposed)
57
+ return;
58
+ this.noteImmediatePresentation();
59
+ this.present();
60
+ }
61
+ /** Presents the newest semantic state immediately only when a stream frame is pending. */
62
+ flush() {
63
+ if (this.#disposed || this.#timer === undefined)
64
+ return;
65
+ this.scheduler.clearTimeout(this.#timer);
66
+ this.#timer = undefined;
67
+ this.#presentNow();
68
+ }
69
+ dispose() {
70
+ this.preempt();
71
+ this.#disposed = true;
72
+ }
73
+ #presentNow() {
74
+ if (this.#disposed)
75
+ return;
76
+ this.#lastPresentationAt = this.scheduler.now();
77
+ this.present();
78
+ }
79
+ }
@@ -1,4 +1,9 @@
1
1
  import type { PiShellClipboardContent } from "../components/index.js";
2
+ export interface SystemClipboardImageReader {
3
+ hasImage(): boolean;
4
+ getImageBinary?(): Promise<readonly number[]>;
5
+ getImageBase64?(): Promise<string>;
6
+ }
2
7
  /** Load the native adapter during shell startup so the first paste is warm. */
3
8
  export declare function preloadSystemClipboard(): void;
4
9
  /**
@@ -7,5 +12,9 @@ export declare function preloadSystemClipboard(): void;
7
12
  * unavailable clipboard simply makes the paste action a no-op.
8
13
  */
9
14
  export declare function readSystemClipboardContent(): Promise<PiShellClipboardContent | null>;
15
+ export declare function readSystemClipboardImage(reader: SystemClipboardImageReader): Promise<{
16
+ readonly data: string;
17
+ readonly mimeType: string;
18
+ } | null>;
10
19
  export declare function readSystemClipboardText(): Promise<string | null>;
11
20
  export declare function writeSystemClipboardText(text: string): Promise<void>;
@@ -1,4 +1,5 @@
1
1
  import { execFile } from "node:child_process";
2
+ import { canonicalizeClipboardImage } from "./clipboard-image.js";
2
3
  const MAX_CLIPBOARD_BYTES = 16 * 1024 * 1024;
3
4
  let nativeClipboardPromise;
4
5
  let pendingClipboardWrite = Promise.resolve();
@@ -22,21 +23,49 @@ export async function readSystemClipboardContent() {
22
23
  if (files !== null)
23
24
  return { kind: "text", text: files };
24
25
  }
25
- if (native.hasImage()) {
26
- const data = await native.getImageBase64();
27
- if (data.length > 0)
28
- return { kind: "image", data, mimeType: "image/png" };
29
- }
26
+ const image = await readSystemClipboardImage(native);
27
+ if (image !== null)
28
+ return { kind: "image", ...image };
30
29
  }
31
30
  catch {
32
- // Fall through to text and platform readers.
31
+ // Compatibility: fall through to text and platform readers.
33
32
  }
34
33
  }
35
34
  const text = await readSystemClipboardText();
36
35
  return text === null ? null : { kind: "text", text };
37
36
  }
37
+ export async function readSystemClipboardImage(reader) {
38
+ if (!reader.hasImage())
39
+ return null;
40
+ if (reader.getImageBinary !== undefined) {
41
+ try {
42
+ const bytes = await reader.getImageBinary();
43
+ if (bytes.length > 0 && bytes.every(byte => Number.isInteger(byte) && byte >= 0 && byte <= 255)) {
44
+ return canonicalizeClipboardImage({
45
+ data: Buffer.from(bytes).toString("base64"),
46
+ mimeType: "image/png",
47
+ });
48
+ }
49
+ }
50
+ catch {
51
+ // Compatibility: older or partially available native bindings may still expose base64.
52
+ }
53
+ }
54
+ if (reader.getImageBase64 !== undefined) {
55
+ try {
56
+ return canonicalizeClipboardImage({
57
+ data: await reader.getImageBase64(),
58
+ mimeType: "image/png",
59
+ });
60
+ }
61
+ catch {
62
+ // Compatibility: fall through to the caller's text path.
63
+ }
64
+ }
65
+ return null;
66
+ }
38
67
  export async function readSystemClipboardText() {
39
- // Ctrl+C/Ctrl+X and Ctrl+V can arrive in adjacent input turns. Serialize the
68
+ // Concurrency: Ctrl+C/Ctrl+X and Ctrl+V can arrive in adjacent input turns. Serialize the
40
69
  // read behind our native write so paste never observes the previous value.
41
70
  await pendingClipboardWrite;
42
71
  if (process.platform === "win32" || process.platform === "darwin") {
@@ -48,7 +77,7 @@ export async function readSystemClipboardText() {
48
77
  return text;
49
78
  }
50
79
  catch {
51
- // Fall through to the platform command.
80
+ // Compatibility: fall through to the platform command.
52
81
  }
53
82
  }
54
83
  }
@@ -74,7 +103,7 @@ export function writeSystemClipboardText(text) {
74
103
  return;
75
104
  }
76
105
  catch {
77
- // The caller still emits its terminal clipboard fallback.
106
+ // Compatibility: the caller still emits its terminal clipboard fallback.
78
107
  }
79
108
  }
80
109
  };
@@ -1,9 +1,11 @@
1
1
  import type { PiTuiComponentPort, PiTuiInputListener, PiTuiOverlayHandle, PiTuiOverlayOptions, PiTuiPreInputListener, PiTuiRuntimeAdapterOptions, PiTuiRuntimeState, PiTuiScrollState, PiTuiStopOptions, PiTuiViewport } from "./contracts.js";
2
2
  export type PiTuiRuntimeErrorStage = "construction" | "start" | "input-drain" | "restoration";
3
+ /** Identifies the Pi TUI lifecycle stage that failed while preserving the original cause. */
3
4
  export declare class PiTuiRuntimeError extends Error {
4
5
  readonly stage: PiTuiRuntimeErrorStage;
5
6
  constructor(stage: PiTuiRuntimeErrorStage, cause: unknown);
6
7
  }
8
+ /** Bridges neutral presentation ports to one Pi TUI instance and owns terminal restoration on stop. */
7
9
  export declare class PiTuiRuntimeAdapter {
8
10
  #private;
9
11
  constructor(options: PiTuiRuntimeAdapterOptions);
@@ -31,7 +33,11 @@ export declare class PiTuiRuntimeAdapter {
31
33
  switchMode(mode: "regular" | "fullscreen"): boolean;
32
34
  setHardwareCursor(enabled: boolean): void;
33
35
  getHardwareCursor(): boolean;
36
+ setClearOnShrink(enabled: boolean): void;
37
+ getClearOnShrink(): boolean;
38
+ setTerminalProgress(active: boolean): void;
34
39
  setTitle(title: string): void;
40
+ writeAfterStop(text: string): void;
35
41
  invalidate(): void;
36
42
  requestRender(force?: boolean): void;
37
43
  renderNow(force?: boolean): void;