@timurproko/a1 0.1.8-dev.139 → 0.1.8-dev.151
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/dist/composition/owned-ui.js +10 -7
- package/dist/composition/settings-route-host.d.ts +1 -1
- package/dist/composition/settings-route-host.js +1 -1
- package/dist/contracts/owned-ui/model.d.ts +8 -1
- package/dist/contracts/owned-ui/validation.js +11 -0
- package/dist/features/owned-ui/settings-app.js +2 -1
- package/dist/integrations/pi/components/owned-editor-ux.d.ts +55 -0
- package/dist/integrations/pi/components/owned-editor-ux.js +778 -0
- package/dist/integrations/pi/components/shell-editor-autocomplete.js +44 -6
- package/dist/integrations/pi/components/shell-shared-facade.d.ts +41 -7
- package/dist/integrations/pi/components/shell-shared-facade.js +3 -1
- package/dist/integrations/pi/components/upstream/adjacent/core/keybindings.d.ts +10 -2
- package/dist/integrations/pi/components/upstream/adjacent/core/keybindings.js +36 -3
- package/dist/integrations/pi/engine/adapter.js +5 -1
- package/dist/integrations/pi/engine/session-integration.d.ts +2 -1
- package/dist/integrations/pi/engine/session-integration.js +14 -3
- package/dist/integrations/pi/session-ui/prompt-chips.d.ts +23 -0
- package/dist/integrations/pi/session-ui/prompt-chips.js +242 -0
- package/dist/integrations/pi/session-ui/route-host.d.ts +5 -0
- package/dist/integrations/pi/{owned-ui → session-ui}/session-shell-root.d.ts +17 -3
- package/dist/integrations/pi/{owned-ui → session-ui}/session-shell-root.js +100 -260
- package/dist/integrations/pi/{owned-ui → session-ui}/session-shell.d.ts +1 -1
- package/dist/integrations/pi/{owned-ui → session-ui}/session-shell.js +52 -12
- package/dist/integrations/pi/session-ui/session-viewport-controller.d.ts +39 -0
- package/dist/integrations/pi/session-ui/session-viewport-controller.js +369 -0
- package/dist/integrations/pi/session-ui/system-clipboard.d.ts +11 -0
- package/dist/integrations/pi/session-ui/system-clipboard.js +101 -0
- package/dist/integrations/pi/tui-runtime/contracts.d.ts +2 -6
- package/dist/native/darwin-arm64/manifest.json +1 -1
- package/dist/native/linux-x64/manifest.json +3 -3
- package/dist/native/linux-x64/process-guardian +0 -0
- package/dist/native/win32-x64/manifest.json +3 -3
- package/dist/native/win32-x64/process-guardian.exe +0 -0
- package/dist/ui/apps/contracts.d.ts +23 -1
- package/dist/ui/apps/host.js +9 -1
- package/dist/ui/components/scrollbar.d.ts +2 -2
- package/dist/ui/components/scrollbar.js +3 -3
- package/dist/ui/components/spans.d.ts +16 -1
- package/dist/ui/components/spans.js +144 -2
- package/dist/ui/components/transcript-viewport.d.ts +6 -0
- package/dist/ui/components/transcript-viewport.js +36 -8
- package/dist/ui/settings/declarations.d.ts +1 -1
- package/dist/ui/settings/declarations.js +3 -3
- package/dist/ui/settings/migrations.js +9 -0
- package/docs/architecture/boundaries.md +1 -1
- package/docs/architecture/project-structure.md +1 -1
- package/package.json +4 -1
- package/dist/integrations/pi/owned-ui/route-host.d.ts +0 -29
- /package/dist/integrations/pi/{owned-ui → session-ui}/index.d.ts +0 -0
- /package/dist/integrations/pi/{owned-ui → session-ui}/index.js +0 -0
- /package/dist/integrations/pi/{owned-ui → session-ui}/route-host.js +0 -0
|
@@ -7,14 +7,14 @@
|
|
|
7
7
|
export function scrollbarWheelRows(speed) {
|
|
8
8
|
return speed === "high" ? 9 : speed === "fast" ? 6 : 3;
|
|
9
9
|
}
|
|
10
|
-
/** Selection edge distance
|
|
10
|
+
/** Selection edge distance uses the same configured rate as a wheel event. */
|
|
11
11
|
export function scrollbarSelectionRows(speed) {
|
|
12
|
-
return speed
|
|
12
|
+
return scrollbarWheelRows(speed);
|
|
13
13
|
}
|
|
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, always
|
|
17
|
+
// As in v2, 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";
|
|
@@ -10,4 +10,19 @@ export declare function overlaySpan(line: string, from: number, to: number, span
|
|
|
10
10
|
* foreground, bold, underline, and hyperlink styling. Embedded resets reapply
|
|
11
11
|
* the selection background, while leaving the span restores the row's styles.
|
|
12
12
|
*/
|
|
13
|
-
|
|
13
|
+
/** Wraps visible columns `[from,to)` in an explicit OSC 8 hyperlink. */
|
|
14
|
+
export declare function hyperlinkSgrSpan(line: string, from: number, to: number, target: string, widthOf?: (text: string) => number): string;
|
|
15
|
+
/**
|
|
16
|
+
* Gives existing and bare web links explicit, tightly bounded OSC 8 regions so
|
|
17
|
+
* the terminal owns dotted-idle/solid-hover decoration without leaking hover
|
|
18
|
+
* state into cells repainted during scrolling.
|
|
19
|
+
*/
|
|
20
|
+
export declare function nativeHyperlinkStyle(line: string, color?: (text: string, target: string) => string): string;
|
|
21
|
+
/**
|
|
22
|
+
* Held-button paint for terminal-native links. OSC 8 is removed so the terminal
|
|
23
|
+
* cannot switch a selected link to its solid hover underline. A dotted SGR
|
|
24
|
+
* underline preserves the idle appearance, and VS15 transiently breaks native
|
|
25
|
+
* plain-text URL detection without changing visible width.
|
|
26
|
+
*/
|
|
27
|
+
export declare function heldNativeHyperlinkStyle(line: string): string;
|
|
28
|
+
export declare function backgroundSgrSpan(line: string, from: number, to: number, on?: string, off?: string, widthOf?: (text: string) => number): string;
|
|
@@ -10,6 +10,8 @@ const ANSI_SPLIT = new RegExp(`(${ANSI_SEQUENCE})`);
|
|
|
10
10
|
const HYPERLINK = /^\]8;;([^]*)(?:|\\)?$/;
|
|
11
11
|
const HYPERLINK_OFF = "]8;;\\";
|
|
12
12
|
const SGR = /^\[[0-9;]*m$/;
|
|
13
|
+
const BARE_URL = /https?:\/\/[^\s\u0000-\u001f\u007f<>"']+/giu;
|
|
14
|
+
const TRAILING_URL_PUNCTUATION = /[.,;:!?]/u;
|
|
13
15
|
const GRAPHEMES = new Intl.Segmenter(undefined, { granularity: "grapheme" });
|
|
14
16
|
/**
|
|
15
17
|
* Replaces the visible columns `[from, to)` of `line` with `span`, preserving
|
|
@@ -63,7 +65,147 @@ export function overlaySpan(line, from, to, span) {
|
|
|
63
65
|
* foreground, bold, underline, and hyperlink styling. Embedded resets reapply
|
|
64
66
|
* the selection background, while leaving the span restores the row's styles.
|
|
65
67
|
*/
|
|
66
|
-
|
|
68
|
+
/** Wraps visible columns `[from,to)` in an explicit OSC 8 hyperlink. */
|
|
69
|
+
export function hyperlinkSgrSpan(line, from, to, target, widthOf = displayWidth) {
|
|
70
|
+
const on = `\u001b]8;;${target}\u001b\\`;
|
|
71
|
+
const off = "\u001b]8;;\u001b\\";
|
|
72
|
+
let output = "";
|
|
73
|
+
let column = 0;
|
|
74
|
+
let inside = false;
|
|
75
|
+
for (const token of line.split(ANSI_SPLIT)) {
|
|
76
|
+
if (!token)
|
|
77
|
+
continue;
|
|
78
|
+
if (token.startsWith("\u001b")) {
|
|
79
|
+
output += token;
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
for (const { segment } of GRAPHEMES.segment(token)) {
|
|
83
|
+
const shouldBeInside = column >= from && column < to;
|
|
84
|
+
if (shouldBeInside !== inside) {
|
|
85
|
+
output += shouldBeInside ? on : off;
|
|
86
|
+
inside = shouldBeInside;
|
|
87
|
+
}
|
|
88
|
+
output += segment;
|
|
89
|
+
column += widthOf(segment);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
if (inside)
|
|
93
|
+
output += off;
|
|
94
|
+
return output;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Gives existing and bare web links explicit, tightly bounded OSC 8 regions so
|
|
98
|
+
* the terminal owns dotted-idle/solid-hover decoration without leaking hover
|
|
99
|
+
* state into cells repainted during scrolling.
|
|
100
|
+
*/
|
|
101
|
+
export function nativeHyperlinkStyle(line, color = text => text) {
|
|
102
|
+
let hyperlinkTarget;
|
|
103
|
+
let sgrReplay = "";
|
|
104
|
+
let output = "";
|
|
105
|
+
for (const token of line.split(ANSI_SPLIT)) {
|
|
106
|
+
if (!token)
|
|
107
|
+
continue;
|
|
108
|
+
const link = HYPERLINK.exec(token);
|
|
109
|
+
if (link) {
|
|
110
|
+
hyperlinkTarget = (link[1] ?? "") || undefined;
|
|
111
|
+
output += token;
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
if (!token.startsWith("\u001b")) {
|
|
115
|
+
output += hyperlinkTarget === undefined
|
|
116
|
+
? decorateBareUrls(token, color, sgrReplay)
|
|
117
|
+
: color(token, hyperlinkTarget);
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
if (SGR.test(token)) {
|
|
121
|
+
if (token === "\u001b[0m")
|
|
122
|
+
sgrReplay = token;
|
|
123
|
+
else
|
|
124
|
+
sgrReplay += token;
|
|
125
|
+
}
|
|
126
|
+
if (hyperlinkTarget === undefined || (token !== "\u001b[4m" && token !== "\u001b[24m"))
|
|
127
|
+
output += token;
|
|
128
|
+
}
|
|
129
|
+
return hyperlinkTarget === undefined ? output : output + HYPERLINK_OFF;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Held-button paint for terminal-native links. OSC 8 is removed so the terminal
|
|
133
|
+
* cannot switch a selected link to its solid hover underline. A dotted SGR
|
|
134
|
+
* underline preserves the idle appearance, and VS15 transiently breaks native
|
|
135
|
+
* plain-text URL detection without changing visible width.
|
|
136
|
+
*/
|
|
137
|
+
export function heldNativeHyperlinkStyle(line) {
|
|
138
|
+
let hyperlinkTarget;
|
|
139
|
+
let fileDetectorBroken = false;
|
|
140
|
+
let output = "";
|
|
141
|
+
for (const token of line.split(ANSI_SPLIT)) {
|
|
142
|
+
if (!token)
|
|
143
|
+
continue;
|
|
144
|
+
const link = HYPERLINK.exec(token);
|
|
145
|
+
if (link) {
|
|
146
|
+
const nextTarget = (link[1] ?? "") || undefined;
|
|
147
|
+
if ((nextTarget !== undefined) !== (hyperlinkTarget !== undefined)) {
|
|
148
|
+
output += nextTarget === undefined ? "\u001b[24m" : "\u001b[4:4m";
|
|
149
|
+
}
|
|
150
|
+
if (nextTarget !== hyperlinkTarget)
|
|
151
|
+
fileDetectorBroken = false;
|
|
152
|
+
hyperlinkTarget = nextTarget;
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
if (token.startsWith("\u001b")) {
|
|
156
|
+
output += token;
|
|
157
|
+
// Foreground helpers may reset every SGR attribute; restore the held
|
|
158
|
+
// dotted underline without disturbing the source colour or intensity.
|
|
159
|
+
if (hyperlinkTarget !== undefined && SGR.test(token))
|
|
160
|
+
output += "\u001b[4:4m";
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
if (hyperlinkTarget === undefined) {
|
|
164
|
+
output += token;
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
let painted = token.replaceAll("https://", "https:\uFE0E//").replaceAll("http://", "http:\uFE0E//");
|
|
168
|
+
// Windows Terminal also detects file-link labels such as package.json and
|
|
169
|
+
// D:/work/file.ts after OSC 8 is removed. Break that detector once inside
|
|
170
|
+
// the paint-only label; semantic transcript text remains untouched.
|
|
171
|
+
if (!fileDetectorBroken && /^file:/iu.test(hyperlinkTarget)) {
|
|
172
|
+
const firstVisible = /\S/u.exec(painted);
|
|
173
|
+
if (firstVisible?.index !== undefined) {
|
|
174
|
+
const index = firstVisible.index;
|
|
175
|
+
const codePoint = painted.codePointAt(index) ?? 0;
|
|
176
|
+
const end = index + (codePoint > 0xffff ? 2 : 1);
|
|
177
|
+
painted = `${painted.slice(0, end)}\uFE0E${painted.slice(end)}`;
|
|
178
|
+
fileDetectorBroken = true;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
output += painted;
|
|
182
|
+
}
|
|
183
|
+
return hyperlinkTarget === undefined ? output : `${output}\u001b[24m`;
|
|
184
|
+
}
|
|
185
|
+
function decorateBareUrls(text, color, sgrReplay) {
|
|
186
|
+
return text.replace(BARE_URL, candidate => {
|
|
187
|
+
const { target, trailing } = splitUrlTrailingPunctuation(candidate);
|
|
188
|
+
if (target.length === 0)
|
|
189
|
+
return candidate;
|
|
190
|
+
return `\u001b]8;;${target}\u001b\\\u001b[24m${color(target, target)}${HYPERLINK_OFF}${sgrReplay}${trailing}`;
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
function splitUrlTrailingPunctuation(candidate) {
|
|
194
|
+
let end = candidate.length;
|
|
195
|
+
while (end > 0 && TRAILING_URL_PUNCTUATION.test(candidate[end - 1] ?? ""))
|
|
196
|
+
end -= 1;
|
|
197
|
+
for (const [opening, closing] of [["(", ")"], ["[", "]"], ["{", "}"]]) {
|
|
198
|
+
while (candidate.slice(0, end).endsWith(closing)
|
|
199
|
+
&& occurrences(candidate.slice(0, end), closing) > occurrences(candidate.slice(0, end), opening)) {
|
|
200
|
+
end -= 1;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return { target: candidate.slice(0, end), trailing: candidate.slice(end) };
|
|
204
|
+
}
|
|
205
|
+
function occurrences(text, value) {
|
|
206
|
+
return text.split(value).length - 1;
|
|
207
|
+
}
|
|
208
|
+
export function backgroundSgrSpan(line, from, to, on = "\u001b[47m", off = "\u001b[49m", widthOf = displayWidth) {
|
|
67
209
|
let output = "";
|
|
68
210
|
let seen = "";
|
|
69
211
|
let column = 0;
|
|
@@ -86,7 +228,7 @@ export function backgroundSgrSpan(line, from, to, on = "\u001b[47m", off = "\u00
|
|
|
86
228
|
inside = shouldBeInside;
|
|
87
229
|
}
|
|
88
230
|
output += segment;
|
|
89
|
-
column +=
|
|
231
|
+
column += widthOf(segment);
|
|
90
232
|
}
|
|
91
233
|
}
|
|
92
234
|
if (inside)
|
|
@@ -20,6 +20,10 @@ export interface TranscriptViewportTheme {
|
|
|
20
20
|
}
|
|
21
21
|
export interface TranscriptViewportFrameInput {
|
|
22
22
|
readonly documentRows: readonly string[];
|
|
23
|
+
/** Optional paint-only transform; semantic selection and copying keep documentRows exact. */
|
|
24
|
+
readonly paintDocumentRow?: (row: string) => string;
|
|
25
|
+
/** Leading document rows that participate in pointer selection and copying. */
|
|
26
|
+
readonly selectableDocumentRowCount?: number;
|
|
23
27
|
readonly dockRows: readonly string[];
|
|
24
28
|
readonly promptAnchors: readonly TranscriptPromptAnchor[];
|
|
25
29
|
readonly width: number;
|
|
@@ -92,6 +96,8 @@ export declare class TranscriptViewport {
|
|
|
92
96
|
scrollToEnd(now?: number): boolean;
|
|
93
97
|
/** Jumps to the pinned prompt governing this position, then earlier prompts. */
|
|
94
98
|
scrollToPreviousPrompt(now?: number): boolean;
|
|
99
|
+
/** Jumps to the next submitted prompt after the prompt at the current stop. */
|
|
100
|
+
scrollToNextPrompt(now?: number): boolean;
|
|
95
101
|
reset(): void;
|
|
96
102
|
clearTransient(): void;
|
|
97
103
|
compose(input: TranscriptViewportFrameInput): TranscriptViewportFrame;
|
|
@@ -4,7 +4,7 @@ import { faint, truncateToWidth, displayWidth, stripAnsi } from "./text.js";
|
|
|
4
4
|
import { extendTextSelection, orderedTextSelection, pressTextSelection, releaseTextSelection, textSelectionLineExtendColumn, textSelectionText, usefulTextLineContent, } from "./text-selection.js";
|
|
5
5
|
const CONTROL_STYLE_RESET = "\u001b]8;;\u001b\\\u001b[0m";
|
|
6
6
|
const DEFAULT_CONFIG = {
|
|
7
|
-
scrollbarAppearance: "
|
|
7
|
+
scrollbarAppearance: "auto",
|
|
8
8
|
scrollbarStyle: "thin",
|
|
9
9
|
};
|
|
10
10
|
const PLAIN_THEME = {
|
|
@@ -33,6 +33,7 @@ export class TranscriptViewport {
|
|
|
33
33
|
#selection;
|
|
34
34
|
#selectionClick;
|
|
35
35
|
#documentRows = [];
|
|
36
|
+
#selectableDocumentRowCount = 0;
|
|
36
37
|
#promptAnchors = [];
|
|
37
38
|
#contentWidth = 0;
|
|
38
39
|
#frame = null;
|
|
@@ -69,6 +70,8 @@ export class TranscriptViewport {
|
|
|
69
70
|
if (viewportRow < 1 || viewportRow > viewportHeight || column < 1 || column > this.#contentWidth)
|
|
70
71
|
return false;
|
|
71
72
|
const line = clamp(this.#scrollTop + viewportRow - 1, 0, Math.max(0, this.#documentRows.length - 1));
|
|
73
|
+
if (line >= this.#selectableDocumentRowCount)
|
|
74
|
+
return false;
|
|
72
75
|
const pressed = pressTextSelection({
|
|
73
76
|
line,
|
|
74
77
|
column,
|
|
@@ -94,8 +97,11 @@ export class TranscriptViewport {
|
|
|
94
97
|
this.scrollBy(1, now);
|
|
95
98
|
else if (autoScroll && viewportRow <= 1 && this.#scrollTop > 0)
|
|
96
99
|
this.scrollBy(-1, now);
|
|
100
|
+
const lastSelectableLine = Math.min(this.#documentRows.length, this.#selectableDocumentRowCount) - 1;
|
|
101
|
+
if (lastSelectableLine < 0)
|
|
102
|
+
return false;
|
|
97
103
|
const visibleRow = clamp(viewportRow, 1, viewportHeight);
|
|
98
|
-
const line = clamp(this.#scrollTop + visibleRow - 1, 0,
|
|
104
|
+
const line = clamp(this.#scrollTop + visibleRow - 1, 0, lastSelectableLine);
|
|
99
105
|
const targetColumn = selection.fullRow
|
|
100
106
|
? textSelectionLineExtendColumn(selection, line, this.#contentWidth)
|
|
101
107
|
: clamp(column, 1, this.#contentWidth);
|
|
@@ -118,7 +124,7 @@ export class TranscriptViewport {
|
|
|
118
124
|
const selection = orderedTextSelection(this.#selection);
|
|
119
125
|
if (selection === undefined)
|
|
120
126
|
return null;
|
|
121
|
-
return textSelectionText(selection, this.#documentRows, line => this.#lineContentAt(line));
|
|
127
|
+
return textSelectionText(selection, this.#documentRows.slice(0, this.#selectableDocumentRowCount), line => this.#lineContentAt(line));
|
|
122
128
|
}
|
|
123
129
|
scrollBy(lines, now = Date.now()) {
|
|
124
130
|
if (this.#maxScroll <= 0 || lines === 0)
|
|
@@ -166,6 +172,21 @@ export class TranscriptViewport {
|
|
|
166
172
|
const destination = target === earliest ? 0 : target;
|
|
167
173
|
return target >= 0 && this.scrollTo(destination, now);
|
|
168
174
|
}
|
|
175
|
+
/** Jumps to the next submitted prompt after the prompt at the current stop. */
|
|
176
|
+
scrollToNextPrompt(now = Date.now()) {
|
|
177
|
+
if (this.#promptAnchors.length === 0)
|
|
178
|
+
return false;
|
|
179
|
+
const earliest = Math.min(...this.#promptAnchors.map(anchor => anchor.firstRow));
|
|
180
|
+
// Scroll position zero is the first-prompt stop because it includes that
|
|
181
|
+
// prompt's opening spacer. Do not spend an extra keypress moving one row.
|
|
182
|
+
const after = this.#scrollTop === 0 ? earliest : this.#scrollTop;
|
|
183
|
+
let target = Number.POSITIVE_INFINITY;
|
|
184
|
+
for (const anchor of this.#promptAnchors) {
|
|
185
|
+
if (anchor.firstRow > after && anchor.firstRow < target)
|
|
186
|
+
target = anchor.firstRow;
|
|
187
|
+
}
|
|
188
|
+
return Number.isFinite(target) && this.scrollTo(target, now);
|
|
189
|
+
}
|
|
169
190
|
reset() {
|
|
170
191
|
this.#scrollTop = 0;
|
|
171
192
|
this.#maxScroll = 0;
|
|
@@ -174,6 +195,7 @@ export class TranscriptViewport {
|
|
|
174
195
|
this.#selection = undefined;
|
|
175
196
|
this.#selectionClick = undefined;
|
|
176
197
|
this.#documentRows = [];
|
|
198
|
+
this.#selectableDocumentRowCount = 0;
|
|
177
199
|
this.#promptAnchors = [];
|
|
178
200
|
this.#contentWidth = 0;
|
|
179
201
|
this.clearTransient();
|
|
@@ -219,9 +241,13 @@ export class TranscriptViewport {
|
|
|
219
241
|
});
|
|
220
242
|
const contentWidth = presentation.reservesSpace ? Math.max(1, width - 1) : width;
|
|
221
243
|
this.#documentRows = input.documentRows;
|
|
244
|
+
this.#selectableDocumentRowCount = clamp(input.selectableDocumentRowCount ?? input.documentRows.length, 0, input.documentRows.length);
|
|
222
245
|
this.#promptAnchors = input.promptAnchors;
|
|
223
246
|
this.#contentWidth = contentWidth;
|
|
224
|
-
const
|
|
247
|
+
const paintDocumentRow = input.paintDocumentRow ?? (row => row);
|
|
248
|
+
const visible = input.documentRows
|
|
249
|
+
.slice(this.#scrollTop, this.#scrollTop + viewportHeight)
|
|
250
|
+
.map(paintDocumentRow);
|
|
225
251
|
while (visible.length < viewportHeight)
|
|
226
252
|
visible.push("");
|
|
227
253
|
const governing = governingPrompt(input.promptAnchors, this.#scrollTop);
|
|
@@ -231,7 +257,7 @@ export class TranscriptViewport {
|
|
|
231
257
|
// Sticky prompts use the same normal/hover surface roles as the bottom
|
|
232
258
|
// control. Hover always starts from the full source row, so a quiet prompt
|
|
233
259
|
// becomes prominent again with its timestamp intact.
|
|
234
|
-
const sticky = theme.sticky(governing.sourceRow, this.#stickyHovered);
|
|
260
|
+
const sticky = theme.sticky(paintDocumentRow(governing.sourceRow), this.#stickyHovered);
|
|
235
261
|
visible[0] = quiet && !this.#stickyHovered ? theme.quietSticky(sticky) : sticky;
|
|
236
262
|
}
|
|
237
263
|
const orderedSelection = orderedTextSelection(this.#selection);
|
|
@@ -239,7 +265,9 @@ export class TranscriptViewport {
|
|
|
239
265
|
let line = padRowPreservingBackground(visible[row] ?? "", width);
|
|
240
266
|
if (orderedSelection !== undefined && !(stickyActive && row === 0)) {
|
|
241
267
|
const documentLine = this.#scrollTop + row;
|
|
242
|
-
if (documentLine
|
|
268
|
+
if (documentLine < this.#selectableDocumentRowCount
|
|
269
|
+
&& documentLine >= orderedSelection.start.line
|
|
270
|
+
&& documentLine <= orderedSelection.end.line) {
|
|
243
271
|
const fullVisualRow = orderedSelection.fullRow === true
|
|
244
272
|
|| (orderedSelection.start.line !== orderedSelection.end.line
|
|
245
273
|
&& documentLine !== orderedSelection.start.line
|
|
@@ -272,9 +300,9 @@ export class TranscriptViewport {
|
|
|
272
300
|
const frameRows = [...visible, ...dock].slice(0, height);
|
|
273
301
|
let bottomHit = null;
|
|
274
302
|
if (geometry !== null && !this.#followingEnd && frameRows.length > 0) {
|
|
275
|
-
const genericLabel = " Jump to bottom (
|
|
303
|
+
const genericLabel = " Jump to bottom (End) ";
|
|
276
304
|
const countedLabel = this.#newMessages > 0
|
|
277
|
-
? ` ${this.#newMessages} new message${this.#newMessages === 1 ? "" : "s"} (
|
|
305
|
+
? ` ${this.#newMessages} new message${this.#newMessages === 1 ? "" : "s"} (End) `
|
|
278
306
|
: genericLabel;
|
|
279
307
|
const label = displayWidth(countedLabel) <= contentWidth ? countedLabel : genericLabel;
|
|
280
308
|
const labelWidth = displayWidth(label);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare const OWNED_UI_SETTINGS_VERSION =
|
|
1
|
+
export declare const OWNED_UI_SETTINGS_VERSION = 3;
|
|
2
2
|
export type OwnedUiSettingValue = string | number | boolean;
|
|
3
3
|
export type OwnedUiSettingApplication = "live" | "restart";
|
|
4
4
|
export interface OwnedUiSettingDeclaration {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export const OWNED_UI_SETTINGS_VERSION =
|
|
1
|
+
export const OWNED_UI_SETTINGS_VERSION = 3;
|
|
2
2
|
const MAX_ID_LENGTH = 64;
|
|
3
3
|
const ID_PATTERN = /^[a-z][a-z0-9]*(?:[A-Z][a-z0-9]*)*$/;
|
|
4
4
|
const SCROLL_SECTION = Object.freeze({ id: "scroll", title: "Scroll" });
|
|
@@ -9,8 +9,8 @@ export const OWNED_UI_SETTING_DECLARATIONS = Object.freeze([
|
|
|
9
9
|
section: SCROLL_SECTION,
|
|
10
10
|
description: "When the session transcript scrollbar is visible.",
|
|
11
11
|
application: "live",
|
|
12
|
-
defaultValue: "
|
|
13
|
-
allowedValues: Object.freeze(["
|
|
12
|
+
defaultValue: "auto",
|
|
13
|
+
allowedValues: Object.freeze(["auto", "always", "hidden"]),
|
|
14
14
|
}),
|
|
15
15
|
Object.freeze({
|
|
16
16
|
id: "scrollbarStyle",
|
|
@@ -13,6 +13,15 @@ export const OWNED_UI_SETTINGS_MIGRATIONS = Object.freeze([
|
|
|
13
13
|
: { ...values };
|
|
14
14
|
},
|
|
15
15
|
}),
|
|
16
|
+
Object.freeze({
|
|
17
|
+
to: 3,
|
|
18
|
+
description: "Match Pi's auto scrollbar appearance name and ordering.",
|
|
19
|
+
migrate(values) {
|
|
20
|
+
return values.scrollbarAppearance === "hover"
|
|
21
|
+
? { ...values, scrollbarAppearance: "auto" }
|
|
22
|
+
: { ...values };
|
|
23
|
+
},
|
|
24
|
+
}),
|
|
16
25
|
]);
|
|
17
26
|
export function assertOwnedUiSettingsMigrations(migrations, currentVersion = OWNED_UI_SETTINGS_VERSION) {
|
|
18
27
|
const firstProduced = currentVersion - migrations.length + 1;
|
|
@@ -30,7 +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
|
- `storage`: SQLite migrations, prior-boot reconciliation, and plural launch-instance persistence. Legacy foreground rows are historical migration input and never authorize current ownership.
|
|
32
32
|
- `supervisor`: endpoint identity, plural cohort ownership, per-instance reconciliation, and aggregate release shutdown coordination. It owns no terminal surface.
|
|
33
|
-
- `pi-engine-adapter`, `pi-component-adapter`, `pi-tui-runtime-adapter`, and `pi-
|
|
33
|
+
- `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.
|
|
34
34
|
- release/update/bootstrap: package-derived immutable release identity, process-guardian integrity, cohort selection, durable update transactions, rollback, and dependency-light command entry.
|
|
35
35
|
|
|
36
36
|
## Dependency direction
|
|
@@ -23,7 +23,7 @@ src/
|
|
|
23
23
|
pi/
|
|
24
24
|
components/ pinned Pi component and theme adaptation
|
|
25
25
|
engine/ pinned Pi engine, settings, resource, package, and workflow integration
|
|
26
|
-
|
|
26
|
+
session-ui/ Pi-backed session shell and A1 viewport integration
|
|
27
27
|
tui-runtime/ neutral presentation runtime over pinned Pi TUI
|
|
28
28
|
foundation/
|
|
29
29
|
launch-guardian/ authenticated launch-instance coordination
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@timurproko/a1",
|
|
3
|
-
"version": "0.1.8-dev.
|
|
3
|
+
"version": "0.1.8-dev.151",
|
|
4
4
|
"description": "Standalone terminal workspace for supervised native and managed agents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"packageManager": "npm@11.13.0",
|
|
@@ -86,5 +86,8 @@
|
|
|
86
86
|
},
|
|
87
87
|
"publishConfig": {
|
|
88
88
|
"access": "public"
|
|
89
|
+
},
|
|
90
|
+
"optionalDependencies": {
|
|
91
|
+
"@mariozechner/clipboard": "^0.3.9"
|
|
89
92
|
}
|
|
90
93
|
}
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import type { PaneMouseEvent } from "../../../ui/components/index.js";
|
|
2
|
-
/**
|
|
3
|
-
* The seam between the pinned shell and A1-owned screens. The shell asks whether
|
|
4
|
-
* a route is claimed, mounts whatever surface it is handed, and forwards keys to
|
|
5
|
-
* it — it never learns what an app is, and the app layer never reaches into the
|
|
6
|
-
* shell.
|
|
7
|
-
*/
|
|
8
|
-
export interface UiRouteSurface {
|
|
9
|
-
readonly id: string;
|
|
10
|
-
/** Renders exactly `height` rows of at most `width` columns. */
|
|
11
|
-
render(width: number, height: number): readonly string[];
|
|
12
|
-
/** True when the key was consumed; false leaves it to the shell. */
|
|
13
|
-
handleInput(data: string): boolean;
|
|
14
|
-
/** Mouse report in surface-local coordinates. True when consumed. */
|
|
15
|
-
handleMouse(event: PaneMouseEvent): boolean;
|
|
16
|
-
/** True once the surface has asked to be dismissed. */
|
|
17
|
-
isClosed(): boolean;
|
|
18
|
-
close(): void;
|
|
19
|
-
/** Called by the shell when the surface should repaint. */
|
|
20
|
-
onRenderRequested(listener: () => void): void;
|
|
21
|
-
/** The surface asks to leave A1 entirely, not merely to close itself. */
|
|
22
|
-
onExitRequested(listener: () => void): void;
|
|
23
|
-
}
|
|
24
|
-
export interface UiRouteHost {
|
|
25
|
-
/** True when a declared A1-owned surface claims this route. */
|
|
26
|
-
claims(route: string): boolean;
|
|
27
|
-
/** Opens the route's surface, or null when it could not be presented. */
|
|
28
|
-
open(route: string): UiRouteSurface | null;
|
|
29
|
-
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|