@linxiraos/pi-tui 1.2.0 → 1.3.0

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 (276) hide show
  1. package/CHANGELOG.md +188 -120
  2. package/README.md +36 -0
  3. package/THIRD-PARTY-NOTICES.txt +55 -5
  4. package/dist/types/app-keybindings.d.ts +18 -8
  5. package/dist/types/apps/git/diff-pane.d.ts +5 -0
  6. package/dist/types/apps/git/git-tui.d.ts +16 -2
  7. package/dist/types/apps/git/state.d.ts +11 -0
  8. package/dist/types/autocomplete.d.ts +2 -0
  9. package/dist/types/charts/chart-svg.d.ts +7 -13
  10. package/dist/types/chat/advisor-message.d.ts +4 -1
  11. package/dist/types/chat/assistant-message.d.ts +9 -4
  12. package/dist/types/chat/figure-markdown.d.ts +7 -2
  13. package/dist/types/chat/messages.d.ts +3 -1
  14. package/dist/types/chat/read-tool-group.d.ts +2 -2
  15. package/dist/types/chat/table-chart.d.ts +11 -15
  16. package/dist/types/chat/tool-execution.d.ts +32 -20
  17. package/dist/types/chat/user-message.d.ts +3 -2
  18. package/dist/types/chrome/countdown-timer.d.ts +3 -2
  19. package/dist/types/chrome/diff.d.ts +2 -3
  20. package/dist/types/chrome/transcript-container.d.ts +2 -0
  21. package/dist/types/components/image.d.ts +2 -0
  22. package/dist/types/components/markdown.d.ts +49 -3
  23. package/dist/types/components/text.d.ts +2 -0
  24. package/dist/types/debug-server.d.ts +2 -0
  25. package/dist/types/keybinding-matchers.d.ts +6 -0
  26. package/dist/types/multiplexers/tmux.d.ts +1 -0
  27. package/dist/types/multiplexers/types.d.ts +6 -0
  28. package/dist/types/native/backend.d.ts +5 -4
  29. package/dist/types/native/blobs.d.ts +24 -12
  30. package/dist/types/overlays/agent-hub-projection.d.ts +10 -5
  31. package/dist/types/overlays/annotation-overlay.d.ts +19 -0
  32. package/dist/types/overlays/approval-dialog.d.ts +79 -0
  33. package/dist/types/overlays/ask-dialog.d.ts +11 -6
  34. package/dist/types/overlays/btw-history-panel.d.ts +2 -6
  35. package/dist/types/overlays/hook-editor.d.ts +7 -2
  36. package/dist/types/overlays/hook-input.d.ts +12 -4
  37. package/dist/types/overlays/job-tree.d.ts +50 -0
  38. package/dist/types/overlays/jobs-panel.d.ts +25 -11
  39. package/dist/types/overlays/login-dialog.d.ts +8 -3
  40. package/dist/types/overlays/mcp-add-wizard.d.ts +9 -1
  41. package/dist/types/overlays/model-browser.d.ts +6 -1
  42. package/dist/types/overlays/model-picker.d.ts +2 -0
  43. package/dist/types/overlays/pause-screen.d.ts +5 -0
  44. package/dist/types/overlays/plan-review-overlay.d.ts +4 -0
  45. package/dist/types/overlays/report-panel.d.ts +5 -1
  46. package/dist/types/overlays/reset-usage-selector.d.ts +7 -5
  47. package/dist/types/overlays/session-account-selector.d.ts +4 -0
  48. package/dist/types/overlays/session-info-overlay.d.ts +7 -3
  49. package/dist/types/overlays/session-selector.d.ts +3 -1
  50. package/dist/types/overlays/usage-dashboard.d.ts +6 -6
  51. package/dist/types/prompt/composer.d.ts +22 -26
  52. package/dist/types/prompt/custom-editor.d.ts +20 -10
  53. package/dist/types/prompt/image-references.d.ts +22 -0
  54. package/dist/types/prompt/internal-url-autocomplete.d.ts +1 -0
  55. package/dist/types/prompt/macos-spelling.d.ts +2 -1
  56. package/dist/types/prompt/prose-gate.d.ts +3 -0
  57. package/dist/types/prompt/queued-messages.d.ts +1 -1
  58. package/dist/types/prompt/typing-word.d.ts +25 -0
  59. package/dist/types/prompt/welcome.d.ts +35 -43
  60. package/dist/types/render/hanging-wrap.d.ts +7 -0
  61. package/dist/types/render/hyperlink.d.ts +2 -0
  62. package/dist/types/render/render-utils.d.ts +9 -1
  63. package/dist/types/render/utils.d.ts +1 -1
  64. package/dist/types/setup/scenes/splash.d.ts +6 -5
  65. package/dist/types/status-line/context-usage.d.ts +4 -1
  66. package/dist/types/status-line/host.d.ts +6 -4
  67. package/dist/types/status-line/startup.d.ts +6 -6
  68. package/dist/types/status-line/types.d.ts +3 -0
  69. package/dist/types/stdin-buffer.d.ts +7 -0
  70. package/dist/types/terminal-capabilities.d.ts +16 -1
  71. package/dist/types/terminal-multiplexer.d.ts +7 -0
  72. package/dist/types/terminal.d.ts +8 -4
  73. package/dist/types/theme/symbols.d.ts +1 -1
  74. package/dist/types/theme/theme.d.ts +11 -0
  75. package/dist/types/thinking.d.ts +5 -0
  76. package/dist/types/tmux.d.ts +39 -4
  77. package/dist/types/tools/ast-edit.d.ts +2 -2
  78. package/dist/types/tools/ast-grep.d.ts +2 -2
  79. package/dist/types/tools/bash.d.ts +6 -1
  80. package/dist/types/tools/edit.d.ts +2 -0
  81. package/dist/types/tools/eval.d.ts +53 -4
  82. package/dist/types/tools/find.d.ts +14 -4
  83. package/dist/types/tools/glob.d.ts +3 -3
  84. package/dist/types/tools/grep.d.ts +2 -2
  85. package/dist/types/tools/grouped-file-output.d.ts +9 -4
  86. package/dist/types/tools/native-view.d.ts +1 -1
  87. package/dist/types/tools/read.d.ts +32 -10
  88. package/dist/types/tools/renderer.d.ts +2 -0
  89. package/dist/types/tools/task.d.ts +31 -0
  90. package/dist/types/tools/write.d.ts +2 -0
  91. package/dist/types/tui.d.ts +20 -3
  92. package/package.json +10 -10
  93. package/src/app-keybindings.d.ts +18 -8
  94. package/src/app-keybindings.ts +20 -8
  95. package/src/apps/autoresearch-dashboard.ts +444 -27
  96. package/src/apps/debug/raw-sse.ts +2 -2
  97. package/src/apps/git/diff-pane.ts +39 -41
  98. package/src/apps/git/git-tui.ts +80 -14
  99. package/src/apps/git/sidebar.ts +10 -1
  100. package/src/apps/git/state.ts +12 -0
  101. package/src/autocomplete.d.ts +2 -0
  102. package/src/autocomplete.ts +21 -19
  103. package/src/bracketed-paste.ts +9 -2
  104. package/src/charts/chart-svg.d.ts +7 -13
  105. package/src/charts/chart-svg.ts +19 -22
  106. package/src/chat/advisor-message.d.ts +4 -1
  107. package/src/chat/advisor-message.ts +77 -24
  108. package/src/chat/assistant-message.d.ts +9 -4
  109. package/src/chat/assistant-message.ts +127 -31
  110. package/src/chat/bash-execution.ts +7 -2
  111. package/src/chat/figure-markdown.d.ts +7 -2
  112. package/src/chat/figure-markdown.ts +10 -3
  113. package/src/chat/image-loading.ts +9 -6
  114. package/src/chat/messages.d.ts +3 -1
  115. package/src/chat/messages.ts +4 -1
  116. package/src/chat/read-tool-group.d.ts +2 -2
  117. package/src/chat/read-tool-group.ts +97 -42
  118. package/src/chat/svg-figure.ts +3 -1
  119. package/src/chat/svg-source.ts +6 -3
  120. package/src/chat/table-chart.d.ts +11 -15
  121. package/src/chat/table-chart.ts +24 -57
  122. package/src/chat/tool-execution.d.ts +32 -20
  123. package/src/chat/tool-execution.ts +103 -36
  124. package/src/chat/user-message.d.ts +3 -2
  125. package/src/chat/user-message.ts +54 -34
  126. package/src/chrome/countdown-timer.ts +3 -3
  127. package/src/chrome/diff.d.ts +2 -3
  128. package/src/chrome/diff.ts +4 -9
  129. package/src/chrome/transcript-container.d.ts +2 -0
  130. package/src/chrome/transcript-container.ts +212 -28
  131. package/src/components/editor.ts +12 -12
  132. package/src/components/image.d.ts +2 -0
  133. package/src/components/image.ts +54 -20
  134. package/src/components/markdown.d.ts +49 -3
  135. package/src/components/markdown.ts +461 -11
  136. package/src/components/settings-list.ts +2 -4
  137. package/src/components/text.d.ts +2 -0
  138. package/src/components/text.ts +11 -1
  139. package/src/debug-server.d.ts +2 -0
  140. package/src/debug-server.ts +51 -69
  141. package/src/hotkeys-markdown.ts +2 -0
  142. package/src/keybinding-matchers.d.ts +6 -0
  143. package/src/keybinding-matchers.ts +19 -0
  144. package/src/multiplexers/tmux.d.ts +1 -0
  145. package/src/multiplexers/tmux.ts +3 -0
  146. package/src/multiplexers/types.d.ts +6 -0
  147. package/src/multiplexers/types.ts +6 -0
  148. package/src/native/backend.d.ts +5 -4
  149. package/src/native/backend.ts +13 -5
  150. package/src/native/blobs.d.ts +24 -12
  151. package/src/native/blobs.ts +56 -7
  152. package/src/native/reconcile.ts +43 -21
  153. package/src/overlays/advisor-config.ts +259 -8
  154. package/src/overlays/agent-hub-projection.d.ts +10 -5
  155. package/src/overlays/agent-hub-projection.ts +63 -12
  156. package/src/overlays/agent-hub.ts +2 -2
  157. package/src/overlays/annotation-overlay.ts +422 -168
  158. package/src/overlays/approval-dialog.ts +389 -0
  159. package/src/overlays/ask-dialog.ts +244 -105
  160. package/src/overlays/btw-history-panel.ts +208 -147
  161. package/src/overlays/extensions/extension-list.ts +4 -6
  162. package/src/overlays/hook-editor.ts +90 -32
  163. package/src/overlays/hook-input.ts +105 -19
  164. package/src/overlays/job-tree.ts +177 -0
  165. package/src/overlays/jobs-panel.ts +222 -142
  166. package/src/overlays/login-dialog.ts +201 -91
  167. package/src/overlays/mcp-add-wizard.ts +562 -279
  168. package/src/overlays/model-browser.ts +32 -7
  169. package/src/overlays/model-hub.ts +4 -10
  170. package/src/overlays/model-picker.ts +58 -21
  171. package/src/overlays/pause-screen.ts +81 -23
  172. package/src/overlays/plan-review-overlay.ts +16 -0
  173. package/src/overlays/plugin-selector.ts +17 -1
  174. package/src/overlays/plugin-settings.ts +8 -2
  175. package/src/overlays/queue-mode-selector.ts +10 -0
  176. package/src/overlays/report-panel.ts +6 -2
  177. package/src/overlays/reset-usage-selector.ts +130 -21
  178. package/src/overlays/session-account-selector.ts +36 -0
  179. package/src/overlays/session-info-overlay.ts +199 -76
  180. package/src/overlays/session-selector.ts +15 -10
  181. package/src/overlays/settings-selector.ts +3 -0
  182. package/src/overlays/show-images-selector.ts +1 -0
  183. package/src/overlays/snapcompact-shape-preview.ts +13 -6
  184. package/src/overlays/theme-selector.ts +6 -1
  185. package/src/overlays/thinking-selector.ts +8 -4
  186. package/src/overlays/tree-selector.ts +117 -2
  187. package/src/overlays/usage-dashboard.ts +498 -144
  188. package/src/prompt/composer-cache.ts +4 -6
  189. package/src/prompt/composer.ts +196 -96
  190. package/src/prompt/custom-editor.ts +168 -65
  191. package/src/prompt/image-references.d.ts +22 -0
  192. package/src/prompt/image-references.ts +79 -2
  193. package/src/prompt/internal-url-autocomplete.ts +1 -0
  194. package/src/prompt/macos-spelling.ts +28 -5
  195. package/src/prompt/prose-gate.ts +3 -0
  196. package/src/prompt/queued-messages.ts +9 -2
  197. package/src/prompt/tips.txt +1 -2
  198. package/src/prompt/typing-word.ts +59 -0
  199. package/src/prompt/welcome.ts +256 -423
  200. package/src/render/code-cell.ts +18 -5
  201. package/src/render/hanging-wrap.d.ts +7 -0
  202. package/src/render/hanging-wrap.ts +58 -0
  203. package/src/render/hyperlink.d.ts +2 -0
  204. package/src/render/hyperlink.ts +5 -0
  205. package/src/render/render-utils.d.ts +9 -1
  206. package/src/render/render-utils.ts +16 -2
  207. package/src/render/utils.d.ts +1 -1
  208. package/src/render/utils.ts +1 -1
  209. package/src/render/width-aware-text.ts +6 -5
  210. package/src/setup/scenes/splash.ts +137 -60
  211. package/src/status-line/component.ts +23 -10
  212. package/src/status-line/context-usage.ts +176 -73
  213. package/src/status-line/footer.ts +18 -35
  214. package/src/status-line/host.ts +6 -1
  215. package/src/status-line/segments.ts +16 -5
  216. package/src/status-line/startup.ts +6 -6
  217. package/src/status-line/types.ts +1 -0
  218. package/src/stdin-buffer.d.ts +7 -0
  219. package/src/stdin-buffer.ts +11 -1
  220. package/src/terminal-capabilities.d.ts +16 -1
  221. package/src/terminal-capabilities.ts +66 -28
  222. package/src/terminal-multiplexer.d.ts +7 -0
  223. package/src/terminal-multiplexer.ts +14 -0
  224. package/src/terminal.d.ts +8 -4
  225. package/src/terminal.ts +160 -46
  226. package/src/theme/symbols.d.ts +1 -1
  227. package/src/theme/symbols.ts +1 -0
  228. package/src/theme/theme-class.ts +1 -0
  229. package/src/theme/theme.d.ts +11 -0
  230. package/src/theme/theme.ts +46 -14
  231. package/src/thinking.d.ts +5 -0
  232. package/src/thinking.ts +9 -1
  233. package/src/tmux.d.ts +39 -4
  234. package/src/tmux.ts +110 -16
  235. package/src/tools/ask.ts +20 -9
  236. package/src/tools/ast-edit.d.ts +2 -2
  237. package/src/tools/ast-edit.ts +30 -7
  238. package/src/tools/ast-grep.d.ts +2 -2
  239. package/src/tools/ast-grep.ts +39 -35
  240. package/src/tools/bash.d.ts +6 -1
  241. package/src/tools/bash.ts +15 -9
  242. package/src/tools/debug.ts +2 -1
  243. package/src/tools/default-renderer.ts +3 -2
  244. package/src/tools/edit.d.ts +2 -0
  245. package/src/tools/edit.ts +100 -21
  246. package/src/tools/eval.d.ts +53 -4
  247. package/src/tools/eval.ts +173 -137
  248. package/src/tools/find.d.ts +14 -4
  249. package/src/tools/find.ts +71 -18
  250. package/src/tools/glob.d.ts +3 -3
  251. package/src/tools/glob.ts +51 -17
  252. package/src/tools/grep.d.ts +2 -2
  253. package/src/tools/grep.ts +99 -22
  254. package/src/tools/grouped-file-output.d.ts +9 -4
  255. package/src/tools/grouped-file-output.ts +21 -6
  256. package/src/tools/lsp.ts +30 -18
  257. package/src/tools/mcp.ts +2 -2
  258. package/src/tools/native-view.d.ts +1 -1
  259. package/src/tools/native-view.ts +2 -2
  260. package/src/tools/read.d.ts +32 -10
  261. package/src/tools/read.ts +89 -43
  262. package/src/tools/renderer.d.ts +2 -0
  263. package/src/tools/renderer.ts +2 -0
  264. package/src/tools/task.d.ts +31 -0
  265. package/src/tools/task.ts +40 -7
  266. package/src/tools/todo.ts +3 -2
  267. package/src/tools/wait.ts +16 -30
  268. package/src/tools/web-search.ts +2 -1
  269. package/src/tools/write.d.ts +2 -0
  270. package/src/tools/write.ts +36 -12
  271. package/src/tools/yield.ts +2 -2
  272. package/src/tui.d.ts +20 -3
  273. package/src/tui.ts +140 -36
  274. package/src/utils.ts +5 -3
  275. package/dist/types/tools/eval-format/code-mode-declarations.d.ts +0 -5
  276. package/src/tools/eval-format/code-mode-declarations.ts +0 -61
