@timurproko/a1 0.2.5-dev.683 → 0.2.5-dev.698

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 (62) hide show
  1. package/dist/app/session-shell/prompt-suggestion-controller.d.ts +2 -0
  2. package/dist/app/session-shell/prompt-suggestion-controller.js +10 -0
  3. package/dist/app/session-shell/session-shell-root.js +15 -2
  4. package/dist/app/session-shell/session-shell.js +71 -15
  5. package/dist/app/session-shell/share-operation.d.ts +3 -0
  6. package/dist/app/session-shell/share-operation.js +11 -0
  7. package/dist/composition/settings-route-host.js +1 -1
  8. package/dist/contracts/presentation/index.d.ts +5 -0
  9. package/dist/contracts/presentation/index.js +2 -0
  10. package/dist/features/owned-ui/project-trust-dialog.js +7 -5
  11. package/dist/features/owned-ui/reference-screen-app.d.ts +2 -2
  12. package/dist/features/owned-ui/reference-screen-app.js +10 -6
  13. package/dist/features/owned-ui/settings-app.js +10 -5
  14. package/dist/integrations/pi/components/index.d.ts +3 -2
  15. package/dist/integrations/pi/components/index.js +2 -1
  16. package/dist/integrations/pi/components/modal-frame.d.ts +11 -1
  17. package/dist/integrations/pi/components/modal-frame.js +29 -1
  18. package/dist/integrations/pi/components/models-dialog.js +17 -17
  19. package/dist/integrations/pi/components/share-operation-dialog.d.ts +6 -0
  20. package/dist/integrations/pi/components/share-operation-dialog.js +40 -0
  21. package/dist/integrations/pi/components/shell-editor-autocomplete.js +15 -6
  22. package/dist/integrations/pi/components/shell-extension-ui.d.ts +3 -0
  23. package/dist/integrations/pi/components/shell-extension-ui.js +8 -4
  24. package/dist/integrations/pi/components/shell-selectors-dialogs.d.ts +0 -4
  25. package/dist/integrations/pi/components/shell-selectors-dialogs.js +56 -34
  26. package/dist/integrations/pi/components/shell-session-info-reference.js +35 -2
  27. package/dist/integrations/pi/components/shell-shared-facade.d.ts +3 -1
  28. package/dist/integrations/pi/components/shell-shared-facade.js +13 -1
  29. package/dist/integrations/pi/components/skills-dialog.js +19 -6
  30. package/dist/integrations/pi/components/theme.d.ts +8 -1
  31. package/dist/integrations/pi/components/theme.js +28 -1
  32. package/dist/integrations/pi/components/upstream/adjacent/core/keybindings.d.ts +10 -2
  33. package/dist/integrations/pi/components/upstream/adjacent/core/keybindings.js +20 -6
  34. package/dist/integrations/pi/components/upstream/components/extension-editor.js +2 -2
  35. package/dist/integrations/pi/components/upstream/components/extension-input.js +4 -5
  36. package/dist/integrations/pi/components/upstream/components/extension-selector.js +2 -3
  37. package/dist/integrations/pi/components/upstream/components/scoped-models-selector.d.ts +3 -2
  38. package/dist/integrations/pi/components/upstream/components/scoped-models-selector.js +4 -15
  39. package/dist/integrations/pi/components/upstream/components/session-selector.d.ts +2 -0
  40. package/dist/integrations/pi/components/upstream/components/session-selector.js +136 -109
  41. package/dist/integrations/pi/components/upstream/components/thinking-selector.d.ts +8 -5
  42. package/dist/integrations/pi/components/upstream/components/thinking-selector.js +63 -26
  43. package/dist/integrations/pi/components/upstream/components/tree-selector.d.ts +29 -20
  44. package/dist/integrations/pi/components/upstream/components/tree-selector.js +245 -210
  45. package/dist/integrations/pi/components/upstream/components/trust-selector.js +13 -15
  46. package/dist/integrations/pi/engine/adapter.js +21 -2
  47. package/dist/integrations/pi/engine/workflow-runner.js +2 -1
  48. package/dist/native/darwin-arm64/manifest.json +1 -1
  49. package/dist/native/linux-x64/manifest.json +1 -1
  50. package/dist/native/win32-x64/manifest.json +2 -2
  51. package/dist/native/win32-x64/process-guardian.exe +0 -0
  52. package/dist/ui/apps/host.d.ts +4 -3
  53. package/dist/ui/apps/host.js +9 -4
  54. package/dist/ui/components/dialog-panel.js +6 -7
  55. package/dist/ui/components/index.d.ts +1 -1
  56. package/dist/ui/components/index.js +1 -1
  57. package/dist/ui/components/list-view.d.ts +3 -3
  58. package/dist/ui/components/list-view.js +8 -9
  59. package/dist/ui/components/shortcut-hints.d.ts +2 -0
  60. package/dist/ui/components/shortcut-hints.js +17 -1
  61. package/dist/ui/components/theme.d.ts +1 -1
  62. package/package.json +1 -1
