@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
@@ -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-08-28T12:29:51.254Z",
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-08-28T12:30:02.835Z",
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-08-28T12:30:44.840Z",
8
+ "builtAt": "2026-09-02T16:57:49.437Z",
9
9
  "artifact": {
10
10
  "filename": "process-guardian.exe",
11
- "sha256": "ba7b712f918bc77cdf847bcb7b2473ded2aaf10b057bd12360ac3fa654b5f36c",
11
+ "sha256": "ec35b032d6aad590e2d8e9889958b552e5f2f6345324442dc2a3ba60be02fdd5",
12
12
  "size": 177664
13
13
  },
14
14
  "provenance": {
@@ -1,5 +1,5 @@
1
1
  import { FrameCache, finalizeFrame, } from "../components/index.js";
2
- // The interrupt byte, written as an escape: as a raw byte it is invisible to
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
- /** When the first interrupt of a chord was seen, or null when disarmed. */
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
- // Host-owned state participates in the app's next frame independently of
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
- /** Contains a failure: the app is closed and the surface restored, never left broken. */
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. */
@@ -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
- // An unselected label is left unpainted and its value quietened, both lifted
21
- // to the accent on the row the cursor is on as the engine paints its own.
22
- const painted = `${theme.fg("accent", cursor)}${selected ? theme.fg("accent", padded) : padded} ${theme.fg("muted", row.value)}`;
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);
@@ -1,4 +1,5 @@
1
1
  import { displayWidth, truncateToWidth } from "./text.js";
2
+ /** Identifies the pane whose bounded frame geometry or rendered rows violated the contract. */
2
3
  export class FrameContractError extends Error {
3
4
  pane;
4
5
  constructor(pane, message) {
@@ -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
- // A word delete is decided before a plain one: on Windows Terminal the raw
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
- // Any other escape sequence is a key this input has no answer for - a page
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
- // Typed text is left unpainted, the caret reverses its cell, and the
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 = 4, gap = 2) {
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 leftRaw = `${cursor} ${row.label}`;
26
- const left = `${theme.fg("accent", cursor)} ${state.selected ? theme.fg("accent", row.label) : theme.plain(row.label)}`;
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
- // Pointing anywhere on the row is pointing at the item; pointing at the value
29
- // is what brightens it. The selection speaks through the label alone.
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 = valueHovered ? theme.plain(row.value) : theme.fg("muted", row.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", "- ")
@@ -0,0 +1,2 @@
1
+ /** Returns spinner-backed status text with A1's canonical three-period marker. */
2
+ export declare function progressStatusText(message: string): string;
@@ -0,0 +1,4 @@
1
+ /** Returns spinner-backed status text with A1's canonical three-period marker. */
2
+ export function progressStatusText(message) {
3
+ return `${message.replace(/(?:…|\.+)$/u, "")}...`;
4
+ }
@@ -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
- // As in v2, auto/always reserve the final rail cell even while content fits.
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
- // Re-assert everything still active: the span's own reset clobbers the tail.
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
- // Foreground helpers may reset every SGR attribute; restore the held
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), using terminal grapheme widths. */
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;