@@ -1338,6 +1338,8 @@ export interface SessionSelectorOptions<T extends SessionSelectorEntry = Session
1338
1338
  allSessions?: T[];
1339
1339
  /** Picker heading; defaults to "Resume Session". */
1340
1340
  title?: string;
1341
+ /** Primary action label; defaults to "Resume" (ANSI footer defaults to "select"). */
1342
+ actionLabel?: string;
1341
1343
  /** Fixed scope label, or false to omit the scope suffix. */
1342
1344
  scopeLabel?: string | false;
1343
1345
  /**
@@ -1363,7 +1365,7 @@ export interface SessionSelectorOptions<T extends SessionSelectorEntry = Session
1363
1365
  /** Path of the live session, or a getter so detach/newSession stays accurate. */
1364
1366
  currentSessionPath?: string | (() => string | undefined);
1365
1367
  /**
1366
- * The picker is the whole program (`zeta-c --resume`): the native picker
1368
+ * The picker is the whole program (`zetacode --resume`): the native picker
1367
1369
  * fills the screen surface (`size:"screen"`) instead of floating as a
1368
1370
  * sheet over the transcript.
1369
1371
  */
@@ -1405,6 +1407,7 @@ export class SessionSelectorComponent<T extends SessionSelectorEntry = SessionSe
1405
1407
  readonly #getTerminalRows: () => number;