@@ -2,14 +2,15 @@
2
2
  * Provenance: @earendil-works/pi-coding-agent 1.0.4 (MIT), commit 7c10bd4337495ee613f2224843ecdf349b80d1df,
3
3
  * packages/coding-agent/src/modes/interactive/components/trust-selector.ts.
4
4
  * Modifications: Mechanical source-synchronized trust selector port with injected public
5
- * ProjectTrustStore-derived options, remapped owned theme imports, and the shared bare-A1 modal
6
- * shortcut row and compact padded modal frame.
5
+ * ProjectTrustStore-derived options, remapped owned theme imports, the shared bare-A1 modal shortcut
6
+ * row and compact padded modal frame, and standard semantic trust styling with marker-free choice
7
+ * geometry.
7
8
  * Deviations: owned-modal-shortcut-hints.
8
9
  */
9
10
  import { DynamicBorder } from "../../../startup-public.js";
10
11
  import { Container, getKeybindings, Spacer, Text } from "@earendil-works/pi-tui";
11
12
  import { addPiModalHeader, adoptPiModalFrame } from "../../modal-frame.js";
12
- import { piTheme, renderPiModalShortcutHints } from "../../theme.js";
13
+ import { DIALOG_CLOSE_SHORTCUT_HINT, piTheme, renderPiModalShortcutHints } from "../../theme.js";
13
14
  function formatDecision(trustPath, decision) {
14
15
  if (decision === null)
15
16
  return "none";
@@ -30,11 +31,11 @@ export class TrustSelectorComponent extends Container {
30
31
  && options.savedDecision?.decision === option.trusted
31
32
  && options.savedDecision.path === option.savedPath;
32
33
  this.selectedIndex = Math.max(0, this.trustOptions.findIndex(isSaved));
33
- const header = addPiModalHeader(this, new DynamicBorder(), new Text(piTheme().fg("accent", piTheme().bold("Project trust")), 0, 0));
34
+ const header = addPiModalHeader(this, new DynamicBorder(text => piTheme().fg("border", text)), new Text(piTheme().fg("accent", piTheme().bold("Project trust")), 0, 0));
34
35
  this.addChild(new Text(piTheme().fg("muted", options.cwd), 0, 0));
35
36
  this.addChild(new Spacer(1));
36
- this.addChild(new Text(piTheme().fg("muted", `Saved decision: ${formatDecision(this.trustOptions[0]?.savedPath, options.savedDecision)}`), 0, 0));
37
- this.addChild(new Text(piTheme().fg("muted", `Current session: ${options.projectTrusted ? "trusted" : "untrusted"}`), 0, 0));
37
+ this.addChild(new Text(`${piTheme().fg("muted", "Saved decision:")} ${piTheme().fg("text", formatDecision(this.trustOptions[0]?.savedPath, options.savedDecision))}`, 0, 0));
38
+ this.addChild(new Text(`${piTheme().fg("muted", "Current session:")} ${piTheme().fg("text", options.projectTrusted ? "trusted" : "untrusted")}`, 0, 0));
38
39
  this.addChild(new Spacer(1));
39
40
  this.listContainer = new Container();
40
41
  this.addChild(this.listContainer);
@@ -43,12 +44,11 @@ export class TrustSelectorComponent extends Container {
43
44
  this.addChild(new Text(renderPiModalShortcutHints([
44
45
  { key: "↑↓", action: "navigate" },
45
46
  { key: keybindings.getKeys("tui.select.confirm").join("/"), action: "save" },
46
- { key: keybindings.getKeys("tui.select.cancel").join("/"), action: "cancel" },
47
+ DIALOG_CLOSE_SHORTCUT_HINT,
47
48
  ]), 0, 0));
48
- this.addChild(new Spacer(1));
49
- this.addChild(new DynamicBorder());
49
+ this.addChild(new DynamicBorder(text => piTheme().fg("border", text)));
50
50
  adoptPiModalFrame(this, { topIndex: 0, bottomIndex: this.children.length - 1, header });
51
- this.updateList(options.savedDecision);
51
+ this.updateList();
52
52
  this.handleInput = data => {
53
53
  const kb = getKeybindings();
54
54
  if (kb.matches(data, "tui.select.up") || data === "k")
@@ -67,21 +67,19 @@ export class TrustSelectorComponent extends Container {
67
67
  }
68
68
  else
69
69
  return;
70
- this.updateList(options.savedDecision);
70
+ this.updateList();
71
71
  };
72
72
  }
73
- updateList(savedDecision) {
73
+ updateList() {
74
74
  this.listContainer.clear();
75
75
  for (let index = 0; index < this.trustOptions.length; index += 1) {
76
76
  const option = this.trustOptions[index];
77
77
  if (!option)
78
78
  continue;
79
79
  const selected = index === this.selectedIndex;
80
- const current = option.savedPath !== undefined && savedDecision?.decision === option.trusted && savedDecision.path === option.savedPath;
81
80
  const prefix = selected ? piTheme().fg("accent", "→ ") : " ";
82
81
  const label = selected ? piTheme().fg("accent", option.label) : piTheme().fg("text", option.label);
83
- const currentMarker = current ? piTheme().fg("accent", "✓ ") : " ";
84
- this.listContainer.addChild(new Text(`${prefix}${currentMarker}${label}`, 0, 0));
82
+ this.listContainer.addChild(new Text(`${prefix}${label}`, 0, 0));
85
83
  }
86
84
  }
87
85
  }
@@ -382,8 +382,12 @@ export class PiEngineAdapter {
382
382
  reloadBlockedResult() {
383
383
  return this.#workflows.reloadBlockedResult();
384
384
  }
385
- executeWorkflow(request) {
386
- return this.#workflows.executeWorkflow(request);
385
+ async executeWorkflow(request) {
386
+ const result = await this.#workflows.executeWorkflow(request);
387
+ if (request.command === "tree" && result.outcome === "completed" && request.selection !== undefined) {
388
+ this.#refreshNavigatedSession(result.detail);
389
+ }
390
+ return result;
387
391
  }
388
392
  /**
389
393
  * The theme the engine is configured with, in the engine's own grammar: a
@@ -575,6 +579,21 @@ export class PiEngineAdapter {
575
579
  throw new Error("owned UI state commands belong to the owned UI layer, not the Pi engine adapter");
576
580
  }
577
581
  }
582
+ // Invariant: in-place tree navigation rebuilds derived content without clearing the editor draft.
583
+ #refreshNavigatedSession(editorText) {
584
+ const session = this.#engine.session;
585
+ if (!session)
586
+ return;
587
+ if (editorText && !this.#editor.text.trim()) {
588
+ this.#editor = { ...this.#editor, text: editorText, selection: null, cursorOffset: editorText.length };
589
+ }
590
+ this.#activeModel = readModel(session.model);
591
+ this.#reconcileActiveModelAvailability();
592
+ this.#thinkingLevel = readThinkingLevel(session.thinkingLevel);
593
+ this.#projection.assets.clear();
594
+ this.#setTranscript(this.#projection.rebuild(session.messages, "finalized"));
595
+ this.#emitView();
596
+ }
578
597
  // Invariant: the runtime already advanced the generation and subscribed; this rebuilds what the view derives from a session.
579
598
  #sessionReplaced(session) {
580
599
  this.#delivery.discardObsolete(this.#engine.generation);
@@ -448,7 +448,8 @@ export class PiWorkflowRunner {
448
448
  return workflowResult(request.command, "cancelled", "Branch summarization cancelled", undefined, "status");
449
449
  if (isRecord(result) && result.cancelled === true)
450
450
  return workflowResult(request.command, "cancelled", "Navigation cancelled", undefined, "status");
451
- return workflowResult(request.command, "completed", "Navigated to selected point");
451
+ const editorText = isRecord(result) && typeof result.editorText === "string" ? result.editorText : undefined;
452
+ return workflowResult(request.command, "completed", "Navigated to selected point", editorText);
452
453
  }
453
454
  case "trust": {
454
455
  if (!selection)
@@ -5,7 +5,7 @@
5
5
  "platform": "darwin",
6
6
  "architecture": "arm64",
7
7
  "capability": "supported",
8
- "builtAt": "2026-10-06T10:29:08.307Z",
8
+ "builtAt": "2026-10-07T10:23:29.595Z",
9
9
  "artifact": {
10
10
  "filename": "process-guardian",
11
11
  "sha256": "51b0b2dc1c9ae577f2586c4217066929278726c2c15308d9144eb6f538e118ff",
@@ -5,7 +5,7 @@
5
5
  "platform": "linux",
6
6
  "architecture": "x64",
7
7
  "capability": "supported",
8
- "builtAt": "2026-10-06T10:28:54.790Z",
8
+ "builtAt": "2026-10-07T10:23:29.449Z",
9
9
  "artifact": {
10
10
  "filename": "process-guardian",
11
11
  "sha256": "ef62679e83bc1fad3d38f9f21675d9256f08d20e67fd4791571032115ca1b0e9",
@@ -5,10 +5,10 @@
5
5
  "platform": "win32",
6
6
  "architecture": "x64",
7
7
  "capability": "supported",
8
- "builtAt": "2026-10-06T10:29:36.873Z",
8
+ "builtAt": "2026-10-07T10:24:08.534Z",
9
9
  "artifact": {
10
10
  "filename": "process-guardian.exe",
11
- "sha256": "26318c5b3429492b1f5f3378b926fb83c83c5d274bc2c6e37f10973b23781920",
11
+ "sha256": "a59b20f3c04cf9d9cadff5ecfe7b35449646e9b51a92e0245d74ecc99e639502",
12
12
  "size": 288256
13
13
  },
14
14
  "provenance": {
@@ -22,9 +22,10 @@ export interface AppHostOptions {
22
22
  readonly theme?: UiTheme;
23
23
  }
24
24
  /**
25
- * Presents at most one app. Input reaches the presented app first; anything it
26
- * does not consume continues to the caller. A failure while rendering or
27
- * handling input closes the app rather than leaving a broken surface on screen.
25
+ * Presents at most one app. An opted-in interrupt closes at the host boundary;
26
+ * other input reaches the presented app first and anything it does not consume
27
+ * continues to the caller. A failure while rendering or handling input closes
28
+ * the app rather than leaving a broken surface on screen.
28
29
  */
29
30
  export declare class UiAppHost {
30
31
  #private;
@@ -5,9 +5,10 @@ const INTERRUPT = "";
5
5
  /** How long the first interrupt of the chord stays armed. */
6
6
  const INTERRUPT_CHORD_MS = 1_500;
7
7
  /**
8
- * Presents at most one app. Input reaches the presented app first; anything it
9
- * does not consume continues to the caller. A failure while rendering or
10
- * handling input closes the app rather than leaving a broken surface on screen.
8
+ * Presents at most one app. An opted-in interrupt closes at the host boundary;
9
+ * other input reaches the presented app first and anything it does not consume
10
+ * continues to the caller. A failure while rendering or handling input closes
11
+ * the app rather than leaving a broken surface on screen.
11
12
  */
12
13
  export class UiAppHost {
13
14
  #registry;
@@ -70,6 +71,10 @@ export class UiAppHost {
70
71
  const app = this.#app;
71
72
  if (app === null)
72
73
  return { consumed: false };
74
+ if (data === INTERRUPT && this.#closeOnInterrupt) {
75
+ this.close();
76
+ return { consumed: true, render: true };
77
+ }
73
78
  const result = this.#guard(() => app.onInput?.(data, this.#services())) ?? { consumed: false };
74
79
  if (this.#app === null)
75
80
  return { consumed: true, render: true };
@@ -91,7 +96,7 @@ export class UiAppHost {
91
96
  this.render();
92
97
  return result;
93
98
  }
94
- // Protocol: two interrupts within the chord window leave A1 from every screen.
99
+ // Protocol: a non-opted-in host preserves the two-interrupt application exit chord.
95
100
  #interrupt() {
96
101
  const now = Date.now();
97
102
  const armed = this.#interruptArmedAt;
@@ -1,4 +1,4 @@
1
- import { renderShortcutHints, shortcutHintsText } from "./shortcut-hints.js";
1
+ import { renderShortcutHintsWithClose } from "./shortcut-hints.js";
2
2
  import { displayWidth, truncateToWidth } from "./text.js";
3
3
  const LABEL_COLUMN_CAP = 30;
4
4
  /** The column values start at: past the widest label, capped so one long name cannot push them off. */
@@ -22,18 +22,17 @@ export function renderDialogPanel(state, width, theme) {
22
22
  const padded = `${row.label}${" ".repeat(Math.max(0, labelColumn - displayWidth(row.label)))}`;
23
23
  const cursor = selected ? "→ " : " ";
24
24
  const raw = `${cursor}${padded} ${row.value}`;
25
- // Compatibility: match pinned SettingsList: selected cursor, label, and value all use the
26
- // accent role; an unselected label is plain and its value is muted.
27
- const painted = selected
28
- ? `${theme.fg("accent", cursor)}${theme.fg("accent", padded)} ${theme.fg("accent", row.value)}`
25
+ const content = selected
26
+ ? `${theme.fg("accent", cursor)}${theme.fg("text", padded)} ${theme.fg("muted", row.value)}`
29
27
  : `${cursor}${padded} ${theme.fg("muted", row.value)}`;
28
+ const painted = selected ? theme.highlight(content) : content;
30
29
  return contentRow(painted, raw);
31
30
  });
32
31
  const description = state.rows[state.index]?.description ?? "";
33
32
  const hint = typeof state.hint === "string"
34
33
  ? theme.fg("dim", state.hint)
35
- : renderShortcutHints(state.hint, theme, 0);
36
- const plainHint = typeof state.hint === "string" ? state.hint : shortcutHintsText(state.hint, 0);
34
+ : renderShortcutHintsWithClose(state.hint, theme, contentWidth, 0);
35
+ const plainHint = typeof state.hint === "string" ? state.hint : " ".repeat(displayWidth(hint));
37
36
  return [
38
37
  rule,
39
38
  ...rows,
@@ -23,7 +23,7 @@ export { MAX_COPY_ROWS, MAX_COPY_SOURCE_UNITS, selectionCopyLineContent, selecti
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";
26
- export { renderShortcutHints } from "./shortcut-hints.js";
26
+ export { renderShortcutHints, renderShortcutHintsWithClose } from "./shortcut-hints.js";
27
27
  export type { ShortcutHintEntry } from "./shortcut-hints.js";
28
28
  export { GLOBAL_SCOPE, ShortcutRegistry, assembleShortcuts, assertNoShortcutConflicts } from "./shortcuts.js";
29
29
  export type { ShortcutConflict, ShortcutDeclaration, ShortcutRegistryResult } from "./shortcuts.js";
@@ -11,7 +11,7 @@ export { progressStatusFrame, progressStatusText } from "./progress-status.js";
11
11
  export { FrameCache, RENDER_REVISION_KINDS, RenderRevisionTracker, ZERO_REVISIONS, normalizeRevisions, revisionsEqual } from "./revision.js";
12
12
  export { MAX_COPY_ROWS, MAX_COPY_SOURCE_UNITS, selectionCopyLineContent, selectionCopyRowText } from "./selection-copy.js";
13
13
  export { ScrollbarRails, isThumbRow, scrollForThumbRow, scrollForTrackPage, scrollbarGeometry, scrollbarPresentation, scrollbarReservesSpace, scrollbarSelectionRows, scrollbarWheelRows, } from "./scrollbar.js";
14
- export { renderShortcutHints } from "./shortcut-hints.js";
14
+ export { renderShortcutHints, renderShortcutHintsWithClose } from "./shortcut-hints.js";
15
15
  export { GLOBAL_SCOPE, ShortcutRegistry, assembleShortcuts, assertNoShortcutConflicts } from "./shortcuts.js";
16
16
  export { backgroundSgrSpan, heldNativeHyperlinkStyle, hyperlinkSgrSpan, hyperlinkTargetAtColumn, nativeHyperlinkStyle, overlaySpan } from "./spans.js";
17
17
  export { readVisibleHyperlinks } from "./visible-hyperlinks.js";
@@ -44,9 +44,9 @@ export declare function valueColumnFor(rows: readonly ListViewRow[], indent?: nu
44
44
  /** A group's name with the shared one-cell section inset used by owned screens. */
45
45
  export declare function renderGroupHeader(title: string, width: number, theme: UiTheme): string;
46
46
  /**
47
- * One row, painted. The label carries the selection, the value carries the
48
- * pointer, and the stepper appears only when the pointer is on the value it
49
- * belongs to — an affordance, not decoration.
47
+ * One row, painted. The selected item keeps an accent cursor, normal-text label,
48
+ * muted value, and item-bounded highlight; the value carries the pointer, and the
49
+ * stepper appears only when the pointer is on the value it belongs to.
50
50
  */
51
51
  export declare function renderListRow(row: ListViewRow, state: ListRowState, valueColumn: number, width: number, theme: UiTheme): string;
52
52
  /** A line that stands in for rows: why a group has none, or what it is waiting on. */
@@ -18,9 +18,9 @@ export function renderGroupHeader(title, width, theme) {
18
18
  return ` ${truncateToWidth(theme.fg("mdHeading", theme.bold(title)), width - 1)}`;
19
19
  }
20
20
  /**
21
- * One row, painted. The label carries the selection, the value carries the
22
- * pointer, and the stepper appears only when the pointer is on the value it
23
- * belongs to — an affordance, not decoration.
21
+ * One row, painted. The selected item keeps an accent cursor, normal-text label,
22
+ * muted value, and item-bounded highlight; the value carries the pointer, and the
23
+ * stepper appears only when the pointer is on the value it belongs to.
24
24
  */
25
25
  export function renderListRow(row, state, valueColumn, width, theme) {
26
26
  const cursor = state.selected ? "→ " : " ";
@@ -28,13 +28,11 @@ export function renderListRow(row, state, valueColumn, width, theme) {
28
28
  const labelPadded = `${row.label}${" ".repeat(Math.max(0, labelColumn - displayWidth(row.label)))}`;
29
29
  const leftRaw = `${cursor}${labelPadded}`;
30
30
  const left = state.selected
31
- ? `${theme.fg("accent", cursor)}${theme.fg("accent", labelPadded)}`
31
+ ? `${theme.fg("accent", cursor)}${theme.fg("text", labelPadded)}`
32
32
  : `${cursor}${theme.plain(labelPadded)}`;
33
33
  const gap = Math.max(2, valueColumn - displayWidth(leftRaw));
34
- // Rationale: a declared difference from pinned SettingsList, which paints the selected
35
- // value in the accent too. Here only the cursor and label carry the selection; the
36
- // value reads the same on every row, and pointer hover brightens it without moving
37
- // the keyboard selection.
34
+ // Rationale: the value keeps the same semantic foreground on selected and unselected
35
+ // rows; pointer hover brightens it without moving keyboard selection.
38
36
  const valueHovered = state.hovered && state.region !== "label";
39
37
  const stepper = row.stepper !== undefined && valueHovered;
40
38
  const value = valueHovered ? theme.plain(row.value) : theme.fg("muted", row.value);
@@ -50,7 +48,8 @@ export function renderListRow(row, state, valueColumn, width, theme) {
50
48
  : "";
51
49
  const indent = Math.max(2, stepper ? gap - STEPPER_RESERVE : gap);
52
50
  const suffix = row.suffix === undefined ? "" : theme.fg("dim", ` ${row.suffix}`);
53
- return truncateToWidth(`${left}${" ".repeat(indent)}${minus}${value}${plus}${suffix}`, width);
51
+ const rendered = truncateToWidth(`${left}${" ".repeat(indent)}${minus}${value}${plus}${suffix}`, width);
52
+ return state.selected ? theme.highlight(rendered) : rendered;
54
53
  }
55
54
  /** A line that stands in for rows: why a group has none, or what it is waiting on. */
56
55
  export function renderNote(text, width, theme) {
@@ -5,3 +5,5 @@ export type ShortcutHintEntry = SemanticShortcutHint;
5
5
  export declare function shortcutHintsText(entries: readonly ShortcutHintEntry[], indent?: number): string;
6
6
  /** Paints one modal instruction row without decorative separator glyphs. */
7
7
  export declare function renderShortcutHints(entries: readonly ShortcutHintEntry[], theme: Pick<UiTheme, "fg">, indent?: number): string;
8
+ /** Clips preceding guidance first so a final canonical close entry remains complete whenever it fits. */
9
+ export declare function renderShortcutHintsWithClose(entries: readonly ShortcutHintEntry[], theme: Pick<UiTheme, "fg">, width: number, indent?: number): string;
@@ -1,4 +1,5 @@
1
- import { renderSemanticShortcutHints, } from "../../contracts/presentation/index.js";
1
+ import { DIALOG_CLOSE_SHORTCUT_HINT, renderSemanticShortcutHints, } from "../../contracts/presentation/index.js";
2
+ import { displayWidth, truncateToWidth } from "./text.js";
2
3
  /** Returns an unstyled modal instruction row for width-aware clipping. */
3
4
  export function shortcutHintsText(entries, indent = 0) {
4
5
  return renderSemanticShortcutHints(entries, { key: text => text, action: text => text }, indent);
@@ -10,3 +11,18 @@ export function renderShortcutHints(entries, theme, indent = 0) {
10
11
  action: text => theme.fg("muted", text),
11
12
  }, indent);
12
13
  }
14
+ /** Clips preceding guidance first so a final canonical close entry remains complete whenever it fits. */
15
+ export function renderShortcutHintsWithClose(entries, theme, width, indent = 0) {
16
+ const hasClose = entries.at(-1)?.key === DIALOG_CLOSE_SHORTCUT_HINT.key
17
+ && entries.at(-1)?.action === DIALOG_CLOSE_SHORTCUT_HINT.action;
18
+ if (!hasClose)
19
+ return truncateToWidth(renderShortcutHints(entries, theme, indent), width);
20
+ const withoutClose = entries.slice(0, -1);
21
+ const close = renderShortcutHints([DIALOG_CLOSE_SHORTCUT_HINT], theme);
22
+ const closeWidth = displayWidth(close);
23
+ if (width <= closeWidth)
24
+ return truncateToWidth(close, width);
25
+ const bodyWidth = Math.max(0, width - closeWidth - 2);
26
+ const body = bodyWidth < 3 ? "" : truncateToWidth(renderShortcutHints(withoutClose, theme, indent), bodyWidth);
27
+ return displayWidth(body) === 0 ? close : `${body} ${close}`;
28
+ }
@@ -8,7 +8,7 @@ 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
- /** Inverted styling for the active row of a floating surface. */
11
+ /** Selection background for an active list item or floating-surface row. */
12
12
  highlight(text: string): string;
13
13
  /** A control that is present but cannot act: quieter than quiet text. */
14
14
  disabled(text: string): string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@timurproko/a1",
3
- "version": "0.2.5-dev.683",
3
+ "version": "0.2.5-dev.698",
4
4
  "description": "Standalone terminal workspace for supervised native and managed agents",
5
5
  "type": "module",
6
6
  "privateLaunchContract": "neutral-launch-v1",