@timurproko/a1 0.2.5 → 0.2.6-dev.727

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 (148) hide show
  1. package/bin/cli.js +13 -2
  2. package/bin/ui.js +13 -0
  3. package/dist/app/session-shell/prompt-suggestion-controller.d.ts +5 -4
  4. package/dist/app/session-shell/prompt-suggestion-controller.js +148 -58
  5. package/dist/app/session-shell/session-shell-root.d.ts +5 -1
  6. package/dist/app/session-shell/session-shell-root.js +13 -6
  7. package/dist/app/session-shell/session-shell.js +132 -51
  8. package/dist/app/session-shell/session-viewport-controller.js +33 -21
  9. package/dist/cli/dispatch.js +5 -3
  10. package/dist/cli/session-context.d.ts +5 -1
  11. package/dist/cli/session-context.js +25 -8
  12. package/dist/composition/owned-ui.d.ts +3 -0
  13. package/dist/composition/owned-ui.js +66 -6
  14. package/dist/composition/settings-route-host.js +4 -3
  15. package/dist/contracts/owned-ui/index.d.ts +4 -2
  16. package/dist/contracts/owned-ui/index.js +1 -1
  17. package/dist/contracts/owned-ui/model.d.ts +4 -0
  18. package/dist/contracts/owned-ui/suggestion-diagnostics.d.ts +5 -1
  19. package/dist/contracts/owned-ui/suggestion-diagnostics.js +3 -1
  20. package/dist/contracts/owned-ui/validation.js +7 -0
  21. package/dist/features/launch/index.d.ts +1 -1
  22. package/dist/features/launch/session-context.d.ts +17 -2
  23. package/dist/features/launch/session-context.js +9 -5
  24. package/dist/features/owned-ui/reference-routes.d.ts +1 -1
  25. package/dist/features/owned-ui/reference-routes.js +1 -1
  26. package/dist/features/owned-ui/settings-app.js +21 -39
  27. package/dist/features/prompt-suggestions/diagnostics.js +4 -1
  28. package/dist/foundation/lifecycle/index.d.ts +2 -2
  29. package/dist/foundation/lifecycle/index.js +1 -1
  30. package/dist/foundation/lifecycle/session-repository-context.d.ts +19 -2
  31. package/dist/foundation/lifecycle/session-repository-context.js +434 -66
  32. package/dist/foundation/process-containment/index.d.ts +1 -0
  33. package/dist/foundation/process-containment/index.js +1 -0
  34. package/dist/foundation/process-containment/native-process-inspector.d.ts +3 -0
  35. package/dist/foundation/process-containment/native-process-inspector.js +21 -0
  36. package/dist/foundation/release/progress-palette.generated.d.ts +2 -2
  37. package/dist/foundation/release/progress-palette.generated.js +2 -2
  38. package/dist/foundation/startup/startup-descriptor.js +2 -2
  39. package/dist/integrations/pi/components/components.js +2 -2
  40. package/dist/integrations/pi/components/index.d.ts +2 -2
  41. package/dist/integrations/pi/components/index.js +1 -1
  42. package/dist/integrations/pi/components/lazy-selectors.d.ts +42 -0
  43. package/dist/integrations/pi/components/lazy-selectors.js +32 -0
  44. package/dist/integrations/pi/components/modal-frame.js +3 -4
  45. package/dist/integrations/pi/components/models-dialog.js +5 -5
  46. package/dist/integrations/pi/components/prompt-input-port.d.ts +1 -0
  47. package/dist/integrations/pi/components/resources/builtin-themes.d.ts +2 -2
  48. package/dist/integrations/pi/components/resources/builtin-themes.js +2 -2
  49. package/dist/integrations/pi/components/share-operation-dialog.js +3 -3
  50. package/dist/integrations/pi/components/shell-editor-autocomplete.js +8 -7
  51. package/dist/integrations/pi/components/shell-extension-ui.d.ts +2 -0
  52. package/dist/integrations/pi/components/shell-extension-ui.js +20 -10
  53. package/dist/integrations/pi/components/shell-hotkey-sections.js +2 -2
  54. package/dist/integrations/pi/components/shell-presenters-info.js +10 -10
  55. package/dist/integrations/pi/components/shell-presenters-transcript.js +10 -10
  56. package/dist/integrations/pi/components/shell-selectors-dialogs.js +5 -6
  57. package/dist/integrations/pi/components/shell-shared-facade.js +1 -1
  58. package/dist/integrations/pi/components/skills-dialog.js +3 -3
  59. package/dist/integrations/pi/components/submitted-prompt-adapter.js +2 -3
  60. package/dist/integrations/pi/components/theme.d.ts +9 -0
  61. package/dist/integrations/pi/components/theme.js +30 -0
  62. package/dist/integrations/pi/components/tool-result-adapter.js +6 -1
  63. package/dist/integrations/pi/components/upstream/adjacent/core/keybindings.d.ts +1 -1
  64. package/dist/integrations/pi/components/upstream/adjacent/core/keybindings.js +1 -1
  65. package/dist/integrations/pi/components/upstream/assets/earendil-image.json +2 -2
  66. package/dist/integrations/pi/components/upstream/components/countdown-timer.d.ts +1 -1
  67. package/dist/integrations/pi/components/upstream/components/earendil-announcement.js +1 -1
  68. package/dist/integrations/pi/components/upstream/components/extension-editor.d.ts +1 -1
  69. package/dist/integrations/pi/components/upstream/components/extension-editor.js +6 -6
  70. package/dist/integrations/pi/components/upstream/components/extension-input.js +4 -4
  71. package/dist/integrations/pi/components/upstream/components/extension-selector.js +4 -4
  72. package/dist/integrations/pi/components/upstream/components/mermaid.js +1 -1
  73. package/dist/integrations/pi/components/upstream/components/owned-editor.d.ts +5 -5
  74. package/dist/integrations/pi/components/upstream/components/owned-editor.js +8 -5
  75. package/dist/integrations/pi/components/upstream/components/pi-logo.js +1 -1
  76. package/dist/integrations/pi/components/upstream/components/scoped-models-selector.d.ts +1 -1
  77. package/dist/integrations/pi/components/upstream/components/scoped-models-selector.js +3 -3
  78. package/dist/integrations/pi/components/upstream/components/session-footer.js +1 -1
  79. package/dist/integrations/pi/components/upstream/components/session-selector-search.js +1 -1
  80. package/dist/integrations/pi/components/upstream/components/session-selector.js +14 -20
  81. package/dist/integrations/pi/components/upstream/components/skill-invocation-message.d.ts +3 -2
  82. package/dist/integrations/pi/components/upstream/components/skill-invocation-message.js +6 -3
  83. package/dist/integrations/pi/components/upstream/components/status-indicator.d.ts +1 -1
  84. package/dist/integrations/pi/components/upstream/components/status-indicator.js +1 -1
  85. package/dist/integrations/pi/components/upstream/components/themed-text.d.ts +1 -1
  86. package/dist/integrations/pi/components/upstream/components/themed-text.js +1 -1
  87. package/dist/integrations/pi/components/upstream/components/thinking-selector.d.ts +1 -1
  88. package/dist/integrations/pi/components/upstream/components/thinking-selector.js +2 -2
  89. package/dist/integrations/pi/components/upstream/components/tool-execution.d.ts +4 -0
  90. package/dist/integrations/pi/components/upstream/components/tool-execution.js +11 -1
  91. package/dist/integrations/pi/components/upstream/components/tree-selector.d.ts +2 -2
  92. package/dist/integrations/pi/components/upstream/components/tree-selector.js +27 -18
  93. package/dist/integrations/pi/components/upstream/components/trust-selector.js +1 -1
  94. package/dist/integrations/pi/components/upstream/external-editor.js +1 -1
  95. package/dist/integrations/pi/components/upstream/history/editor-core.d.ts +4 -4
  96. package/dist/integrations/pi/components/upstream/history/editor-core.js +11 -7
  97. package/dist/integrations/pi/components/upstream/history/kill-ring.d.ts +1 -1
  98. package/dist/integrations/pi/components/upstream/history/kill-ring.js +1 -1
  99. package/dist/integrations/pi/components/upstream/history/printable-key.js +1 -1
  100. package/dist/integrations/pi/components/upstream/history/text-helpers.js +1 -1
  101. package/dist/integrations/pi/components/upstream/history/undo-stack.d.ts +1 -1
  102. package/dist/integrations/pi/components/upstream/history/undo-stack.js +1 -1
  103. package/dist/integrations/pi/components/upstream/history/word-navigation.js +1 -1
  104. package/dist/integrations/pi/components/upstream/model-search.d.ts +1 -1
  105. package/dist/integrations/pi/components/upstream/program-status-reporter.d.ts +31 -0
  106. package/dist/integrations/pi/components/upstream/program-status-reporter.js +89 -0
  107. package/dist/integrations/pi/components/upstream/theme/system-theme.d.ts +1 -1
  108. package/dist/integrations/pi/components/upstream/theme/system-theme.js +1 -1
  109. package/dist/integrations/pi/components/upstream/theme/theme-controller.d.ts +1 -1
  110. package/dist/integrations/pi/components/upstream/theme/theme.d.ts +18 -1
  111. package/dist/integrations/pi/components/upstream/theme/theme.js +207 -10
  112. package/dist/integrations/pi/engine/pi-settings-metadata.json +1 -1
  113. package/dist/integrations/pi/engine/resources/changelog.json +4 -4
  114. package/dist/integrations/pi/engine/session-events.js +10 -2
  115. package/dist/integrations/pi/engine/transcript-projection.js +2 -1
  116. package/dist/integrations/pi/startup-public.js +306 -290
  117. package/dist/integrations/pi/startup-public.manifest.json +289 -265
  118. package/dist/integrations/pi/tui-runtime/adapter.d.ts +8 -1
  119. package/dist/integrations/pi/tui-runtime/adapter.js +31 -1
  120. package/dist/integrations/pi/tui-runtime/contracts.d.ts +3 -1
  121. package/dist/integrations/pi/tui-runtime/damage-aware-terminal.d.ts +1 -1
  122. package/dist/integrations/pi/tui-runtime/damage-aware-terminal.js +1 -1
  123. package/dist/native/darwin-arm64/manifest.json +1 -1
  124. package/dist/native/linux-x64/manifest.json +1 -1
  125. package/dist/native/win32-x64/manifest.json +2 -2
  126. package/dist/native/win32-x64/process-guardian.exe +0 -0
  127. package/dist/product-identity.d.ts +2 -2
  128. package/dist/product-identity.js +2 -2
  129. package/dist/product-identity.json +3 -1
  130. package/dist/runtime-payload-inventory.json +3092 -2878
  131. package/dist/ui/components/prompt-input.d.ts +2 -0
  132. package/dist/ui/components/prompt-input.js +5 -1
  133. package/dist/ui/components/text-selection.d.ts +1 -1
  134. package/dist/ui/components/text-selection.js +3 -1
  135. package/dist/ui/components/theme.d.ts +2 -0
  136. package/dist/ui/components/transcript-viewport.d.ts +6 -4
  137. package/dist/ui/components/transcript-viewport.js +94 -16
  138. package/dist/ui/components/value-menu.d.ts +2 -0
  139. package/dist/ui/components/value-menu.js +13 -8
  140. package/dist/ui/settings/declarations.d.ts +26 -14
  141. package/dist/ui/settings/declarations.js +24 -11
  142. package/dist/ui/settings/migrations.js +16 -0
  143. package/docs/architecture/project-structure.md +4 -3
  144. package/docs/architecture/prompt-suggestions.md +14 -10
  145. package/docs/local-worktree-cleanup.md +23 -9
  146. package/docs/manual-owned-ui-checkpoint.md +1 -1
  147. package/docs/openspec-archive-automation.md +20 -19
  148. package/package.json +3 -3