1406
1408
  readonly #fillHeight: boolean;
1407
1409
  readonly #title: string;
1410
+ readonly #actionLabel: string;
1408
1411
  readonly #scopeLabel: string | false | undefined;
1409
1412
  /** What `#messageContainer` shows, for the native description. */
1410
1413
  #message: SessionPickerMessage | undefined;
@@ -1462,6 +1465,7 @@ export class SessionSelectorComponent<T extends SessionSelectorEntry = SessionSe
1462
1465
  this.#getTerminalRows = options.getTerminalRows ?? (() => 24);
1463
1466
  this.#fillHeight = options.fillHeight ?? false;
1464
1467
  this.#title = options.title ?? "Resume Session";
1468
+ this.#actionLabel = options.actionLabel ?? "Resume";
1465
1469
  this.#pickerTitle = options.title;
1466
1470
  this.#standalone = options.standalone ?? false;
1467
1471
  this.#scopeLabel = options.scopeLabel;
@@ -1496,9 +1500,11 @@ export class SessionSelectorComponent<T extends SessionSelectorEntry = SessionSe
1496
1500
  onExit();
1497
1501
  };
1498
1502
  this.#sessionList.onRequestRender = () => this.#onRequestRender?.();
1499
- this.#sessionList.onDeleteRequest = (session: T) => {
1500
- this.#showDeleteConfirmation(session);
1501
- };
1503
+ if (this.#onDelete) {
1504
+ this.#sessionList.onDeleteRequest = (session: T) => {
1505
+ this.#showDeleteConfirmation(session);
1506
+ };
1507
+ }
1502
1508
  if (this.#loadAllSessions || this.#globalSessions) {
1503
1509
  this.#sessionList.onToggleScope = () => {
1504
1510
  void this.#toggleScope();
@@ -1676,7 +1682,7 @@ export class SessionSelectorComponent<T extends SessionSelectorEntry = SessionSe
1676
1682
  const cancel = interruptKey();
1677
1683
  const hint = theme.fg(
1678
1684
  "muted",
1679
- `[${formatKeyHints(["delete", "backspace"])} delete · ${formatKeyHint("enter")} select · ${formatKeyHint("tab")} ${scopeHint} · ${cancel} cancel]`,
1685
+ `[${this.#onDelete ? `${formatKeyHints(["delete", "backspace"])} delete · ` : ""}${formatKeyHint("enter")} select · ${formatKeyHint("tab")} ${scopeHint} · ${cancel} cancel]`,
1680
1686
  );
1681
1687
  return [row("", width), row(hint, width), row("", width), bottomBorder(width)];
1682
1688
  }
