@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.
Files changed (51) hide show
  1. package/dist/composition/owned-ui.js +10 -7
  2. package/dist/composition/settings-route-host.d.ts +1 -1
  3. package/dist/composition/settings-route-host.js +1 -1
  4. package/dist/contracts/owned-ui/model.d.ts +8 -1
  5. package/dist/contracts/owned-ui/validation.js +11 -0
  6. package/dist/features/owned-ui/settings-app.js +2 -1
  7. package/dist/integrations/pi/components/owned-editor-ux.d.ts +55 -0
  8. package/dist/integrations/pi/components/owned-editor-ux.js +778 -0
  9. package/dist/integrations/pi/components/shell-editor-autocomplete.js +44 -6
  10. package/dist/integrations/pi/components/shell-shared-facade.d.ts +41 -7
  11. package/dist/integrations/pi/components/shell-shared-facade.js +3 -1
  12. package/dist/integrations/pi/components/upstream/adjacent/core/keybindings.d.ts +10 -2
  13. package/dist/integrations/pi/components/upstream/adjacent/core/keybindings.js +36 -3
  14. package/dist/integrations/pi/engine/adapter.js +5 -1
  15. package/dist/integrations/pi/engine/session-integration.d.ts +2 -1
  16. package/dist/integrations/pi/engine/session-integration.js +14 -3
  17. package/dist/integrations/pi/session-ui/prompt-chips.d.ts +23 -0
  18. package/dist/integrations/pi/session-ui/prompt-chips.js +242 -0
  19. package/dist/integrations/pi/session-ui/route-host.d.ts +5 -0
  20. package/dist/integrations/pi/{owned-ui → session-ui}/session-shell-root.d.ts +17 -3
  21. package/dist/integrations/pi/{owned-ui → session-ui}/session-shell-root.js +100 -260
  22. package/dist/integrations/pi/{owned-ui → session-ui}/session-shell.d.ts +1 -1
  23. package/dist/integrations/pi/{owned-ui → session-ui}/session-shell.js +52 -12
  24. package/dist/integrations/pi/session-ui/session-viewport-controller.d.ts +39 -0
  25. package/dist/integrations/pi/session-ui/session-viewport-controller.js +369 -0
  26. package/dist/integrations/pi/session-ui/system-clipboard.d.ts +11 -0
  27. package/dist/integrations/pi/session-ui/system-clipboard.js +101 -0
  28. package/dist/integrations/pi/tui-runtime/contracts.d.ts +2 -6
  29. package/dist/native/darwin-arm64/manifest.json +1 -1
  30. package/dist/native/linux-x64/manifest.json +3 -3
  31. package/dist/native/linux-x64/process-guardian +0 -0
  32. package/dist/native/win32-x64/manifest.json +3 -3
  33. package/dist/native/win32-x64/process-guardian.exe +0 -0
  34. package/dist/ui/apps/contracts.d.ts +23 -1
  35. package/dist/ui/apps/host.js +9 -1
  36. package/dist/ui/components/scrollbar.d.ts +2 -2
  37. package/dist/ui/components/scrollbar.js +3 -3
  38. package/dist/ui/components/spans.d.ts +16 -1
  39. package/dist/ui/components/spans.js +144 -2
  40. package/dist/ui/components/transcript-viewport.d.ts +6 -0
  41. package/dist/ui/components/transcript-viewport.js +36 -8
  42. package/dist/ui/settings/declarations.d.ts +1 -1
  43. package/dist/ui/settings/declarations.js +3 -3
  44. package/dist/ui/settings/migrations.js +9 -0
  45. package/docs/architecture/boundaries.md +1 -1
  46. package/docs/architecture/project-structure.md +1 -1
  47. package/package.json +4 -1
  48. package/dist/integrations/pi/owned-ui/route-host.d.ts +0 -29
  49. /package/dist/integrations/pi/{owned-ui → session-ui}/index.d.ts +0 -0
  50. /package/dist/integrations/pi/{owned-ui → session-ui}/index.js +0 -0
  51. /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 at each fixed 30ms tick. */
10
+ /** Selection edge distance uses the same configured rate as a wheel event. */
11
11
  export function scrollbarSelectionRows(speed) {
12
- return speed === "high" ? 3 : speed === "fast" ? 2 : 1;
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/hover reserve the final rail cell even while content fits.
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
- export declare function backgroundSgrSpan(line: string, from: number, to: number, on?: string, off?: string): string;
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
- export function backgroundSgrSpan(line, from, to, on = "\u001b[47m", off = "\u001b[49m") {
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 += displayWidth(segment);
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: "hover",
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, this.#documentRows.length - 1);
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 visible = input.documentRows.slice(this.#scrollTop, this.#scrollTop + viewportHeight);
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 >= orderedSelection.start.line && documentLine <= orderedSelection.end.line) {
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 (Alt+End) ";
303
+ const genericLabel = " Jump to bottom (End) ";
276
304
  const countedLabel = this.#newMessages > 0
277
- ? ` ${this.#newMessages} new message${this.#newMessages === 1 ? "" : "s"} (Alt+End) `
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 = 2;
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 = 2;
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: "hover",
13
- allowedValues: Object.freeze(["always", "hover", "hidden"]),
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-owned-ui-integration`: isolate pinned Pi engine and presentation knowledge behind neutral contracts; product features do not import them directly.
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
- owned-ui/ Pi-backed owned session shell and route host
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.139",
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
- }