@@ -10,6 +10,8 @@ export interface PromptInputBody {
10
10
  readonly topRule?: string | undefined;
11
11
  readonly bottomRule?: string | undefined;
12
12
  readonly after?: readonly string[];
13
+ /** Outer indentation for after-body rows; defaults to the prompt prefix width. */
14
+ readonly afterIndent?: number;
13
15
  }
14
16
  export interface PromptInputMetrics {
15
17
  readonly measure: (text: string) => number;
@@ -31,6 +31,10 @@ export class PromptInput {
31
31
  return [];
32
32
  const { prefixWidth, innerWidth } = this.geometry(width, padding, promptGlyph);
33
33
  const body = renderBody(innerWidth);
34
+ const requestedAfterIndent = body.afterIndent ?? prefixWidth;
35
+ const afterIndent = Number.isFinite(requestedAfterIndent)
36
+ ? Math.min(width, Math.max(0, Math.floor(requestedAfterIndent)))
37
+ : prefixWidth;
34
38
  const fit = (row) => {
35
39
  const clipped = this.metrics.truncate(row, width);
36
40
  return clipped + " ".repeat(Math.max(0, width - this.metrics.measure(clipped)));
@@ -45,7 +49,7 @@ export class PromptInput {
45
49
  ...(ruled ? [rule(body.topRule)] : []),
46
50
  ...rows,
47
51
  ...(ruled ? [rule(body.bottomRule)] : []),
48
- ...(body.after ?? []).map(row => fit(`${" ".repeat(prefixWidth)}${row}`)),
52
+ ...(body.after ?? []).map(row => fit(`${" ".repeat(afterIndent)}${row}`)),
49
53
  ];
50
54
  }
51
55
  }
@@ -56,7 +56,7 @@ export declare function releaseTextSelection(selection: TextSelection | undefine
56
56
  export declare function textSelectionLineExtendColumn(selection: TextSelection, line: number, contentWidth: number): number;
57
57
  /** Resolves a reported one-based pointer cell to the complete grapheme it intersects. */
58
58
  export declare function textSelectionPointAt(line: number, column: number, lineText: string): TextSelectionPoint;
59
- export declare function textSelectionText(selection: OrderedTextSelection, rows: readonly string[], lineContent?: (line: number) => TextSelectionLineContent | undefined): string;
59
+ export declare function textSelectionText(selection: OrderedTextSelection, rows: readonly string[], lineContent?: (line: number) => TextSelectionLineContent | undefined, includeLine?: (line: number) => boolean): string;
60
60
  /** Returns the plain cells covered by [from,to), expanding at grapheme edges. */
61
61
  export declare function plainTextBetweenColumns(line: string, from: number, to: number): string;
62
62
  export declare function usefulTextLineContent(line: string, from?: number): TextSelectionLineContent;
@@ -111,9 +111,11 @@ export function textSelectionPointAt(line, column, lineText) {
111
111
  }
112
112
  return { line, column, from: target, to: target + 1 };
113
113
  }
114
- export function textSelectionText(selection, rows, lineContent) {
114
+ export function textSelectionText(selection, rows, lineContent, includeLine = () => true) {
115
115
  const parts = [];
116
116
  for (let line = selection.start.line; line <= selection.end.line && line < rows.length; line += 1) {
117
+ if (!includeLine(line))
118
+ continue;
117
119
  const plain = stripAnsi(rows[line] ?? "");
118
120
  const content = lineContent?.(line);
119
121
  if (content?.selectable === false) {
@@ -8,6 +8,8 @@ export interface UiTheme {
8
8
  * into a dark terminal, which is how the engine leaves its lists too.
9
9
  */
10
10
  plain(text: string): string;
11
+ /** Optional palette preview used by controls that choose the active accent. */
12
+ accentPreview?(color: string, text: string): string | null;
11
13
  /** Selection background for an active list item or floating-surface row. */
12
14
  highlight(text: string): string;
13
15
  /** A control that is present but cannot act: quieter than quiet text. */
@@ -25,7 +25,7 @@ export interface TranscriptViewportFrameInput {
25
25
  readonly documentRows: readonly string[];
26
26
  /** Optional paint-only transform; semantic selection and copying keep documentRows exact. */
27
27
  readonly paintDocumentRow?: (row: string) => string;
28
- /** Leading document rows that participate in pointer selection and copying. */
28
+ /** Leading persistent document rows; later transient rows remain selectable visible-frame content. */
29
29
  readonly selectableDocumentRowCount?: number;
30
30
  /**
31
31
  * First document row owned by the block that is currently streaming, if any. Rows from
@@ -84,7 +84,7 @@ export interface TranscriptViewportHitRegions {
84
84
  readonly columnStart: number;
85
85
  readonly columnEnd: number;
86
86
  } | null;
87
- /** Transient non-selectable document-tail rows currently visible in the viewport. */
87
+ /** Transient non-persistent document-tail rows currently visible in the viewport. */
88
88
  readonly transientTail: readonly number[];
89
89
  }
90
90
  export interface TranscriptViewportFrameDescriptor {
@@ -109,7 +109,7 @@ export interface TranscriptViewportFrameDescriptor {
109
109
  };
110
110
  readonly previousFollowingEnd: boolean | null;
111
111
  readonly followingEnd: boolean;
112
- /** Complete non-selectable suffix, including pending steering, alignment, and status rows. */
112
+ /** Complete non-persistent suffix, including pending steering, alignment, and status rows. */
113
113
  readonly transientRowCount: number;
114
114
  /** Flexible rows inserted before the bottom-aligned live status while content fits. */
115
115
  readonly transientAlignmentGapRows: number;
@@ -164,7 +164,9 @@ export declare class TranscriptViewport {
164
164
  get hasSelection(): boolean;
165
165
  /** True when the active gesture began on transcript rows rather than the dock. */
166
166
  get selectionFromContent(): boolean;
167
- pressSelection(column: number, frameRow: number, now?: number): boolean;
167
+ pressSelection(column: number, frameRow: number, now?: number, options?: {
168
+ readonly continueMultiClick?: boolean;
169
+ }): boolean;
168
170
  extendSelection(column: number, frameRow: number, now?: number, autoScroll?: boolean): boolean;
169
171
  releaseSelection(): boolean;
170
172
  clearSelection(): boolean;
@@ -1,4 +1,4 @@
1
- import { MAX_COPY_SOURCE_UNITS } from "./selection-copy.js";
1
+ import { MAX_COPY_SOURCE_UNITS, selectionCopyLineContent } from "./selection-copy.js";
2
2
  import { isThumbRow, scrollbarGeometry, scrollbarPresentation, } from "./scrollbar.js";
3
3
  import { backgroundSgrSpan, overlaySpan } from "./spans.js";
4
4
  import { faint, truncateToWidth, displayWidth, stripAnsi } from "./text.js";
@@ -8,8 +8,13 @@ const CONTROL_STYLE_RESET = "\u001b]8;;\u001b\\\u001b[0m";
8
8
  // the row surface painted beneath its dedicated, non-semantic gutter.
9
9
  const GUTTER_DECORATION_RESET = "\u001b]8;;\u001b\\\u001b[22;23;24;25;27;28;29;39;54;55m";
10
10
  const IDENTITY_ROW = (row) => row;
11
- function documentSelectionAnchor(row, source) {
12
- return { kind: "document", row, source, identity: stripAnsi(source).trimEnd() };
11
+ function documentSelectionAnchor(row, source, liveFromBottom) {
12
+ return { kind: "document", row, source, identity: stripAnsi(source).trimEnd(), liveFromBottom };
13
+ }
14
+ function viewportSelectionAnchor(row, source, selectableDocumentRowCount, documentRowCount) {
15
+ return row < selectableDocumentRowCount
16
+ ? documentSelectionAnchor(row, source)
17
+ : { kind: "transient", fromBottom: documentRowCount - row - 1 };
13
18
  }
14
19
  const DEFAULT_CONFIG = {
15
20
  scrollbarAppearance: "auto",
@@ -53,6 +58,7 @@ export class TranscriptViewport {
53
58
  #previousVisibleStates = [];
54
59
  #documentRows = [];
55
60
  #selectableDocumentRowCount = 0;
61
+ #liveTailStartRow;
56
62
  #promptAnchors = [];
57
63
  #contentWidth = 0;
58
64
  #selectionWidth = 0;
@@ -91,7 +97,7 @@ export class TranscriptViewport {
91
97
  get hasSelection() { return orderedTextSelection(this.#selection) !== undefined; }
92
98
  /** True when the active gesture began on transcript rows rather than the dock. */
93
99
  get selectionFromContent() { return this.#selectionAnchors !== undefined && this.#selectionAnchors.anchor.kind !== "dock"; }
94
- pressSelection(column, frameRow, now = Date.now()) {
100
+ pressSelection(column, frameRow, now = Date.now(), options = {}) {
95
101
  const frameHeight = this.#frame?.rows.length ?? 0;
96
102
  const selectionWidth = frameRow <= (this.#frame?.hits.viewportHeight ?? 0)
97
103
  ? this.#contentWidth
@@ -105,7 +111,7 @@ export class TranscriptViewport {
105
111
  contentWidth: selectionWidth,
106
112
  lineText: this.#selectionRows[line] ?? "",
107
113
  lineContent: usefulTextLineContent(this.#selectionRows[line] ?? ""),
108
- ...(this.#selectionClick === undefined ? {} : { previousClick: this.#selectionClick }),
114
+ ...(options.continueMultiClick !== false && this.#selectionClick !== undefined ? { previousClick: this.#selectionClick } : {}),
109
115
  now,
110
116
  });
111
117
  this.#selection = pressed.selection;
@@ -176,7 +182,29 @@ export class TranscriptViewport {
176
182
  const selection = this.#visibleSelection();
177
183
  if (selection === undefined)
178
184
  return null;
179
- const text = textSelectionText(selection, this.#selectionRows, line => usefulTextLineContent(this.#selectionRows[line] ?? ""));
185
+ // Invariant: the blue range spans the complete frame, but automatic and Ctrl+C frame copy
186
+ // admit only persistent transcript rows. Transient status, suggestions, input, and footer
187
+ // chrome remain visually selectable without becoming clipboard payload.
188
+ const copiedLines = Array.from({ length: Math.max(0, selection.end.line - selection.start.line + 1) }, (_value, index) => selection.start.line + index).filter(line => {
189
+ const anchor = this.#selectionRowAnchors[line];
190
+ return anchor?.kind === "document" && anchor.row < this.#selectableDocumentRowCount;
191
+ });
192
+ if (copiedLines.length === 0)
193
+ return null;
194
+ // Rationale: a prompt selected by itself uses semantic prompt text. Once surrounding transcript text
195
+ // participates, preserve the prompt's visible prefix, alignment, and timestamp in the bulk range.
196
+ const prompt = this.#promptAnchors.find(candidate => copiedLines.every(line => {
197
+ const anchor = this.#selectionRowAnchors[line];
198
+ return anchor?.kind === "document" && anchor.row >= candidate.firstRow && anchor.row <= candidate.lastRow;
199
+ }));
200
+ const included = new Set(copiedLines);
201
+ const text = textSelectionText(selection, this.#selectionRows, line => {
202
+ const row = this.#selectionRows[line] ?? "";
203
+ const anchor = this.#selectionRowAnchors[line];
204
+ return prompt !== undefined && anchor?.kind === "document"
205
+ ? selectionCopyLineContent(row, anchor.row === prompt.firstRow ? "first" : "continuation")
206
+ : usefulTextLineContent(row);
207
+ }, line => included.has(line));
180
208
  return text.length === 0 ? null : text;
181
209
  }
182
210
  /** Captures visible selected text, not off-screen history. */
@@ -188,6 +216,8 @@ export class TranscriptViewport {
188
216
  if (text === null)
189
217
  return null;
190
218
  text = text.trim();
219
+ if (text.length === 0)
220
+ return null;
191
221
  const normalized = {
192
222
  start: { line: 0, column: 0 },
193
223
  end: { line: 0, column: Number.MAX_SAFE_INTEGER },
@@ -280,6 +310,7 @@ export class TranscriptViewport {
280
310
  this.#previousVisibleStates = [];
281
311
  this.#documentRows = [];
282
312
  this.#selectableDocumentRowCount = 0;
313
+ this.#liveTailStartRow = undefined;
283
314
  this.#promptAnchors = [];
284
315
  this.#contentWidth = 0;
285
316
  this.#selectionWidth = 0;
@@ -340,6 +371,9 @@ export class TranscriptViewport {
340
371
  const contentWidth = presentation.reservesSpace ? Math.max(1, width - 1) : width;
341
372
  this.#documentRows = documentRows;
342
373
  this.#selectableDocumentRowCount = clamp(input.selectableDocumentRowCount ?? input.documentRows.length, 0, input.documentRows.length);
374
+ this.#liveTailStartRow = input.liveTailStartRow === undefined || !Number.isSafeInteger(input.liveTailStartRow)
375
+ ? undefined
376
+ : clamp(input.liveTailStartRow, 0, this.#selectableDocumentRowCount);
343
377
  this.#promptAnchors = input.promptAnchors;
344
378
  this.#contentWidth = contentWidth;
345
379
  // Invariant: dock rows remain selectable at full width; scrollable source rows
@@ -389,7 +423,7 @@ export class TranscriptViewport {
389
423
  const visibleAnchors = Array.from({ length: viewportHeight }, (_value, row) => {
390
424
  const documentRow = this.#scrollTop + row;
391
425
  return documentRow < documentRows.length
392
- ? documentSelectionAnchor(documentRow, documentRows[documentRow] ?? "")
426
+ ? viewportSelectionAnchor(documentRow, documentRows[documentRow] ?? "", this.#selectableDocumentRowCount, documentRows.length)
393
427
  : { kind: "screen", row, source: this.#selectionRows[row] ?? "" };
394
428
  });
395
429
  // Invariant: a pinned prompt is chrome over its hidden source row, which clips like off-screen rows.
@@ -486,7 +520,7 @@ export class TranscriptViewport {
486
520
  // Invariant: floating control styling is applied only after the row and
487
521
  // gutter surfaces are resolved, so its inline background cannot leak.
488
522
  const withControl = rowControl === null ? withRail : overlaySpan(withRail, rowControl.left, rowControl.right, rowControl.text);
489
- return overlayWidth === 0 ? withControl : overlaySpan(padRowPreservingBackground(withControl, width), overlayLimit - overlayWidth, overlayLimit, `${CONTROL_STYLE_RESET}${overlayText}`);
523
+ return overlayWidth === 0 ? withControl : overlaySpan(padRowPreservingBackground(withControl, width), overlayLimit - overlayWidth, overlayLimit, `${GUTTER_DECORATION_RESET}${overlayText}`, { inheritStartStyle: true });
490
524
  });
491
525
  rowRecomputed ||= !final.reused;
492
526
  frameRows[row] = final.value;
@@ -643,17 +677,30 @@ export class TranscriptViewport {
643
677
  return undefined;
644
678
  if (line < this.#viewportHeight) {
645
679
  const existing = this.#selectionRowAnchors[line];
646
- if (this.#frame?.scrollTop === this.#scrollTop)
647
- return existing;
680
+ if (this.#frame?.scrollTop === this.#scrollTop) {
681
+ return existing?.kind === "document"
682
+ ? this.#interactionDocumentAnchor(existing.row, existing.source)
683
+ : existing;
684
+ }
648
685
  // Concurrency: selection edge auto-scroll updates scrollTop before the next frame is
649
686
  // composed. Resolve ordinary viewport rows from that latest position instead of stale paint.
650
687
  const documentRow = this.#scrollTop + line;
651
688
  if (documentRow >= 0 && documentRow < this.#documentRows.length) {
652
- return documentSelectionAnchor(documentRow, this.#documentRows[documentRow] ?? "");
689
+ return documentRow < this.#selectableDocumentRowCount
690
+ ? this.#interactionDocumentAnchor(documentRow, this.#documentRows[documentRow] ?? "")
691
+ : viewportSelectionAnchor(documentRow, this.#documentRows[documentRow] ?? "", this.#selectableDocumentRowCount, this.#documentRows.length);
653
692
  }
654
693
  }
655
694
  return this.#selectionRowAnchors[line];
656
695
  }
696
+ #interactionDocumentAnchor(row, source) {
697
+ const liveFromBottom = this.#liveTailStartRow !== undefined
698
+ && row >= this.#liveTailStartRow
699
+ && row < this.#selectableDocumentRowCount
700
+ ? this.#selectableDocumentRowCount - row - 1
701
+ : undefined;
702
+ return documentSelectionAnchor(row, source, liveFromBottom);
703
+ }
657
704
  #projectSelection(documentRows, dockRows, height) {
658
705
  const selection = this.#selection;
659
706
  const anchors = this.#selectionAnchors;
@@ -672,9 +719,16 @@ export class TranscriptViewport {
672
719
  ? undefined
673
720
  : { line: anchor.row, anchor };
674
721
  }
722
+ if (anchor.kind === "transient") {
723
+ const documentRow = documentRows.length - anchor.fromBottom - 1;
724
+ if (documentRow < this.#selectableDocumentRowCount || documentRow >= documentRows.length)
725
+ return undefined;
726
+ const visible = this.#selectionRowAnchors.findIndex(candidate => candidate?.kind === "transient" && candidate.fromBottom === anchor.fromBottom);
727
+ return visible < 0 ? undefined : { line: visible, anchor };
728
+ }
675
729
  if (anchor.row >= 0 && anchor.row < documentRows.length
676
730
  && stripAnsi(documentRows[anchor.row] ?? "").trimEnd() === anchor.identity) {
677
- const current = documentSelectionAnchor(anchor.row, documentRows[anchor.row] ?? "");
731
+ const current = documentSelectionAnchor(anchor.row, documentRows[anchor.row] ?? "", anchor.liveFromBottom);
678
732
  const visible = this.#selectionRowAnchors.findIndex(candidate => candidate?.kind === "document"
679
733
  && candidate.row === current.row && candidate.identity === current.identity);
680
734
  return { line: visible >= 0 ? visible : current.row - this.#scrollTop, anchor: current };
@@ -684,9 +738,29 @@ export class TranscriptViewport {
684
738
  const visibleMatches = this.#selectionRowAnchors
685
739
  .map((candidate, line) => ({ candidate, line }))
686
740
  .filter((entry) => entry.candidate?.kind === "document" && entry.candidate.identity === anchor.identity);
687
- return visibleMatches.length === 1
688
- ? { line: visibleMatches[0].line, anchor: visibleMatches[0].candidate }
689
- : undefined;
741
+ if (visibleMatches.length === 1) {
742
+ const match = visibleMatches[0];
743
+ return {
744
+ line: match.line,
745
+ anchor: documentSelectionAnchor(match.candidate.row, match.candidate.source, anchor.liveFromBottom),
746
+ };
747
+ }
748
+ // Concurrency: a streaming chunk may replace or reflow the pointer's current live-tail
749
+ // row before the next motion report. During the held gesture only, retain its distance
750
+ // from the persistent tail; release restores identity-only projection.
751
+ if (selection.selecting && anchor.liveFromBottom !== undefined && this.#liveTailStartRow !== undefined) {
752
+ const documentRow = this.#selectableDocumentRowCount - anchor.liveFromBottom - 1;
753
+ if (documentRow >= this.#liveTailStartRow && documentRow < this.#selectableDocumentRowCount) {
754
+ const visible = this.#selectionRowAnchors.findIndex(candidate => candidate?.kind === "document" && candidate.row === documentRow);
755
+ if (visible >= 0) {
756
+ return {
757
+ line: visible,
758
+ anchor: documentSelectionAnchor(documentRow, documentRows[documentRow] ?? "", anchor.liveFromBottom),
759
+ };
760
+ }
761
+ }
762
+ }
763
+ return undefined;
690
764
  };
691
765
  const projectedAnchor = project(anchors.anchor);
692
766
  const projectedHead = project(anchors.head);
@@ -704,7 +778,11 @@ export class TranscriptViewport {
704
778
  };
705
779
  }
706
780
  #visibleSelection() {
707
- const rows = this.#selectionAnchors?.anchor.kind === "document" ? this.#viewportHeight : this.#selectionRows.length;
781
+ const anchors = this.#selectionAnchors;
782
+ const crossesDock = anchors?.anchor.kind === "dock" || anchors?.head.kind === "dock";
783
+ // Invariant: mixed viewport/dock ranges project across the same complete frame regardless
784
+ // of origin. Document-only ranges remain clipped to the scrollable viewport when off-screen.
785
+ const rows = crossesDock ? this.#selectionRows.length : this.#viewportHeight;
708
786
  return visibleTextSelection(orderedTextSelection(this.#selection), rows, this.#firstSelectableRow);
709
787
  }
710
788
  #updateCopyableSelection() {
@@ -7,6 +7,8 @@ import type { UiTheme } from "./theme.js";
7
7
  export interface ValueMenuState {
8
8
  /** Values offered, as they should read. */
9
9
  readonly choices: readonly string[];
10
+ /** Optional already-painted square previews, aligned before each choice label. */
11
+ readonly previews?: readonly string[];
10
12
  /** The value in effect, marked rather than highlighted. */
11
13
  readonly current: string | null;
12
14
  /** The entry picked by a key or the pointer, or -1 while none is. */
@@ -8,10 +8,12 @@ export function valueMenuFrame(state, anchor, layout) {
8
8
  const top = below + state.choices.length <= bodyBottom
9
9
  ? below
10
10
  : Math.max(bodyTop, anchor.screenRow - state.choices.length);
11
- const width = Math.max(...state.choices.map(choice => displayWidth(choice) + 4), 6);
12
- // Invariant: each row reserves two cells for its effective-value mark. Place those
13
- // cells before the anchor so visible choice text starts in the source value column.
14
- const column = Math.min(Math.max(0, anchor.valueColumn - 2), Math.max(0, layout.surfaceWidth - width - layout.reservedRight));
11
+ const previewWidth = Math.max(0, ...(state.previews ?? []).map(displayWidth));
12
+ const previewColumns = previewWidth === 0 ? 0 : previewWidth + 1;
13
+ const width = Math.max(...state.choices.map(choice => displayWidth(choice) + previewColumns + 4), 6);
14
+ // Invariant: rows reserve two cells for the effective-value mark and, when present,
15
+ // a preview plus one gap. Choice text remains aligned with the source value column.
16
+ const column = Math.min(Math.max(0, anchor.valueColumn - 2 - previewColumns), Math.max(0, layout.surfaceWidth - width - layout.reservedRight));
15
17
  return { top, column, width, rows: state.choices.length };
16
18
  }
17
19
  /** Draws the menu over the rows behind it, leaving them otherwise untouched. */
@@ -24,11 +26,14 @@ export function renderValueMenu(lines, state, frame, theme) {
24
26
  const active = index === state.index;
25
27
  const paint = active ? theme.highlight : theme.panel;
26
28
  const current = choice === state.current;
27
- const tail = padToWidth(`${current ? " " : " "}${choice} `, frame.width - (current ? 1 : 0));
28
- // Invariant: the effective-value check uses the same accent as modal marks, while
29
- // the rest of an active row keeps its highlighted foreground and background.
29
+ const previews = state.previews ?? [];
30
+ const previewWidth = Math.max(0, ...previews.map(displayWidth));
31
+ const preview = previewWidth === 0 ? "" : `${padToWidth(previews[index] ?? "", previewWidth)} `;
32
+ // Invariant: the current-value check stays on ordinary text while the optional
33
+ // square carries the palette color; the row treatment supplies only its surface.
34
+ const tail = padToWidth(`${current ? " " : " "}${preview}${choice} `, frame.width - (current ? 1 : 0));
30
35
  const painted = current
31
- ? `${paint(theme.fg("accent", "✓"))}${paint(tail)}`
36
+ ? `${paint(theme.fg("text", "✓"))}${paint(tail)}`
32
37
  : paint(tail);
33
38
  output[target] = overlaySpan(output[target] ?? "", frame.column, frame.column + frame.width, painted);
34
39
  });
@@ -1,4 +1,4 @@
1
- export declare const OWNED_UI_SETTINGS_VERSION = 10;
1
+ export declare const OWNED_UI_SETTINGS_VERSION = 12;
2
2
  export type OwnedUiSettingValue = string | number | boolean;
3
3
  export type OwnedUiSettingApplication = "live" | "restart";
4
4
  export interface OwnedUiSettingDeclaration {
@@ -20,18 +20,6 @@ export interface OwnedUiSettingDeclaration {
20
20
  * values are read from it, and `OwnedSettingValueOf` derives each getter's type from it.
21
21
  */
22
22
  export declare const OWNED_SETTING_DECLARATIONS: Readonly<{
23
- readonly quitAnimation: Readonly<{
24
- id: "quitAnimation";
25
- label: "Quit animation";
26
- section: Readonly<{
27
- id: "generic";
28
- title: "Generic";
29
- }>;
30
- description: "Play the fall effect over the last screen when the session quits. Off returns to the terminal immediately.";
31
- application: "live";
32
- defaultValue: true;
33
- allowedValues: readonly [true, false];
34
- }>;
35
23
  readonly updateCheck: Readonly<{
36
24
  id: "updateCheck";
37
25
  label: "Update check";
@@ -44,6 +32,30 @@ export declare const OWNED_SETTING_DECLARATIONS: Readonly<{
44
32
  defaultValue: true;
45
33
  allowedValues: readonly [true, false];
46
34
  }>;
35
+ readonly accentColor: Readonly<{
36
+ id: "accentColor";
37
+ label: "Accent color";
38
+ section: Readonly<{
39
+ id: "appearance";
40
+ title: "Appearance";
41
+ }>;
42
+ description: "Color used by highlighted interface controls, list markers, and tonal interface surfaces.";
43
+ application: "live";
44
+ defaultValue: "purple";
45
+ allowedValues: readonly ["purple", "blue", "cyan", "green", "orange", "pink"];
46
+ }>;
47
+ readonly quitAnimation: Readonly<{
48
+ id: "quitAnimation";
49
+ label: "Quit animation";
50
+ section: Readonly<{
51
+ id: "appearance";
52
+ title: "Appearance";
53
+ }>;
54
+ description: "Play the fall effect over the last screen when the session quits. Off returns to the terminal immediately.";
55
+ application: "live";
56
+ defaultValue: true;
57
+ allowedValues: readonly [true, false];
58
+ }>;
47
59
  readonly scrollbarAppearance: Readonly<{
48
60
  id: "scrollbarAppearance";
49
61
  label: "Scrollbar mode";
@@ -111,7 +123,7 @@ export declare const OWNED_SETTING_DECLARATIONS: Readonly<{
111
123
  id: "agent";
112
124
  title: "Agent";
113
125
  }>;
114
- description: "Predict likely next prompts with one additional background request using the selected model.";
126
+ description: "Predict likely next prompts with one background request, plus one bounded recovery request when needed, using the selected model.";
115
127
  application: "live";
116
128
  defaultValue: true;
117
129
  allowedValues: readonly [true, false];
@@ -1,8 +1,12 @@
1
- export const OWNED_UI_SETTINGS_VERSION = 10;
1
+ export const OWNED_UI_SETTINGS_VERSION = 12;
2
+ const UI_ACCENT_COLOR_CHOICES = Object.freeze([
3
+ "purple", "blue", "cyan", "green", "orange", "pink",
4
+ ]);
2
5
  const MAX_ID_LENGTH = 64;
3
6
  const ID_PATTERN = /^[a-z][a-z0-9]*(?:[A-Z][a-z0-9]*)*$/;
4
7
  /** Declared first so the settings screen opens on it; sections follow first-declaration order. */
5
8
  const GENERIC_SECTION = Object.freeze({ id: "generic", title: "Generic" });
9
+ const APPEARANCE_SECTION = Object.freeze({ id: "appearance", title: "Appearance" });
6
10
  const SCROLL_SECTION = Object.freeze({ id: "scroll", title: "Scroll" });
7
11
  const AGENT_SECTION = Object.freeze({ id: "agent", title: "Agent" });
8
12
  /**
@@ -10,15 +14,6 @@ const AGENT_SECTION = Object.freeze({ id: "agent", title: "Agent" });
10
14
  * values are read from it, and `OwnedSettingValueOf` derives each getter's type from it.
11
15
  */
12
16
  export const OWNED_SETTING_DECLARATIONS = Object.freeze({
13
- quitAnimation: Object.freeze({
14
- id: "quitAnimation",
15
- label: "Quit animation",
16
- section: GENERIC_SECTION,
17
- description: "Play the fall effect over the last screen when the session quits. Off returns to the terminal immediately.",
18
- application: "live",
19
- defaultValue: true,
20
- allowedValues: Object.freeze([true, false]),
21
- }),
22
17
  updateCheck: Object.freeze({
23
18
  id: "updateCheck",
24
19
  label: "Update check",
@@ -28,6 +23,24 @@ export const OWNED_SETTING_DECLARATIONS = Object.freeze({
28
23
  defaultValue: true,
29
24
  allowedValues: Object.freeze([true, false]),
30
25
  }),
26
+ accentColor: Object.freeze({
27
+ id: "accentColor",
28
+ label: "Accent color",
29
+ section: APPEARANCE_SECTION,
30
+ description: "Color used by highlighted interface controls, list markers, and tonal interface surfaces.",
31
+ application: "live",
32
+ defaultValue: "purple",
33
+ allowedValues: UI_ACCENT_COLOR_CHOICES,
34
+ }),
35
+ quitAnimation: Object.freeze({
36
+ id: "quitAnimation",
37
+ label: "Quit animation",
38
+ section: APPEARANCE_SECTION,
39
+ description: "Play the fall effect over the last screen when the session quits. Off returns to the terminal immediately.",
40
+ application: "live",
41
+ defaultValue: true,
42
+ allowedValues: Object.freeze([true, false]),
43
+ }),
31
44
  scrollbarAppearance: Object.freeze({
32
45
  id: "scrollbarAppearance",
33
46
  label: "Scrollbar mode",
@@ -77,7 +90,7 @@ export const OWNED_SETTING_DECLARATIONS = Object.freeze({
77
90
  id: "promptSuggestions",
78
91
  label: "Prompt suggestions",
79
92
  section: AGENT_SECTION,
80
- description: "Predict likely next prompts with one additional background request using the selected model.",
93
+ description: "Predict likely next prompts with one background request, plus one bounded recovery request when needed, using the selected model.",
81
94
  application: "live",
82
95
  defaultValue: true,
83
96
  allowedValues: Object.freeze([true, false]),
@@ -81,6 +81,22 @@ export const OWNED_UI_SETTINGS_MIGRATIONS = Object.freeze([
81
81
  return { ...values };
82
82
  },
83
83
  }),
84
+ Object.freeze({
85
+ to: 11,
86
+ description: "Introduce semantic accent selection with the active-theme default.",
87
+ migrate(values) {
88
+ return { ...values };
89
+ },
90
+ }),
91
+ Object.freeze({
92
+ to: 12,
93
+ description: "Rename the inherited default accent choice to explicit purple.",
94
+ migrate(values) {
95
+ return values.accentColor === "default"
96
+ ? { ...values, accentColor: "purple" }
97
+ : { ...values };
98
+ },
99
+ }),
84
100
  ]);
85
101
  export function assertOwnedUiSettingsMigrations(migrations, currentVersion = OWNED_UI_SETTINGS_VERSION) {
86
102
  const firstProduced = currentVersion - migrations.length + 1;
@@ -92,18 +92,19 @@ Build output mirrors the production namespaces directly under ignored `dist/`, w
92
92
 
93
93
  Every task worktree must be created at `{working-dir}/.worktrees/<task-id>`, where `{working-dir}` is the agent session's initial working directory. The `.worktrees` directory is inside that working directory, not beside it. For working directory `D:/Git/a1`, `D:/Git/a1/.worktrees/<task-id>` is correct and `D:/Git/a1-<task-id>` is forbidden.
94
94
 
95
- From the initial working directory, create the task branch and worktree, run `a1 session link-worktree <absolute-worktree>` from the owning A1 session, and continue to address it explicitly:
95
+ From the initial working directory, run `a1 session worktrees` to inspect cooperative live ownership before selecting an existing checkout. A `busy` or `unverifiable` worktree is unavailable. An `available` checkout may be reused only when its exact branch, OpenSpec change, and pull request match the requested stream and atomic `a1 session link-worktree <absolute-worktree>` succeeds; names, similar files, recency, cleanliness, and ancestry are not ownership evidence. Otherwise create a fresh task branch/worktree, link it from the owning A1 session, and continue to address it explicitly:
96
96
 
97
97
  ```sh
98
+ a1 session worktrees
98
99
  git fetch origin develop
99
100
  git worktree add -b <type>/<short-description> .worktrees/<task-id> origin/develop
100
101
  a1 session link-worktree <absolute-worktree>
101
102
  git -C .worktrees/<task-id> status
102
103
  ```
103
104
 
104
- A successful link response confirming the exact canonical worktree is required before planning, implementation, test, or delivery-documentation edits. If linking fails or confirms another path, stop task edits and report the blocker instead of continuing with primary-checkout footer metadata. A session resuming an existing delivery or switching streams links the exact owned worktree before editing it.
105
+ Inventory is read-only and advisory because another runtime may win a later race. A successful link response confirming the exact canonical worktree is required before planning, implementation, test, or delivery-documentation edits. If inventory or linking fails, reports a live/unverifiable owner, loses a race, or confirms another path, stop task edits and report the blocker; use a separate fresh worktree rather than overriding, unlinking, or recovering that owner. A session resuming an existing delivery or switching streams atomically claims and links the exact worktree before editing it.
105
106
 
106
- The association switches bare A1's footer repository context and pull-request discovery; it does not change process or tool cwd and does not register, claim, release, or authorize cleanup of the worktree. Repository commands therefore keep an explicit worktree path. The primary worktree stays on `develop` for integration and must not be used for task edits. Do not edit, adopt, move, or remove another session's worktree.
107
+ The link acquires a cooperative live-session editing claim and switches bare A1's footer repository context and pull-request discovery. It does not change process or tool cwd, create a Git worktree lock, or register, release, or authorize cleanup. Clean unlink/disposal releases live editing authority; a crashed owner becomes reusable only after exact process death is verified. Repository commands therefore keep an explicit worktree path. The primary worktree stays on `develop` for integration and must not be used for task edits. Do not edit, adopt, move, or remove another session's worktree.
107
108
 
108
109
  An agent that directly chooses a transient file path for a PR/comment body, command payload, captured output, temporary patch/diff, or ad hoc log must place it beneath the linked worktree's exact `.artifacts/` root, never the primary checkout, another worktree, OS temp, home/desktop, or a sibling path. Such scratch remains ignored, unstaged, uncommitted, disposable, non-authoritative, and secret-free. This repository path rule does not relocate storage internally selected by tools, product runtime code, or hermetic tests.
109
110