@@ -1724,10 +1730,9 @@ export class SessionSelectorComponent<T extends SessionSelectorEntry = SessionSe
1724
1730
  } else {
1725
1731
  children.push(this.#sessionList.searchInput, list);
1726
1732
  }
1727
- const hints: (NativeHint | undefined)[] = [
1728
- { keys: ["delete", "backspace"], label: "delete" },
1729
- { keys: ["enter"], label: "select" },
1730
- ];
1733
+ const hints: (NativeHint | undefined)[] = [];
1734
+ if (this.#onDelete) hints.push({ keys: ["delete", "backspace"], label: "delete" });
1735
+ hints.push({ keys: ["enter"], label: "select" });
1731
1736
  if (this.#sessionList.onToggleScope) {
1732
1737
  hints.push({ keys: ["tab"], label: this.#scope === "all" ? "current folder" : "all projects" });
1733
1738
  }
@@ -1774,7 +1779,7 @@ export class SessionSelectorComponent<T extends SessionSelectorEntry = SessionSe
1774
1779
  }
1775
1780
  const loading = message?.kind === "loading";
1776
1781
  const toggle = list.onToggleScope !== undefined;
1777
- const actions = [pickerAction("resume", "Resume", "enter", { primary: true })];
1782
+ const actions = [pickerAction("resume", this.#actionLabel, "enter", { primary: true })];
1778
1783
  if (this.#onDelete) actions.push(pickerAction("delete", "Delete", "backspace"));
1779
1784
  if (toggle) {
1780
1785
  actions.push(pickerAction("scope", this.#scope === "all" ? "This folder" : "All projects", "tab"));
@@ -1102,6 +1102,9 @@ export class SettingsSelectorComponent implements Component {
1102
1102
  const open = list.openSubmenu;
1103
1103
  if (open?.id === id && open.component instanceof MultiSelectSubmenu) {
1104
1104
  open.component.setValues(value);
1105
+ // The native page applies a chip toggle without closing its
1106
+ // editor, so the row and its tab badge only update here.
1107
+ this.#refreshItems();
1105
1108
  return;
1106
1109
  }
1107
1110
  const next = [...value];
@@ -46,6 +46,7 @@ export class ShowImagesSelectorComponent extends OverlayPanel {
46
46
  this.addChild(this.#selectList);
47
47
  this.#sheet = new SelectListSheet(this.#selectList, {
48
48
  title: "Show images",
49
+ icon: "image",
49
50
  noun: "options",
50
51
  current: [currentValue ? "yes" : "no"],
51
52
  });
@@ -24,6 +24,7 @@ import {
24
24
  SHAPE_VARIANT_NAMES,
25
25
  SHAPE_VARIANTS,
26
26
  type Shape,
27
+ type ShapeGeometry,
27
28
  type ShapeTarget,
28
29
  type ShapeVariantName,
29
30
  } from "@linxiraos/pi-snapcompact";
@@ -90,7 +91,7 @@ export class SnapcompactShapePreview implements Component {
90
91
 
91
92
  describe(cx: DescribeContext): NativeNode {
92
93
  const shape = resolveShape(this.#model, this.#variant);
93
- const name = resolvedVariantName(shape);
94
+ const name = this.#variant === "auto" ? resolvedVariantName(shape) : this.#variant;
94
95
  const entry = cx.supports("image") ? this.#ensureEntry(name, shape) : undefined;
95
96
  const memo = this.#native;
96
97
  if (memo !== undefined && memo.variant === this.#variant && memo.entry === entry) return memo.node;
@@ -128,7 +129,7 @@ export class SnapcompactShapePreview implements Component {
128
129
 
129
130
  render(width: number): readonly string[] {
130
131
  const shape = resolveShape(this.#model, this.#variant);
131
- const name = resolvedVariantName(shape);
132
+ const name = this.#variant === "auto" ? resolvedVariantName(shape) : this.#variant;
132
133
  const { label, stats } = this.#caption(shape, name);
133
134
  const lines: string[] = [theme.fg("muted", ` Sample (zoomed) · ${label} · ${stats}`), ""];
134
135
 
@@ -215,17 +216,23 @@ export class SnapcompactShapePreview implements Component {
215
216
  }
216
217
  }
217
218
 
218
- /** Research name of the concrete geometry `resolveShape` picked (for `auto`). */
219
+ /**
220
+ * Research name of the geometry `resolveShape` picked for `auto`. The frame
221
+ * edge is not compared: `auto` widens it per model line and hosts cap it
222
+ * (catalog `image-max-edge`), and the sample renders a fixed mini-page anyway.
223
+ */
219
224
  function resolvedVariantName(shape: Shape): ShapeVariantName {
220
225
  for (const name of SHAPE_VARIANT_NAMES) {
221
- const candidate = SHAPE_VARIANTS[name];
226
+ const candidate: ShapeGeometry = SHAPE_VARIANTS[name];
222
227
  if (
223
228
  candidate.font === shape.font &&
224
229
  candidate.cellWidth === shape.cellWidth &&
225
230
  candidate.cellHeight === shape.cellHeight &&
231
+ candidate.stretch === shape.stretch &&
226
232
  candidate.variant === shape.variant &&
227
- candidate.lineRepeat === shape.lineRepeat &&
228
- candidate.frameSize === shape.frameSize
233
+ candidate.stopwordDim === shape.stopwordDim &&
234
+ candidate.columns === shape.columns &&
235
+ candidate.lineRepeat === shape.lineRepeat
229
236
  ) {
230
237
  return name;
231
238
  }
@@ -71,7 +71,12 @@ export class ThemeSelectorComponent extends OverlayPanel {
71
71
  };
72
72
 
73
73
  this.addChild(this.#selectList);
74
- this.#sheet = new SelectListSheet(this.#selectList, themePickerOptions("Theme", currentTheme));
74
+ // The picker checks the current theme, so its "(current)" text stays in the terminal list.
75
+ const options = themePickerOptions("Theme", currentTheme);
76
+ this.#sheet = new SelectListSheet(this.#selectList, {
77
+ ...options,
78
+ decorate: item => ({ ...options.decorate?.(item), detail: "" }),
79
+ });
75
80
  }
76
81
 
77
82
  override describe(cx: DescribeContext): NativeNode | null {
@@ -1,3 +1,4 @@
1
+ import { THINKING_EFFORTS } from "@linxiraos/pi-catalog/effort";
1
2
  import { AUTO_THINKING, type ConfiguredThinkingLevel, getConfiguredThinkingLevelMetadata } from "../thinking";
2
3
  import type { Effort } from "@linxiraos/pi-ai";
3
4
  import { tuiText } from "../i18n";
@@ -48,10 +49,13 @@ export class ThinkingSelectorComponent extends OverlayPanel {
48
49
  icon: "brain",
49
50
  noun: "levels",
50
51
  current: currentLevel ? [currentLevel] : [],
51
- decorate: item =>
52
- item.value === AUTO_THINKING
53
- ? {}
54
- : { chips: [{ text: "", dot: `thinking${item.value.charAt(0).toUpperCase()}${item.value.slice(1)}` }] },
52
+ // How hard each level thinks, as a meter from off (empty) to the deepest effort (full); auto has none.
53
+ columns: [{ id: "depth", format: "bar" }],
54
+ decorate: item => {
55
+ if (item.value === AUTO_THINKING) return undefined;
56
+ const effort = THINKING_EFFORTS.indexOf(item.value as (typeof THINKING_EFFORTS)[number]);
57
+ return { facts: { depth: (effort + 1) / THINKING_EFFORTS.length } };
58
+ },
55
59
  });
56
60
  }
57
61
 
@@ -56,7 +56,8 @@ import { resolveAssistantErrorPresentation } from "../chat/transcript-render-hel
56
56
  import { OverlayPanel, PanelDivider } from "../chrome/overlay-box";
57
57
  import { TreeView, type TreeAncestor, type TreeRow } from "../components/tree-view";
58
58
  import { formatKeyHint, formatKeyHints } from "../app-keybindings";
59
- import { boundKeys, editorKeys, interruptKey } from "../chrome/keybinding-hints";
59
+ import { boundKeys, editorKey, editorKeys, interruptKey } from "../chrome/keybinding-hints";
60
+ import { getKeybindings } from "../keybindings";
60
61
  import type { TspPickerItem, TspPickerProps, TspSpan, TspText } from "@linxiraos/pi-wire";
61
62
  import type { ThemeColor } from "../theme/schema";
62
63
  import { col, keyed, node, span, text } from "../native/describe";
@@ -204,6 +205,7 @@ class TreeList implements Component {
204
205
  #roots: TreeSelectorNode[] = [];
205
206
  #rootIds: Set<string> = new Set();
206
207
  #nodeById: Map<string, TreeSelectorNode> = new Map();
208
+ #parentById: Map<string, string | null> = new Map();
207
209
  #filterMode: FilterMode;
208
210
  /** The search field; its value is the fuzzy query. */
209
211
  readonly searchInput = new Input();
@@ -236,6 +238,14 @@ class TreeList implements Component {
236
238
  | { rows: readonly TreeRow<TreeSelectorNode, string>[]; labels: number; items: TspPickerItem[] }
237
239
  | undefined;
238
240
  #pickerOrder: { rows: readonly TreeRow<TreeSelectorNode, string>[]; order: string[] } | undefined;
241
+ /** Branch-navigation targets for one filtered rows snapshot; filtering replaces the rows array. */
242
+ #branchTargets:
243
+ | {
244
+ rows: readonly TreeRow<TreeSelectorNode, string>[];
245
+ targets: readonly TreeSelectorNode[];
246
+ targetIndexByRootId: ReadonlyMap<string, number>;
247
+ }
248
+ | undefined;
239
249
  #previewMemo: { node: TreeSelectorNode | undefined; labels: number; preview: NativeChild[] } | undefined;
240
250
 
241
251
  constructor(
@@ -293,8 +303,9 @@ class TreeList implements Component {
293
303
  this.#containsActive.clear();
294
304
  this.#nodeById.clear();
295
305
  this.#activePathIds.clear();
306
+ this.#parentById.clear();
296
307
 
297
- const parentIds = new Map<string, string | null>();
308
+ const parentIds = this.#parentById;
298
309
  const allNodes: TreeSelectorNode[] = [];
299
310
  const visitStack: TreeSelectorNode[] = [...roots];
300
311
  while (visitStack.length > 0) {
@@ -367,6 +378,100 @@ class TreeList implements Component {
367
378
  return [...children].sort((a, b) => Number(this.#containsActive.get(b)) - Number(this.#containsActive.get(a)));
368
379
  }
369
380
 
381
+ /** Visible branch targets in rendered order, keyed by every raw branch head. */
382
+ #visibleBranchProjection(rows: readonly TreeRow<TreeSelectorNode, string>[]): {
383
+ targets: readonly TreeSelectorNode[];
384
+ targetIndexByRootId: ReadonlyMap<string, number>;
385
+ } {
386
+ const visibleIndex = new Map<string, number>();
387
+ for (let index = 0; index < rows.length; index++) visibleIndex.set(rows[index]!.key, index);
388
+ const nodes: TreeSelectorNode[] = [];
389
+ const stack = [...this.#roots].reverse();
390
+ while (stack.length > 0) {
391
+ const node = stack.pop()!;
392
+ nodes.push(node);
393
+ const children = this.#orderedChildren(node);
394
+ for (let index = children.length - 1; index >= 0; index--) stack.push(children[index]!);
395
+ }
396
+
397
+ // One bottom-up pass makes filtered-head promotion O(nodes), rather than
398
+ // rescanning a branch subtree for every fork.
399
+ const firstVisibleById = new Map<string, TreeSelectorNode>();
400
+ for (let index = nodes.length - 1; index >= 0; index--) {
401
+ const node = nodes[index]!;
402
+ let target = visibleIndex.has(node.entry.id) ? node : undefined;
403
+ if (!target) {
404
+ for (const child of this.#orderedChildren(node)) {
405
+ target = firstVisibleById.get(child.entry.id);
406
+ if (target) break;
407
+ }
408
+ }
409
+ if (target) firstVisibleById.set(node.entry.id, target);
410
+ }
411
+
412
+ const byTargetId = new Map<string, { rootIds: string[]; target: TreeSelectorNode }>();
413
+ const addFork = (branches: readonly TreeSelectorNode[]) => {
414
+ if (branches.length < 2) return;
415
+ for (const branch of branches) {
416
+ const target = firstVisibleById.get(branch.entry.id);
417
+ if (!target) continue;
418
+ const existing = byTargetId.get(target.entry.id);
419
+ if (existing) existing.rootIds.push(branch.entry.id);
420
+ else byTargetId.set(target.entry.id, { rootIds: [branch.entry.id], target });
421
+ }
422
+ };
423
+ addFork(this.#roots);
424
+ for (const node of nodes) addFork(this.#orderedChildren(node));
425
+
426
+ const ordered = [...byTargetId.values()].sort(
427
+ (a, b) => visibleIndex.get(a.target.entry.id)! - visibleIndex.get(b.target.entry.id)!,
428
+ );
429
+ const targetIndexByRootId = new Map<string, number>();
430
+ for (let index = 0; index < ordered.length; index++) {
431
+ for (const rootId of ordered[index]!.rootIds) targetIndexByRootId.set(rootId, index);
432
+ }
433
+ return { targets: ordered.map(entry => entry.target), targetIndexByRootId };
434
+ }
435
+
436
+ /** Raw branch root containing `entryId` at its nearest ancestor fork. */
437
+ #nearestBranchRootId(entryId: string): string | undefined {
438
+ let branchId = entryId;
439
+ let parentId = this.#parentById.get(branchId);
440
+ while (parentId !== undefined) {
441
+ const siblings = parentId === null ? this.#roots : this.#orderedChildren(this.#nodeById.get(parentId)!);
442
+ if (siblings.length > 1) return branchId;
443
+ if (parentId === null) break;
444
+ branchId = parentId;
445
+ parentId = this.#parentById.get(parentId);
446
+ }
447
+ return undefined;
448
+ }
449
+
450
+ /**
451
+ * Move through every visible branch in rendered order, crossing nested fork
452
+ * depths. Raw ancestry identifies branch membership; visibility only chooses
453
+ * the first selectable descendant when a branch head is filtered.
454
+ */
455
+ #moveBranch(direction: -1 | 1): void {
456
+ const selected = this.#tree.selectedItem;
457
+ if (!selected) return;
458
+ // Projection is O(nodes); reuse it across presses until filtering replaces the rows.
459
+ const rows = this.#tree.rows;
460
+ if (this.#branchTargets?.rows !== rows) this.#branchTargets = { rows, ...this.#visibleBranchProjection(rows) };
461
+ const { targets, targetIndexByRootId } = this.#branchTargets;
462
+
463
+ if (targets.length === 0) return;
464
+ const currentRootId = this.#nearestBranchRootId(selected.entry.id);
465
+ const currentIndex = currentRootId === undefined ? -1 : (targetIndexByRootId.get(currentRootId) ?? -1);
466
+ const nextIndex =
467
+ currentIndex < 0
468
+ ? direction > 0
469
+ ? 0
470
+ : targets.length - 1
471
+ : (currentIndex + direction + targets.length) % targets.length;
472
+ this.#tree.setSelectedKey(targets[nextIndex]!.entry.id);
473
+ }
474
+
370
475
  #applyFilter(): void {
371
476
  // TreeView retains the nearest visible selection (including an anchor
372
477
  // through empty filter results) when the predicate is replaced.
@@ -1210,6 +1315,10 @@ class TreeList implements Component {
1210
1315
  (node.entry.message.role === "user" || node.entry.message.role === "assistant"),
1211
1316
  1,
1212
1317
  );
1318
+ } else if (getKeybindings().matches(keyData, "app.tree.previousBranch")) {
1319
+ this.#moveBranch(-1);
1320
+ } else if (getKeybindings().matches(keyData, "app.tree.nextBranch")) {
1321
+ this.#moveBranch(1);
1213
1322
  } else if (matchesKey(keyData, "home")) {
1214
1323
  this.#tree.setSelectionIndex(0);
1215
1324
  } else if (matchesKey(keyData, "end")) {
@@ -1398,6 +1507,10 @@ export class TreeSelectorComponent extends OverlayPanel {
1398
1507
  this.#treeContainer.addChild(this.#treeList);
1399
1508
 
1400
1509
  this.#labelInputContainer = new Container();
1510
+ // Either action may be unbound; hint only the keys that still do something.
1511
+ const branchKeys = [editorKey("app.tree.previousBranch"), editorKey("app.tree.nextBranch")]
1512
+ .filter(Boolean)
1513
+ .join("/");
1401
1514
 
1402
1515
  this.addChild(new Spacer(1));
1403
1516
  this.addChild(
@@ -1407,6 +1520,7 @@ export class TreeSelectorComponent extends OverlayPanel {
1407
1520
  [
1408
1521
  `${formatKeyHint("enter")}: switch.`,
1409
1522
  `${formatKeyHints(["alt+up", "alt+down"])}: previous/next turn.`,
1523
+ ...(branchKeys ? [`${branchKeys}: previous/next branch.`] : []),
1410
1524
  `${editorKeys("tui.select.pageUp", "tui.select.pageDown")} (${formatKeyHints(["left", "right"])}): page.`,
1411
1525
  `${formatKeyHints(["home", "end"])}: first/last item.`,
1412
1526
  `${formatKeyHint("shift+enter")}: summarize & switch.`,
@@ -1563,6 +1677,7 @@ export class TreeSelectorComponent extends OverlayPanel {
1563
1677
  hintsRow([
1564
1678
  { keys: ["enter"], label: "switch" },
1565
1679
  { keys: ["alt+up", "alt+down"], label: "previous/next turn" },
1680
+ actionHint(["app.tree.previousBranch", "app.tree.nextBranch"], "previous/next branch"),
1566
1681
  actionHint(["tui.select.pageUp", "tui.select.pageDown"], "page"),
1567
1682
  { keys: ["home", "end"], label: "first/last" },
1568
1683
  { keys: ["shift+enter"], label: "summarize & switch" },