@timurproko/a1 0.2.7-dev.1221 → 0.2.7-dev.1225

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 (65) hide show
  1. package/README.md +8 -6
  2. package/bin/ui.js +1 -0
  3. package/dist/app/session-shell/dock-chrome.js +1 -1
  4. package/dist/app/session-shell/exit-notice.d.ts +2 -2
  5. package/dist/app/session-shell/exit-notice.js +4 -4
  6. package/dist/app/session-shell/session-lifecycle.d.ts +2 -1
  7. package/dist/app/session-shell/session-lifecycle.js +4 -2
  8. package/dist/app/session-shell/session-shell-root.d.ts +2 -0
  9. package/dist/app/session-shell/session-shell-root.js +3 -2
  10. package/dist/app/session-shell/session-shell.js +9 -1
  11. package/dist/app/session-shell/session-viewport-controller.d.ts +2 -1
  12. package/dist/app/session-shell/session-viewport-controller.js +26 -37
  13. package/dist/app/session-shell/shell-editor-factory.js +4 -1
  14. package/dist/cli/dispatch.js +15 -2
  15. package/dist/composition/owned-ui.d.ts +2 -0
  16. package/dist/composition/owned-ui.js +43 -11
  17. package/dist/composition/reference-selection-options.d.ts +3 -0
  18. package/dist/composition/reference-selection-options.js +16 -0
  19. package/dist/composition/settings-route-host.d.ts +1 -2
  20. package/dist/composition/settings-route-host.js +19 -13
  21. package/dist/composition/settings-search-input.js +4 -1
  22. package/dist/composition/utility-tab.js +4 -2
  23. package/dist/features/launch/intent.js +2 -2
  24. package/dist/features/owned-ui/plus-menu-dialog.js +28 -20
  25. package/dist/features/owned-ui/reference-document-selection.d.ts +30 -0
  26. package/dist/features/owned-ui/reference-document-selection.js +179 -0
  27. package/dist/features/owned-ui/reference-screen-app.d.ts +4 -1
  28. package/dist/features/owned-ui/reference-screen-app.js +39 -8
  29. package/dist/features/owned-ui/settings-app.d.ts +1 -0
  30. package/dist/features/owned-ui/settings-app.js +23 -19
  31. package/dist/foundation/release/automatic-update.js +90 -22
  32. package/dist/foundation/release/bootstrap.js +2 -2
  33. package/dist/foundation/release/latest-release.d.ts +5 -1
  34. package/dist/foundation/release/latest-release.js +74 -31
  35. package/dist/integrations/pi/components/owned-editor-ux.js +48 -5
  36. package/dist/integrations/pi/engine/workflow-runner.js +1 -1
  37. package/dist/native/darwin-arm64/manifest.json +1 -1
  38. package/dist/native/darwin-arm64/terminal-host.manifest.json +3 -3
  39. package/dist/native/darwin-arm64/terminal-host.node +0 -0
  40. package/dist/native/darwin-x64/manifest.json +1 -1
  41. package/dist/native/darwin-x64/terminal-host.manifest.json +3 -3
  42. package/dist/native/darwin-x64/terminal-host.node +0 -0
  43. package/dist/native/linux-x64/manifest.json +1 -1
  44. package/dist/native/linux-x64/terminal-host.manifest.json +3 -3
  45. package/dist/native/linux-x64/terminal-host.node +0 -0
  46. package/dist/native/win32-x64/manifest.json +2 -2
  47. package/dist/native/win32-x64/process-guardian.node +0 -0
  48. package/dist/native/win32-x64/terminal-host.manifest.json +3 -3
  49. package/dist/native/win32-x64/terminal-host.node +0 -0
  50. package/dist/ui/components/dialog-panel.d.ts +17 -8
  51. package/dist/ui/components/dialog-panel.js +97 -26
  52. package/dist/ui/components/index.d.ts +3 -3
  53. package/dist/ui/components/index.js +3 -3
  54. package/dist/ui/components/scrollbar.d.ts +1 -1
  55. package/dist/ui/components/scrollbar.js +1 -1
  56. package/dist/ui/components/selection-copy.d.ts +2 -4
  57. package/dist/ui/components/selection-copy.js +4 -2
  58. package/dist/ui/components/text-selection.d.ts +1 -2
  59. package/dist/ui/components/text-selection.js +5 -2
  60. package/dist/ui/components/transcript-viewport.js +3 -3
  61. package/dist/ui/settings/declarations.d.ts +1 -1
  62. package/dist/ui/settings/declarations.js +1 -1
  63. package/docs/architecture/resident-tabs.md +5 -4
  64. package/docs/features/launch-profiles.md +7 -4
  65. package/package.json +3 -3
@@ -9,11 +9,11 @@
9
9
  "architecture": "x64",
10
10
  "target": "x86_64-apple-darwin",
11
11
  "capability": "supported",
12
- "builtAt": "2026-10-28T07:20:46.993Z",
12
+ "builtAt": "2026-10-29T14:05:46.398Z",
13
13
  "artifact": {
14
14
  "filename": "terminal-host.node",
15
- "sha256": "b3dfb167c40179f65bb381bfd701f97bce42ee99bf935ae98c99e4a5bd4c7f01",
16
- "size": 6552896
15
+ "sha256": "5b2b2de17a50a371271c32546cc3c89bc6b16a96c39a534174fe5bb4ca1d60d6",
16
+ "size": 6562120
17
17
  },
