@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.
- package/README.md +32 -0
- package/bin/cli.js +2 -2
- package/bin/sync-pi-tui-proxy.js +1 -1
- package/bin/ui.js +18 -3
- 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.d.ts +4 -1
- package/dist/composition/owned-ui.js +9 -6
- package/dist/composition/settings-route-host.js +6 -4
- package/dist/contracts/agent-engine/capability-ports.d.ts +2 -2
- package/dist/contracts/agent-engine/domain-validation.js +22 -4
- package/dist/contracts/agent-engine/domain.d.ts +17 -0
- package/dist/contracts/owned-ui/model.d.ts +8 -0
- package/dist/contracts/owned-ui/validation.js +11 -0
- 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/index.d.ts +1 -0
- package/dist/features/owned-ui/index.js +1 -0
- package/dist/features/owned-ui/project-trust-prompt.d.ts +25 -0
- package/dist/features/owned-ui/project-trust-prompt.js +149 -0
- package/dist/features/owned-ui/settings-app.js +69 -44
- 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-components.d.ts +1 -0
- package/dist/integrations/pi/components/shell-components.js +1 -0
- package/dist/integrations/pi/components/shell-editor-autocomplete.js +10 -0
- package/dist/integrations/pi/components/shell-footer-status.d.ts +1 -1
- package/dist/integrations/pi/components/shell-footer-status.js +19 -13
- package/dist/integrations/pi/components/shell-presenters-info.d.ts +36 -0
- package/dist/integrations/pi/components/shell-presenters-info.js +78 -0
- package/dist/integrations/pi/components/shell-presenters-transcript.d.ts +3 -36
- package/dist/integrations/pi/components/shell-presenters-transcript.js +88 -128
- package/dist/integrations/pi/components/shell-shared-facade.d.ts +11 -1
- package/dist/integrations/pi/components/shell-shared-facade.js +1 -1
- package/dist/integrations/pi/engine/adapter.d.ts +22 -4
- package/dist/integrations/pi/engine/adapter.js +220 -70
- 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.d.ts +4 -0
- package/dist/integrations/pi/engine/http-dispatcher.js +25 -0
- package/dist/integrations/pi/engine/index.d.ts +3 -0
- package/dist/integrations/pi/engine/index.js +3 -0
- 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.d.ts +22 -0
- package/dist/integrations/pi/engine/project-trust-preflight.js +50 -0
- 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/runtime-integration.d.ts +16 -1
- package/dist/integrations/pi/engine/runtime-integration.js +34 -4
- 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 +66 -0
- package/dist/integrations/pi/engine/settings-effects.js +249 -0
- package/dist/integrations/pi/engine/settings-integration.d.ts +14 -26
- package/dist/integrations/pi/engine/settings-integration.js +102 -108
- package/dist/integrations/pi/engine/workflow-controllers.d.ts +2 -1
- package/dist/integrations/pi/engine/workflow-controllers.js +1 -0
- package/dist/integrations/pi/session-ui/clipboard-image.d.ts +11 -0
- package/dist/integrations/pi/session-ui/clipboard-image.js +29 -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 +6 -2
- package/dist/integrations/pi/session-ui/session-shell-root.d.ts +39 -4
- package/dist/integrations/pi/session-ui/session-shell-root.js +215 -80
- package/dist/integrations/pi/session-ui/session-shell.d.ts +10 -1
- package/dist/integrations/pi/session-ui/session-shell.js +256 -45
- package/dist/integrations/pi/session-ui/session-viewport-controller.d.ts +5 -0
- package/dist/integrations/pi/session-ui/session-viewport-controller.js +76 -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.d.ts +9 -0
- package/dist/integrations/pi/session-ui/system-clipboard.js +38 -9
- package/dist/integrations/pi/tui-runtime/adapter.d.ts +6 -0
- package/dist/integrations/pi/tui-runtime/adapter.js +166 -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 +2 -5
- package/dist/ui/components/spans.js +28 -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 +6 -19
- package/dist/ui/settings/sections.js +17 -15
- package/dist/ui/settings/session.d.ts +8 -18
- package/dist/ui/settings/session.js +60 -52
- 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 +15 -1
- package/docs/architecture/ui-reference-provenance.md +23 -3
- package/docs/ci-release-runbook.md +98 -7
- package/docs/manual-owned-ui-checkpoint.md +28 -1
- package/docs/manual-terminal-colour-check.md +4 -3
- package/docs/repository-governance-live-acceptance.md +77 -0
- package/package.json +11 -3
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export type PiTuiInputCoordinationDecision = "safe" | "barrier";
|
|
2
|
+
export type PiTuiInputSurfaceKind = "editor" | "owned" | "opaque";
|
|
3
|
+
export interface PiTuiInputCoordinationScheduler {
|
|
4
|
+
scheduleImmediate(callback: () => void): ReturnType<typeof setImmediate>;
|
|
5
|
+
cancelImmediate(handle: ReturnType<typeof setImmediate>): void;
|
|
6
|
+
}
|
|
7
|
+
export interface PiTuiInputCoordinationTrace {
|
|
8
|
+
readonly phase: "receipt" | "semantic-start" | "semantic-end";
|
|
9
|
+
readonly revision: number;
|
|
10
|
+
readonly atMs: number;
|
|
11
|
+
readonly pendingDepth: number;
|
|
12
|
+
readonly pendingPresentationDepth: 0 | 1;
|
|
13
|
+
readonly appliedRevision: number;
|
|
14
|
+
}
|
|
15
|
+
export interface InputPresentationCoordinatorOptions {
|
|
16
|
+
readonly classify: (data: string) => PiTuiInputCoordinationDecision;
|
|
17
|
+
readonly deliver: (data: string) => void;
|
|
18
|
+
readonly onReceipt?: () => void;
|
|
19
|
+
readonly onTrace?: (event: PiTuiInputCoordinationTrace) => void;
|
|
20
|
+
readonly now?: () => number;
|
|
21
|
+
readonly scheduler?: PiTuiInputCoordinationScheduler;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Delivers complete terminal chunks in their original order while allowing Pi
|
|
25
|
+
* TUI's existing pending-render guard to present one current state per event
|
|
26
|
+
* loop turn. Unknown or effectful input is an immediate ordering barrier.
|
|
27
|
+
*/
|
|
28
|
+
export declare class InputPresentationCoordinator {
|
|
29
|
+
#private;
|
|
30
|
+
constructor(options: InputPresentationCoordinatorOptions);
|
|
31
|
+
get pendingDepth(): number;
|
|
32
|
+
get appliedRevision(): number;
|
|
33
|
+
get pendingPresentation(): boolean;
|
|
34
|
+
accept(data: string): void;
|
|
35
|
+
flush(): void;
|
|
36
|
+
dispose(deliverPending?: boolean): void;
|
|
37
|
+
}
|
|
38
|
+
/** Finite fail-closed grammar for input whose semantic handlers may run in one ordered drain. */
|
|
39
|
+
export declare function classifyPiTuiInput(data: string, surface: PiTuiInputSurfaceKind): PiTuiInputCoordinationDecision;
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
const SYSTEM_SCHEDULER = {
|
|
2
|
+
scheduleImmediate: callback => setImmediate(callback),
|
|
3
|
+
cancelImmediate: handle => clearImmediate(handle),
|
|
4
|
+
};
|
|
5
|
+
/**
|
|
6
|
+
* Delivers complete terminal chunks in their original order while allowing Pi
|
|
7
|
+
* TUI's existing pending-render guard to present one current state per event
|
|
8
|
+
* loop turn. Unknown or effectful input is an immediate ordering barrier.
|
|
9
|
+
*/
|
|
10
|
+
export class InputPresentationCoordinator {
|
|
11
|
+
#classify;
|
|
12
|
+
#deliver;
|
|
13
|
+
#onReceipt;
|
|
14
|
+
#onTrace;
|
|
15
|
+
#now;
|
|
16
|
+
#scheduler;
|
|
17
|
+
#pending = [];
|
|
18
|
+
#scheduled;
|
|
19
|
+
#nextRevision = 1;
|
|
20
|
+
#appliedRevision = 0;
|
|
21
|
+
#disposed = false;
|
|
22
|
+
constructor(options) {
|
|
23
|
+
this.#classify = options.classify;
|
|
24
|
+
this.#deliver = options.deliver;
|
|
25
|
+
this.#onReceipt = options.onReceipt;
|
|
26
|
+
this.#onTrace = options.onTrace;
|
|
27
|
+
this.#now = options.now ?? (() => performance.now());
|
|
28
|
+
this.#scheduler = options.scheduler ?? SYSTEM_SCHEDULER;
|
|
29
|
+
}
|
|
30
|
+
get pendingDepth() { return this.#pending.length; }
|
|
31
|
+
get appliedRevision() { return this.#appliedRevision; }
|
|
32
|
+
get pendingPresentation() { return this.#scheduled !== undefined; }
|
|
33
|
+
accept(data) {
|
|
34
|
+
if (this.#disposed || data.length === 0)
|
|
35
|
+
return;
|
|
36
|
+
const input = { revision: this.#nextRevision++, data };
|
|
37
|
+
this.#onReceipt?.();
|
|
38
|
+
if (this.#classify(data) === "barrier") {
|
|
39
|
+
this.#trace("receipt", input.revision);
|
|
40
|
+
this.flush();
|
|
41
|
+
this.#deliverOne(input);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
this.#pending.push(input);
|
|
45
|
+
if (this.#scheduled === undefined) {
|
|
46
|
+
this.#scheduled = this.#scheduler.scheduleImmediate(() => {
|
|
47
|
+
this.#scheduled = undefined;
|
|
48
|
+
this.#drain();
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
this.#trace("receipt", input.revision);
|
|
52
|
+
}
|
|
53
|
+
flush() {
|
|
54
|
+
if (this.#scheduled !== undefined)
|
|
55
|
+
this.#scheduler.cancelImmediate(this.#scheduled);
|
|
56
|
+
this.#scheduled = undefined;
|
|
57
|
+
this.#drain();
|
|
58
|
+
}
|
|
59
|
+
dispose(deliverPending = true) {
|
|
60
|
+
if (this.#disposed)
|
|
61
|
+
return;
|
|
62
|
+
if (deliverPending)
|
|
63
|
+
this.flush();
|
|
64
|
+
else {
|
|
65
|
+
if (this.#scheduled !== undefined)
|
|
66
|
+
this.#scheduler.cancelImmediate(this.#scheduled);
|
|
67
|
+
this.#scheduled = undefined;
|
|
68
|
+
this.#pending.length = 0;
|
|
69
|
+
}
|
|
70
|
+
this.#disposed = true;
|
|
71
|
+
}
|
|
72
|
+
#drain() {
|
|
73
|
+
while (this.#pending.length > 0)
|
|
74
|
+
this.#deliverOne(this.#pending.shift());
|
|
75
|
+
}
|
|
76
|
+
#deliverOne(input) {
|
|
77
|
+
this.#trace("semantic-start", input.revision);
|
|
78
|
+
try {
|
|
79
|
+
this.#deliver(input.data);
|
|
80
|
+
}
|
|
81
|
+
finally {
|
|
82
|
+
this.#appliedRevision = input.revision;
|
|
83
|
+
this.#trace("semantic-end", input.revision);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
#trace(phase, revision) {
|
|
87
|
+
this.#onTrace?.({
|
|
88
|
+
phase,
|
|
89
|
+
revision,
|
|
90
|
+
atMs: this.#now(),
|
|
91
|
+
pendingDepth: this.#pending.length,
|
|
92
|
+
pendingPresentationDepth: this.#scheduled === undefined ? 0 : 1,
|
|
93
|
+
appliedRevision: this.#appliedRevision,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
const SAFE_EDITING_INPUTS = new Set([
|
|
98
|
+
"\b",
|
|
99
|
+
"\u007f",
|
|
100
|
+
"\u001b[A",
|
|
101
|
+
"\u001b[B",
|
|
102
|
+
"\u001b[C",
|
|
103
|
+
"\u001b[D",
|
|
104
|
+
"\u001b[1;2A",
|
|
105
|
+
"\u001b[1;2B",
|
|
106
|
+
"\u001b[1;5C",
|
|
107
|
+
"\u001b[1;5D",
|
|
108
|
+
"\u001b[3~",
|
|
109
|
+
"\u001b[H",
|
|
110
|
+
"\u001b[F",
|
|
111
|
+
"\u001b[1~",
|
|
112
|
+
"\u001b[4~",
|
|
113
|
+
]);
|
|
114
|
+
const MAX_SAFE_TEXT_DELIVERY = 4096;
|
|
115
|
+
/** Finite fail-closed grammar for input whose semantic handlers may run in one ordered drain. */
|
|
116
|
+
export function classifyPiTuiInput(data, surface) {
|
|
117
|
+
if (surface === "opaque" || data.length === 0 || data.length > MAX_SAFE_TEXT_DELIVERY)
|
|
118
|
+
return "barrier";
|
|
119
|
+
if (SAFE_EDITING_INPUTS.has(data))
|
|
120
|
+
return "safe";
|
|
121
|
+
if (data.includes("\u001b") || data.includes("\r") || data.includes("\n") || data.includes("\u0003"))
|
|
122
|
+
return "barrier";
|
|
123
|
+
for (let index = 0; index < data.length; index += 1) {
|
|
124
|
+
const code = data.charCodeAt(index);
|
|
125
|
+
if (code < 0x20 || code === 0x7f)
|
|
126
|
+
return "barrier";
|
|
127
|
+
if (code >= 0xd800 && code <= 0xdbff) {
|
|
128
|
+
if (index + 1 >= data.length)
|
|
129
|
+
return "barrier";
|
|
130
|
+
const next = data.charCodeAt(index + 1);
|
|
131
|
+
if (next < 0xdc00 || next > 0xdfff)
|
|
132
|
+
return "barrier";
|
|
133
|
+
index += 1;
|
|
134
|
+
}
|
|
135
|
+
else if (code >= 0xdc00 && code <= 0xdfff)
|
|
136
|
+
return "barrier";
|
|
137
|
+
}
|
|
138
|
+
return "safe";
|
|
139
|
+
}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"platform": "darwin",
|
|
6
6
|
"architecture": "arm64",
|
|
7
7
|
"capability": "unsupported",
|
|
8
|
-
"builtAt": "2026-
|
|
8
|
+
"builtAt": "2026-09-02T16:56:10.577Z",
|
|
9
9
|
"artifact": {
|
|
10
10
|
"filename": "process-guardian",
|
|
11
11
|
"sha256": "7524bf568992517f50ed53196c861c5edeba0d7275633bb4735ab45bd5963a28",
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"platform": "linux",
|
|
6
6
|
"architecture": "x64",
|
|
7
7
|
"capability": "supported",
|
|
8
|
-
"builtAt": "2026-
|
|
8
|
+
"builtAt": "2026-09-02T16:56:00.627Z",
|
|
9
9
|
"artifact": {
|
|
10
10
|
"filename": "process-guardian",
|
|
11
11
|
"sha256": "ee8a00eaaf79c707459bbbfb52518e9739314967049fbe5fa625f36ce33db9ee",
|
|
@@ -5,10 +5,10 @@
|
|
|
5
5
|
"platform": "win32",
|
|
6
6
|
"architecture": "x64",
|
|
7
7
|
"capability": "supported",
|
|
8
|
-
"builtAt": "2026-
|
|
8
|
+
"builtAt": "2026-09-02T16:57:49.437Z",
|
|
9
9
|
"artifact": {
|
|
10
10
|
"filename": "process-guardian.exe",
|
|
11
|
-
"sha256": "
|
|
11
|
+
"sha256": "ec35b032d6aad590e2d8e9889958b552e5f2f6345324442dc2a3ba60be02fdd5",
|
|
12
12
|
"size": 177664
|
|
13
13
|
},
|
|
14
14
|
"provenance": {
|
|
Binary file
|
package/dist/ui/apps/host.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { FrameCache, finalizeFrame, } from "../components/index.js";
|
|
2
|
-
//
|
|
2
|
+
// Invariant: the interrupt byte is written as an escape: as a raw byte it is invisible to
|
|
3
3
|
// every text tool, and this constant had already been emptied by one.
|
|
4
4
|
const INTERRUPT = "";
|
|
5
5
|
/** How long the first interrupt of the chord stays armed. */
|
|
@@ -14,7 +14,7 @@ export class UiAppHost {
|
|
|
14
14
|
#surface;
|
|
15
15
|
#closeOnInterrupt;
|
|
16
16
|
#theme;
|
|
17
|
-
|
|
17
|
+
// Invariant: the first interrupt timestamp is null whenever the chord is disarmed.
|
|
18
18
|
#interruptArmedAt = null;
|
|
19
19
|
#renderedInterruptArmed;
|
|
20
20
|
#cache = new FrameCache();
|
|
@@ -91,11 +91,7 @@ export class UiAppHost {
|
|
|
91
91
|
this.render();
|
|
92
92
|
return result;
|
|
93
93
|
}
|
|
94
|
-
|
|
95
|
-
* Two interrupts leave A1, from any screen. One arms the chord and repaints so
|
|
96
|
-
* the screen can say so; a second within the window exits rather than merely
|
|
97
|
-
* closing the screen, which is what the chord means everywhere else.
|
|
98
|
-
*/
|
|
94
|
+
// Protocol: two interrupts within the chord window leave A1 from every screen.
|
|
99
95
|
#interrupt() {
|
|
100
96
|
const now = Date.now();
|
|
101
97
|
const armed = this.#interruptArmedAt;
|
|
@@ -106,7 +102,7 @@ export class UiAppHost {
|
|
|
106
102
|
return { consumed: true, render: true };
|
|
107
103
|
}
|
|
108
104
|
this.#interruptArmedAt = now;
|
|
109
|
-
//
|
|
105
|
+
// Invariant: host-owned state participates in the app's next frame independently of
|
|
110
106
|
// the app's own revision contract.
|
|
111
107
|
this.#cache.invalidate();
|
|
112
108
|
return { consumed: true, render: true };
|
|
@@ -143,7 +139,7 @@ export class UiAppHost {
|
|
|
143
139
|
...(this.#theme === undefined ? {} : { theme: this.#theme }),
|
|
144
140
|
};
|
|
145
141
|
}
|
|
146
|
-
|
|
142
|
+
// Security: an app failure closes its app and restores the surface.
|
|
147
143
|
#guard(work) {
|
|
148
144
|
try {
|
|
149
145
|
return work();
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type AppRegistration } from "./contracts.js";
|
|
2
|
+
/** Registers owned applications under unique routes and unregisters only the matching definition. */
|
|
2
3
|
export declare class UiAppRegistry {
|
|
3
4
|
#private;
|
|
4
5
|
/** Registering a known id replaces its definition rather than adding a second app. */
|
package/dist/ui/apps/registry.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { assertAppRegistration } from "./contracts.js";
|
|
2
|
+
/** Registers owned applications under unique routes and unregisters only the matching definition. */
|
|
2
3
|
export class UiAppRegistry {
|
|
3
4
|
#byId = new Map();
|
|
4
5
|
/** Registering a known id replaces its definition rather than adding a second app. */
|
|
@@ -17,9 +17,11 @@ export function renderDialogPanel(state, width, theme) {
|
|
|
17
17
|
const padded = `${row.label}${" ".repeat(Math.max(0, labelColumn - displayWidth(row.label)))}`;
|
|
18
18
|
const cursor = selected ? "→ " : " ";
|
|
19
19
|
const raw = `${cursor}${padded} ${row.value}`;
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
const painted =
|
|
20
|
+
// Compatibility: match pinned SettingsList: selected cursor, label, and value all use the
|
|
21
|
+
// accent role; an unselected label is plain and its value is muted.
|
|
22
|
+
const painted = selected
|
|
23
|
+
? `${theme.fg("accent", cursor)}${theme.fg("accent", padded)} ${theme.fg("accent", row.value)}`
|
|
24
|
+
: `${cursor}${padded} ${theme.fg("muted", row.value)}`;
|
|
23
25
|
return pad(truncateToWidth(painted, width), width, raw);
|
|
24
26
|
});
|
|
25
27
|
const description = state.rows[state.index]?.description ?? "";
|
|
@@ -2,6 +2,7 @@ export interface PaneRect {
|
|
|
2
2
|
readonly width: number;
|
|
3
3
|
readonly height: number;
|
|
4
4
|
}
|
|
5
|
+
/** Identifies the pane whose bounded frame geometry or rendered rows violated the contract. */
|
|
5
6
|
export declare class FrameContractError extends Error {
|
|
6
7
|
readonly pane: string;
|
|
7
8
|
constructor(pane: string, message: string);
|
|
@@ -6,6 +6,7 @@ export * from "./list-block.js";
|
|
|
6
6
|
export * from "./list-view.js";
|
|
7
7
|
export * from "./mouse.js";
|
|
8
8
|
export * from "./pane.js";
|
|
9
|
+
export * from "./progress-status.js";
|
|
9
10
|
export * from "./revision.js";
|
|
10
11
|
export * from "./scrollbar.js";
|
|
11
12
|
export * from "./shortcuts.js";
|
|
@@ -6,6 +6,7 @@ export * from "./list-block.js";
|
|
|
6
6
|
export * from "./list-view.js";
|
|
7
7
|
export * from "./mouse.js";
|
|
8
8
|
export * from "./pane.js";
|
|
9
|
+
export * from "./progress-status.js";
|
|
9
10
|
export * from "./revision.js";
|
|
10
11
|
export * from "./scrollbar.js";
|
|
11
12
|
export * from "./shortcuts.js";
|
|
@@ -47,12 +47,7 @@ export declare class LineInput {
|
|
|
47
47
|
}
|
|
48
48
|
/** Applies one key to the input and reports whether the caller should commit. */
|
|
49
49
|
export declare function handleLineInputKey(input: LineInput, data: string): LineInputOutcome;
|
|
50
|
-
/**
|
|
51
|
-
* The prompt the reference draws at the head of an input row, in the grey it
|
|
52
|
-
* uses for one (#9AA0A6), painted foreground-only so anything drawn behind the
|
|
53
|
-
* row survives.
|
|
54
|
-
*/
|
|
55
|
-
/** A rule drawn in the prompt's own grey, as the reference rules an input row. */
|
|
50
|
+
/** Draws the reference prompt rule foreground-only so underlying cells survive. */
|
|
56
51
|
export declare function promptRule(width: number): string;
|
|
57
52
|
export declare const PROMPT_GLYPH = "\u001B[38;2;154;160;166m\u276F\u001B[39m ";
|
|
58
53
|
/**
|
|
@@ -148,7 +148,7 @@ export function handleLineInputKey(input, data) {
|
|
|
148
148
|
return { kind: "accepted", value: input.value };
|
|
149
149
|
if (data === "\u001b" || data === "\u0003")
|
|
150
150
|
return { kind: "cancelled" };
|
|
151
|
-
//
|
|
151
|
+
// Platform: a word delete is decided before a plain one: on Windows Terminal the raw
|
|
152
152
|
// backspace byte IS ctrl+backspace, and the plain branch would swallow it.
|
|
153
153
|
if (data === "\b" || data === "\u001b\u007f" || data === "\u0017") {
|
|
154
154
|
input.deleteWordBefore();
|
|
@@ -190,7 +190,7 @@ export function handleLineInputKey(input, data) {
|
|
|
190
190
|
input.moveCaretToEnd();
|
|
191
191
|
return { kind: "editing" };
|
|
192
192
|
}
|
|
193
|
-
//
|
|
193
|
+
// Rationale: any other escape sequence is a key this input has no answer for - a page
|
|
194
194
|
// key, a function key, a chord. It is swallowed rather than typed, because the
|
|
195
195
|
// one thing it certainly is not is text the reader meant to enter.
|
|
196
196
|
if (data.startsWith("\u001b"))
|
|
@@ -198,12 +198,7 @@ export function handleLineInputKey(input, data) {
|
|
|
198
198
|
input.insert(data);
|
|
199
199
|
return { kind: "editing" };
|
|
200
200
|
}
|
|
201
|
-
/**
|
|
202
|
-
* The prompt the reference draws at the head of an input row, in the grey it
|
|
203
|
-
* uses for one (#9AA0A6), painted foreground-only so anything drawn behind the
|
|
204
|
-
* row survives.
|
|
205
|
-
*/
|
|
206
|
-
/** A rule drawn in the prompt's own grey, as the reference rules an input row. */
|
|
201
|
+
/** Draws the reference prompt rule foreground-only so underlying cells survive. */
|
|
207
202
|
export function promptRule(width) {
|
|
208
203
|
return `\u001b[38;2;154;160;166m${"─".repeat(Math.max(0, width))}\u001b[39m`;
|
|
209
204
|
}
|
|
@@ -224,7 +219,7 @@ export function renderInputRow(input, width, options = {}) {
|
|
|
224
219
|
const before = view.text.slice(0, view.caretColumn);
|
|
225
220
|
const under = view.text.slice(view.caretColumn, view.caretColumn + 1) || " ";
|
|
226
221
|
const after = view.text.slice(view.caretColumn + 1);
|
|
227
|
-
//
|
|
222
|
+
// Rationale: typed text is left unpainted, the caret reverses its cell, and the
|
|
228
223
|
// placeholder is quietened by weight rather than by a colour of its own.
|
|
229
224
|
const body = empty
|
|
230
225
|
? `${caretCell(placeholder.slice(0, 1))}${faint(placeholder.slice(1))}`
|
|
@@ -1,10 +1,4 @@
|
|
|
1
1
|
import type { UiTheme } from "./theme.js";
|
|
2
|
-
/**
|
|
3
|
-
* A list of things that each carry a label and a value. It owns where a value
|
|
4
|
-
* starts, how a row reads when it is selected or pointed at, and which part of a
|
|
5
|
-
* row the pointer is on — so a screen decides what a row means and this decides
|
|
6
|
-
* how it looks and where it can be touched.
|
|
7
|
-
*/
|
|
8
2
|
/** The part of a row the pointer is over. A label selects; a value acts. */
|
|
9
3
|
export type ListRegion = "label" | "value" | "minus" | "plus";
|
|
10
4
|
/** Columns a stepper's controls occupy on either side of the value. */
|
|
@@ -6,8 +6,8 @@ export const STEPPER_RESERVE = 2;
|
|
|
6
6
|
* and with the stepper's columns reserved for every row when any row has one, so
|
|
7
7
|
* a number does not shift its own value out of the column it shares.
|
|
8
8
|
*/
|
|
9
|
-
export function valueColumnFor(rows, indent =
|
|
10
|
-
const widest = Math.max(0, ...rows.map(row => displayWidth(row.label)));
|
|
9
|
+
export function valueColumnFor(rows, indent = 2, gap = 2) {
|
|
10
|
+
const widest = Math.min(30, Math.max(0, ...rows.map(row => displayWidth(row.label))));
|
|
11
11
|
const stepper = rows.some(row => row.stepper !== undefined) ? STEPPER_RESERVE : 0;
|
|
12
12
|
return indent + widest + gap + stepper;
|
|
13
13
|
}
|
|
@@ -22,14 +22,21 @@ export function renderGroupHeader(title, width, theme) {
|
|
|
22
22
|
*/
|
|
23
23
|
export function renderListRow(row, state, valueColumn, width, theme) {
|
|
24
24
|
const cursor = state.selected ? "→ " : " ";
|
|
25
|
-
const
|
|
26
|
-
const
|
|
25
|
+
const labelColumn = Math.max(displayWidth(row.label), valueColumn - 4 - (row.stepper === undefined ? 0 : STEPPER_RESERVE));
|
|
26
|
+
const labelPadded = `${row.label}${" ".repeat(Math.max(0, labelColumn - displayWidth(row.label)))}`;
|
|
27
|
+
const leftRaw = `${cursor}${labelPadded}`;
|
|
28
|
+
const left = state.selected
|
|
29
|
+
? `${theme.fg("accent", cursor)}${theme.fg("accent", labelPadded)}`
|
|
30
|
+
: `${cursor}${theme.plain(labelPadded)}`;
|
|
27
31
|
const gap = Math.max(2, valueColumn - displayWidth(leftRaw));
|
|
28
|
-
//
|
|
29
|
-
//
|
|
32
|
+
// Compatibility: pinned SettingsList gives the selected label and value the same accent
|
|
33
|
+
// role. Pointer hover may brighten an unselected value without changing the
|
|
34
|
+
// keyboard selection.
|
|
30
35
|
const valueHovered = state.hovered && state.region !== "label";
|
|
31
36
|
const stepper = row.stepper !== undefined && valueHovered;
|
|
32
|
-
const value =
|
|
37
|
+
const value = state.selected
|
|
38
|
+
? theme.fg("accent", row.value)
|
|
39
|
+
: valueHovered ? theme.plain(row.value) : theme.fg("muted", row.value);
|
|
33
40
|
const minus = stepper
|
|
34
41
|
? row.stepper?.lower === true
|
|
35
42
|
? state.region === "minus" ? theme.plain("- ") : theme.fg("dim", "- ")
|
|
@@ -14,7 +14,7 @@ export function scrollbarSelectionRows(speed) {
|
|
|
14
14
|
/** Pure appearance decision; activity time is supplied so tests own the clock. */
|
|
15
15
|
export function scrollbarPresentation(input) {
|
|
16
16
|
const overflowing = input.geometry !== null;
|
|
17
|
-
//
|
|
17
|
+
// Invariant: auto/always reserve the final rail cell even while content fits.
|
|
18
18
|
// The blank gutter prevents prompt timestamps from touching the terminal edge
|
|
19
19
|
// and keeps later scrollbar appearance from reflowing the transcript.
|
|
20
20
|
const reservesSpace = input.appearance !== "hidden";
|
|
@@ -40,6 +40,7 @@ export interface ShortcutRegistryResult {
|
|
|
40
40
|
*/
|
|
41
41
|
export declare function assembleShortcuts(declarations: readonly ShortcutDeclaration[]): ShortcutRegistryResult;
|
|
42
42
|
export declare function assertNoShortcutConflicts(result: ShortcutRegistryResult): void;
|
|
43
|
+
/** Keeps shortcut dispatch and reader-facing listings derived from one ordered declaration set. */
|
|
43
44
|
export declare class ShortcutRegistry<Action extends string = string> {
|
|
44
45
|
#private;
|
|
45
46
|
declare(declaration: ShortcutDeclaration, action: Action): void;
|
|
@@ -40,6 +40,7 @@ export function assertNoShortcutConflicts(result) {
|
|
|
40
40
|
+ `"${duplicate.descriptions[0]}" and "${duplicate.descriptions[1]}"`);
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
|
+
/** Keeps shortcut dispatch and reader-facing listings derived from one ordered declaration set. */
|
|
43
44
|
export class ShortcutRegistry {
|
|
44
45
|
#declarations = [];
|
|
45
46
|
#actions = new Map();
|
|
@@ -5,13 +5,10 @@
|
|
|
5
5
|
* replaced by spaces rather than split.
|
|
6
6
|
*/
|
|
7
7
|
export declare function overlaySpan(line: string, from: number, to: number, span: string): string;
|
|
8
|
-
/**
|
|
9
|
-
* Paints only a background over [from,to), preserving every glyph and its
|
|
10
|
-
* foreground, bold, underline, and hyperlink styling. Embedded resets reapply
|
|
11
|
-
* the selection background, while leaving the span restores the row's styles.
|
|
12
|
-
*/
|
|
13
8
|
/** Wraps visible columns `[from,to)` in an explicit OSC 8 hyperlink. */
|
|
14
9
|
export declare function hyperlinkSgrSpan(line: string, from: number, to: number, target: string, widthOf?: (text: string) => number): string;
|
|
10
|
+
/** Returns the OSC 8 target painted at a zero-based visible terminal column. */
|
|
11
|
+
export declare function hyperlinkTargetAtColumn(line: string, targetColumn: number, widthOf?: (text: string) => number): string | undefined;
|
|
15
12
|
/**
|
|
16
13
|
* Gives existing and bare web links explicit, tightly bounded OSC 8 regions so
|
|
17
14
|
* the terminal owns dotted-idle/solid-hover decoration without leaking hover
|
|
@@ -39,7 +39,7 @@ export function overlaySpan(line, from, to, span) {
|
|
|
39
39
|
if (link)
|
|
40
40
|
hyperlinkOpen = (link[1] ?? "").length > 0;
|
|
41
41
|
}
|
|
42
|
-
//
|
|
42
|
+
// Invariant: re-assert everything still active: the span's own reset clobbers the tail.
|
|
43
43
|
replay += token;
|
|
44
44
|
continue;
|
|
45
45
|
}
|
|
@@ -60,11 +60,6 @@ export function overlaySpan(line, from, to, span) {
|
|
|
60
60
|
head += " ".repeat(from - column);
|
|
61
61
|
return head + (hyperlinkOpen ? HYPERLINK_OFF : "") + span + replay + tail;
|
|
62
62
|
}
|
|
63
|
-
/**
|
|
64
|
-
* Paints only a background over [from,to), preserving every glyph and its
|
|
65
|
-
* foreground, bold, underline, and hyperlink styling. Embedded resets reapply
|
|
66
|
-
* the selection background, while leaving the span restores the row's styles.
|
|
67
|
-
*/
|
|
68
63
|
/** Wraps visible columns `[from,to)` in an explicit OSC 8 hyperlink. */
|
|
69
64
|
export function hyperlinkSgrSpan(line, from, to, target, widthOf = displayWidth) {
|
|
70
65
|
const on = `\u001b]8;;${target}\u001b\\`;
|
|
@@ -93,6 +88,31 @@ export function hyperlinkSgrSpan(line, from, to, target, widthOf = displayWidth)
|
|
|
93
88
|
output += off;
|
|
94
89
|
return output;
|
|
95
90
|
}
|
|
91
|
+
/** Returns the OSC 8 target painted at a zero-based visible terminal column. */
|
|
92
|
+
export function hyperlinkTargetAtColumn(line, targetColumn, widthOf = displayWidth) {
|
|
93
|
+
if (targetColumn < 0)
|
|
94
|
+
return undefined;
|
|
95
|
+
let hyperlinkTarget;
|
|
96
|
+
let column = 0;
|
|
97
|
+
for (const token of line.split(ANSI_SPLIT)) {
|
|
98
|
+
if (!token)
|
|
99
|
+
continue;
|
|
100
|
+
const link = HYPERLINK.exec(token);
|
|
101
|
+
if (link) {
|
|
102
|
+
hyperlinkTarget = (link[1] ?? "") || undefined;
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
if (token.startsWith("\u001b"))
|
|
106
|
+
continue;
|
|
107
|
+
for (const { segment } of GRAPHEMES.segment(token)) {
|
|
108
|
+
const width = widthOf(segment);
|
|
109
|
+
if (targetColumn >= column && targetColumn < column + width)
|
|
110
|
+
return hyperlinkTarget;
|
|
111
|
+
column += width;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return undefined;
|
|
115
|
+
}
|
|
96
116
|
/**
|
|
97
117
|
* Gives existing and bare web links explicit, tightly bounded OSC 8 regions so
|
|
98
118
|
* the terminal owns dotted-idle/solid-hover decoration without leaking hover
|
|
@@ -154,7 +174,7 @@ export function heldNativeHyperlinkStyle(line) {
|
|
|
154
174
|
}
|
|
155
175
|
if (token.startsWith("\u001b")) {
|
|
156
176
|
output += token;
|
|
157
|
-
//
|
|
177
|
+
// Protocol: foreground helpers may reset every SGR attribute; restore the held
|
|
158
178
|
// dotted underline without disturbing the source colour or intensity.
|
|
159
179
|
if (hyperlinkTarget !== undefined && SGR.test(token))
|
|
160
180
|
output += "\u001b[4:4m";
|
|
@@ -165,7 +185,7 @@ export function heldNativeHyperlinkStyle(line) {
|
|
|
165
185
|
continue;
|
|
166
186
|
}
|
|
167
187
|
let painted = token.replaceAll("https://", "https:\uFE0E//").replaceAll("http://", "http:\uFE0E//");
|
|
168
|
-
// Windows Terminal also detects file-link labels such as package.json and
|
|
188
|
+
// Platform: Windows Terminal also detects file-link labels such as package.json and
|
|
169
189
|
// D:/work/file.ts after OSC 8 is removed. Break that detector once inside
|
|
170
190
|
// the paint-only label; semantic transcript text remains untouched.
|
|
171
191
|
if (!fileDetectorBroken && /^file:/iu.test(hyperlinkTarget)) {
|
|
@@ -1,10 +1,5 @@
|
|
|
1
1
|
import { type ScrollbarGeometry } from "./scrollbar.js";
|
|
2
2
|
import type { UiTheme } from "./theme.js";
|
|
3
|
-
/**
|
|
4
|
-
* The chrome around a list: the rail down its right edge, and what it shows when
|
|
5
|
-
* there is nothing to list. Both are the same on any screen, so neither is a
|
|
6
|
-
* screen's to draw.
|
|
7
|
-
*/
|
|
8
3
|
/** Columns the rail occupies: its own, plus the gap before it. */
|
|
9
4
|
export declare const RAIL_COLUMNS = 2;
|
|
10
5
|
export interface RailOptions {
|
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
import { isThumbRow } from "./scrollbar.js";
|
|
2
2
|
import { displayWidth } from "./text.js";
|
|
3
|
-
|
|
4
|
-
* The chrome around a list: the rail down its right edge, and what it shows when
|
|
5
|
-
* there is nothing to list. Both are the same on any screen, so neither is a
|
|
6
|
-
* screen's to draw.
|
|
7
|
-
*/
|
|
3
|
+
// Rationale: shared list chrome belongs here rather than in individual screens.
|
|
8
4
|
/** Columns the rail occupies: its own, plus the gap before it. */
|
|
9
5
|
export const RAIL_COLUMNS = 2;
|
|
10
6
|
/** Draws the rail beside each row, padding the rows to a common width first. */
|
|
@@ -1,11 +1,17 @@
|
|
|
1
|
+
/** A reported pointer cell plus its grapheme-aligned zero-based boundaries. */
|
|
1
2
|
export interface TextSelectionPoint {
|
|
2
3
|
readonly line: number;
|
|
3
4
|
readonly column: number;
|
|
5
|
+
readonly from?: number;
|
|
6
|
+
readonly to?: number;
|
|
4
7
|
}
|
|
5
8
|
export interface TextSelection {
|
|
6
9
|
readonly anchor: TextSelectionPoint;
|
|
7
10
|
readonly head: TextSelectionPoint;
|
|
8
11
|
readonly selecting: boolean;
|
|
12
|
+
/** Ordinary point selection has received at least one distinct motion report. */
|
|
13
|
+
readonly dragged?: boolean;
|
|
14
|
+
/** Word selection uses inclusive pointer-cell endpoints supplied by its semantic range. */
|
|
9
15
|
readonly cell?: boolean;
|
|
10
16
|
readonly fullRow?: boolean;
|
|
11
17
|
/** Paint an edge whitespace run through the viewport's overlaid rail cell. */
|
|
@@ -22,6 +28,7 @@ export interface TextSelectionLineContent {
|
|
|
22
28
|
readonly to?: number;
|
|
23
29
|
readonly selectable?: boolean;
|
|
24
30
|
}
|
|
31
|
+
/** Ordered zero-based, half-open display-column boundaries. */
|
|
25
32
|
export interface OrderedTextSelection {
|
|
26
33
|
readonly start: TextSelectionPoint;
|
|
27
34
|
readonly end: TextSelectionPoint;
|
|
@@ -47,7 +54,9 @@ export declare function pressTextSelection(input: TextSelectionPressInput): {
|
|
|
47
54
|
export declare function extendTextSelection(selection: TextSelection | undefined, point: TextSelectionPoint): TextSelection | undefined;
|
|
48
55
|
export declare function releaseTextSelection(selection: TextSelection | undefined): TextSelection | undefined;
|
|
49
56
|
export declare function textSelectionLineExtendColumn(selection: TextSelection, line: number, contentWidth: number): number;
|
|
57
|
+
/** Resolves a reported one-based pointer cell to the complete grapheme it intersects. */
|
|
58
|
+
export declare function textSelectionPointAt(line: number, column: number, lineText: string): TextSelectionPoint;
|
|
50
59
|
export declare function textSelectionText(selection: OrderedTextSelection, rows: readonly string[], lineContent?: (line: number) => TextSelectionLineContent | undefined): string;
|
|
51
|
-
/** Returns the plain cells covered by [from,to),
|
|
60
|
+
/** Returns the plain cells covered by [from,to), expanding at grapheme edges. */
|
|
52
61
|
export declare function plainTextBetweenColumns(line: string, from: number, to: number): string;
|
|
53
62
|
export declare function usefulTextLineContent(line: string, from?: number): TextSelectionLineContent;
|