@timurproko/a1 0.1.8-dev.182 → 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.
- package/bin/cli.js +2 -2
- package/bin/sync-pi-tui-proxy.js +1 -1
- package/bin/ui.js +1 -1
- package/dist/cli/dispatch.js +3 -3
- package/dist/cli/packages.js +1 -1
- package/dist/cli/version-stats.js +3 -3
- package/dist/composition/owned-ui.js +1 -1
- package/dist/composition/settings-route-host.js +6 -4
- package/dist/features/launch/development-launch.js +1 -1
- package/dist/features/owned-ui/customization.d.ts +2 -0
- package/dist/features/owned-ui/customization.js +2 -0
- package/dist/features/owned-ui/diagnostics.d.ts +1 -0
- package/dist/features/owned-ui/diagnostics.js +1 -0
- package/dist/features/owned-ui/project-trust-prompt.d.ts +11 -6
- package/dist/features/owned-ui/project-trust-prompt.js +134 -17
- package/dist/features/owned-ui/settings-app.js +47 -50
- package/dist/features/workspace/reducer.d.ts +1 -0
- package/dist/features/workspace/reducer.js +1 -0
- package/dist/features/workspace/router.d.ts +1 -0
- package/dist/features/workspace/router.js +1 -0
- package/dist/features/workspace/store.d.ts +1 -0
- package/dist/features/workspace/store.js +1 -0
- package/dist/foundation/launch-guardian/main.js +1 -1
- package/dist/foundation/native-host-protocol/codec.d.ts +2 -0
- package/dist/foundation/native-host-protocol/codec.js +2 -0
- package/dist/foundation/process-containment/linux-process-inspector.d.ts +1 -0
- package/dist/foundation/process-containment/linux-process-inspector.js +1 -0
- package/dist/foundation/process-containment/native-guardian-containment.d.ts +1 -0
- package/dist/foundation/process-containment/native-guardian-containment.js +1 -0
- package/dist/foundation/process-containment/windows-process-inspector.d.ts +1 -0
- package/dist/foundation/process-containment/windows-process-inspector.js +1 -0
- package/dist/foundation/protocol/client.d.ts +1 -0
- package/dist/foundation/protocol/client.js +1 -0
- package/dist/foundation/protocol/messages.d.ts +1 -0
- package/dist/foundation/protocol/messages.js +1 -0
- package/dist/foundation/release/bootstrap.js +6 -6
- package/dist/foundation/release/cohort-state.d.ts +1 -0
- package/dist/foundation/release/cohort-state.js +1 -0
- package/dist/foundation/release/release-gc.js +1 -1
- package/dist/foundation/release/release.js +1 -1
- package/dist/foundation/release/update-transaction.d.ts +1 -0
- package/dist/foundation/release/update-transaction.js +1 -0
- package/dist/foundation/release/update.js +12 -12
- package/dist/foundation/storage/control-store.d.ts +1 -0
- package/dist/foundation/storage/control-store.js +1 -0
- package/dist/foundation/structured-agent-runtime/backpressure.d.ts +1 -0
- package/dist/foundation/structured-agent-runtime/backpressure.js +1 -0
- package/dist/foundation/structured-agent-runtime/commands.d.ts +1 -0
- package/dist/foundation/structured-agent-runtime/commands.js +1 -0
- package/dist/foundation/structured-agent-runtime/reconnection.d.ts +1 -0
- package/dist/foundation/structured-agent-runtime/reconnection.js +1 -0
- package/dist/foundation/structured-agent-runtime/state.d.ts +1 -0
- package/dist/foundation/structured-agent-runtime/state.js +1 -0
- package/dist/foundation/supervision/main.js +1 -1
- package/dist/foundation/supervision/server.d.ts +1 -0
- package/dist/foundation/supervision/server.js +4 -6
- package/dist/integrations/pi/components/conformance.d.ts +1 -0
- package/dist/integrations/pi/components/conformance.js +1 -0
- package/dist/integrations/pi/components/shell-footer-status.d.ts +1 -1
- package/dist/integrations/pi/components/shell-footer-status.js +8 -7
- package/dist/integrations/pi/components/shell-shared-facade.js +1 -1
- package/dist/integrations/pi/engine/adapter.d.ts +7 -0
- package/dist/integrations/pi/engine/adapter.js +37 -35
- package/dist/integrations/pi/engine/conformance.d.ts +2 -0
- package/dist/integrations/pi/engine/conformance.js +2 -0
- package/dist/integrations/pi/engine/http-dispatcher.js +1 -1
- package/dist/integrations/pi/engine/model-auth-integration.d.ts +1 -0
- package/dist/integrations/pi/engine/model-auth-integration.js +1 -0
- package/dist/integrations/pi/engine/package-integration.js +1 -1
- package/dist/integrations/pi/engine/project-trust-preflight.js +1 -1
- package/dist/integrations/pi/engine/resource-extension-integration.d.ts +1 -0
- package/dist/integrations/pi/engine/resource-extension-integration.js +1 -0
- package/dist/integrations/pi/engine/session-integration.d.ts +1 -0
- package/dist/integrations/pi/engine/session-integration.js +2 -1
- package/dist/integrations/pi/engine/settings-effects.d.ts +13 -2
- package/dist/integrations/pi/engine/settings-effects.js +62 -42
- package/dist/integrations/pi/engine/settings-integration.js +4 -4
- package/dist/integrations/pi/engine/workflow-controllers.d.ts +1 -0
- package/dist/integrations/pi/engine/workflow-controllers.js +1 -0
- package/dist/integrations/pi/session-ui/index.d.ts +1 -0
- package/dist/integrations/pi/session-ui/index.js +1 -0
- package/dist/integrations/pi/session-ui/prompt-chips.js +1 -1
- package/dist/integrations/pi/session-ui/session-shell-root.d.ts +30 -2
- package/dist/integrations/pi/session-ui/session-shell-root.js +133 -72
- package/dist/integrations/pi/session-ui/session-shell.d.ts +9 -2
- package/dist/integrations/pi/session-ui/session-shell.js +112 -37
- package/dist/integrations/pi/session-ui/session-viewport-controller.d.ts +5 -0
- package/dist/integrations/pi/session-ui/session-viewport-controller.js +44 -11
- package/dist/integrations/pi/session-ui/stream-presentation-coalescer.d.ts +29 -0
- package/dist/integrations/pi/session-ui/stream-presentation-coalescer.js +79 -0
- package/dist/integrations/pi/session-ui/system-clipboard.js +6 -6
- package/dist/integrations/pi/tui-runtime/adapter.d.ts +2 -0
- package/dist/integrations/pi/tui-runtime/adapter.js +138 -8
- package/dist/integrations/pi/tui-runtime/contracts.d.ts +25 -0
- package/dist/integrations/pi/tui-runtime/damage-aware-terminal.d.ts +63 -0
- package/dist/integrations/pi/tui-runtime/damage-aware-terminal.js +240 -0
- package/dist/integrations/pi/tui-runtime/index.d.ts +2 -0
- package/dist/integrations/pi/tui-runtime/index.js +2 -0
- package/dist/integrations/pi/tui-runtime/input-presentation-coordinator.d.ts +39 -0
- package/dist/integrations/pi/tui-runtime/input-presentation-coordinator.js +139 -0
- package/dist/native/darwin-arm64/manifest.json +1 -1
- package/dist/native/linux-x64/manifest.json +1 -1
- package/dist/native/win32-x64/manifest.json +2 -2
- package/dist/native/win32-x64/process-guardian.exe +0 -0
- package/dist/ui/apps/host.js +5 -9
- package/dist/ui/apps/registry.d.ts +1 -0
- package/dist/ui/apps/registry.js +1 -0
- package/dist/ui/components/dialog-panel.js +5 -3
- package/dist/ui/components/frame.d.ts +1 -0
- package/dist/ui/components/frame.js +1 -0
- package/dist/ui/components/index.d.ts +1 -0
- package/dist/ui/components/index.js +1 -0
- package/dist/ui/components/line-input.d.ts +1 -6
- package/dist/ui/components/line-input.js +4 -9
- package/dist/ui/components/list-view.d.ts +0 -6
- package/dist/ui/components/list-view.js +14 -7
- package/dist/ui/components/progress-status.d.ts +2 -0
- package/dist/ui/components/progress-status.js +4 -0
- package/dist/ui/components/scrollbar.js +1 -1
- package/dist/ui/components/shortcuts.d.ts +1 -0
- package/dist/ui/components/shortcuts.js +1 -0
- package/dist/ui/components/spans.d.ts +0 -5
- package/dist/ui/components/spans.js +3 -8
- package/dist/ui/components/surface.d.ts +0 -5
- package/dist/ui/components/surface.js +1 -5
- package/dist/ui/components/text-selection.d.ts +10 -1
- package/dist/ui/components/text-selection.js +96 -29
- package/dist/ui/components/text.js +3 -3
- package/dist/ui/components/transcript-viewport.d.ts +42 -0
- package/dist/ui/components/transcript-viewport.js +287 -39
- package/dist/ui/settings/sections.d.ts +0 -2
- package/dist/ui/settings/sections.js +10 -16
- package/dist/ui/settings/session.js +5 -4
- package/dist/ui/settings/store.d.ts +1 -0
- package/dist/ui/settings/store.js +2 -1
- package/docs/architecture/code-documentation.md +77 -0
- package/docs/architecture/project-structure.md +1 -1
- package/docs/architecture/ui-reference-provenance.md +23 -3
- package/docs/ci-release-runbook.md +30 -4
- package/docs/manual-owned-ui-checkpoint.md +28 -1
- package/docs/manual-terminal-colour-check.md +4 -3
- package/package.json +7 -2
|
@@ -28,7 +28,7 @@ export async function readSystemClipboardContent() {
|
|
|
28
28
|
return { kind: "image", ...image };
|
|
29
29
|
}
|
|
30
30
|
catch {
|
|
31
|
-
//
|
|
31
|
+
// Compatibility: fall through to text and platform readers.
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
34
|
const text = await readSystemClipboardText();
|
|
@@ -48,7 +48,7 @@ export async function readSystemClipboardImage(reader) {
|
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
50
|
catch {
|
|
51
|
-
//
|
|
51
|
+
// Compatibility: older or partially available native bindings may still expose base64.
|
|
52
52
|
}
|
|
53
53
|
}
|
|
54
54
|
if (reader.getImageBase64 !== undefined) {
|
|
@@ -59,13 +59,13 @@ export async function readSystemClipboardImage(reader) {
|
|
|
59
59
|
});
|
|
60
60
|
}
|
|
61
61
|
catch {
|
|
62
|
-
//
|
|
62
|
+
// Compatibility: fall through to the caller's text path.
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
65
|
return null;
|
|
66
66
|
}
|
|
67
67
|
export async function readSystemClipboardText() {
|
|
68
|
-
// 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
|
|
69
69
|
// read behind our native write so paste never observes the previous value.
|
|
70
70
|
await pendingClipboardWrite;
|
|
71
71
|
if (process.platform === "win32" || process.platform === "darwin") {
|
|
@@ -77,7 +77,7 @@ export async function readSystemClipboardText() {
|
|
|
77
77
|
return text;
|
|
78
78
|
}
|
|
79
79
|
catch {
|
|
80
|
-
//
|
|
80
|
+
// Compatibility: fall through to the platform command.
|
|
81
81
|
}
|
|
82
82
|
}
|
|
83
83
|
}
|
|
@@ -103,7 +103,7 @@ export function writeSystemClipboardText(text) {
|
|
|
103
103
|
return;
|
|
104
104
|
}
|
|
105
105
|
catch {
|
|
106
|
-
//
|
|
106
|
+
// Compatibility: the caller still emits its terminal clipboard fallback.
|
|
107
107
|
}
|
|
108
108
|
}
|
|
109
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);
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { HStack, ProcessTerminal, ScrollView, TuiAltScreen, TuiMainScreen, VStack, visibleWidth, } from "#pi-tui";
|
|
2
|
+
import { InputPresentationCoordinator, } from "./input-presentation-coordinator.js";
|
|
3
|
+
/** Identifies the Pi TUI lifecycle stage that failed while preserving the original cause. */
|
|
2
4
|
export class PiTuiRuntimeError extends Error {
|
|
3
5
|
stage;
|
|
4
6
|
constructor(stage, cause) {
|
|
@@ -9,9 +11,11 @@ export class PiTuiRuntimeError extends Error {
|
|
|
9
11
|
}
|
|
10
12
|
class ComponentBridge {
|
|
11
13
|
port;
|
|
14
|
+
traceComposition;
|
|
12
15
|
#focused = false;
|
|
13
|
-
constructor(port) {
|
|
16
|
+
constructor(port, traceComposition) {
|
|
14
17
|
this.port = port;
|
|
18
|
+
this.traceComposition = traceComposition;
|
|
15
19
|
}
|
|
16
20
|
get focused() {
|
|
17
21
|
return this.#focused;
|
|
@@ -24,12 +28,18 @@ class ComponentBridge {
|
|
|
24
28
|
return this.port.wantsKeyRelease ?? false;
|
|
25
29
|
}
|
|
26
30
|
render(width) {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
+
this.traceComposition?.("composition-start");
|
|
32
|
+
try {
|
|
33
|
+
const lines = [...this.port.render(width)];
|
|
34
|
+
const overflow = lines.findIndex(line => visibleWidth(line) > width);
|
|
35
|
+
if (overflow >= 0) {
|
|
36
|
+
throw new RangeError(`Pi TUI component row ${overflow} exceeds available width ${width}`);
|
|
37
|
+
}
|
|
38
|
+
return lines;
|
|
39
|
+
}
|
|
40
|
+
finally {
|
|
41
|
+
this.traceComposition?.("composition-end");
|
|
31
42
|
}
|
|
32
|
-
return lines;
|
|
33
43
|
}
|
|
34
44
|
handleInput(data) {
|
|
35
45
|
this.port.handleInput?.(data);
|
|
@@ -79,6 +89,7 @@ class OverlayHandleBridge {
|
|
|
79
89
|
return !this.#disposed && this.handle.isFocused();
|
|
80
90
|
}
|
|
81
91
|
}
|
|
92
|
+
/** Bridges neutral presentation ports to one Pi TUI instance and owns terminal restoration on stop. */
|
|
82
93
|
export class PiTuiRuntimeAdapter {
|
|
83
94
|
#terminal;
|
|
84
95
|
#tuiTerminal;
|
|
@@ -92,8 +103,13 @@ export class PiTuiRuntimeAdapter {
|
|
|
92
103
|
#mountedComponents = new Set();
|
|
93
104
|
#scrollViews = new Map();
|
|
94
105
|
#overlayDisposers = new Set();
|
|
106
|
+
#overlayInputCoordination = new Map();
|
|
95
107
|
#inputListeners = new Map();
|
|
96
108
|
#preInputListeners = new Set();
|
|
109
|
+
#inputCoordinator;
|
|
110
|
+
#inputDiagnostics;
|
|
111
|
+
#diagnosticNow;
|
|
112
|
+
#inputSink;
|
|
97
113
|
#state = "idle";
|
|
98
114
|
#stopPromise;
|
|
99
115
|
#rootDisposed = false;
|
|
@@ -101,10 +117,39 @@ export class PiTuiRuntimeAdapter {
|
|
|
101
117
|
constructor(options) {
|
|
102
118
|
this.#root = options.root;
|
|
103
119
|
this.#terminal = options.terminal ?? new ProcessTerminal();
|
|
104
|
-
this.#
|
|
120
|
+
this.#inputDiagnostics = options.inputDiagnostics;
|
|
121
|
+
this.#diagnosticNow = options.inputDiagnostics?.now ?? (() => performance.now());
|
|
122
|
+
const tracedTerminal = options.inputDiagnostics === undefined
|
|
123
|
+
? this.#terminal
|
|
124
|
+
: diagnosticTerminal(this.#terminal, phase => this.#traceRuntimePhase(phase));
|
|
125
|
+
const decoratedTerminal = options.decorateTerminal?.(tracedTerminal) ?? tracedTerminal;
|
|
126
|
+
const coordination = options.inputCoordination ?? (options.inputDiagnostics === undefined
|
|
127
|
+
? undefined
|
|
128
|
+
: { classify: () => "barrier" });
|
|
129
|
+
this.#inputCoordinator = coordination === undefined
|
|
130
|
+
? undefined
|
|
131
|
+
: new InputPresentationCoordinator({
|
|
132
|
+
classify: data => coordination.classify(data, this.#focusedOverlayInputCoordination()),
|
|
133
|
+
deliver: data => {
|
|
134
|
+
const routed = this.#routePreInput(data);
|
|
135
|
+
if (routed.length > 0)
|
|
136
|
+
this.#inputSink?.(routed);
|
|
137
|
+
},
|
|
138
|
+
...(coordination.onReceipt === undefined ? {} : { onReceipt: coordination.onReceipt }),
|
|
139
|
+
...(coordination.scheduler === undefined ? {} : { scheduler: coordination.scheduler }),
|
|
140
|
+
now: this.#diagnosticNow,
|
|
141
|
+
...(options.inputDiagnostics === undefined ? {} : {
|
|
142
|
+
onTrace: event => this.#traceInputCoordination(event),
|
|
143
|
+
}),
|
|
144
|
+
});
|
|
145
|
+
this.#tuiTerminal = this.#inputCoordinator === undefined
|
|
146
|
+
? preInputTerminal(decoratedTerminal, data => this.#routePreInput(data))
|
|
147
|
+
: coordinatedInputTerminal(decoratedTerminal, this.#inputCoordinator, sink => { this.#inputSink = sink; });
|
|
105
148
|
this.#layoutRoot = options.layoutRoot;
|
|
106
149
|
this.#logDirectory = options.logDirectory;
|
|
107
|
-
this.#rootBridge = new ComponentBridge(options.root
|
|
150
|
+
this.#rootBridge = new ComponentBridge(options.root, options.inputDiagnostics === undefined
|
|
151
|
+
? undefined
|
|
152
|
+
: phase => this.#traceRuntimePhase(phase));
|
|
108
153
|
this.#bridges.set(options.root, this.#rootBridge);
|
|
109
154
|
this.#mountedComponents.add(options.root);
|
|
110
155
|
try {
|
|
@@ -224,10 +269,12 @@ export class PiTuiRuntimeAdapter {
|
|
|
224
269
|
return;
|
|
225
270
|
hidden = true;
|
|
226
271
|
this.#overlayDisposers.delete(dispose);
|
|
272
|
+
this.#overlayInputCoordination.delete(bridge);
|
|
227
273
|
this.#bridges.delete(component);
|
|
228
274
|
component.dispose?.();
|
|
229
275
|
};
|
|
230
276
|
this.#overlayDisposers.add(dispose);
|
|
277
|
+
this.#overlayInputCoordination.set(bridge, options?.inputCoordination ?? "opaque");
|
|
231
278
|
const handle = this.#tui.showOverlay(bridge, toOverlayOptions(options));
|
|
232
279
|
return new OverlayHandleBridge(handle, target => this.#bridgeFor(target), dispose);
|
|
233
280
|
}
|
|
@@ -355,6 +402,7 @@ export class PiTuiRuntimeAdapter {
|
|
|
355
402
|
}
|
|
356
403
|
async #stop(options) {
|
|
357
404
|
this.#state = "stopping";
|
|
405
|
+
this.#inputCoordinator?.flush();
|
|
358
406
|
let failure;
|
|
359
407
|
if (options.drainInput !== false) {
|
|
360
408
|
try {
|
|
@@ -398,6 +446,29 @@ export class PiTuiRuntimeAdapter {
|
|
|
398
446
|
}
|
|
399
447
|
return current;
|
|
400
448
|
}
|
|
449
|
+
#focusedOverlayInputCoordination() {
|
|
450
|
+
for (const [bridge, coordination] of this.#overlayInputCoordination) {
|
|
451
|
+
if (bridge.focused)
|
|
452
|
+
return coordination;
|
|
453
|
+
}
|
|
454
|
+
return undefined;
|
|
455
|
+
}
|
|
456
|
+
#traceInputCoordination(event) {
|
|
457
|
+
this.#inputDiagnostics?.onEvent(event);
|
|
458
|
+
}
|
|
459
|
+
#traceRuntimePhase(phase) {
|
|
460
|
+
if (this.#inputDiagnostics === undefined)
|
|
461
|
+
return;
|
|
462
|
+
const coordinator = this.#inputCoordinator;
|
|
463
|
+
this.#inputDiagnostics.onEvent({
|
|
464
|
+
phase,
|
|
465
|
+
revision: coordinator?.appliedRevision ?? 0,
|
|
466
|
+
atMs: this.#diagnosticNow(),
|
|
467
|
+
pendingDepth: coordinator?.pendingDepth ?? 0,
|
|
468
|
+
pendingPresentationDepth: coordinator?.pendingPresentation === true ? 1 : 0,
|
|
469
|
+
appliedRevision: coordinator?.appliedRevision ?? 0,
|
|
470
|
+
});
|
|
471
|
+
}
|
|
401
472
|
#mountTui(tui) {
|
|
402
473
|
tui.addChild(this.#rootBridge);
|
|
403
474
|
if (tui instanceof TuiAltScreen && this.#layoutRoot !== undefined) {
|
|
@@ -506,15 +577,74 @@ export class PiTuiRuntimeAdapter {
|
|
|
506
577
|
this.#mountedComponents.clear();
|
|
507
578
|
}
|
|
508
579
|
#disposeComponents() {
|
|
580
|
+
this.#inputCoordinator?.dispose(false);
|
|
581
|
+
this.#inputSink = undefined;
|
|
509
582
|
for (const remove of this.#inputListeners.values())
|
|
510
583
|
remove();
|
|
511
584
|
this.#inputListeners.clear();
|
|
512
585
|
this.#preInputListeners.clear();
|
|
513
586
|
for (const dispose of [...this.#overlayDisposers])
|
|
514
587
|
dispose();
|
|
588
|
+
this.#overlayInputCoordination.clear();
|
|
515
589
|
this.#disposeRoot();
|
|
516
590
|
}
|
|
517
591
|
}
|
|
592
|
+
function coordinatedInputTerminal(terminal, coordinator, setSink) {
|
|
593
|
+
return {
|
|
594
|
+
get columns() { return terminal.columns; },
|
|
595
|
+
get rows() { return terminal.rows; },
|
|
596
|
+
get kittyProtocolActive() { return terminal.kittyProtocolActive; },
|
|
597
|
+
start(onInput, onResize) {
|
|
598
|
+
setSink(onInput);
|
|
599
|
+
terminal.start(data => coordinator.accept(data), () => {
|
|
600
|
+
coordinator.flush();
|
|
601
|
+
onResize();
|
|
602
|
+
});
|
|
603
|
+
},
|
|
604
|
+
stop() {
|
|
605
|
+
coordinator.flush();
|
|
606
|
+
setSink(undefined);
|
|
607
|
+
terminal.stop();
|
|
608
|
+
},
|
|
609
|
+
drainInput: (maxMs, idleMs) => terminal.drainInput(maxMs, idleMs),
|
|
610
|
+
write: data => terminal.write(data),
|
|
611
|
+
moveBy: lines => terminal.moveBy(lines),
|
|
612
|
+
hideCursor: () => terminal.hideCursor(),
|
|
613
|
+
showCursor: () => terminal.showCursor(),
|
|
614
|
+
clearLine: () => terminal.clearLine(),
|
|
615
|
+
clearFromCursor: () => terminal.clearFromCursor(),
|
|
616
|
+
clearScreen: () => terminal.clearScreen(),
|
|
617
|
+
setTitle: title => terminal.setTitle(title),
|
|
618
|
+
setProgress: active => terminal.setProgress(active),
|
|
619
|
+
};
|
|
620
|
+
}
|
|
621
|
+
function diagnosticTerminal(terminal, trace) {
|
|
622
|
+
return {
|
|
623
|
+
get columns() { return terminal.columns; },
|
|
624
|
+
get rows() { return terminal.rows; },
|
|
625
|
+
get kittyProtocolActive() { return terminal.kittyProtocolActive; },
|
|
626
|
+
start: (onInput, onResize) => terminal.start(onInput, onResize),
|
|
627
|
+
stop: () => terminal.stop(),
|
|
628
|
+
drainInput: (maxMs, idleMs) => terminal.drainInput(maxMs, idleMs),
|
|
629
|
+
write(data) {
|
|
630
|
+
trace("write-start");
|
|
631
|
+
try {
|
|
632
|
+
terminal.write(data);
|
|
633
|
+
}
|
|
634
|
+
finally {
|
|
635
|
+
trace("write-end");
|
|
636
|
+
}
|
|
637
|
+
},
|
|
638
|
+
moveBy: lines => terminal.moveBy(lines),
|
|
639
|
+
hideCursor: () => terminal.hideCursor(),
|
|
640
|
+
showCursor: () => terminal.showCursor(),
|
|
641
|
+
clearLine: () => terminal.clearLine(),
|
|
642
|
+
clearFromCursor: () => terminal.clearFromCursor(),
|
|
643
|
+
clearScreen: () => terminal.clearScreen(),
|
|
644
|
+
setTitle: title => terminal.setTitle(title),
|
|
645
|
+
setProgress: active => terminal.setProgress(active),
|
|
646
|
+
};
|
|
647
|
+
}
|
|
518
648
|
function preInputTerminal(terminal, route) {
|
|
519
649
|
return {
|
|
520
650
|
get columns() { return terminal.columns; },
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { PresentationComponentPort } from "../../../contracts/presentation/index.js";
|
|
2
|
+
import type { PiTuiInputCoordinationDecision, PiTuiInputCoordinationScheduler, PiTuiInputSurfaceKind } from "./input-presentation-coordinator.js";
|
|
2
3
|
export type PiTuiRuntimeState = "idle" | "running" | "stopping" | "stopped" | "failed";
|
|
3
4
|
export interface PiTuiViewport {
|
|
4
5
|
readonly columns: number;
|
|
@@ -50,6 +51,8 @@ export interface PiTuiOverlayOptions {
|
|
|
50
51
|
readonly margin?: PiTuiOverlayMargin | number;
|
|
51
52
|
readonly visible?: (columns: number, rows: number) => boolean;
|
|
52
53
|
readonly nonCapturing?: boolean;
|
|
54
|
+
/** A1-owned input scheduling declaration; omitted extension overlays fail closed. */
|
|
55
|
+
readonly inputCoordination?: Exclude<PiTuiInputSurfaceKind, "editor">;
|
|
53
56
|
}
|
|
54
57
|
export interface PiTuiOverlayUnfocusOptions {
|
|
55
58
|
readonly target: PiTuiComponentPort | null;
|
|
@@ -98,11 +101,33 @@ export type PiTuiLayoutNode = {
|
|
|
98
101
|
readonly scrollbarStyle?: (text: string) => string;
|
|
99
102
|
readonly scrollbarHideDelayMs?: number;
|
|
100
103
|
};
|
|
104
|
+
export type PiTuiInputDiagnosticsPhase = "receipt" | "semantic-start" | "semantic-end" | "composition-start" | "composition-end" | "write-start" | "write-end";
|
|
105
|
+
export interface PiTuiInputDiagnosticsEvent {
|
|
106
|
+
readonly phase: PiTuiInputDiagnosticsPhase;
|
|
107
|
+
readonly revision: number;
|
|
108
|
+
readonly atMs: number;
|
|
109
|
+
readonly pendingDepth: number;
|
|
110
|
+
readonly pendingPresentationDepth: 0 | 1;
|
|
111
|
+
readonly appliedRevision: number;
|
|
112
|
+
}
|
|
101
113
|
export interface PiTuiRuntimeAdapterOptions {
|
|
102
114
|
readonly root: PiTuiComponentPort;
|
|
103
115
|
readonly mode?: "regular" | "fullscreen";
|
|
104
116
|
readonly layoutRoot?: PiTuiLayoutNode;
|
|
105
117
|
readonly terminal?: PiTuiTerminalPort;
|
|
118
|
+
/** A1-owned public-boundary decorator; comparison paths omit it. */
|
|
119
|
+
readonly decorateTerminal?: (terminal: PiTuiTerminalPort) => PiTuiTerminalPort;
|
|
120
|
+
/** Bare-custom-viewport scheduling policy; comparison paths omit it. */
|
|
121
|
+
readonly inputCoordination?: {
|
|
122
|
+
readonly classify: (data: string, focusedOverlay?: Exclude<PiTuiInputSurfaceKind, "editor">) => PiTuiInputCoordinationDecision;
|
|
123
|
+
readonly onReceipt?: () => void;
|
|
124
|
+
readonly scheduler?: PiTuiInputCoordinationScheduler;
|
|
125
|
+
};
|
|
126
|
+
/** Optional bounded evidence hook. Production composition omits it. */
|
|
127
|
+
readonly inputDiagnostics?: {
|
|
128
|
+
readonly onEvent: (event: PiTuiInputDiagnosticsEvent) => void;
|
|
129
|
+
readonly now?: () => number;
|
|
130
|
+
};
|
|
106
131
|
readonly hardwareCursor?: boolean;
|
|
107
132
|
readonly mouse?: boolean;
|
|
108
133
|
readonly wheelScrollLines?: number;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { PiTuiTerminalPort } from "./contracts.js";
|
|
2
|
+
export declare const PINNED_PI_TUI_DAMAGE_GRAMMAR = "@earendil-works/pi-tui@0.84.2:tui-alt-screen-one-write-v1";
|
|
3
|
+
export interface PiTuiDamageFrameDescriptor {
|
|
4
|
+
readonly frameId: number;
|
|
5
|
+
readonly width: number;
|
|
6
|
+
readonly height: number;
|
|
7
|
+
readonly transcript: {
|
|
8
|
+
readonly rowStart: number;
|
|
9
|
+
readonly rowEnd: number;
|
|
10
|
+
} | null;
|
|
11
|
+
readonly dock: {
|
|
12
|
+
readonly rowStart: number;
|
|
13
|
+
readonly rowEnd: number;
|
|
14
|
+
} | null;
|
|
15
|
+
readonly verticalShiftRows: number;
|
|
16
|
+
readonly safeVerticalShift: boolean;
|
|
17
|
+
readonly cause: string;
|
|
18
|
+
}
|
|
19
|
+
export interface PiTuiDamageFrameSafety {
|
|
20
|
+
readonly overlayActive: boolean;
|
|
21
|
+
readonly selectionActive: boolean;
|
|
22
|
+
readonly replacementSurfaceActive: boolean;
|
|
23
|
+
}
|
|
24
|
+
export type PiTuiDamageDecisionReason = "transformed" | "suppressed-redundant-clear" | "not-armed" | "unsafe-frame" | "stale-frame" | "unsupported-region-scroll" | "geometry-mismatch" | "grammar-mismatch" | "unsafe-terminal-content" | "incomplete-prior-frame" | "excessive-real-damage";
|
|
25
|
+
export interface PiTuiDamageDecision {
|
|
26
|
+
readonly frameId: number | null;
|
|
27
|
+
readonly transformed: boolean;
|
|
28
|
+
readonly reason: PiTuiDamageDecisionReason;
|
|
29
|
+
readonly shiftRows: number;
|
|
30
|
+
readonly paintedRows: readonly number[];
|
|
31
|
+
}
|
|
32
|
+
export interface DamageAwareTerminalOptions {
|
|
33
|
+
readonly regionalScroll: boolean;
|
|
34
|
+
readonly onResize?: () => void;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* A fail-closed adapter for one pinned Pi fullscreen write grammar. Semantic
|
|
38
|
+
* viewport metadata authorizes a shift; bytes are inspected only to validate
|
|
39
|
+
* and preserve Pi's complete row paint and cursor syntax.
|
|
40
|
+
*/
|
|
41
|
+
export declare class DamageAwareTerminalAdapter implements PiTuiTerminalPort {
|
|
42
|
+
#private;
|
|
43
|
+
readonly inner: PiTuiTerminalPort;
|
|
44
|
+
readonly options: DamageAwareTerminalOptions;
|
|
45
|
+
constructor(inner: PiTuiTerminalPort, options: DamageAwareTerminalOptions);
|
|
46
|
+
get columns(): number;
|
|
47
|
+
get rows(): number;
|
|
48
|
+
get kittyProtocolActive(): boolean;
|
|
49
|
+
get lastDecision(): PiTuiDamageDecision;
|
|
50
|
+
arm(descriptor: PiTuiDamageFrameDescriptor, safety: PiTuiDamageFrameSafety): void;
|
|
51
|
+
start(onInput: (data: string) => void, onResize: () => void): void;
|
|
52
|
+
stop(): void;
|
|
53
|
+
drainInput(maxMs?: number, idleMs?: number): Promise<void>;
|
|
54
|
+
write(data: string): void;
|
|
55
|
+
moveBy(lines: number): void;
|
|
56
|
+
hideCursor(): void;
|
|
57
|
+
showCursor(): void;
|
|
58
|
+
clearLine(): void;
|
|
59
|
+
clearFromCursor(): void;
|
|
60
|
+
clearScreen(): void;
|
|
61
|
+
setTitle(title: string): void;
|
|
62
|
+
setProgress(active: boolean): void;
|
|
63
|
+
}
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
export const PINNED_PI_TUI_DAMAGE_GRAMMAR = "@earendil-works/pi-tui@0.84.2:tui-alt-screen-one-write-v1";
|
|
2
|
+
const BEGIN_SYNCHRONIZED_OUTPUT = "\u001b[?2026h";
|
|
3
|
+
const END_SYNCHRONIZED_OUTPUT = "\u001b[?2026l";
|
|
4
|
+
const ROW_MARKER = /\u001b\[(\d+);1H\u001b\[2K/gu;
|
|
5
|
+
const CURSOR_SUFFIX = /\u001b\[(\d+);(\d+)H\u001b\[\?25[hl]\u001b\[\?2026l$/u;
|
|
6
|
+
const OSC8 = /\u001b\]8;[^;\u0007\u001b]*;([^\u0007\u001b]*)(?:\u0007|\u001b\\)/gu;
|
|
7
|
+
/**
|
|
8
|
+
* A fail-closed adapter for one pinned Pi fullscreen write grammar. Semantic
|
|
9
|
+
* viewport metadata authorizes a shift; bytes are inspected only to validate
|
|
10
|
+
* and preserve Pi's complete row paint and cursor syntax.
|
|
11
|
+
*/
|
|
12
|
+
export class DamageAwareTerminalAdapter {
|
|
13
|
+
inner;
|
|
14
|
+
options;
|
|
15
|
+
#rows = new Map();
|
|
16
|
+
#armed;
|
|
17
|
+
#lastConsumedFrameId = 0;
|
|
18
|
+
#cacheWidth = 0;
|
|
19
|
+
#cacheHeight = 0;
|
|
20
|
+
#decision = {
|
|
21
|
+
frameId: null,
|
|
22
|
+
transformed: false,
|
|
23
|
+
reason: "not-armed",
|
|
24
|
+
shiftRows: 0,
|
|
25
|
+
paintedRows: [],
|
|
26
|
+
};
|
|
27
|
+
constructor(inner, options) {
|
|
28
|
+
this.inner = inner;
|
|
29
|
+
this.options = options;
|
|
30
|
+
}
|
|
31
|
+
get columns() { return this.inner.columns; }
|
|
32
|
+
get rows() { return this.inner.rows; }
|
|
33
|
+
get kittyProtocolActive() { return this.inner.kittyProtocolActive; }
|
|
34
|
+
get lastDecision() { return this.#decision; }
|
|
35
|
+
arm(descriptor, safety) {
|
|
36
|
+
this.#armed = { descriptor, safety };
|
|
37
|
+
}
|
|
38
|
+
start(onInput, onResize) {
|
|
39
|
+
this.inner.start(onInput, () => {
|
|
40
|
+
this.#invalidateRows();
|
|
41
|
+
this.options.onResize?.();
|
|
42
|
+
onResize();
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
stop() {
|
|
46
|
+
this.#armed = undefined;
|
|
47
|
+
this.#invalidateRows();
|
|
48
|
+
this.inner.stop();
|
|
49
|
+
}
|
|
50
|
+
drainInput(maxMs, idleMs) { return this.inner.drainInput(maxMs, idleMs); }
|
|
51
|
+
write(data) {
|
|
52
|
+
const armed = this.#armed;
|
|
53
|
+
this.#armed = undefined;
|
|
54
|
+
const parsed = parsePinnedFullscreenWrite(data);
|
|
55
|
+
const decision = this.#decide(armed, parsed);
|
|
56
|
+
this.#decision = decision;
|
|
57
|
+
if (decision.reason === "suppressed-redundant-clear" && parsed !== null) {
|
|
58
|
+
this.inner.write(buildClearlessWrite(parsed));
|
|
59
|
+
}
|
|
60
|
+
else if (decision.transformed && armed !== undefined && parsed !== null) {
|
|
61
|
+
this.inner.write(buildDamageWrite(parsed, armed.descriptor, new Set(decision.paintedRows), this.#rows));
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
this.inner.write(data);
|
|
65
|
+
}
|
|
66
|
+
this.#rememberFrame(armed?.descriptor, parsed, data);
|
|
67
|
+
}
|
|
68
|
+
moveBy(lines) { this.inner.moveBy(lines); }
|
|
69
|
+
hideCursor() { this.inner.hideCursor(); }
|
|
70
|
+
showCursor() { this.inner.showCursor(); }
|
|
71
|
+
clearLine() { this.#invalidateRows(); this.inner.clearLine(); }
|
|
72
|
+
clearFromCursor() { this.#invalidateRows(); this.inner.clearFromCursor(); }
|
|
73
|
+
clearScreen() { this.#invalidateRows(); this.inner.clearScreen(); }
|
|
74
|
+
setTitle(title) { this.inner.setTitle(title); }
|
|
75
|
+
setProgress(active) { this.inner.setProgress(active); }
|
|
76
|
+
#decide(armed, parsed) {
|
|
77
|
+
if (armed === undefined) {
|
|
78
|
+
return { frameId: null, transformed: false, reason: "not-armed", shiftRows: 0, paintedRows: [] };
|
|
79
|
+
}
|
|
80
|
+
const descriptor = armed.descriptor;
|
|
81
|
+
const base = {
|
|
82
|
+
frameId: descriptor.frameId,
|
|
83
|
+
transformed: false,
|
|
84
|
+
shiftRows: descriptor.verticalShiftRows,
|
|
85
|
+
paintedRows: [],
|
|
86
|
+
};
|
|
87
|
+
if (descriptor.frameId <= this.#lastConsumedFrameId)
|
|
88
|
+
return { ...base, reason: "stale-frame" };
|
|
89
|
+
this.#lastConsumedFrameId = descriptor.frameId;
|
|
90
|
+
if (parsed !== null && parsed.structuralPrefix === "\u001b[2J"
|
|
91
|
+
&& this.#cacheWidth === descriptor.width && this.#cacheHeight === descriptor.height
|
|
92
|
+
&& hasCompleteScreenRows(parsed.rows, descriptor.height) && !hasUnsafeTerminalContent(parsed)) {
|
|
93
|
+
return {
|
|
94
|
+
frameId: descriptor.frameId,
|
|
95
|
+
transformed: true,
|
|
96
|
+
reason: "suppressed-redundant-clear",
|
|
97
|
+
shiftRows: 0,
|
|
98
|
+
paintedRows: parsed.rows.map(row => row.row),
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
if (!descriptor.safeVerticalShift || armed.safety.overlayActive || armed.safety.selectionActive
|
|
102
|
+
|| armed.safety.replacementSurfaceActive || descriptor.transcript === null) {
|
|
103
|
+
return { ...base, reason: "unsafe-frame" };
|
|
104
|
+
}
|
|
105
|
+
if (!this.options.regionalScroll)
|
|
106
|
+
return { ...base, reason: "unsupported-region-scroll" };
|
|
107
|
+
if (descriptor.width !== this.columns || descriptor.height !== this.rows
|
|
108
|
+
|| descriptor.verticalShiftRows < 1
|
|
109
|
+
|| descriptor.verticalShiftRows >= descriptor.transcript.rowEnd - descriptor.transcript.rowStart + 1) {
|
|
110
|
+
return { ...base, reason: "geometry-mismatch" };
|
|
111
|
+
}
|
|
112
|
+
if (parsed === null)
|
|
113
|
+
return { ...base, reason: "grammar-mismatch" };
|
|
114
|
+
if (parsed.structuralPrefix.length > 0 || hasUnsafeTerminalContent(parsed)) {
|
|
115
|
+
return { ...base, reason: "unsafe-terminal-content" };
|
|
116
|
+
}
|
|
117
|
+
const { rowStart, rowEnd } = descriptor.transcript;
|
|
118
|
+
const desired = new Map(parsed.rows.map(row => [row.row, row.content]));
|
|
119
|
+
for (let row = rowStart; row <= rowEnd; row += 1) {
|
|
120
|
+
if (!desired.has(row) && !this.#rows.has(row))
|
|
121
|
+
return { ...base, reason: "incomplete-prior-frame" };
|
|
122
|
+
if (row <= rowEnd - descriptor.verticalShiftRows && !this.#rows.has(row + descriptor.verticalShiftRows)) {
|
|
123
|
+
return { ...base, reason: "incomplete-prior-frame" };
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
for (const row of parsed.rows) {
|
|
127
|
+
const inTranscript = row.row >= rowStart && row.row <= rowEnd;
|
|
128
|
+
const inDock = descriptor.dock !== null && row.row >= descriptor.dock.rowStart && row.row <= descriptor.dock.rowEnd;
|
|
129
|
+
if (!inTranscript && !inDock)
|
|
130
|
+
return { ...base, reason: "grammar-mismatch" };
|
|
131
|
+
}
|
|
132
|
+
const painted = new Set();
|
|
133
|
+
for (let row = rowStart; row <= rowEnd; row += 1) {
|
|
134
|
+
const source = row + descriptor.verticalShiftRows;
|
|
135
|
+
const desiredContent = desired.get(row) ?? this.#rows.get(row);
|
|
136
|
+
if (source > rowEnd || desiredContent !== this.#rows.get(source))
|
|
137
|
+
painted.add(row);
|
|
138
|
+
}
|
|
139
|
+
for (const row of parsed.rows) {
|
|
140
|
+
if (descriptor.dock !== null && row.row >= descriptor.dock.rowStart && row.row <= descriptor.dock.rowEnd)
|
|
141
|
+
painted.add(row.row);
|
|
142
|
+
}
|
|
143
|
+
// Rationale: ordinary followed prose may change one active tail source row, expose
|
|
144
|
+
// the shifted suffix, and restyle one sticky boundary row. Larger changes indicate
|
|
145
|
+
// Markdown/theme/overlay reflow and fail closed.
|
|
146
|
+
if (painted.size > descriptor.verticalShiftRows + 2 + countDockRows(parsed.rows, descriptor.dock)) {
|
|
147
|
+
return { ...base, reason: "excessive-real-damage" };
|
|
148
|
+
}
|
|
149
|
+
return {
|
|
150
|
+
frameId: descriptor.frameId,
|
|
151
|
+
transformed: true,
|
|
152
|
+
reason: "transformed",
|
|
153
|
+
shiftRows: descriptor.verticalShiftRows,
|
|
154
|
+
paintedRows: [...painted].sort((left, right) => left - right),
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
#rememberFrame(descriptor, parsed, raw) {
|
|
158
|
+
if (raw.includes("\u001b[2J") || raw.includes("\u001b[?1049") || raw.includes("\u001b_G") || raw.includes("\u001bPq")) {
|
|
159
|
+
this.#invalidateRows();
|
|
160
|
+
}
|
|
161
|
+
if (parsed === null)
|
|
162
|
+
return;
|
|
163
|
+
if (descriptor !== undefined && (descriptor.width !== this.#cacheWidth || descriptor.height !== this.#cacheHeight)) {
|
|
164
|
+
this.#invalidateRows();
|
|
165
|
+
this.#cacheWidth = descriptor.width;
|
|
166
|
+
this.#cacheHeight = descriptor.height;
|
|
167
|
+
}
|
|
168
|
+
for (const row of parsed.rows)
|
|
169
|
+
this.#rows.set(row.row, row.content);
|
|
170
|
+
}
|
|
171
|
+
#invalidateRows() {
|
|
172
|
+
this.#rows.clear();
|
|
173
|
+
this.#cacheWidth = 0;
|
|
174
|
+
this.#cacheHeight = 0;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
function parsePinnedFullscreenWrite(data) {
|
|
178
|
+
if (!data.startsWith(BEGIN_SYNCHRONIZED_OUTPUT) || !data.endsWith(END_SYNCHRONIZED_OUTPUT))
|
|
179
|
+
return null;
|
|
180
|
+
const suffix = data.match(CURSOR_SUFFIX);
|
|
181
|
+
if (suffix?.index === undefined)
|
|
182
|
+
return null;
|
|
183
|
+
const body = data.slice(BEGIN_SYNCHRONIZED_OUTPUT.length, suffix.index);
|
|
184
|
+
const matches = [...body.matchAll(ROW_MARKER)];
|
|
185
|
+
if (matches.length === 0)
|
|
186
|
+
return null;
|
|
187
|
+
const structuralPrefix = body.slice(0, matches[0].index);
|
|
188
|
+
if (structuralPrefix !== "" && structuralPrefix !== "\u001b[2J")
|
|
189
|
+
return null;
|
|
190
|
+
const rows = [];
|
|
191
|
+
let previousRow = 0;
|
|
192
|
+
for (let index = 0; index < matches.length; index += 1) {
|
|
193
|
+
const match = matches[index];
|
|
194
|
+
const row = Number.parseInt(match[1], 10);
|
|
195
|
+
if (!Number.isSafeInteger(row) || row < 1 || row <= previousRow)
|
|
196
|
+
return null;
|
|
197
|
+
previousRow = row;
|
|
198
|
+
const start = match.index;
|
|
199
|
+
const contentStart = start + match[0].length;
|
|
200
|
+
const end = matches[index + 1]?.index ?? body.length;
|
|
201
|
+
const content = body.slice(contentStart, end);
|
|
202
|
+
rows.push({ row, content, segment: body.slice(start, end) });
|
|
203
|
+
}
|
|
204
|
+
return { structuralPrefix, rows, cursorSuffix: data.slice(suffix.index) };
|
|
205
|
+
}
|
|
206
|
+
function hasUnsafeTerminalContent(parsed) {
|
|
207
|
+
const data = parsed.rows.map(row => row.content).join("");
|
|
208
|
+
if (data.includes("\u001b_G") || data.includes("\u001bPq") || data.includes("\u001b[2J"))
|
|
209
|
+
return true;
|
|
210
|
+
for (const match of data.matchAll(OSC8))
|
|
211
|
+
if ((match[1] ?? "").length > 0)
|
|
212
|
+
return true;
|
|
213
|
+
return false;
|
|
214
|
+
}
|
|
215
|
+
function buildDamageWrite(parsed, descriptor, paintedRows, previousRows) {
|
|
216
|
+
const transcript = descriptor.transcript;
|
|
217
|
+
const regionShift = `\u001b[${transcript.rowStart};${transcript.rowEnd}r`
|
|
218
|
+
+ `\u001b[${transcript.rowStart};1H`
|
|
219
|
+
+ `\u001b[${descriptor.verticalShiftRows}S`
|
|
220
|
+
+ "\u001b[r";
|
|
221
|
+
const parsedRows = new Map(parsed.rows.map(row => [row.row, row]));
|
|
222
|
+
const rowPaint = [...paintedRows].sort((left, right) => left - right).map(row => {
|
|
223
|
+
const parsedRow = parsedRows.get(row);
|
|
224
|
+
if (parsedRow !== undefined)
|
|
225
|
+
return parsedRow.segment;
|
|
226
|
+
return `\u001b[${row};1H\u001b[2K${previousRows.get(row) ?? ""}`;
|
|
227
|
+
}).join("");
|
|
228
|
+
return `${BEGIN_SYNCHRONIZED_OUTPUT}${regionShift}${rowPaint}${parsed.cursorSuffix}`;
|
|
229
|
+
}
|
|
230
|
+
function buildClearlessWrite(parsed) {
|
|
231
|
+
return `${BEGIN_SYNCHRONIZED_OUTPUT}${parsed.rows.map(row => row.segment).join("")}${parsed.cursorSuffix}`;
|
|
232
|
+
}
|
|
233
|
+
function hasCompleteScreenRows(rows, height) {
|
|
234
|
+
return rows.length === height && rows.every((row, index) => row.row === index + 1);
|
|
235
|
+
}
|
|
236
|
+
function countDockRows(rows, dock) {
|
|
237
|
+
if (dock === null)
|
|
238
|
+
return 0;
|
|
239
|
+
return rows.filter(row => row.row >= dock.rowStart && row.row <= dock.rowEnd).length;
|
|
240
|
+
}
|