@timurproko/a1 0.1.8-dev.335 → 0.1.8-dev.368
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 +18 -0
- package/dist/composition/owned-ui.d.ts +2 -0
- package/dist/composition/owned-ui.js +20 -3
- package/dist/contracts/owned-ui/index.d.ts +2 -0
- package/dist/contracts/owned-ui/index.js +2 -0
- package/dist/contracts/owned-ui/model.d.ts +38 -3
- package/dist/contracts/owned-ui/prompt-history.d.ts +18 -0
- package/dist/contracts/owned-ui/prompt-suggestions.d.ts +1 -1
- package/dist/contracts/owned-ui/prompt-suggestions.js +4 -1
- package/dist/contracts/owned-ui/suggestion-diagnostics.d.ts +22 -0
- package/dist/contracts/owned-ui/suggestion-diagnostics.js +9 -0
- package/dist/contracts/owned-ui/transcript-lifecycle.d.ts +5 -0
- package/dist/contracts/owned-ui/transcript-lifecycle.js +33 -0
- package/dist/contracts/owned-ui/validation.js +45 -2
- package/dist/contracts/presentation/index.d.ts +8 -0
- package/dist/features/owned-ui/settings-app.js +1 -1
- package/dist/features/prompt-history/image-sidecar.d.ts +32 -0
- package/dist/features/prompt-history/image-sidecar.js +130 -0
- package/dist/features/prompt-history/index.d.ts +2 -0
- package/dist/features/prompt-history/index.js +1 -0
- package/dist/features/prompt-history/paths.d.ts +1 -0
- package/dist/features/prompt-history/paths.js +2 -1
- package/dist/features/prompt-history/store.d.ts +1 -1
- package/dist/features/prompt-history/store.js +46 -2
- package/dist/features/prompt-history/worker.js +1 -1
- package/dist/features/prompt-suggestions/diagnostics.d.ts +18 -0
- package/dist/features/prompt-suggestions/diagnostics.js +82 -0
- package/dist/features/prompt-suggestions/index.d.ts +1 -0
- package/dist/features/prompt-suggestions/index.js +1 -0
- package/dist/integrations/pi/components/owned-editor-ux.d.ts +9 -1
- package/dist/integrations/pi/components/owned-editor-ux.js +11 -8
- package/dist/integrations/pi/components/prompt-input-port.d.ts +18 -0
- package/dist/integrations/pi/components/prompt-input-port.js +1 -0
- package/dist/integrations/pi/components/shell-editor-autocomplete.js +66 -11
- package/dist/integrations/pi/components/shell-footer-status.d.ts +1 -1
- package/dist/integrations/pi/components/shell-footer-status.js +9 -5
- package/dist/integrations/pi/components/shell-presenters-info.js +7 -2
- package/dist/integrations/pi/components/shell-presenters-transcript.d.ts +6 -2
- package/dist/integrations/pi/components/shell-presenters-transcript.js +81 -49
- package/dist/integrations/pi/components/shell-shared-facade.d.ts +17 -4
- package/dist/integrations/pi/components/shell-shared-facade.js +2 -1
- package/dist/integrations/pi/components/submitted-prompt-adapter.d.ts +3 -1
- package/dist/integrations/pi/components/submitted-prompt-adapter.js +20 -4
- package/dist/integrations/pi/components/tool-result-adapter.d.ts +6 -0
- package/dist/integrations/pi/components/tool-result-adapter.js +27 -0
- package/dist/integrations/pi/components/upstream/adjacent/core/keybindings.d.ts +1 -0
- package/dist/integrations/pi/components/upstream/adjacent/core/keybindings.js +5 -0
- package/dist/integrations/pi/components/upstream/components/owned-editor.d.ts +7 -2
- package/dist/integrations/pi/components/upstream/components/owned-editor.js +41 -33
- package/dist/integrations/pi/components/upstream/components/session-footer.d.ts +2 -1
- package/dist/integrations/pi/components/upstream/components/session-footer.js +12 -7
- package/dist/integrations/pi/components/upstream/history/editor-core.js +1 -1
- package/dist/integrations/pi/engine/adapter.d.ts +2 -1
- package/dist/integrations/pi/engine/adapter.js +181 -84
- package/dist/integrations/pi/engine/tool-rendering.d.ts +13 -0
- package/dist/integrations/pi/engine/tool-rendering.js +122 -0
- package/dist/integrations/pi/session-ui/prompt-chips.d.ts +30 -0
- package/dist/integrations/pi/session-ui/prompt-chips.js +128 -1
- package/dist/integrations/pi/session-ui/prompt-history-controller.d.ts +8 -0
- package/dist/integrations/pi/session-ui/prompt-history-controller.js +30 -2
- package/dist/integrations/pi/session-ui/prompt-suggestion-controller.d.ts +11 -3
- package/dist/integrations/pi/session-ui/prompt-suggestion-controller.js +126 -51
- package/dist/integrations/pi/session-ui/session-shell-root.d.ts +24 -2
- package/dist/integrations/pi/session-ui/session-shell-root.js +108 -38
- package/dist/integrations/pi/session-ui/session-shell.js +51 -31
- package/dist/integrations/pi/session-ui/session-viewport-controller.d.ts +7 -1
- package/dist/integrations/pi/session-ui/session-viewport-controller.js +106 -16
- package/dist/integrations/pi/tui-runtime/adapter.d.ts +2 -0
- package/dist/integrations/pi/tui-runtime/adapter.js +35 -11
- package/dist/integrations/pi/tui-runtime/contracts.d.ts +4 -1
- package/dist/integrations/pi/tui-runtime/index.d.ts +1 -0
- package/dist/integrations/pi/tui-runtime/index.js +1 -0
- package/dist/integrations/pi/tui-runtime/mouse-report-input.d.ts +13 -0
- package/dist/integrations/pi/tui-runtime/mouse-report-input.js +59 -0
- package/dist/integrations/pi/tui-runtime/overlay-geometry.d.ts +32 -0
- package/dist/integrations/pi/tui-runtime/overlay-geometry.js +159 -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/product-identity.d.ts +1 -1
- package/dist/product-identity.js +1 -1
- package/dist/product-identity.json +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 +2 -3
- package/dist/ui/components/line-input.js +4 -20
- package/dist/ui/components/mouse.d.ts +1 -1
- package/dist/ui/components/mouse.js +4 -3
- package/dist/ui/components/prompt-input.d.ts +32 -0
- package/dist/ui/components/prompt-input.js +54 -0
- package/docs/architecture/boundaries.md +1 -0
- package/docs/architecture/history-editor-provenance.md +5 -1
- package/docs/architecture/project-structure.md +1 -0
- package/docs/architecture/prompt-suggestions.md +68 -0
- package/docs/architecture/ui-reference-provenance.md +23 -0
- package/docs/features/modal-content-interaction.md +37 -0
- package/package.json +1 -1
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { TuiAltScreen, isFocusable } from "#pi-tui";
|
|
2
|
+
/** Observes public component renders in compositor order, without accessing renderer state. */
|
|
3
|
+
export class OverlayGeometryTracker {
|
|
4
|
+
publish;
|
|
5
|
+
#frame = [];
|
|
6
|
+
#pending;
|
|
7
|
+
#columns = 0;
|
|
8
|
+
#rows = 0;
|
|
9
|
+
ports = new WeakMap();
|
|
10
|
+
constructor(publish) {
|
|
11
|
+
this.publish = publish;
|
|
12
|
+
}
|
|
13
|
+
invalidate() {
|
|
14
|
+
if (this.#frame === null)
|
|
15
|
+
return;
|
|
16
|
+
this.#frame = null;
|
|
17
|
+
this.publish(null);
|
|
18
|
+
}
|
|
19
|
+
begin(columns, rows) {
|
|
20
|
+
this.#columns = columns;
|
|
21
|
+
this.#rows = rows;
|
|
22
|
+
this.#pending = [];
|
|
23
|
+
}
|
|
24
|
+
record(component, options, width, height) {
|
|
25
|
+
if (this.#pending === undefined)
|
|
26
|
+
return;
|
|
27
|
+
const region = overlayPointerSurface(component, options, width, height, this.#columns, this.#rows);
|
|
28
|
+
if (region !== undefined)
|
|
29
|
+
this.#pending.push(region);
|
|
30
|
+
}
|
|
31
|
+
end() {
|
|
32
|
+
const next = this.#pending ?? [];
|
|
33
|
+
this.#pending = undefined;
|
|
34
|
+
if (this.#frame !== null && samePointerSurfaces(this.#frame, next))
|
|
35
|
+
return;
|
|
36
|
+
this.#frame = next;
|
|
37
|
+
this.publish(next);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/** The protected composition hook brackets the actual visible, focus-ordered overlay renders. */
|
|
41
|
+
export class GeometryObservedAltScreen extends TuiAltScreen {
|
|
42
|
+
geometry;
|
|
43
|
+
#overlayComponents = new WeakMap();
|
|
44
|
+
get overlayFocused() {
|
|
45
|
+
return this.isOverlayFocused();
|
|
46
|
+
}
|
|
47
|
+
setFocus(component) {
|
|
48
|
+
super.setFocus(component === null ? null : this.#overlayComponents.get(component) ?? component);
|
|
49
|
+
}
|
|
50
|
+
showOverlay(component, options) {
|
|
51
|
+
// Compatibility: all overlay entries cross the public method, including the runtime's search
|
|
52
|
+
// dialog. Observe a wrapper, never replace a method on an installed component.
|
|
53
|
+
const geometry = this.geometry;
|
|
54
|
+
const port = geometry?.ports.get(component) ?? component;
|
|
55
|
+
const wrapper = {
|
|
56
|
+
get focused() { return isFocusable(component) && component.focused; },
|
|
57
|
+
set focused(value) { if (isFocusable(component))
|
|
58
|
+
component.focused = value; },
|
|
59
|
+
get wantsKeyRelease() { return component.wantsKeyRelease ?? false; },
|
|
60
|
+
render(width) {
|
|
61
|
+
const lines = component.render(width);
|
|
62
|
+
geometry?.record(port, options, width, lines.length);
|
|
63
|
+
return lines;
|
|
64
|
+
},
|
|
65
|
+
invalidate: () => component.invalidate(),
|
|
66
|
+
handleInput: data => component.handleInput?.(data),
|
|
67
|
+
};
|
|
68
|
+
this.#overlayComponents.set(component, wrapper);
|
|
69
|
+
geometry?.invalidate();
|
|
70
|
+
const handle = super.showOverlay(wrapper, options);
|
|
71
|
+
return {
|
|
72
|
+
hide: () => { geometry?.invalidate(); handle.hide(); this.#overlayComponents.delete(component); },
|
|
73
|
+
setHidden: hidden => {
|
|
74
|
+
if (hidden !== handle.isHidden())
|
|
75
|
+
geometry?.invalidate();
|
|
76
|
+
handle.setHidden(hidden);
|
|
77
|
+
},
|
|
78
|
+
isHidden: () => handle.isHidden(),
|
|
79
|
+
focus: () => { geometry?.invalidate(); handle.focus(); },
|
|
80
|
+
unfocus: target => {
|
|
81
|
+
geometry?.invalidate();
|
|
82
|
+
if (target === undefined)
|
|
83
|
+
handle.unfocus();
|
|
84
|
+
else
|
|
85
|
+
handle.unfocus({ target: target.target === null ? null : this.#overlayComponents.get(target.target) ?? target.target });
|
|
86
|
+
},
|
|
87
|
+
isFocused: () => handle.isFocused(),
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
hideOverlay() {
|
|
91
|
+
this.geometry?.invalidate();
|
|
92
|
+
super.hideOverlay();
|
|
93
|
+
}
|
|
94
|
+
compositeOverlays(lines, width, height) {
|
|
95
|
+
this.geometry?.begin(width, height);
|
|
96
|
+
const result = super.compositeOverlays(lines, width, height);
|
|
97
|
+
this.geometry?.end();
|
|
98
|
+
return result;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/** Compare only geometry and ownership, never animation content or transcript position. */
|
|
102
|
+
export function samePointerSurfaces(a, b) {
|
|
103
|
+
return a.length === b.length && a.every((surface, index) => {
|
|
104
|
+
const other = b[index];
|
|
105
|
+
return surface.component === other.component && surface.columnStart === other.columnStart
|
|
106
|
+
&& surface.columnEnd === other.columnEnd && surface.rowStart === other.rowStart && surface.rowEnd === other.rowEnd;
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Resolve the pinned public overlay options against the actual render width/height.
|
|
111
|
+
* The runtime still owns layout and painting. Independent terminal-cell tests certify
|
|
112
|
+
* this option-to-hit-region projection against its compositor, including clipping.
|
|
113
|
+
*/
|
|
114
|
+
export function overlayPointerSurface(component, options, width, renderedHeight, columns, rows) {
|
|
115
|
+
const opt = options ?? {};
|
|
116
|
+
const margin = typeof opt.margin === "number"
|
|
117
|
+
? { top: opt.margin, right: opt.margin, bottom: opt.margin, left: opt.margin }
|
|
118
|
+
: opt.margin ?? {};
|
|
119
|
+
const top = Math.max(0, margin.top ?? 0);
|
|
120
|
+
const right = Math.max(0, margin.right ?? 0);
|
|
121
|
+
const bottom = Math.max(0, margin.bottom ?? 0);
|
|
122
|
+
const left = Math.max(0, margin.left ?? 0);
|
|
123
|
+
const availableHeight = Math.max(1, rows - top - bottom);
|
|
124
|
+
const availableWidth = Math.max(1, columns - left - right);
|
|
125
|
+
const limit = size(opt.maxHeight, rows);
|
|
126
|
+
const height = limit === undefined ? renderedHeight : Math.min(renderedHeight, Math.floor(Math.max(1, Math.min(limit, availableHeight))));
|
|
127
|
+
const anchor = opt.anchor ?? "center";
|
|
128
|
+
const anchorRow = anchor.startsWith("top-") ? top
|
|
129
|
+
: anchor.startsWith("bottom-") ? top + availableHeight - height
|
|
130
|
+
: top + Math.floor((availableHeight - height) / 2);
|
|
131
|
+
const anchorColumn = anchor.endsWith("-left") || anchor === "left-center" ? left
|
|
132
|
+
: anchor.endsWith("-right") || anchor === "right-center" ? left + availableWidth - width
|
|
133
|
+
: left + Math.floor((availableWidth - width) / 2);
|
|
134
|
+
const row = Math.max(top, Math.min(position(opt.row, top, availableHeight, height, anchorRow) + (opt.offsetY ?? 0), rows - bottom - height));
|
|
135
|
+
const column = Math.max(left, Math.min(position(opt.col, left, availableWidth, width, anchorColumn) + (opt.offsetX ?? 0), columns - right - width));
|
|
136
|
+
if (height <= 0 || row >= rows || column >= columns)
|
|
137
|
+
return undefined;
|
|
138
|
+
return {
|
|
139
|
+
component,
|
|
140
|
+
rowStart: row + 1,
|
|
141
|
+
rowEnd: Math.min(rows, row + height),
|
|
142
|
+
columnStart: column + 1,
|
|
143
|
+
columnEnd: Math.min(columns, column + width),
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
function size(value, extent) {
|
|
147
|
+
if (typeof value !== "string")
|
|
148
|
+
return value;
|
|
149
|
+
const match = /^(\d+(?:\.\d+)?)%$/.exec(value);
|
|
150
|
+
return match === null ? undefined : Math.floor(extent * Number(match[1]) / 100);
|
|
151
|
+
}
|
|
152
|
+
function position(value, margin, available, extent, anchor) {
|
|
153
|
+
if (value === undefined)
|
|
154
|
+
return anchor;
|
|
155
|
+
if (typeof value === "number")
|
|
156
|
+
return value;
|
|
157
|
+
const percent = size(value, Math.max(0, available - extent));
|
|
158
|
+
return percent === undefined ? margin + Math.floor((available - extent) / 2) : margin + percent;
|
|
159
|
+
}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"platform": "darwin",
|
|
6
6
|
"architecture": "arm64",
|
|
7
7
|
"capability": "supported",
|
|
8
|
-
"builtAt": "2026-09-
|
|
8
|
+
"builtAt": "2026-09-13T13:11:55.967Z",
|
|
9
9
|
"artifact": {
|
|
10
10
|
"filename": "process-guardian",
|
|
11
11
|
"sha256": "9db1726bbe3fc2e8292f2ead1e7e9d0fd4d7d0dc9217b372582bf354b0f565dc",
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"platform": "linux",
|
|
6
6
|
"architecture": "x64",
|
|
7
7
|
"capability": "supported",
|
|
8
|
-
"builtAt": "2026-09-
|
|
8
|
+
"builtAt": "2026-09-13T13:12:46.201Z",
|
|
9
9
|
"artifact": {
|
|
10
10
|
"filename": "process-guardian",
|
|
11
11
|
"sha256": "d8cda6b0c7cb36c0cc41e90802aceebf6c02eaebbc08a83d7d963d2e918dbd7a",
|
|
@@ -5,10 +5,10 @@
|
|
|
5
5
|
"platform": "win32",
|
|
6
6
|
"architecture": "x64",
|
|
7
7
|
"capability": "supported",
|
|
8
|
-
"builtAt": "2026-09-
|
|
8
|
+
"builtAt": "2026-09-13T13:12:19.326Z",
|
|
9
9
|
"artifact": {
|
|
10
10
|
"filename": "process-guardian.exe",
|
|
11
|
-
"sha256": "
|
|
11
|
+
"sha256": "e6a7be98f08249e90a59466be1c132959a55a80830b000a660724da4d572887a",
|
|
12
12
|
"size": 177664
|
|
13
13
|
},
|
|
14
14
|
"provenance": {
|
|
Binary file
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
declare const ENVIRONMENT_KEYS: readonly ["configDir", "dataDir", "databasePath", "developmentInstanceId", "developmentRoot", "endpoint", "paneId", "probeTrace", "processGuardianPath", "profileHome", "runtimeDir", "startupTrace", "terminalSessionId"];
|
|
1
|
+
declare const ENVIRONMENT_KEYS: readonly ["configDir", "dataDir", "databasePath", "developmentInstanceId", "developmentRoot", "endpoint", "paneId", "probeTrace", "processGuardianPath", "profileHome", "runtimeDir", "startupTrace", "suggestionDiagnostics", "terminalSessionId"];
|
|
2
2
|
declare const FILESYSTEM_KEYS: readonly ["slug", "windowsDirectory", "unixDirectory", "temporaryPrefix"];
|
|
3
3
|
declare const STATE_KEYS: readonly ["windowsControlDirectory", "unixControlDirectory", "developmentDirectory", "piAgentProfile", "piVanillaProfile"];
|
|
4
4
|
declare const ENDPOINT_KEYS: readonly ["windowsPipeStem", "unixSocketFilename", "metadataFilename", "supervisorLogFilename", "databaseFilename"];
|
package/dist/product-identity.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import rawIdentity from "./product-identity.json" with { type: "json" };
|
|
2
2
|
const ENVIRONMENT_KEYS = [
|
|
3
3
|
"configDir", "dataDir", "databasePath", "developmentInstanceId", "developmentRoot", "endpoint",
|
|
4
|
-
"paneId", "probeTrace", "processGuardianPath", "profileHome", "runtimeDir", "startupTrace", "terminalSessionId",
|
|
4
|
+
"paneId", "probeTrace", "processGuardianPath", "profileHome", "runtimeDir", "startupTrace", "suggestionDiagnostics", "terminalSessionId",
|
|
5
5
|
];
|
|
6
6
|
const FILESYSTEM_KEYS = ["slug", "windowsDirectory", "unixDirectory", "temporaryPrefix"];
|
|
7
7
|
const STATE_KEYS = ["windowsControlDirectory", "unixControlDirectory", "developmentDirectory", "piAgentProfile", "piVanillaProfile"];
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { UiTheme } from "./theme.js";
|
|
1
2
|
export type LineInputOutcome = {
|
|
2
3
|
readonly kind: "editing";
|
|
3
4
|
} | {
|
|
@@ -47,9 +48,6 @@ export declare class LineInput {
|
|
|
47
48
|
}
|
|
48
49
|
/** Applies one key to the input and reports whether the caller should commit. */
|
|
49
50
|
export declare function handleLineInputKey(input: LineInput, data: string): LineInputOutcome;
|
|
50
|
-
/** Draws the reference prompt rule foreground-only so underlying cells survive. */
|
|
51
|
-
export declare function promptRule(width: number): string;
|
|
52
|
-
export declare const PROMPT_GLYPH = "\u001B[38;2;154;160;166m\u276F\u001B[39m ";
|
|
53
51
|
/**
|
|
54
52
|
* The caret the reference draws: the cell under it is reversed rather than given
|
|
55
53
|
* a colour of its own, so it reads as a block in whatever theme is in use.
|
|
@@ -60,6 +58,7 @@ export interface InputRowOptions {
|
|
|
60
58
|
readonly placeholder?: string;
|
|
61
59
|
/** Rules above and below, in the prompt's own grey. Default true. */
|
|
62
60
|
readonly ruled?: boolean;
|
|
61
|
+
readonly theme?: Pick<UiTheme, "fg">;
|
|
63
62
|
}
|
|
64
63
|
export interface InputRow {
|
|
65
64
|
/** The rows to draw, already padded to the width. */
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { displayWidth, faint, truncateToWidth } from "./text.js";
|
|
2
|
+
import { PromptInput } from "./prompt-input.js";
|
|
2
3
|
const MAX_VALUE_LENGTH = 4_096;
|
|
3
4
|
const GRAPHEMES = new Intl.Segmenter(undefined, { granularity: "grapheme" });
|
|
4
5
|
/** Character class for a word boundary: 0 space, 1 word, 2 anything else. */
|
|
@@ -198,11 +199,6 @@ export function handleLineInputKey(input, data) {
|
|
|
198
199
|
input.insert(data);
|
|
199
200
|
return { kind: "editing" };
|
|
200
201
|
}
|
|
201
|
-
/** Draws the reference prompt rule foreground-only so underlying cells survive. */
|
|
202
|
-
export function promptRule(width) {
|
|
203
|
-
return `\u001b[38;2;154;160;166m${"─".repeat(Math.max(0, width))}\u001b[39m`;
|
|
204
|
-
}
|
|
205
|
-
export const PROMPT_GLYPH = `\u001b[38;2;154;160;166m❯\u001b[39m `;
|
|
206
202
|
/**
|
|
207
203
|
* The caret the reference draws: the cell under it is reversed rather than given
|
|
208
204
|
* a colour of its own, so it reads as a block in whatever theme is in use.
|
|
@@ -212,8 +208,8 @@ export function caretCell(text) {
|
|
|
212
208
|
}
|
|
213
209
|
/** The input row as the reference draws one, padded to exactly the width. */
|
|
214
210
|
export function renderInputRow(input, width, options = {}) {
|
|
215
|
-
const
|
|
216
|
-
const view = input.view(
|
|
211
|
+
const presentation = new PromptInput(options.theme);
|
|
212
|
+
const view = input.view(Math.max(0, width - presentation.geometry(width).prefixWidth));
|
|
217
213
|
const placeholder = options.placeholder ?? "";
|
|
218
214
|
const empty = view.text.length === 0 && placeholder.length > 0;
|
|
219
215
|
const before = view.text.slice(0, view.caretColumn);
|
|
@@ -224,17 +220,5 @@ export function renderInputRow(input, width, options = {}) {
|
|
|
224
220
|
const body = empty
|
|
225
221
|
? `${caretCell(placeholder.slice(0, 1))}${faint(placeholder.slice(1))}`
|
|
226
222
|
: `${before}${caretCell(under)}${after}`;
|
|
227
|
-
|
|
228
|
-
? placeholder
|
|
229
|
-
: `${view.text}${view.caretColumn >= view.text.length ? " " : ""}`;
|
|
230
|
-
const row = padVisible(truncateToWidth(`${PROMPT_GLYPH}${body}`, width), width, `❯ ${plain}`);
|
|
231
|
-
if (options.ruled === false)
|
|
232
|
-
return { lines: [row] };
|
|
233
|
-
const rule = promptRule(width);
|
|
234
|
-
return { lines: [rule, row, rule] };
|
|
235
|
-
}
|
|
236
|
-
/** Pads by visible width, so styling escapes do not shift the layout. */
|
|
237
|
-
function padVisible(line, width, raw) {
|
|
238
|
-
const visible = displayWidth(raw);
|
|
239
|
-
return visible >= width ? line : line + " ".repeat(width - visible);
|
|
223
|
+
return { lines: presentation.render(width, () => ({ rows: [body] }), options.ruled) };
|
|
240
224
|
}
|
|
@@ -22,4 +22,4 @@ export interface RoutedMouseInput {
|
|
|
22
22
|
* Routes every SGR report independently, preserving keyboard bytes and mouse
|
|
23
23
|
* reports the caller does not claim even when they share one physical chunk.
|
|
24
24
|
*/
|
|
25
|
-
export declare function routeMouseInput(data: string, claim: (event: PaneMouseEvent) => boolean): RoutedMouseInput;
|
|
25
|
+
export declare function routeMouseInput(data: string, claim: (event: PaneMouseEvent, report: string) => boolean): RoutedMouseInput;
|
|
@@ -40,12 +40,13 @@ export function routeMouseInput(data, claim) {
|
|
|
40
40
|
let output = "";
|
|
41
41
|
let index = 0;
|
|
42
42
|
let consumed = false;
|
|
43
|
-
|
|
44
|
-
|
|
43
|
+
// Concurrency: reentrant routing may deliver a report to another component using this parser.
|
|
44
|
+
const pattern = new RegExp(SGR_PATTERN.source, "g");
|
|
45
|
+
for (let match = pattern.exec(data); match !== null; match = pattern.exec(data)) {
|
|
45
46
|
output += data.slice(index, match.index);
|
|
46
47
|
index = match.index + match[0].length;
|
|
47
48
|
const event = toEvent(Number.parseInt(match[1] ?? "", 10), Number.parseInt(match[2] ?? "", 10), Number.parseInt(match[3] ?? "", 10), match[4] === "m");
|
|
48
|
-
if (event !== null && claim(event))
|
|
49
|
+
if (event !== null && claim(event, match[0]))
|
|
49
50
|
consumed = true;
|
|
50
51
|
else
|
|
51
52
|
output += match[0];
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { type UiTheme } from "./theme.js";
|
|
2
|
+
export declare const PROMPT_GLYPH = "\u276F ";
|
|
3
|
+
export declare const PROMPT_PREFIX_WIDTH = 2;
|
|
4
|
+
export declare function promptRuleText(text: string): string;
|
|
5
|
+
export declare function promptRule(width: number): string;
|
|
6
|
+
export declare function promptArrow(text: string, theme: Pick<UiTheme, "fg">): string;
|
|
7
|
+
export interface PromptInputBody {
|
|
8
|
+
readonly rows: readonly string[];
|
|
9
|
+
/** Semantic border fragments retain editor history and scroll annotations. */
|
|
10
|
+
readonly topRule?: string | undefined;
|
|
11
|
+
readonly bottomRule?: string | undefined;
|
|
12
|
+
readonly after?: readonly string[];
|
|
13
|
+
}
|
|
14
|
+
export interface PromptInputMetrics {
|
|
15
|
+
readonly measure: (text: string) => number;
|
|
16
|
+
readonly truncate: (text: string, width: number) => string;
|
|
17
|
+
}
|
|
18
|
+
/** Composes shared input chrome around controller-owned body rows without owning editing state. */
|
|
19
|
+
export declare class PromptInput {
|
|
20
|
+
readonly theme: Pick<UiTheme, "fg">;
|
|
21
|
+
readonly metrics: PromptInputMetrics;
|
|
22
|
+
constructor(theme?: Pick<UiTheme, "fg">, metrics?: PromptInputMetrics);
|
|
23
|
+
styleRule(text: string): string;
|
|
24
|
+
geometry(width: number, padding?: number): {
|
|
25
|
+
prefixWidth: number;
|
|
26
|
+
innerWidth: number;
|
|
27
|
+
paddingX: number;
|
|
28
|
+
contentWidth: number;
|
|
29
|
+
layoutWidth: number;
|
|
30
|
+
};
|
|
31
|
+
render(width: number, renderBody: (innerWidth: number) => PromptInputBody, ruled?: boolean, padding?: number): string[];
|
|
32
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { displayWidth, truncateToWidth } from "./text.js";
|
|
2
|
+
import { PLAIN_THEME } from "./theme.js";
|
|
3
|
+
export const PROMPT_GLYPH = "❯ ";
|
|
4
|
+
export const PROMPT_PREFIX_WIDTH = 2;
|
|
5
|
+
export function promptRuleText(text) {
|
|
6
|
+
return `\u001b[38;2;154;160;166m${text}\u001b[39m`;
|
|
7
|
+
}
|
|
8
|
+
export function promptRule(width) {
|
|
9
|
+
return promptRuleText("─".repeat(Math.max(0, width)));
|
|
10
|
+
}
|
|
11
|
+
export function promptArrow(text, theme) {
|
|
12
|
+
return theme.fg("muted", text);
|
|
13
|
+
}
|
|
14
|
+
/** Composes shared input chrome around controller-owned body rows without owning editing state. */
|
|
15
|
+
export class PromptInput {
|
|
16
|
+
theme;
|
|
17
|
+
metrics;
|
|
18
|
+
constructor(theme = PLAIN_THEME, metrics = { measure: displayWidth, truncate: truncateToWidth }) {
|
|
19
|
+
this.theme = theme;
|
|
20
|
+
this.metrics = metrics;
|
|
21
|
+
}
|
|
22
|
+
styleRule(text) { return promptRuleText(text); }
|
|
23
|
+
geometry(width, padding = 0) {
|
|
24
|
+
const prefixWidth = Math.min(PROMPT_PREFIX_WIDTH, Math.max(0, width));
|
|
25
|
+
// Compatibility: editor wrapping needs room for a two-cell grapheme plus its cursor cell.
|
|
26
|
+
// Lay out against a safe virtual width, then clip only at the shared frame boundary.
|
|
27
|
+
const innerWidth = Math.max(3 + padding * 2, width - prefixWidth);
|
|
28
|
+
const paddingX = Math.min(padding, Math.max(0, Math.floor((innerWidth - 1) / 2)));
|
|
29
|
+
const contentWidth = Math.max(1, innerWidth - paddingX * 2);
|
|
30
|
+
return { prefixWidth, innerWidth, paddingX, contentWidth, layoutWidth: Math.max(1, contentWidth - (paddingX ? 0 : 1)) };
|
|
31
|
+
}
|
|
32
|
+
render(width, renderBody, ruled = true, padding = 0) {
|
|
33
|
+
if (width <= 0)
|
|
34
|
+
return [];
|
|
35
|
+
const { prefixWidth, innerWidth } = this.geometry(width, padding);
|
|
36
|
+
const body = renderBody(innerWidth);
|
|
37
|
+
const fit = (row) => {
|
|
38
|
+
const clipped = this.metrics.truncate(row, width);
|
|
39
|
+
return clipped + " ".repeat(Math.max(0, width - this.metrics.measure(clipped)));
|
|
40
|
+
};
|
|
41
|
+
const rows = body.rows.map((row, index) => fit(`${index === 0
|
|
42
|
+
? promptArrow(PROMPT_GLYPH, this.theme)
|
|
43
|
+
: " ".repeat(prefixWidth)}${row}`));
|
|
44
|
+
const rule = (fragment) => fragment === undefined
|
|
45
|
+
? promptRule(width)
|
|
46
|
+
: fit(fragment + promptRule(Math.max(0, width - this.metrics.measure(fragment))));
|
|
47
|
+
return [
|
|
48
|
+
...(ruled ? [rule(body.topRule)] : []),
|
|
49
|
+
...rows,
|
|
50
|
+
...(ruled ? [rule(body.bottomRule)] : []),
|
|
51
|
+
...(body.after ?? []).map(row => fit(`${" ".repeat(prefixWidth)}${row}`)),
|
|
52
|
+
];
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -30,6 +30,7 @@ The owned Pi-backed surface is not an arbitrary-CLI terminal multiplexer. A feat
|
|
|
30
30
|
- `protocol`: additive control handshake, bounded line framing, authenticated launch-instance commands, typed stop intent, snapshots, and command results.
|
|
31
31
|
- `launch`: resolves the effective profile home and the history-specific data root. Composition uses its public path resolver to select `~/.a1/data` for history unless `A1_DATA_DIR` is explicit; global control/release/runtime/cache paths stay unchanged.
|
|
32
32
|
- `prompt-history`: narrowly typed user-input retention in separate profile-owned databases under `~/.a1/data/history` (or `<A1_DATA_DIR>/history`), with bounded worker queues and no supervisor/control-store payloads. It receives a resolved root rather than importing launch-feature internals; it never probes, imports, or deletes the former platform-default history. The editor's owned history state machine remains inside the Pi component boundary; the session UI only coordinates snapshots and classified submissions.
|
|
33
|
+
- `prompt-suggestions`: opt-in bounded diagnostic snapshots of neutral suggestion lifecycle metadata. Composition supplies an explicit local destination; this feature never imports Pi, generates suggestions, stores conversation text, or owns the editor. See [prompt suggestion diagnostics](prompt-suggestions.md).
|
|
33
34
|
- `storage`: SQLite migrations, prior-boot reconciliation, and plural launch-instance persistence. Legacy foreground rows are historical migration input and never authorize current ownership.
|
|
34
35
|
- `supervisor`: endpoint identity, plural cohort ownership, per-instance reconciliation, and aggregate release shutdown coordination. It owns no terminal surface.
|
|
35
36
|
- `pi-engine-adapter`, `pi-component-adapter`, `pi-tui-runtime-adapter`, and `pi-session-ui-integration`: isolate pinned Pi engine and presentation knowledge behind neutral contracts; product features do not import them directly. The session UI render root assembles semantic document and dock rows, while its focused viewport controller owns follow state, pointer routing, selection, and interaction timers. Owned-app route lifecycle belongs to the neutral `ui-apps` owner and is only hosted by the Pi session UI.
|
|
@@ -39,7 +39,11 @@ snapshot installation and observation methods preserve input and undo state,
|
|
|
39
39
|
freeze an active browse cycle, and change caret placement only in that mode.
|
|
40
40
|
The editor renders position/overflow in its existing border. The position label
|
|
41
41
|
uses an injected neutral status-text style (`dim`), while the surrounding rules
|
|
42
|
-
retain the active input-border color, as clarified during manual review.
|
|
42
|
+
retain the active input-border color, as clarified during manual review. The approved
|
|
43
|
+
`show-autocomplete-above-prompt` review refinement omits the literal `History` title:
|
|
44
|
+
`─── 1/100 ─…` keeps the same four-cell inset, count calculation, dim color, optional
|
|
45
|
+
scroll-overflow suffix, and clipping. Core and shell regressions verify numbering,
|
|
46
|
+
100-entry recall, draft restoration, and the absence of the title. Recall temporarily
|
|
43
47
|
separates the draft's live paste backing from recalled literal text, restoring it
|
|
44
48
|
on return; ordinary pinned mode remains source-equivalent.
|
|
45
49
|
|
|
@@ -14,6 +14,7 @@ src/
|
|
|
14
14
|
launch/ launch profiles, profile paths, and runtime selection
|
|
15
15
|
owned-ui/ owned screens, settings application, diagnostics, and runtime lifecycle
|
|
16
16
|
prompt-history/ profile-isolated prompt retention and bounded SQLite worker lifecycle
|
|
17
|
+
prompt-suggestions/ opt-in bounded metadata capture and local diagnostic snapshots
|
|
17
18
|
workspace/ multi-agent presentation, reducer state, routing, and persistence orchestration
|
|
18
19
|
contracts/
|
|
19
20
|
agent-engine/ dependency-free agent engine, session, package, and capability ports
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Prompt suggestion reliability
|
|
2
|
+
|
|
3
|
+
Bare A1 predicts a short next user input with one isolated request using the selected model. A clear offered action consistent with recent user intent, such as `archive it` after accepted merged work, is preferred over abstaining merely because optional testing was also offered. Required validation, conflicting intent, and unresolved choices still allow no suggestion. There is no quoted-text extraction fallback.
|
|
4
|
+
|
|
5
|
+
## Request and editor boundaries
|
|
6
|
+
|
|
7
|
+
The adapter resolves the lowest supported effort from the selected session model's available thinking levels, without changing the main thinking setting. Non-reasoning models omit reasoning controls; supported `off` also omits the optional reasoning argument, matching the model runtime's ordinary completion contract. Other supported efforts are supplied explicitly. Missing capabilities produce `unavailable`, not a guessed provider option. Provider mapping/clamping remains owned by the pinned model runtime; the diagnostic policy describes the applied request option, not inferred server-side computation.
|
|
8
|
+
|
|
9
|
+
The controller retains the 15-second deadline, does not retry or switch models, and retires a timeout before aborting. A provider that ignores abort cannot revive retired text. A timely candidate remains private until matching settlement. Ghost text is not editor content, approval, or a submission: Tab accepts it into the editor, and Enter must be pressed separately to submit it. Typing, session/model replacement, continuation, or disabling suggestions invalidates unaccepted work. `a1 pi` does not install this feature.
|
|
10
|
+
|
|
11
|
+
## Opt-in local diagnostics
|
|
12
|
+
|
|
13
|
+
Set `A1_SUGGESTION_DIAGNOSTICS` to a **dedicated writable file** when launching bare A1. It is an explicit snapshot destination, not a log directory. The parent directory must already exist. The selected file is overwritten with the latest bounded snapshot; never select a session, source, credential, or other valuable file. Do not share one destination between running processes.
|
|
14
|
+
|
|
15
|
+
For a repository checkout in Git Bash:
|
|
16
|
+
|
|
17
|
+
```sh
|
|
18
|
+
npm run build && A1_SUGGESTION_DIAGNOSTICS="$PWD/.artifacts/suggestion-diagnostics.json" ./scripts/dev
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Create `.artifacts` first if it does not exist. For an installed release, use the same environment assignment before `a1`. The variable follows the ordinary launch environment; it adds no interactive flag, setting, footer row, or transcript message. SDK composition can supply `suggestionDiagnosticsPath` explicitly. Comparison or settings-free compositions ignore both paths.
|
|
22
|
+
|
|
23
|
+
While that session is running, inspect the selected JSON file using a local editor or:
|
|
24
|
+
|
|
25
|
+
```sh
|
|
26
|
+
node -e 'const fs=require("node:fs"); console.table(JSON.parse(fs.readFileSync(process.argv[1],"utf8")).records)' .artifacts/suggestion-diagnostics.json
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
The `prompt-suggestion-diagnostics-v1` snapshot contains at most 128 metadata records and 64 KiB. Capture is off by default. Records include process-local session/run/response/request numbers, bounded provider/model identifiers, applied reasoning policy, elapsed milliseconds, event, and optional reason. They exclude conversation/candidate text, tool data, raw errors, credentials, and session/worktree paths. There is no upload. Older records are evicted, so a retained terminal event can outlive its evicted start event. `request: 0` means no request was started for that decision.
|
|
30
|
+
|
|
31
|
+
| Event | Meaning |
|
|
32
|
+
| --- | --- |
|
|
33
|
+
| `skipped` | No request started; inspect `reason`. |
|
|
34
|
+
| `started` | One generator request was initiated; reasoning may be `unavailable` for an injected generator without policy metadata. |
|
|
35
|
+
| `displayed` | A current candidate was shown after settlement. |
|
|
36
|
+
| `empty` | The provider returned no text; this does not reveal the model's private reason for abstaining. |
|
|
37
|
+
| `rejected` | Returned content violated the candidate contract, contained tool calls, was truncated, or had invalid formatting/voice. |
|
|
38
|
+
| `provider-failure` | Provider error/rejection; raw error details are intentionally omitted. |
|
|
39
|
+
| `unavailable` | Model/runtime/capabilities or request identity were unavailable at the generator boundary. |
|
|
40
|
+
| `timeout` | The controller's 15-second deadline expired. |
|
|
41
|
+
| `cancelled` | Work was invalidated or aborted, including typing or continuation. |
|
|
42
|
+
| `stale-result` | A result or settlement identity did not match its request. |
|
|
43
|
+
| `presentation-blocked` | A valid candidate could not be shown; inspect `reason`. |
|
|
44
|
+
| `late-result-discarded` | A retired request eventually resolved/rejected; this is not a second terminal outcome or request. |
|
|
45
|
+
|
|
46
|
+
Eligibility/presentation reasons include `disabled`, `disposed`, `early-conversation`, `no-model`, `failed-response`, `incomplete-response`, `tool-continuation`, `replacement-input`, `modal`, `draft`, `not-ready`, `not-focused`, `autocomplete`, `prompt-mode`, and `stale-identity`. `ineligible` and `presentation-unavailable` cover injected ports without finer metadata. A missing suggestion alone is never evidence of timeout or intentional model abstention.
|
|
47
|
+
|
|
48
|
+
Writes are asynchronous and coalesced: at most one write and one pending latest snapshot. Sink failures do not affect generation, input, or rendering and do not appear in the UI. An unwritable destination may therefore produce no file; check the destination locally rather than diagnosing the model from that absence. Disposal clears in-memory capture and pending writes; a write already in progress can finish. Exported local files remain until you remove them.
|
|
49
|
+
|
|
50
|
+
To disable capture, omit/unset `A1_SUGGESTION_DIAGNOSTICS` before the next launch and remove the dedicated snapshot if no longer needed. This does not disable suggestions; `/settings` -> Agent -> Prompt suggestions controls generation independently.
|
|
51
|
+
|
|
52
|
+
## Verification and evidence limits
|
|
53
|
+
|
|
54
|
+
The focused tests cover the synthetic archive offer and required-testing/unresolved-choice counterexamples, unchanged primary thinking, typed outcomes, timeout/cancellation races, editor acceptance, and local privacy/bounds:
|
|
55
|
+
|
|
56
|
+
```sh
|
|
57
|
+
npx vitest run test/integrations/pi/session-ui/prompt-suggestion-controller.test.ts test/integrations/pi/engine/adapter.test.ts test/features/prompt-suggestions/diagnostics.test.ts test/contracts/owned-ui/contracts.test.ts test/integrations/pi/session-ui/session-shell.test.ts
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
A separate opt-in probe uses the profile's saved model/authentication with a scratch cwd, in-memory sessions/settings, disabled extensions/skills/prompts, and no tools. It performs five original-policy predictions, five revised-policy predictions, and one required-testing counterexample. This makes **11 provider requests** and can consume quota. Run only with explicit authorization:
|
|
61
|
+
|
|
62
|
+
```sh
|
|
63
|
+
RUN_PROMPT_SUGGESTION_PROVIDER_TEST=1 PROMPT_SUGGESTION_AGENT_DIR="C:/path/to/agent-profile" npx vitest run test/integrations/pi/engine/prompt-suggestion-provider.integration.test.ts
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
The probe reports outcome/latency summaries and archival-candidate counts, not raw text. It requires at least one revised archival continuation and rejects an archival continuation in the required-testing case. Five samples are a smoke comparison, not a statistical reliability guarantee. Missing credentials, model availability, or a successful real archival prediction leave provider acceptance pending; fake tests do not prove model quality.
|
|
67
|
+
|
|
68
|
+
For manual review, use a harmless synthetic conversation with two assistant responses: establish that an imaginary change was tested, accepted, and merged, then ask for a closeout offer containing `Say archive it` with optional extra testing. Do not ask the agent to perform real archival to test ghost text. Keep main thinking high and the editor untouched while waiting, inspect the outcome snapshot, check Tab/Enter separately, and repeat with typing cancellation and suggestions disabled. Verify required testing is not treated as optional. Real latency, model abstention, and existing context-reconstruction limitations can still leave the editor empty.
|
|
@@ -47,6 +47,29 @@ its own `core` facade layer; A1 is a product, so the port adapts imports and kee
|
|
|
47
47
|
|
|
48
48
|
## Deliberate differences
|
|
49
49
|
|
|
50
|
+
- **Above-prompt autocomplete.** The accepted `show-autocomplete-above-prompt` change
|
|
51
|
+
moves the existing default-editor completion block above the prompt in bare A1.
|
|
52
|
+
`src/integrations/pi/components/upstream/components/owned-editor.ts` exposes its
|
|
53
|
+
border-inclusive body height using the existing atomic-aware layout boundary;
|
|
54
|
+
`shell-editor-autocomplete.ts` moves rows only after prefix/selection decoration
|
|
55
|
+
and adds one top line using the editor's current border-color function.
|
|
56
|
+
The accepted refinement (#343) restores this line instead of panel shading.
|
|
57
|
+
The user-approved review refinement in #344 moves the existing trailing counter
|
|
58
|
+
into that line without parentheses, at the history-label inset and in the same dim
|
|
59
|
+
color. The select-list `scrollInfo` callback identifies its final counter row;
|
|
60
|
+
private selection state and rendered-frame parsing are not used. Counter meaning
|
|
61
|
+
and visibility remain unchanged, and candidate ANSI styling/padding stay intact. The line is
|
|
62
|
+
included in the body offset, disappears with the menu, and is non-text chrome.
|
|
63
|
+
Theme/mode/resize tests verify matching line color and width; terminal background
|
|
64
|
+
replay verifies the menu has no added shading.
|
|
65
|
+
The session shell uses the resulting body offset for pointer routing. Neither
|
|
66
|
+
editor implementation, history mode, menu sizing, completion state, nor installed
|
|
67
|
+
Pi package changes. `a1 pi` remains below-prompt and byte-identical to the independently
|
|
68
|
+
run pinned editor in `test/integrations/pi/components/pinned-editor-input-parity.test.ts`.
|
|
69
|
+
Placement and whole-menu ANSI evidence is in `editor-autocomplete-placement.test.ts`;
|
|
70
|
+
shell checkpoint replay covers final cells, cursor rows, resizing, detached/streaming
|
|
71
|
+
transcripts, widgets, pointer selection, and extension-editor restoration. Physical
|
|
72
|
+
acceptance of the exact build remains required before merging the implementation.
|
|
50
73
|
- **Owned in-session routes are overlays.** The A1 UI reference owns its surface and can switch screens; A1 renders in-session owned routes through the pinned Pi TUI as full-viewport overlays. The pre-resource trust selector is separate and uses a bounded alternate startup surface solely so every completion path can restore the untouched parent terminal before engine activation or a fail-closed diagnostic.
|
|
51
74
|
- **Colour is a port, not an import.** The reference takes a Pi `Theme` directly. A1 defines
|
|
52
75
|
`UiTheme` so the component layer never imports a Pi adapter and can be rendered plainly in
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Transcript interaction with modals
|
|
2
|
+
|
|
3
|
+
Bare A1 keeps the exposed transcript under its normal viewport controller while a modal is open. Scrolling, scrollbar settings, selection styling and boundaries, clipboard output, and speed are the same as without a modal. This is not a new selection implementation or a speed adjustment. The comparison `a1 pi` path is unchanged.
|
|
4
|
+
|
|
5
|
+
Pointer ownership follows the painted region, not keyboard focus. Docked replacements publish their input bounds from shell layout. The runtime observes public overlay component renders in the protected composition hook, including runtime-created search overlays, and projects the pinned public layout options into terminal hit regions. Terminal-cell tests compare that projection with actual overlay paint across anchors, percentages, margins, clipping, stacking, and resize. No installed dependency or private renderer state is modified.
|
|
6
|
+
|
|
7
|
+
A pointer gesture stays with its initiating surface through release. Geometry or ownership changes cancel the old viewport gesture, stop its timers, and consume the remaining gesture rather than delivering it to a newly revealed surface. Modal-covered cells do not accept new background gestures. Keyboard input remains with the focused modal, except that a nonempty A1 transcript selection owns its copy chord exactly once. There is no vanilla selection layer or vanilla copy notification for transcript gestures.
|
|
8
|
+
|
|
9
|
+
## Host inventory
|
|
10
|
+
|
|
11
|
+
The existing [modal transition inventory](../../config/baselines/modal-surface-inventory.json) remains the authority for specialized workflows. All its nodes cross one of these shared hosts; there is no command allowlist in pointer routing.
|
|
12
|
+
|
|
13
|
+
| Inventory family | Shared host | Interaction evidence |
|
|
14
|
+
| --- | --- | --- |
|
|
15
|
+
| `editor.root` | Ordinary editor and viewport | Existing viewport/controller and shell regression cases |
|
|
16
|
+
| `settings.*`, `models.*`, `trust.project` | Shell replacement input, including nested component states | Real settings/thinking, Model Configuration and model selection; generic replacement geometry and gesture tests |
|
|
17
|
+
| `session.*`, `tree.*` | Shell replacement input and nested replacement/confirmation controllers | Real fork plus existing resume/tree/missing-directory lifecycle cases; shared replacement tests |
|
|
18
|
+
| `auth.*` | Shell replacement input, including provider and nested prompts | Real login/logout plus existing OAuth/API-key/nested restoration cases; shared replacement tests |
|
|
19
|
+
| `command.import-confirm`, `operation.*` | Shell replacement confirmation/loader | Existing confirmation/loader lifecycle cases; shared replacement tests |
|
|
20
|
+
| `extension.select`, `extension.confirm`, `extension.input`, `extension.editor`, `extension.custom-editor`, `extension.custom-replacement` | Extension bridge to shell replacement input | Parameterized public extension UI tests, including an unlisted custom replacement |
|
|
21
|
+
| `extension.overlay`, backend dialogs, owned route panels | Runtime overlay host | Unlisted extension overlay, partial/full-cover/stacked overlay paint and pointer tests |
|
|
22
|
+
| Runtime transcript search | Public runtime overlay entry, not a shell command registration | Runtime-created search geometry/focus/restoration test |
|
|
23
|
+
|
|
24
|
+
Focused files:
|
|
25
|
+
|
|
26
|
+
- `test/integrations/pi/session-ui/session-viewport-controller.test.ts`: modal/no-modal selection/copy and render-cadence comparison, wheel/auto-scroll speed comparison, settings, boundary crossings, and gesture cancellation.
|
|
27
|
+
- `test/integrations/pi/session-ui/session-shell.test.ts`: real workflows, extension-host families, ordered mixed/chunked input, nested settings, resize, streaming, copy notification exclusion, and modal-over-selection terminal paint.
|
|
28
|
+
- `test/integrations/pi/tui-runtime/overlay-geometry.test.ts`: independent terminal-cell geometry and runtime search coverage.
|
|
29
|
+
- `test/integrations/pi/tui-runtime/mouse-report-input.test.ts`: ordered report delivery, partial reports, and opaque pasted text.
|
|
30
|
+
|
|
31
|
+
## Manual review
|
|
32
|
+
|
|
33
|
+
Build the exact candidate, then launch it through `./scripts/dev` from its worktree in Windows Terminal/Git Bash. Use `/hotkeys` to populate a long transcript, then open `/scoped-models`. Compare scrolling and drag/word/line selection with the dialog closed and open. Try the rail, copy, and jump-to-bottom; the model dialog must not navigate or close as a side effect of transcript gestures. Copy must not show Pi's `Copied` notification.
|
|
34
|
+
|
|
35
|
+
Repeat with `/model`, settings and a nested settings dialog, and extension-provided docked and floating surfaces. Check that typing, modal buttons and wheel handling, save, and cancel still work. Resize during a drag, close/reopen the modal, and repeat while output streams. A full-cover screen must not allow click-through into hidden transcript cells. `auto` scrollbar visibility remains activity-dependent; `hidden` remains hidden.
|
|
36
|
+
|
|
37
|
+
User confirmation of the exact built candidate is still required before merging the code PR. The baseline's pre-existing selection boundaries and speed are intentionally preserved, as clarified by the user; this change does not attempt to reconcile older specification wording by changing those behaviors.
|
package/package.json
CHANGED