18
18
  "entry": {
19
19
  "filename": "dist/foundation/resident-tabs/host-entry.js",
@@ -7,7 +7,7 @@
7
7
  "platform": "linux",
8
8
  "architecture": "x64",
9
9
  "capability": "supported",
10
- "builtAt": "2026-10-28T07:15:51.475Z",
10
+ "builtAt": "2026-10-29T14:00:03.332Z",
11
11
  "artifact": {
12
12
  "filename": "process-guardian.node",
13
13
  "sha256": "118e5954e3864672bd8482ab7edf419cb27dcdf2533720316979b7f5cc5a53a5",
@@ -9,11 +9,11 @@
9
9
  "architecture": "x64",
10
10
  "target": "x86_64-unknown-linux-gnu",
11
11
  "capability": "supported",
12
- "builtAt": "2026-10-28T07:18:32.802Z",
12
+ "builtAt": "2026-10-29T14:03:25.788Z",
13
13
  "artifact": {
14
14
  "filename": "terminal-host.node",
15
- "sha256": "16f3ea5e77bb19588e4ad97cd58b048dd6ab8295296eef106ec54ef4a13912b6",
16
- "size": 7296664
15
+ "sha256": "d55bf113092dcb926a6fb10961f30207b3624c3727357de0913a01a40026f594",
16
+ "size": 7303320
17
17
  },
18
18
  "entry": {
19
19
  "filename": "dist/foundation/resident-tabs/host-entry.js",
@@ -7,10 +7,10 @@
7
7
  "platform": "win32",
8
8
  "architecture": "x64",
9
9
  "capability": "supported",
10
- "builtAt": "2026-10-28T07:16:45.338Z",
10
+ "builtAt": "2026-10-29T14:00:59.192Z",
11
11
  "artifact": {
12
12
  "filename": "process-guardian.node",
13
- "sha256": "dd5110a0e331ca09b419147c628d54d8bc6394a142f1a52203923a1bf39827f7",
13
+ "sha256": "b855e7cd7efb5ff483e24c06247eddcafd3519e8bec4681d959f41dc3b08400e",
14
14
  "size": 402432
15
15
  },
16
16
  "provenance": {
@@ -9,11 +9,11 @@
9
9
  "architecture": "x64",
10
10
  "target": "x86_64-pc-windows-msvc",
11
11
  "capability": "supported",
12
- "builtAt": "2026-10-28T07:18:52.019Z",
12
+ "builtAt": "2026-10-29T14:04:19.346Z",
13
13
  "artifact": {
14
14
  "filename": "terminal-host.node",
15
- "sha256": "5eebca6c16d6b403275dcaa03579c05df47c218a0c57a6aa2b6e7e1c9763f03f",
16
- "size": 6381056
15
+ "sha256": "f60e7e32812f0288ac7480809e4a023f0dada6d8171d75a169ac653658d48809",
16
+ "size": 6392832
17
17
  },
18
18
  "entry": {
19
19
  "filename": "dist/foundation/resident-tabs/host-entry.js",
@@ -1,10 +1,10 @@
1
+ import type { PaneRect } from "./frame.js";
1
2
  import { LineInput } from "./line-input.js";
2
3
  import { type ShortcutHintEntry } from "./shortcut-hints.js";
3
4
  import type { UiTheme } from "./theme.js";
4
5
  /**
5
- * The panel a value with parts is edited in: at the foot of the screen, over the
6
- * surface it was opened from, ruled off above and below. It shows every part,
7
- * marks the one in hand, says what that part does, and says how to change it.
6
+ * Semantic content and overlay composition for Settings configure dialogs. The setting supplies
7
+ * rows and behavior; this component owns compact geometry, backdrop dimming, and panel placement.
8
8
  */
9
9
  export interface DialogRow {
10
10
  readonly label: string;
@@ -39,14 +39,23 @@ export interface SteppedDialogPanelState {
39
39
  readonly index: number;
40
40
  readonly hint: readonly ShortcutHintEntry[];
41
41
  }
42
+ /** Canonical native-matching background for every owned floating dialog surface. */
43
+ export declare function renderFloatingDialogSurface(text: string, mode: "truecolor" | "256color"): string;
44
+ /** Existing raised-menu background, centralized beside the darker dialog surface. */
45
+ export declare function renderFloatingMenuSurface(text: string, mode: "truecolor" | "256color"): string;
46
+ /** The shared compact width used by Settings configure dialogs and native tab dialogs. */
47
+ export declare function configureDialogWidth(width: number): number;
48
+ /** Rows available to a selector after retaining its title, description, control, and guidance. */
49
+ export declare function configureDialogChoiceCapacity(height: number, hasInput: boolean, descriptionRows?: number): number;
50
+ /** Number of modal rows a plain description occupies at the shared content width. */
51
+ export declare function configureDialogTextRows(text: string, width: number): number;
52
+ /** Dims a completed screen and overlays one full-intensity panel without disturbing ANSI tails. */
53
+ export declare function renderCenteredDialogOverlay(frame: readonly string[], dialogRows: readonly string[], rect: PaneRect, theme: UiTheme): readonly string[];
42
54
  /** The column values start at: past the widest label, capped so one long name cannot push them off. */
43
55
  export declare function dialogValueColumn(rows: readonly DialogRow[]): number;
44
- /**
45
- * The panel's lines. The first line is its rule, so the row at index N is drawn
46
- * one line below the panel's top.
47
- */
56
+ /** The semantic rows for one generic configure modal. */
48
57
  export declare function renderDialogPanel(state: DialogPanelState, width: number, theme: UiTheme): readonly string[];
49
- /** Owned stepped-selector composition for structured settings with nested choices. */
58
+ /** Owned stepped-selector content for structured settings with nested choices. */
50
59
  export declare function renderSteppedDialogPanel(state: SteppedDialogPanelState, width: number, theme: UiTheme): readonly string[];
51
60
  /** The row a pointer report lands on, or null when it is not on one. */
52
61
  export declare function dialogRowAt(frame: DialogPanelFrame, row: number, column: number, valueWidth: number): number | null;
@@ -1,27 +1,74 @@
1
- import { dialogFrameGeometry, insetDialogContent } from "../../contracts/presentation/index.js";
2
1
  import { LineInput, renderInputRow } from "./line-input.js";
3
2
  import { renderShortcutHintsWithClose } from "./shortcut-hints.js";
4
- import { displayWidth, truncateToWidth } from "./text.js";
3
+ import { overlaySpan } from "./spans.js";
4
+ import { displayWidth, padToWidth, truncateToWidth } from "./text.js";
5
5
  const LABEL_COLUMN_CAP = 30;
6
6
  const STEPPED_LABEL_COLUMN_CAP = 46;
7
- /** One frame path for every semantic row, regardless of which dialog supplies the content. */
7
+ const CONFIGURE_MODAL_MAX_WIDTH = 60;
8
+ const CONFIGURE_MODAL_PADDING_X = 2;
9
+ const SGR = /\u001b\[[0-9;]*m/gu;
10
+ /** Canonical native-matching background for every owned floating dialog surface. */
11
+ export function renderFloatingDialogSurface(text, mode) {
12
+ const background = mode === "truecolor" ? "\u001b[48;2;38;38;38m" : "\u001b[48;5;235m";
13
+ return `${background}${text}\u001b[49m`;
14
+ }
15
+ /** Existing raised-menu background, centralized beside the darker dialog surface. */
16
+ export function renderFloatingMenuSurface(text, mode) {
17
+ const background = mode === "truecolor" ? "\u001b[48;2;55;55;55m" : "\u001b[48;5;237m";
18
+ return `${background}${text}\u001b[49m`;
19
+ }
20
+ /** The shared compact width used by Settings configure dialogs and native tab dialogs. */
21
+ export function configureDialogWidth(width) {
22
+ return Math.min(CONFIGURE_MODAL_MAX_WIDTH, Math.max(0, width));
23
+ }
24
+ /** Rows available to a selector after retaining its title, description, control, and guidance. */
25
+ export function configureDialogChoiceCapacity(height, hasInput, descriptionRows = 1) {
26
+ const wrappedDescriptionRows = Math.max(1, descriptionRows);
27
+ return Math.max(1, Math.max(0, height) - (hasInput ? 9 : 7) - (wrappedDescriptionRows - 1));
28
+ }
29
+ /** Number of modal rows a plain description occupies at the shared content width. */
30
+ export function configureDialogTextRows(text, width) {
31
+ return wrapText(text, Math.max(0, configureDialogWidth(width) - CONFIGURE_MODAL_PADDING_X * 2)).length;
32
+ }
33
+ /** One frame path for every semantic row, regardless of which configure dialog supplies content. */
8
34
  function dialogPanelLayout(width, theme) {
9
- const geometry = dialogFrameGeometry(width);
35
+ const frameWidth = Math.max(0, width);
36
+ const left = Math.min(CONFIGURE_MODAL_PADDING_X, frameWidth);
37
+ const right = Math.min(CONFIGURE_MODAL_PADDING_X, frameWidth - left);
38
+ const contentWidth = Math.max(0, frameWidth - left - right);
10
39
  return {
11
- rule: theme.fg("border", "─".repeat(geometry.frameWidth)),
12
- contentWidth: geometry.contentWidth,
13
- contentRow: (painted, raw = painted) => insetDialogContent(pad(truncateToWidth(painted, geometry.contentWidth), geometry.contentWidth, raw), geometry),
40
+ blank: " ".repeat(frameWidth),
41
+ contentWidth,
42
+ contentRow: (painted, raw = painted, selected = false) => {
43
+ const content = pad(truncateToWidth(painted, contentWidth), contentWidth, raw);
44
+ const rightPadding = " ".repeat(right);
45
+ return `${" ".repeat(left)}${selected ? theme.highlight(content) : content}${selected ? theme.panel(rightPadding) : rightPadding}`;
46
+ },
14
47
  };
15
48
  }
49
+ /** Dims a completed screen and overlays one full-intensity panel without disturbing ANSI tails. */
50
+ export function renderCenteredDialogOverlay(frame, dialogRows, rect, theme) {
51
+ const width = Math.max(0, rect.width);
52
+ const height = Math.max(0, rect.height);
53
+ const panelWidth = configureDialogWidth(width);
54
+ const visibleDialog = dialogRows.slice(0, height);
55
+ const top = Math.max(0, Math.floor((height - visibleDialog.length) / 2));
56
+ const left = Math.max(0, Math.floor((width - panelWidth) / 2));
57
+ return Array.from({ length: height }, (_, row) => {
58
+ const backdrop = dimStyledLine(frame[row] ?? "", width);
59
+ const dialogRow = visibleDialog[row - top];
60
+ if (dialogRow === undefined || panelWidth === 0)
61
+ return backdrop;
62
+ const panel = `\u001b[22;39;49m${theme.panel(padToWidth(dialogRow, panelWidth))}`;
63
+ return overlaySpan(backdrop, left, left + panelWidth, panel);
64
+ });
65
+ }
16
66
  /** The column values start at: past the widest label, capped so one long name cannot push them off. */
17
67
  export function dialogValueColumn(rows) {
18
68
  const widest = Math.min(LABEL_COLUMN_CAP, Math.max(0, ...rows.map(row => displayWidth(row.label))));
19
69
  return 2 + widest + 2;
20
70
  }
21
- /**
22
- * The panel's lines. The first line is its rule, so the row at index N is drawn
23
- * one line below the panel's top.
24
- */
71
+ /** The semantic rows for one generic configure modal. */
25
72
  export function renderDialogPanel(state, width, theme) {
26
73
  const labelColumn = Math.min(LABEL_COLUMN_CAP, Math.max(0, ...state.rows.map(row => displayWidth(row.label))));
27
74
  const layout = dialogPanelLayout(width, theme);
@@ -33,26 +80,26 @@ export function renderDialogPanel(state, width, theme) {
33
80
  const content = selected
34
81
  ? `${theme.fg("accent", cursor)}${theme.fg("text", padded)} ${theme.fg("muted", row.value)}`
35
82
  : `${cursor}${padded} ${theme.fg("muted", row.value)}`;
36
- const painted = selected ? theme.highlight(content) : content;
37
- return layout.contentRow(painted, raw);
83
+ return layout.contentRow(content, raw, selected);
38
84
  });
39
85
  const description = state.rows[state.index]?.description ?? "";
86
+ const descriptionRows = wrapText(description, layout.contentWidth);
40
87
  const hint = typeof state.hint === "string"
41
88
  ? theme.fg("dim", state.hint)
42
89
  : renderShortcutHintsWithClose(state.hint, theme, layout.contentWidth, 0);
43
90
  const plainHint = typeof state.hint === "string" ? state.hint : " ".repeat(displayWidth(hint));
44
91
  return [
45
- layout.rule,
92
+ layout.blank,
46
93
  layout.contentRow(theme.bold(theme.fg("accent", state.title)), state.title),
47
- layout.contentRow(theme.fg("dim", description), description),
48
- "",
94
+ ...descriptionRows.map(line => layout.contentRow(theme.fg("dim", line), line)),
95
+ layout.blank,
49
96
  ...rows,
50
- "",
97
+ layout.blank,
51
98
  layout.contentRow(hint, plainHint),
52
- layout.rule,
99
+ layout.blank,
53
100
  ];
54
101
  }
55
- /** Owned stepped-selector composition for structured settings with nested choices. */
102
+ /** Owned stepped-selector content for structured settings with nested choices. */
56
103
  export function renderSteppedDialogPanel(state, width, theme) {
57
104
  const layout = dialogPanelLayout(width, theme);
58
105
  const labelColumn = Math.min(STEPPED_LABEL_COLUMN_CAP, Math.max(0, ...state.rows.map(row => displayWidth(`${row.label}${row.labelSuffix ?? ""}`))));
@@ -67,7 +114,7 @@ export function renderSteppedDialogPanel(state, width, theme) {
67
114
  const content = selected
68
115
  ? `${theme.fg("accent", cursor)}${label}${theme.fg("muted", suffix)}`
69
116
  : `${cursor}${label}${theme.fg("muted", suffix)}`;
70
- return layout.contentRow(selected ? theme.highlight(content) : content, raw);
117
+ return layout.contentRow(content, raw, selected);
71
118
  });
72
119
  const input = state.input === undefined
73
120
  ? []
@@ -78,21 +125,22 @@ export function renderSteppedDialogPanel(state, width, theme) {
78
125
  promptRole: "accent",
79
126
  theme,
80
127
  }).lines.map(row => layout.contentRow(row)),
81
- "",
128
+ layout.blank,
82
129
  ];
130
+ const descriptionRows = wrapText(state.description, layout.contentWidth);
83
131
  const step = `(step ${state.step}/${state.steps})`;
84
132
  const title = `${state.title} ${step}`;
85
133
  const paintedTitle = `${theme.bold(theme.fg("accent", state.title))} ${theme.fg("dim", step)}`;
86
134
  return [
87
- layout.rule,
135
+ layout.blank,
88
136
  layout.contentRow(paintedTitle, title),
89
- layout.contentRow(theme.fg("dim", state.description), state.description),
90
- "",
137
+ ...descriptionRows.map(line => layout.contentRow(theme.fg("dim", line), line)),
138
+ layout.blank,
91
139
  ...input,
92
140
  ...rows,
93
- "",
141
+ layout.blank,
94
142
  layout.contentRow(renderShortcutHintsWithClose(state.hint, theme, layout.contentWidth)),
95
- layout.rule,
143
+ layout.blank,
96
144
  ];
97
145
  }
98
146
  /** The row a pointer report lands on, or null when it is not on one. */
@@ -107,3 +155,26 @@ function pad(line, width, raw) {
107
155
  const visible = displayWidth(raw);
108
156
  return visible >= width ? line : line + " ".repeat(width - visible);
109
157
  }
158
+ function dimStyledLine(line, width) {
159
+ const padded = padToWidth(line, width);
160
+ return `\u001b[2m${padded.replace(SGR, sequence => `${sequence}\u001b[2m`)}\u001b[22m`;
161
+ }
162
+ function wrapText(text, width) {
163
+ if (text.length === 0 || width <= 0)
164
+ return [""];
165
+ const lines = [];
166
+ let line = "";
167
+ for (const word of text.split(/\s+/u)) {
168
+ const candidate = `${line}${line.length === 0 ? "" : " "}${word}`;
169
+ if (line.length > 0 && displayWidth(candidate) > width) {
170
+ lines.push(line);
171
+ line = word;
172
+ }
173
+ else {
174
+ line = candidate;
175
+ }
176
+ }
177
+ if (line.length > 0)
178
+ lines.push(truncateToWidth(line, width));
179
+ return lines.length === 0 ? [""] : lines;
180
+ }
@@ -1,4 +1,4 @@
1
- export { dialogRowAt, dialogValueColumn, renderDialogPanel, renderSteppedDialogPanel } from "./dialog-panel.js";
1
+ export { configureDialogChoiceCapacity, configureDialogTextRows, configureDialogWidth, dialogRowAt, dialogValueColumn, renderCenteredDialogOverlay, renderDialogPanel, renderSteppedDialogPanel, } from "./dialog-panel.js";
2
2
  export type { DialogPanelFrame, DialogPanelState, DialogRow, SteppedDialogPanelState } from "./dialog-panel.js";
3
3
  export { FrameContractError, assertPaneRect, finalizeFrame, validateFrame } from "./frame.js";
4
4
  export type { PaneRect } from "./frame.js";
@@ -19,7 +19,7 @@ export { progressStatusFrame, progressStatusText } from "./progress-status.js";
19
19
  export type { ProgressStatusFrameStyles, ProgressStatusMarker } from "./progress-status.js";
20
20
  export { FrameCache, RENDER_REVISION_KINDS, RenderRevisionTracker, ZERO_REVISIONS, normalizeRevisions, revisionsEqual } from "./revision.js";
21
21
  export type { RenderCacheContract, RenderRevisionKind, RenderRevisions } from "./revision.js";
22
- export { MAX_COPY_ROWS, MAX_COPY_SOURCE_UNITS, selectionCopyLineContent, selectionCopyRowText } from "./selection-copy.js";
22
+ export { COPY_ACKNOWLEDGEMENT_DURATION_MS, MAX_COPY_ROWS, MAX_COPY_SOURCE_UNITS, copyAcknowledgementText, selectionCopyLineContent, selectionCopyRowText } from "./selection-copy.js";
23
23
  export type { SelectionCopyPrompt, SelectionCopyRow, SelectionCopySnapshot } from "./selection-copy.js";
24
24
  export { ScrollbarRails, isThumbRow, scrollForThumbRow, scrollForTrackPage, scrollbarGeometry, scrollbarPresentation, scrollbarReservesSpace, scrollbarSelectionRows, scrollbarWheelRows, } from "./scrollbar.js";
25
25
  export type { RailPointer, RailPosition, ScrollbarAppearance, ScrollbarGeometry, ScrollbarInput, ScrollbarPresentation, ScrollbarPresentationInput, ScrollbarSpeed, ScrollbarStyle, } from "./scrollbar.js";
@@ -42,7 +42,7 @@ export { RAIL_COLUMNS, renderEmptyState, withScrollbarRail } from "./surface.js"
42
42
  export type { RailOptions } from "./surface.js";
43
43
  export { displayColumnSlice, displayWidth, displayWordColumnRange, faint, padToWidth, stripAnsi, truncateToWidth } from "./text.js";
44
44
  export type { DisplayColumnRange, DisplayColumnSlice } from "./text.js";
45
- export { TEXT_SELECTION_MULTI_CLICK_MS, extendTextSelection, orderedTextSelection, plainTextBetweenColumns, pressTextSelection, releaseTextSelection, textSelectionLineExtendColumn, textSelectionPointAt, textSelectionText, usefulTextLineContent, } from "./text-selection.js";
45
+ export { TEXT_SELECTION_MULTI_CLICK_MS, extendTextSelection, orderedTextSelection, paintTextSelection, plainTextBetweenColumns, pressTextSelection, releaseTextSelection, textSelectionLineExtendColumn, textSelectionPointAt, textSelectionText, usefulTextLineContent, } from "./text-selection.js";
46
46
  export type { OrderedTextSelection, TextSelection, TextSelectionClick, TextSelectionLineContent, TextSelectionPoint, TextSelectionPressInput, } from "./text-selection.js";
47
47
  export { PLAIN_THEME } from "./theme.js";
48
48
  export type { UiTheme, UiThemeToken } from "./theme.js";
@@ -1,4 +1,4 @@
1
- export { dialogRowAt, dialogValueColumn, renderDialogPanel, renderSteppedDialogPanel } from "./dialog-panel.js";
1
+ export { configureDialogChoiceCapacity, configureDialogTextRows, configureDialogWidth, dialogRowAt, dialogValueColumn, renderCenteredDialogOverlay, renderDialogPanel, renderSteppedDialogPanel, } from "./dialog-panel.js";
2
2
  export { FrameContractError, assertPaneRect, finalizeFrame, validateFrame } from "./frame.js";
3
3
  export { humanizeLabel, humanizeTitle } from "./label.js";
4
4
  export { LineInput, caretCell, handleLineInputKey, renderInputRow, wordLeft, wordRight } from "./line-input.js";
@@ -9,7 +9,7 @@ export { MOUSE_TRACKING_OFF, MOUSE_TRACKING_ON, parseMouseInput, routeMouseInput
9
9
  export { isInsidePane, toPaneLocalMouse } from "./pane.js";
10
10
  export { progressStatusFrame, progressStatusText } from "./progress-status.js";
11
11
  export { FrameCache, RENDER_REVISION_KINDS, RenderRevisionTracker, ZERO_REVISIONS, normalizeRevisions, revisionsEqual } from "./revision.js";
12
- export { MAX_COPY_ROWS, MAX_COPY_SOURCE_UNITS, selectionCopyLineContent, selectionCopyRowText } from "./selection-copy.js";
12
+ export { COPY_ACKNOWLEDGEMENT_DURATION_MS, MAX_COPY_ROWS, MAX_COPY_SOURCE_UNITS, copyAcknowledgementText, selectionCopyLineContent, selectionCopyRowText } from "./selection-copy.js";
13
13
  export { ScrollbarRails, isThumbRow, scrollForThumbRow, scrollForTrackPage, scrollbarGeometry, scrollbarPresentation, scrollbarReservesSpace, scrollbarSelectionRows, scrollbarWheelRows, } from "./scrollbar.js";
14
14
  export { assertOwnedShortcutHintConventions, renderShortcutHints, renderShortcutHintsWithClose, shortcutHintsText } from "./shortcut-hints.js";
15
15
  export { GLOBAL_SCOPE, ShortcutRegistry, assembleShortcuts, assertNoShortcutConflicts } from "./shortcuts.js";
@@ -21,6 +21,6 @@ export { numericValues, steppedValue, stepperEnds } from "./stepper.js";
21
21
  export { TranscriptViewport, assertTranscriptViewportFrameDescriptor } from "./transcript-viewport.js";
22
22
  export { RAIL_COLUMNS, renderEmptyState, withScrollbarRail } from "./surface.js";
23
23
  export { displayColumnSlice, displayWidth, displayWordColumnRange, faint, padToWidth, stripAnsi, truncateToWidth } from "./text.js";
24
- export { TEXT_SELECTION_MULTI_CLICK_MS, extendTextSelection, orderedTextSelection, plainTextBetweenColumns, pressTextSelection, releaseTextSelection, textSelectionLineExtendColumn, textSelectionPointAt, textSelectionText, usefulTextLineContent, } from "./text-selection.js";
24
+ export { TEXT_SELECTION_MULTI_CLICK_MS, extendTextSelection, orderedTextSelection, paintTextSelection, plainTextBetweenColumns, pressTextSelection, releaseTextSelection, textSelectionLineExtendColumn, textSelectionPointAt, textSelectionText, usefulTextLineContent, } from "./text-selection.js";
25
25
  export { PLAIN_THEME } from "./theme.js";
26
26
  export { menuRowAt, renderValueMenu, valueMenuFrame } from "./value-menu.js";
@@ -6,7 +6,7 @@
6
6
  export interface ScrollbarGeometry {
7
7
  /** Rows in the track. */
8
8
  readonly trackHeight: number;
9
- /** Thumb height in rows, at least one. */
9
+ /** Thumb height in rows, at least two when the track permits. */
10
10
  readonly thumbHeight: number;
11
11
  /** Zero-based row of the thumb's top within the track. */
12
12
  readonly thumbTop: number;
@@ -39,7 +39,7 @@ export function scrollbarGeometry(input) {
39
39
  return null;
40
40
  const maxScroll = contentLength - viewportHeight;
41
41
  const scroll = Math.min(Math.max(input.scroll, 0), maxScroll);
42
- const thumbHeight = Math.max(1, Math.min(trackHeight, Math.round(trackHeight * viewportHeight / contentLength)));
42
+ const thumbHeight = Math.min(trackHeight, Math.max(2, Math.round(trackHeight * viewportHeight / contentLength)));
43
43
  const travel = trackHeight - thumbHeight;
44
44
  const thumbTop = maxScroll === 0 ? 0 : Math.min(travel, Math.round(travel * scroll / maxScroll));
45
45
  return { trackHeight, thumbHeight, thumbTop, maxScroll };
@@ -2,25 +2,23 @@ import { type OrderedTextSelection, type TextSelectionLineContent } from "./text
2
2
  /** Bound selected-source retention independently of history length and encoded payload size. */
3
3
  export declare const MAX_COPY_SOURCE_UNITS: number;
4
4
  export declare const MAX_COPY_ROWS = 65536;
5
+ export declare const COPY_ACKNOWLEDGEMENT_DURATION_MS = 1000;
6
+ export declare function copyAcknowledgementText(characters: number): string;
5
7
  export type SelectionCopyPrompt = "first" | "continuation" | undefined;
6
8
  export interface SelectionCopyRow {
7
9
  readonly text: string;
8
10
  readonly prompt?: "first" | "continuation";
9
11
  }
10
- /** Immutable selected rows only; safe to transfer to an isolated text preparer. */
11
12
  export interface SelectionCopySnapshot {
12
13
  readonly selection: OrderedTextSelection;
13
14
  readonly revision: number;
14
- /** Prompt copy/cut already supplies exact expanded text; do not apply transcript chrome stripping. */
15
15
  readonly literal?: boolean;
16
16
  readonly rows: readonly SelectionCopyRow[];
17
17
  readonly sourceUnits: number;
18
18
  readonly captureMs?: number;
19
19
  readonly rejected?: "size";
20
20
  }
21
- /** Shares the viewport's semantic chrome exclusions with isolated copy preparation. */
22
21
  export declare function selectionCopyLineContent(text: string, prompt: SelectionCopyPrompt): TextSelectionLineContent;
23
- /** Builds an immutable exact-text snapshot without changing editor-owned selection state. */
24
22
  export declare function literalSelectionCopySnapshot(text: string, revision?: number): SelectionCopySnapshot | null;
25
23
  /** Extracts one snapshot row with the existing grapheme/newline policy; run expensive rows off-thread. */
26
24
  export declare function selectionCopyRowText(snapshot: Pick<SelectionCopySnapshot, "selection" | "literal">, row: SelectionCopyRow, index: number): string;
@@ -3,7 +3,10 @@ import { stripAnsi } from "./text.js";
3
3
  /** Bound selected-source retention independently of history length and encoded payload size. */
4
4
  export const MAX_COPY_SOURCE_UNITS = 32 * 1024 * 1024;
5
5
  export const MAX_COPY_ROWS = 65_536;
6
- /** Shares the viewport's semantic chrome exclusions with isolated copy preparation. */
6
+ export const COPY_ACKNOWLEDGEMENT_DURATION_MS = 1_000;
7
+ export function copyAcknowledgementText(characters) {
8
+ return `copied ${characters} chars to clipboard`;
9
+ }
7
10
  export function selectionCopyLineContent(text, prompt) {
8
11
  const plain = stripAnsi(text);
9
12
  if (prompt !== undefined) {
@@ -19,7 +22,6 @@ export function selectionCopyLineContent(text, prompt) {
19
22
  return { from, to: from + 1 };
20
23
  return usefulTextLineContent(plain, from);
21
24
  }
22
- /** Builds an immutable exact-text snapshot without changing editor-owned selection state. */
23
25
  export function literalSelectionCopySnapshot(text, revision = 0) {
24
26
  if (text.trim().length === 0)
25
27
  return null;
@@ -1,4 +1,3 @@
1
- /** A reported pointer cell plus its grapheme-aligned zero-based boundaries. */
2
1
  export interface TextSelectionPoint {
3
2
  readonly line: number;
4
3
  readonly column: number;
@@ -28,7 +27,6 @@ export interface TextSelectionLineContent {
28
27
  readonly to?: number;
29
28
  readonly selectable?: boolean;
30
29
  }
31
- /** Ordered zero-based, half-open display-column boundaries. */
32
30
  export interface OrderedTextSelection {
33
31
  readonly start: TextSelectionPoint;
34
32
  readonly end: TextSelectionPoint;
@@ -45,6 +43,7 @@ export interface TextSelectionPressInput {
45
43
  readonly now?: number;
46
44
  }
47
45
  export declare const TEXT_SELECTION_MULTI_CLICK_MS = 500;
46
+ export declare function paintTextSelection(line: string, from: number, to: number, widthOf?: (text: string) => number, clearInverse?: boolean): string;
48
47
  export declare function orderedTextSelection(selection: TextSelection | undefined): OrderedTextSelection | undefined;
49
48
  export declare function pressTextSelection(input: TextSelectionPressInput): {
50
49
  readonly selection?: TextSelection;
@@ -1,6 +1,11 @@
1
+ import { backgroundSgrSpan } from "./spans.js";
1
2
  import { displayWidth, stripAnsi } from "./text.js";
2
3
  const GRAPHEMES = new Intl.Segmenter(undefined, { granularity: "grapheme" });
3
4
  export const TEXT_SELECTION_MULTI_CLICK_MS = 500;
5
+ const TEXT_SELECTION_BACKGROUND = "\u001b[48;2;38;79;120m";
6
+ export function paintTextSelection(line, from, to, widthOf = displayWidth, clearInverse = false) {
7
+ return backgroundSgrSpan(line, from, to, `${clearInverse ? "\u001b[27m" : ""}${TEXT_SELECTION_BACKGROUND}`, "\u001b[49m", widthOf);
8
+ }
4
9
  export function orderedTextSelection(selection) {
5
10
  if (selection === undefined)
6
11
  return undefined;
@@ -17,8 +22,6 @@ export function orderedTextSelection(selection) {
17
22
  }
18
23
  if (!selection.cell && selection.dragged !== true)
19
24
  return undefined;
20
- // Invariant: an accepted drag includes both endpoint graphemes, even after returning
21
- // to its anchor. Only an unextended point press is empty; paint and copy stay half-open.
22
25
  const anchorFirst = comparePoints(anchor, head) <= 0;
23
26
  const first = anchorFirst ? anchor : head;
24
27
  const last = anchorFirst ? head : anchor;
@@ -1,8 +1,8 @@
1
1
  import { MAX_COPY_SOURCE_UNITS, selectionCopyLineContent } from "./selection-copy.js";
2
2
  import { isThumbRow, scrollbarGeometry, scrollbarPresentation, } from "./scrollbar.js";
3
- import { backgroundSgrSpan, overlaySpan } from "./spans.js";
3
+ import { overlaySpan } from "./spans.js";
4
4
  import { faint, truncateToWidth, displayWidth, stripAnsi } from "./text.js";
5
- import { extendTextSelection, orderedTextSelection, pressTextSelection, releaseTextSelection, textSelectionLineExtendColumn, textSelectionPointAt, textSelectionText, usefulTextLineContent, } from "./text-selection.js";
5
+ import { extendTextSelection, orderedTextSelection, paintTextSelection, pressTextSelection, releaseTextSelection, textSelectionLineExtendColumn, textSelectionPointAt, textSelectionText, usefulTextLineContent, } from "./text-selection.js";
6
6
  const CONTROL_STYLE_RESET = "\u001b]8;;\u001b\\\u001b[0m";
7
7
  // Protocol: upstream OSC 133 zones are layout annotations, not fullscreen cursor commands.
8
8
  // Styling/padding can move them away from the row prefix Pi strips; 133;A then advances a real
@@ -43,7 +43,7 @@ const PLAIN_THEME = {
43
43
  sticky: (text, hovered) => hovered ? `\u001b[7m${text}\u001b[27m` : text,
44
44
  quietSticky: faint,
45
45
  bottomControl: (text, hovered) => hovered ? `\u001b[7m${text}\u001b[27m` : `\u001b[7m${text}\u001b[27m`,
46
- selection: (line, from, to) => backgroundSgrSpan(line, from, to),
46
+ selection: paintTextSelection,
47
47
  };
48
48
  /**
49
49
  * A1-owned viewport state. It knows only rows and semantic prompt anchors; Pi
@@ -30,7 +30,7 @@ export declare const OWNED_SETTING_DECLARATIONS: Readonly<{
30
30
  id: "generic";
31
31
  title: "Generic";
32
32
  }>;
33
- description: "Auto installs verified channel updates silently, Notify shows the current update notice, and No leaves updates manual. Checks use the public npm registry; applies on next start.";
33
+ description: "Auto checks the running channel every 15 minutes while open and installs verified updates silently. Notify checks on startup and shows an update notice; No leaves updates manual. Applies on next start.";
34
34
  application: "restart";
35
35
  defaultValue: "auto";
36
36
  allowedValues: readonly ["auto", "notify", "no"];
@@ -24,7 +24,7 @@ export const OWNED_SETTING_DECLARATIONS = Object.freeze({
24
24
  id: "updateCheck",
25
25
  label: "Update",
26
26
  section: GENERIC_SECTION,
27
- description: "Auto installs verified channel updates silently, Notify shows the current update notice, and No leaves updates manual. Checks use the public npm registry; applies on next start.",
27
+ description: "Auto checks the running channel every 15 minutes while open and installs verified updates silently. Notify checks on startup and shows an update notice; No leaves updates manual. Applies on next start.",
28
28
  application: "restart",
29
29
  defaultValue: "auto",
30
30
  allowedValues: Object.freeze(["auto", "notify", "no"]),
@@ -373,12 +373,13 @@ Tab status is an A1-owned state machine fed by `bridge.status`:
373
373
  - Rename is inline in the chip: `Enter` commits, `Esc` or a click elsewhere cancels, and `Ctrl+Left` / `Ctrl+Right` move by word. The name is trimmed, must not be empty, is at most 64 characters, and has control characters stripped.
374
374
  - **Plus menu.** The A1 setting `tabBarPlusMenu`, shown as `Plus menu` in the `Tab Bar` Settings section, holds the kinds `+` offers, Agent and every utility kind, as comma-joined keys in that order and defaults to `agent`. The `Plus Menu` dialog lists every kind with `●` when enabled and `○` when not under `Choose what the + button in the tab bar can add.`, with `Space` to toggle, Up and Down to move, typing to filter, and `Esc` to close. `+`'s `Configure` opens it in the attach client as a modal over the dimmed active tab or landing, built on the command palette's list and renderer, with a reversed white search caret like owned inputs. The Settings `configure` entry opens the same modal: the Settings child sends a `configure-plus` bridge request as the tab's controlling client, which the server forwards to that client as `plus.configure`; only without a bridge does Settings open its own equivalent dialog. Each toggle is written at once: the attach client takes the `settings.json.lock` directory lock the TypeScript writer takes through `proper-lockfile`, rereads the document, replaces only `a1.tabBarPlusMenu` (removed at the default), and renames a temporary file over it. A refused write restores the stored menu. The attach client reads the setting live, and with no kind enabled the strip hides `+`. Because a modal's overlay saves and restores the cursor while faint is active, the strip starts every frame from reset attributes, so no chip stays faint after a modal closes.
375
375
  - **Command palette.** `Alt+/` (`tabs.commands`) opens a centered box over the whole frame in the owned list style: an accent `Commands` title, a `> ` search row, `Agent`, `Settings`, and `Changelog` with their effective shortcuts right-aligned, and a key hint. Typing or pasting filters it, Up and Down move, `Enter` runs the selected entry through its shortcut action, and `Esc` or `Alt+/` closes it; another tab shortcut closes it and runs. While it is open no key, paste, or pointer event reaches the strip or a child, the cursor is hidden, and the composer redraws the strip and body with faint re-asserted after every SGR beneath the bright overlay.
376
- - **Changelog.** The changelog opens as a singleton `changelog-` utility tab titled `Changelog`, from the palette or the unbound `tabs.changelog` action. The tab registry accepts the `agent`, `settings`, and `changelog` kinds. Like Settings it is never renamed, offers only `Close`, is placed last, does not count as an Agent, and is prewarmed and hidden: the attach client starts it with `tab.prewarm-changelog` once Settings is ready, and again after a Changelog tab closes, so opening it shows the running tab at once. Like a hidden Settings tab, a hidden Changelog never takes the view when the viewed tab closes, so closing the last Agent shows the landing. Its screen is the tab: it shows no `Esc close` hint, and neither `Esc` nor one `Ctrl+C` closes it.
377
- - **Utility tab rules.** Settings and Changelog are kinds in one table, `UtilityKind` in the attach core and `UTILITY_KINDS` on the server, and every rule follows the kind rather than a per-kind path: a kind's toggle shows a tab already in the strip in its slot, reveals a hidden or absent one through `tab.open-<kind>`, which the server answers by starting it or placing the running one last, and hides the viewed one while its process keeps running; the client prewarms each kind with `tab.prewarm-<kind>` once the kind before it runs; and a hidden one never takes the view when another tab closes. In the child, the route matching a resident tab's id is that tab's pinned screen. Adding a kind is a table entry, its prewarm state, and its route.
376
+ - **Changelog.** The changelog opens as a singleton `changelog-` tab titled `Changelog`, from the palette or the unbound `tabs.changelog` action. The tab registry accepts the `agent`, `settings`, and `changelog` kinds. Its provider registration makes it non-renameable, Close-only, sessionless, prewarmed, and hidden: the attach client starts it with `tab.prewarm-changelog` once Settings is ready, and again after a Changelog tab closes, so opening it shows the running tab at once. Like a hidden Settings tab, a hidden Changelog never takes the view when the viewed tab closes, so closing the last Agent shows the landing. Its screen is the tab: it shows no `Esc close` hint, and neither `Esc` nor one `Ctrl+C` closes it.
377
+ - **Tab-bar provider.** Agent, Settings, and Changelog are registered once in the I/O-free `TabKind` provider shared by the attach client and server. Each registration declares its label, shortcut action, multiplicity, create-or-toggle activation, prewarm policy, renameability, Agent-session ownership, child route, and launcher marker. Shortcuts, the command palette, `+`, popup capabilities, registry validation, session handling, and route delivery consume that provider instead of rebuilding Agent or utility tables. Multi-instance Agent activation creates a tab; singleton-toggle activation creates or reveals its one tab, selects it in place when already shown, and hides it while viewed according to the common toggle rule. Singleton visibility and prewarm state are arrays in provider order, so adding another singleton does not add a concrete view field.
378
+ - The tab bar also owns one kind-neutral ordering API: every newly created tab and every hidden tab newly joining the strip appends after all live tabs; selecting an already shown tab retains its slot; drag order remains authoritative; and stopped registry records never affect the visible insertion destination. Cold holder creation, standby promotion, and singleton reveal use these same operations, so `Agent`, then `Settings`, then another `Agent` stays in that order.
378
379
  - `bin/ui.js` resolves the utility route from the consumed tab id before loading the declared Agent startup graph. A utility id imports the slim composition instead, so no Pi engine host, Agent session, extensions, prompt history, suggestions, or draft recovery are loaded or started; Agent tabs, the standby, `a1 solo`, and `a1 pi` keep the full graph.
379
380
  - The slim process still owns one fullscreen `OwnedUiTerminalHost`, the background canvas, theme and accent singletons, Pi keybindings, controller-boundary input, and the tab bridge. A minimal presenter opens only the pinned route. It publishes effective tab shortcuts, requests the attach client's Plus Menu through `configure-plus`, requests tab close when the screen closes, detaches the controlling client on the interrupt exit, and ends on `bridge.stop`.
380
381
  - Settings builds its Agent section from Pi's persisted `SettingsManager` after the ordinary project-trust decision, with persist-only bindings because each Agent process owns live effects. Its per-model thinking choices come from an offline `ModelRuntime` over the profile's `auth.json` and `models.json`. Changelog reads the packaged release-note catalog directly and never takes or acknowledges an Agent startup "What's New" claim.
381
- - **Reveal.** Opening a tab draws it at once: a created Agent and a revealed Settings or Changelog tab both keep the current content, another tab or the landing, until their first drawn frame commits, a retained blank surface included, and a tab that never draws shows after the bounded first-draw wait. Only a resize fades (`roles/reveal.rs`): once the curtain lifts, the whole frame draws faint for one fade interval with no cursor, then at full intensity. A starting chip shows no spinner. One tab opens at a time: new-Agent input while a tab the client asked for is starting or not yet shown starts nothing.
382
+ - **Reveal.** Opening a tab draws it at once: a created Agent and a revealed Settings or Changelog tab both keep the current content, another tab or the landing, until their first nonblank drawn frame commits, a retained blank surface included. Blank child revisions remain parked for any duration rather than exposing an empty body or child cursor; explicit failure and recovery outcomes still replace the startup presentation. Every changed-size resize forwards geometry immediately and repaints the retained strip and child surface directly at the new dimensions; no resize curtain or fade remains. Inactive terminal-tab resizes also repaint without an overlay, and focus return refreshes the complete frame. A starting chip shows no spinner. One tab opens at a time: new-Agent input while a tab the client asked for is starting or not yet shown starts nothing.
382
383
  - **Rename selection.** The inline rename draws a selection on the prompt editor's selection background (RGB `38,79,120`, palette `24`) and keeps the chip's text color, restoring the chip's background after it; only the caret is a reversed cell.
383
384
  - **Close.** `Alt+W` and the right-click menu close a tab directly, including while it is working. Closing removes the chip without a confirmation, stopping/read-only frame, or close glyph, then gracefully stops the child under the existing bounded termination policy. Its session stays resumable. Closing the last tab leaves `+` and the clock beside the landing.
384
385
  - **Landing.** While the attach client has no tab to show, the attach role renders the a1 landing as the frame's body. The launch cwd sits where an Agent tab's footer shows it, flush left on the second-to-last body row with the same `~` formatting and `dim` color, so the folder does not move when a tab opens or closes; `v<version>`, read once from the release root's `package.json`, sits below it and the three-row half-block `a1` wordmark in the profile's accent above it (omitted in Apple Terminal). The accent colors are a native table pinned by `landing_accents.json`, which a TypeScript test checks against `derivePiAccentProjection`. The body is painted with the canvas `backgroundStyle` gives Agent tabs. The landing holds no input: tab shortcuts, the launcher, strip mouse, and double-`Ctrl+C` behave as with tabs, and no cursor shows. A hidden Settings tab is prewarmed and kept running beside the landing, as beside Agents, so Settings opens without a start; it closes when the client detaches, and an attach treats a registry holding only Settings as empty, so it starts and views its first Agent. An Agent record whose child exited and that no holder owns (an update shutdown, a crash, a kill) is treated the same way: `attach.start` drops those stopped records and starts a new Agent under a fresh `tab-` id so it never adopts a stopped tab's directory, draft, or recovery screen, while a profile that never had an Agent keeps `tab-1`; admission never pins a stopped record as the view, and it names the fresh id itself, which `attach.start` reads back from the client session, so the surface subscription admission takes and the tab it starts agree. On the client, a first registry list with nothing to show counts as the strip shown empty, so the landing with `+` and the clock paints at once instead of an empty screen, while a listed starting tab keeps its first-draw gate. Adding a tab from the landing keeps it until the tab's first drawn frame replaces it at once; the landing has no animation of its own.
@@ -617,7 +618,7 @@ On 2026-10-24 the user refined the tab bar:
617
618
  - `+` adds an Agent by default; what it offers, Agent, Settings, and Changelog, is configurable from a right-click `Configure` modal over the active tab and from `Tab Bar` › `Plus menu` in Settings, where the `Tabs` section is renamed `Tab Bar`. With nothing enabled, `+` is hidden.
618
619
  - `Alt+/` opens a keyboard-only command palette over a dimmed frame: `Agent`, `Settings`, and `Changelog`.
619
620
  - The changelog opens at once as its own prewarmed singleton tab that `Esc` does not close, and leaves resident Agents' commands.
620
- - Opening a tab, a new Agent or a revealed Settings or Changelog tab, draws it at once when its first frame is drawn, with no fade and no starting spinner; the current content, landing included, stays until then. Only a resize fades. The landing's wipe animation is removed.
621
+ - Opening a tab, a new Agent or a revealed Settings or Changelog tab, draws it at once when its first nonblank frame is drawn, with no fade and no starting spinner; the current content, landing included, stays across blank revisions until then, while explicit failure and recovery remain visible. Quick snaps, prolonged drags, and inactive terminal-tab resizes all repaint directly without a curtain or fade. The landing's wipe animation is removed.
621
622
  - The tab rename selection matches the editor's selection style, and a tab's name and its session's name stay in sync.
622
623
 
623
624
  On 2026-10-16 the `ci-critical-path` change replaced the Intel runner for the release builds:
@@ -6,10 +6,11 @@
6
6
  |---|---|---|
7
7
  | `a1` | A1 | `~/.a1/agent` |
8
8
  | `a1 --session <path\|id>` | Resume a persisted A1 session | `~/.a1/agent` (or explicitly selected storage) |
9
- | `a1 solo` | A1 direct single-agent mode | `~/.a1/agent` |
9
+ | `a1 solo` | Fresh A1 direct single-agent mode | `~/.a1/agent` |
10
+ | `a1 solo --session <path\|id>` | Resume a persisted A1 session in direct single-agent mode | `~/.a1/agent` (or explicitly selected storage) |
10
11
  | `a1 pi` | Pi-compatible direct comparison surface using the ordinary Pi profile | ordinary `~/.pi/agent` |
11
12
 
12
- `a1 solo` is available in release and prerelease builds. It starts A1 in single agent mode, is argument-free, and omits both the tab strip and the Tabs settings section.
13
+ `a1 solo` is available in release and prerelease builds. Without arguments it starts a fresh single-agent session; with `--session` and optional `--session-dir` it resumes a saved session. Both forms omit the tab strip and the Tabs settings section.
13
14
 
14
15
  `a1 pi` is a development instrument for comparing A1 against pinned Pi. Prerelease builds — what `a1 update --develop` or `a1 update --develop <number>` installs — expose it. A release build does not recognize the launch form. Repository development can run the comparison directly with `npm start:pi`.
15
16
 
@@ -36,16 +37,18 @@ Run Pi’s normal `/login` independently in each profile that needs stored authe
36
37
 
37
38
  ## Resume a session
38
39
 
39
- After a conversation has been persisted, exit A1 and run the command it prints from the original project directory:
40
+ After a conversation has been persisted, exit A1 and run the command it prints from the original project directory. Exited solo sessions print the solo form; tabbed A1 sessions keep the tabbed form:
40
41
 
41
42
  ```sh
42
43
  a1 --session <id>
43
44
  a1 --session-dir '/path/to/session store' --session <id>
45
+ a1 solo --session <id>
46
+ a1 solo --session-dir '/path/to/session store' --session <id>
44
47
  ```
45
48
 
46
49
  `--session` also accepts a `.jsonl` file path; `--session-dir` may come before or after it. Explicit storage overrides `PI_CODING_AGENT_SESSION_DIR`, which overrides default storage. IDs are searched in the current project before other projects within the A1 store. A cross-project ID match asks before creating a fork with a new identity; an explicit file resumes directly using its stored working directory. Missing or invalid targets fail rather than create an empty replacement.
47
50
 
48
- Resume uses the shipped Pi's public restoration behavior, including the compaction format it writes. It sends no model prompt by itself and does not recover, convert, or modify an unrelated conversation. The selected session opens in a new A1 tab or focuses the tab already holding it. Pi picker/continue CLI aliases, `a1 solo --session`, and `a1 pi --session` are not supported; in-UI `/resume` is unchanged.
51
+ Resume uses the shipped Pi's public restoration behavior, including the compaction format it writes. It sends no model prompt by itself and does not recover, convert, or modify an unrelated conversation. A tabbed selection opens in a new A1 tab or focuses the tab already holding it; a solo selection opens in the direct strip-free UI without starting the resident host. A live session remains protected by its single-writer lease. Pi picker/continue CLI aliases and `a1 pi --session` are not supported; in-UI `/resume` is unchanged.
49
52
 
50
53
  Regression evidence creates disposable sessions through Pi 0.84.2, then executes the emitted command through the packed public entry, bootstrap, guardian, and owned UI. Default/custom storage, Windows Git Bash quoting, supervisor restart, failure cleanup, and simultaneous distinct selections are covered in `test/foundation/release/session-resume.integration.test.ts`. CI runs it in the resource-sensitive `package-smoke` scope, not the fast remainder. Unsupported synthetic retained-tail certification is separate deferred work.
51
54