@oh-my-pi/pi-tui 18.13.2 → 18.14.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 (245) hide show
  1. package/CHANGELOG.md +40 -54
  2. package/README.md +34 -34
  3. package/dist/types/app-keybindings.d.ts +15 -0
  4. package/dist/types/apps/session-picker.d.ts +5 -0
  5. package/dist/types/charts/chart-plan.d.ts +9 -7
  6. package/dist/types/chat/assistant-message.d.ts +15 -3
  7. package/dist/types/chat/bash-execution.d.ts +2 -0
  8. package/dist/types/chat/display-preferences.d.ts +2 -0
  9. package/dist/types/chat/extension-types.d.ts +31 -0
  10. package/dist/types/chat/reply-write.d.ts +48 -0
  11. package/dist/types/chat/table-chart.d.ts +4 -3
  12. package/dist/types/chat/tool-execution.d.ts +1 -2
  13. package/dist/types/chat/transcript-outline.d.ts +7 -0
  14. package/dist/types/chat/transcript-render-helpers.d.ts +2 -0
  15. package/dist/types/chrome/index.d.ts +1 -0
  16. package/dist/types/chrome/transcript-notice.d.ts +35 -0
  17. package/dist/types/chrome/transcript-status.d.ts +20 -8
  18. package/dist/types/components/editor.d.ts +8 -0
  19. package/dist/types/components/markdown.d.ts +7 -0
  20. package/dist/types/native/blobs.d.ts +2 -1
  21. package/dist/types/native/picker.d.ts +3 -3
  22. package/dist/types/overlays/annotation-overlay.d.ts +7 -1
  23. package/dist/types/overlays/annotation-types.d.ts +28 -0
  24. package/dist/types/overlays/login-dialog.d.ts +5 -1
  25. package/dist/types/overlays/model-browser.d.ts +10 -1
  26. package/dist/types/overlays/oauth-selector.d.ts +23 -0
  27. package/dist/types/overlays/plan-review-overlay.d.ts +2 -0
  28. package/dist/types/overlays/rewind-selector.d.ts +1 -1
  29. package/dist/types/overlays/session-selector.d.ts +22 -2
  30. package/dist/types/prompt/composer.d.ts +41 -4
  31. package/dist/types/prompt/interactive-context-helpers.d.ts +10 -3
  32. package/dist/types/prompt/welcome.d.ts +6 -2
  33. package/dist/types/render/extension-links.d.ts +65 -0
  34. package/dist/types/render/hyperlink.d.ts +25 -7
  35. package/dist/types/render/index.d.ts +1 -0
  36. package/dist/types/setup/scenes/model.d.ts +1 -1
  37. package/dist/types/setup/scenes/outro.d.ts +5 -4
  38. package/dist/types/setup/scenes/sign-in.d.ts +13 -8
  39. package/dist/types/setup/scenes/splash.d.ts +4 -4
  40. package/dist/types/setup/scenes/types.d.ts +16 -1
  41. package/dist/types/setup/startup-splash.d.ts +1 -1
  42. package/dist/types/setup/wizard-overlay.d.ts +11 -7
  43. package/dist/types/setup/wizard.d.ts +5 -1
  44. package/dist/types/status-line/component.d.ts +1 -1
  45. package/dist/types/status-line/context-usage.d.ts +28 -6
  46. package/dist/types/status-line/host.d.ts +6 -2
  47. package/dist/types/theme/theme.d.ts +0 -23
  48. package/dist/types/tools/bash-interactive.d.ts +6 -0
  49. package/dist/types/tools/bash.d.ts +2 -0
  50. package/dist/types/tools/default-renderer.d.ts +6 -4
  51. package/dist/types/tools/irc.d.ts +6 -0
  52. package/dist/types/tools/json-tree.d.ts +23 -4
  53. package/dist/types/tools/lsp.d.ts +2 -0
  54. package/dist/types/tools/mcp.d.ts +6 -5
  55. package/dist/types/tools/native-view.d.ts +1 -1
  56. package/dist/types/tools/renderer.d.ts +2 -0
  57. package/dist/types/tools/streaming-output.d.ts +11 -0
  58. package/dist/types/tools/task.d.ts +9 -1
  59. package/dist/types/tools/url-scheme-host.d.ts +17 -0
  60. package/dist/types/tools/yield.d.ts +1 -1
  61. package/dist/types/tui.d.ts +7 -2
  62. package/package.json +10 -15
  63. package/src/app-keybindings.ts +15 -0
  64. package/src/apps/session-picker.ts +7 -0
  65. package/src/charts/chart-plan.ts +20 -50
  66. package/src/chat/advisor-message.ts +2 -0
  67. package/src/chat/assistant-message.ts +137 -19
  68. package/src/chat/background-tan-message.ts +4 -0
  69. package/src/chat/bash-execution.ts +6 -0
  70. package/src/chat/chat-transcript-builder.ts +58 -28
  71. package/src/chat/compaction-summary-message.ts +2 -1
  72. package/src/chat/display-preferences.ts +3 -0
  73. package/src/chat/extension-types.ts +34 -0
  74. package/src/chat/reply-write.ts +127 -0
  75. package/src/chat/table-chart.ts +10 -4
  76. package/src/chat/tool-execution.ts +20 -13
  77. package/src/chat/transcript-outline.ts +27 -0
  78. package/src/chat/transcript-render-helpers.ts +66 -24
  79. package/src/chrome/index.ts +1 -0
  80. package/src/chrome/transcript-notice.ts +83 -0
  81. package/src/chrome/transcript-status.ts +29 -29
  82. package/src/components/editor.ts +10 -1
  83. package/src/components/markdown.ts +116 -6
  84. package/src/hotkeys-markdown.ts +1 -0
  85. package/src/native/backend.ts +5 -66
  86. package/src/native/blobs.ts +3 -2
  87. package/src/native/picker.ts +3 -3
  88. package/src/overlays/advisor-config.ts +1 -10
  89. package/src/overlays/annotation-overlay.ts +125 -37
  90. package/src/overlays/annotation-types.ts +24 -0
  91. package/src/overlays/hook-selector.ts +2 -2
  92. package/src/overlays/login-dialog.ts +31 -5
  93. package/src/overlays/model-browser.ts +37 -0
  94. package/src/overlays/model-hub.ts +1 -7
  95. package/src/overlays/model-picker.ts +1 -10
  96. package/src/overlays/oauth-selector.ts +44 -6
  97. package/src/overlays/plan-review-overlay.ts +13 -2
  98. package/src/overlays/rewind-selector.ts +29 -12
  99. package/src/overlays/session-selector.ts +305 -39
  100. package/src/overlays/usage-dashboard.ts +5 -12
  101. package/src/prompt/composer.ts +213 -5
  102. package/src/prompt/custom-editor.ts +6 -1
  103. package/src/prompt/emoji-autocomplete.ts +0 -2
  104. package/src/prompt/interactive-context-helpers.ts +29 -4
  105. package/src/prompt/welcome.ts +31 -45
  106. package/src/render/extension-links.ts +214 -0
  107. package/src/render/hyperlink.ts +65 -9
  108. package/src/render/index.ts +1 -0
  109. package/src/setup/scenes/composer.ts +2 -0
  110. package/src/setup/scenes/glyph.ts +2 -0
  111. package/src/setup/scenes/model.ts +192 -29
  112. package/src/setup/scenes/outro.ts +30 -17
  113. package/src/setup/scenes/sign-in.ts +196 -400
  114. package/src/setup/scenes/splash.ts +25 -21
  115. package/src/setup/scenes/theme.ts +2 -0
  116. package/src/setup/scenes/types.ts +18 -1
  117. package/src/setup/startup-splash.ts +8 -7
  118. package/src/setup/wizard-overlay.ts +164 -33
  119. package/src/setup/wizard.ts +11 -2
  120. package/src/status-line/component.ts +4 -10
  121. package/src/status-line/context-usage.ts +72 -21
  122. package/src/status-line/host.ts +6 -2
  123. package/src/terminal.ts +24 -2
  124. package/src/theme/defaults.json +1 -0
  125. package/src/theme/loader.ts +1 -1
  126. package/src/theme/theme.ts +2 -75
  127. package/src/tools/bash-interactive.ts +6 -1
  128. package/src/tools/bash.ts +2 -0
  129. package/src/tools/default-renderer.ts +9 -6
  130. package/src/tools/eval.ts +14 -4
  131. package/src/tools/irc.ts +6 -0
  132. package/src/tools/job-widget.ts +13 -1
  133. package/src/tools/json-tree.ts +132 -42
  134. package/src/tools/lsp.ts +36 -15
  135. package/src/tools/mcp.ts +20 -12
  136. package/src/tools/native-view.ts +1 -1
  137. package/src/tools/renderer.ts +2 -0
  138. package/src/tools/streaming-output.ts +41 -0
  139. package/src/tools/task.ts +59 -17
  140. package/src/tools/url-scheme-host.ts +21 -0
  141. package/src/tools/yield.ts +18 -9
  142. package/src/tui.ts +43 -15
  143. package/THIRD-PARTY-NOTICES.txt +0 -22901
  144. package/dist/types/theme/defaults/index.d.ts +0 -10289
  145. package/src/theme/defaults/alabaster.json +0 -103
  146. package/src/theme/defaults/amethyst.json +0 -106
  147. package/src/theme/defaults/anthracite.json +0 -103
  148. package/src/theme/defaults/basalt.json +0 -101
  149. package/src/theme/defaults/birch.json +0 -105
  150. package/src/theme/defaults/dark-abyss.json +0 -101
  151. package/src/theme/defaults/dark-arctic.json +0 -114
  152. package/src/theme/defaults/dark-aurora.json +0 -105
  153. package/src/theme/defaults/dark-catppuccin.json +0 -117
  154. package/src/theme/defaults/dark-cavern.json +0 -101
  155. package/src/theme/defaults/dark-celestial.json +0 -116
  156. package/src/theme/defaults/dark-copper.json +0 -105
  157. package/src/theme/defaults/dark-cosmos.json +0 -100
  158. package/src/theme/defaults/dark-cyberpunk.json +0 -112
  159. package/src/theme/defaults/dark-dracula.json +0 -108
  160. package/src/theme/defaults/dark-eclipse.json +0 -101
  161. package/src/theme/defaults/dark-ember.json +0 -105
  162. package/src/theme/defaults/dark-equinox.json +0 -100
  163. package/src/theme/defaults/dark-forest.json +0 -106
  164. package/src/theme/defaults/dark-github.json +0 -115
  165. package/src/theme/defaults/dark-gruvbox.json +0 -122
  166. package/src/theme/defaults/dark-lavender.json +0 -105
  167. package/src/theme/defaults/dark-lunar.json +0 -99
  168. package/src/theme/defaults/dark-midnight.json +0 -105
  169. package/src/theme/defaults/dark-monochrome.json +0 -104
  170. package/src/theme/defaults/dark-monokai.json +0 -108
  171. package/src/theme/defaults/dark-nebula.json +0 -100
  172. package/src/theme/defaults/dark-neon-noir.json +0 -110
  173. package/src/theme/defaults/dark-nord.json +0 -108
  174. package/src/theme/defaults/dark-ocean.json +0 -111
  175. package/src/theme/defaults/dark-one.json +0 -110
  176. package/src/theme/defaults/dark-poimandres.json +0 -125
  177. package/src/theme/defaults/dark-rainforest.json +0 -101
  178. package/src/theme/defaults/dark-reef.json +0 -101
  179. package/src/theme/defaults/dark-retro.json +0 -102
  180. package/src/theme/defaults/dark-rose-pine.json +0 -106
  181. package/src/theme/defaults/dark-sakura.json +0 -105
  182. package/src/theme/defaults/dark-slate.json +0 -105
  183. package/src/theme/defaults/dark-solarized.json +0 -107
  184. package/src/theme/defaults/dark-solstice.json +0 -100
  185. package/src/theme/defaults/dark-starfall.json +0 -101
  186. package/src/theme/defaults/dark-sunset.json +0 -109
  187. package/src/theme/defaults/dark-swamp.json +0 -100
  188. package/src/theme/defaults/dark-synthwave.json +0 -113
  189. package/src/theme/defaults/dark-taiga.json +0 -101
  190. package/src/theme/defaults/dark-terminal.json +0 -105
  191. package/src/theme/defaults/dark-tokyo-night.json +0 -111
  192. package/src/theme/defaults/dark-tundra.json +0 -101
  193. package/src/theme/defaults/dark-twilight.json +0 -101
  194. package/src/theme/defaults/dark-volcanic.json +0 -101
  195. package/src/theme/defaults/graphite.json +0 -102
  196. package/src/theme/defaults/index.ts +0 -203
  197. package/src/theme/defaults/light-arctic.json +0 -117
  198. package/src/theme/defaults/light-aurora-day.json +0 -101
  199. package/src/theme/defaults/light-canyon.json +0 -101
  200. package/src/theme/defaults/light-catppuccin.json +0 -116
  201. package/src/theme/defaults/light-cirrus.json +0 -100
  202. package/src/theme/defaults/light-coral.json +0 -105
  203. package/src/theme/defaults/light-cyberpunk.json +0 -106
  204. package/src/theme/defaults/light-dawn.json +0 -100
  205. package/src/theme/defaults/light-dunes.json +0 -101
  206. package/src/theme/defaults/light-eucalyptus.json +0 -105
  207. package/src/theme/defaults/light-forest.json +0 -110
  208. package/src/theme/defaults/light-frost.json +0 -105
  209. package/src/theme/defaults/light-github.json +0 -125
  210. package/src/theme/defaults/light-glacier.json +0 -101
  211. package/src/theme/defaults/light-gruvbox.json +0 -118
  212. package/src/theme/defaults/light-haze.json +0 -100
  213. package/src/theme/defaults/light-honeycomb.json +0 -105
  214. package/src/theme/defaults/light-lagoon.json +0 -101
  215. package/src/theme/defaults/light-lavender.json +0 -105
  216. package/src/theme/defaults/light-meadow.json +0 -101
  217. package/src/theme/defaults/light-mint.json +0 -105
  218. package/src/theme/defaults/light-monochrome.json +0 -111
  219. package/src/theme/defaults/light-ocean.json +0 -109
  220. package/src/theme/defaults/light-one.json +0 -109
  221. package/src/theme/defaults/light-opal.json +0 -101
  222. package/src/theme/defaults/light-orchard.json +0 -101
  223. package/src/theme/defaults/light-paper.json +0 -105
  224. package/src/theme/defaults/light-poimandres.json +0 -125
  225. package/src/theme/defaults/light-prism.json +0 -100
  226. package/src/theme/defaults/light-retro.json +0 -108
  227. package/src/theme/defaults/light-sand.json +0 -105
  228. package/src/theme/defaults/light-savanna.json +0 -101
  229. package/src/theme/defaults/light-solarized.json +0 -112
  230. package/src/theme/defaults/light-soleil.json +0 -100
  231. package/src/theme/defaults/light-sunset.json +0 -109
  232. package/src/theme/defaults/light-synthwave.json +0 -108
  233. package/src/theme/defaults/light-tokyo-night.json +0 -121
  234. package/src/theme/defaults/light-wetland.json +0 -101
  235. package/src/theme/defaults/light-zenith.json +0 -99
  236. package/src/theme/defaults/limestone.json +0 -104
  237. package/src/theme/defaults/mahogany.json +0 -107
  238. package/src/theme/defaults/marble.json +0 -103
  239. package/src/theme/defaults/obsidian.json +0 -101
  240. package/src/theme/defaults/onyx.json +0 -101
  241. package/src/theme/defaults/pearl.json +0 -103
  242. package/src/theme/defaults/porcelain.json +0 -101
  243. package/src/theme/defaults/quartz.json +0 -106
  244. package/src/theme/defaults/sandstone.json +0 -105
  245. package/src/theme/defaults/titanium.json +0 -100
@@ -42,6 +42,7 @@ import type {
42
42
  TspPickerGroup,
43
43
  TspPickerItem,
44
44
  TspPickerMark,
45
+ TspPickerProps,
45
46
  TspSpan,
46
47
  TspText,
47
48
  } from "@oh-my-pi/pi-wire";
@@ -918,6 +919,22 @@ export interface ModelPickerCatalogue {
918
919
  readonly itemsDel?: readonly string[];
919
920
  }
920
921
 
922
+ /** The search, fact-column and row props every model `picker` sheet sends ({@link ModelBrowser.pickerRows}). */
923
+ export type ModelPickerRows = Pick<
924
+ TspPickerProps,
925
+ | "query"
926
+ | "cursor"
927
+ | "columns"
928
+ | "items"
929
+ | "itemsAdd"
930
+ | "itemsDel"
931
+ | "order"
932
+ | "hits"
933
+ | "selected"
934
+ | "current"
935
+ | "total"
936
+ >;
937
+
921
938
  /** How {@link ModelBrowser.pickerOrder} heads its rows. */
922
939
  export interface ModelPickerGrouping {
923
940
  /** One group per provider after the Recent block (the unfiltered All models view). */
@@ -1933,6 +1950,26 @@ export class ModelBrowser implements Component {
1933
1950
  return selected && !this.#isDisabled(selected) ? selected.selector : null;
1934
1951
  }
1935
1952
 
1953
+ /**
1954
+ * A model picker's search field, fact columns and rows: `catalogue` (from
1955
+ * {@link pickerItems}) in `view` order (from {@link pickerOrder}), the
1956
+ * selection, and `current` checked. Memoize hosts on its inputs plus
1957
+ * {@link query}, {@link cursor} and {@link pickerSelected}.
1958
+ */
1959
+ pickerRows(catalogue: ModelPickerCatalogue, view: ModelPickerOrder, current: string | undefined): ModelPickerRows {
1960
+ return {
1961
+ query: this.query,
1962
+ cursor: this.cursor,
1963
+ columns: MODEL_PICKER_COLUMNS,
1964
+ ...catalogue,
1965
+ order: view.order,
1966
+ ...(view.hits ? { hits: view.hits } : {}),
1967
+ selected: this.pickerSelected,
1968
+ ...(current ? { current: [current] } : {}),
1969
+ total: this.baseCount,
1970
+ };
1971
+ }
1972
+
1936
1973
  /**
1937
1974
  * Picker rows for `catalogue`: the same object while the catalogue, roles
1938
1975
  * and row inputs are unchanged, so typing and scope hops never resend it.
@@ -3370,13 +3370,7 @@ export class ModelHubComponent implements Component {
3370
3370
  ...props,
3371
3371
  tabs: this.#pickerKindTabs(),
3372
3372
  tab: this.#modelKindTab,
3373
- columns: MODEL_PICKER_COLUMNS,
3374
- ...catalogue,
3375
- order: view.order,
3376
- ...(view.hits ? { hits: view.hits } : {}),
3377
- selected: this.#browser.pickerSelected,
3378
- ...(current ? { current } : {}),
3379
- total: this.#browser.baseCount,
3373
+ ...this.#browser.pickerRows(catalogue, view, this.#currentSelector),
3380
3374
  state,
3381
3375
  ...(state === "error" && this.#configError ? { message: this.#configError } : {}),
3382
3376
  empty:
@@ -11,7 +11,6 @@ import type { Component, TUI } from "../tui";
11
11
  import { type ThemeColor, theme } from "../theme/theme";
12
12
  import {
13
13
  buildSessionModelScope,
14
- MODEL_PICKER_COLUMNS,
15
14
  ModelBrowser,
16
15
  type ModelBrowserItem,
17
16
  type ModelPickerCatalogue,
@@ -424,17 +423,9 @@ export class ModelPickerComponent implements Component {
424
423
  size: "md",
425
424
  layout: "rows",
426
425
  preview: "below",
427
- query,
428
- cursor,
429
426
  placeholder:
430
427
  mode === "task" || mode === "selection" ? "Search models…" : "Search models, @ for quick roles…",
431
- columns: MODEL_PICKER_COLUMNS,
432
- ...catalogue,
433
- order: view.order,
434
- ...(view.hits ? { hits: view.hits } : {}),
435
- selected,
436
- ...(current ? { current: [current] } : {}),
437
- total: this.#browser.baseCount,
428
+ ...this.#browser.pickerRows(catalogue, view, current),
438
429
  empty: mode === "role" ? `No quick roles in the ${cycleKey} cycle` : "No models available",
439
430
  actions: compact([
440
431
  pickerAction(
@@ -17,7 +17,7 @@ import { OverlayPanel } from "../chrome/overlay-box";
17
17
  import { Input } from "../components/input";
18
18
  import { MenuSelection } from "../components/menu-selection";
19
19
  import { centeredViewportRange } from "../components/scroll-viewport";
20
- import type { TspPickerItem, TspSpan, TspTone } from "@oh-my-pi/pi-wire";
20
+ import type { TspPickerAction, TspPickerItem, TspSpan, TspText, TspTone } from "@oh-my-pi/pi-wire";
21
21
  import { node, span, text } from "../native/describe";
22
22
  import type { DescribeContext, NativeNode, NativeUiEvent } from "../native/node";
23
23
  import { CLOSE_ACTION, dockedPicker, PICKER_KEY, pickerAction, pickerEvent, pickerQuery } from "../native/picker";
@@ -32,6 +32,21 @@ export interface OAuthSelectorAuthSource {
32
32
  readonly keys: Pick<KeysApi, "source">;
33
33
  }
34
34
 
35
+ /**
36
+ * How a host other than `/login` presents the native provider sheet (the
37
+ * setup wizard): its heading, `role` and size, a status line docked above the
38
+ * action bar, and the Esc action that leaves in place of Close (the action's
39
+ * `id` cancels like Esc).
40
+ */
41
+ export interface OAuthSelectorSheet {
42
+ readonly title: string;
43
+ readonly subtitle: TspText;
44
+ readonly role: string;
45
+ readonly size: "md" | "lg";
46
+ readonly status?: TspText;
47
+ readonly leave: { readonly id: string; readonly label: string };
48
+ }
49
+
35
50
  /**
36
51
  * Rendered lines before the provider rows: top border
37
52
  * (must mirror the constructor's addChild order).
@@ -80,6 +95,7 @@ export class OAuthSelectorComponent extends OverlayPanel {
80
95
  /** Picker rows for the whole catalogue; dropped when an auth state changes. */
81
96
  #pickerItems: readonly TspPickerItem[] | undefined;
82
97
  #pickerRoot: NativeNode | undefined;
98
+ #sheet: OAuthSelectorSheet | undefined;
83
99
  constructor(
84
100
  mode: "login" | "logout",
85
101
  authStorage: OAuthSelectorAuthSource,
@@ -89,6 +105,7 @@ export class OAuthSelectorComponent extends OverlayPanel {
89
105
  disabledProviders?: readonly string[];
90
106
  validateAuth?: (providerId: string) => Promise<boolean>;
91
107
  requestRender?: () => void;
108
+ sheet?: OAuthSelectorSheet;
92
109
  },
93
110
  ) {
94
111
  super(mode === "login" ? "Select provider to login" : "Select provider to logout", "omp.overlay.oauth");
@@ -98,6 +115,7 @@ export class OAuthSelectorComponent extends OverlayPanel {
98
115
  this.#onCancelCallback = onCancel;
99
116
  this.#validateAuthCallback = options?.validateAuth;
100
117
  this.#requestRenderCallback = options?.requestRender;
118
+ this.#sheet = options?.sheet;
101
119
  this.#menu = new MenuSelection<OAuthProviderInfo>([], {
102
120
  getKey: provider => provider.id,
103
121
  getSearchText: provider => this.#getProviderSearchText(provider),
@@ -117,6 +135,20 @@ export class OAuthSelectorComponent extends OverlayPanel {
117
135
  this.#stopSpinner();
118
136
  }
119
137
 
138
+ /** Replace the native sheet's presentation (a new outcome line, a relabelled leave action). */
139
+ setSheet(sheet: OAuthSelectorSheet): void {
140
+ this.#sheet = sheet;
141
+ this.#pickerRoot = undefined;
142
+ }
143
+
144
+ /** Re-read credential state after a sign-in stored one, keeping the query and selection. */
145
+ refreshAuth(): void {
146
+ this.#nativeItems = undefined;
147
+ this.#pickerItems = undefined;
148
+ this.#menu.setItems(this.#menu.items);
149
+ this.#updateList();
150
+ }
151
+
120
152
  /**
121
153
  * Fit the selector into `lines` rendered rows by shrinking the visible list
122
154
  * window (the window is centered on the selection, so the selected row is
@@ -504,12 +536,17 @@ export class OAuthSelectorComponent extends OverlayPanel {
504
536
  const query = this.#menu.query;
505
537
  const login = this.#mode === "login";
506
538
  const search = this.#shouldRenderSearchStatus() ? pickerQuery(this.#search) : pickerQuery(null);
539
+ const sheet = this.#sheet;
540
+ const leave: TspPickerAction = sheet
541
+ ? pickerAction(sheet.leave.id, sheet.leave.label, "escape", { end: true })
542
+ : CLOSE_ACTION;
507
543
  this.#pickerRoot = dockedPicker({
508
- title: login ? "Sign in" : "Sign out",
509
- subtitle: login ? "Pick a provider" : "Remove stored credentials",
544
+ ...(sheet ? { role: sheet.role } : {}),
545
+ title: sheet?.title ?? (login ? "Sign in" : "Sign out"),
546
+ subtitle: sheet?.subtitle ?? (login ? "Pick a provider" : "Remove stored credentials"),
510
547
  icon: "key-round",
511
548
  noun: "providers",
512
- size: "md",
549
+ size: sheet?.size ?? "md",
513
550
  layout: "cards",
514
551
  preview: "none",
515
552
  ...search,
@@ -520,6 +557,7 @@ export class OAuthSelectorComponent extends OverlayPanel {
520
557
  total: all.length,
521
558
  empty: login ? "No OAuth providers available" : "No stored provider credentials to log out",
522
559
  ...(this.#statusMessage ? { message: this.#statusMessage } : {}),
560
+ ...(sheet?.status ? { strip: { label: sheet.status, items: [] } } : {}),
523
561
  actions: [
524
562
  pickerAction(
525
563
  "confirm",
@@ -527,7 +565,7 @@ export class OAuthSelectorComponent extends OverlayPanel {
527
565
  "enter",
528
566
  login ? { primary: true } : { primary: true, danger: true },
529
567
  ),
530
- CLOSE_ACTION,
568
+ leave,
531
569
  ],
532
570
  });
533
571
  return this.#pickerRoot;
@@ -596,7 +634,7 @@ export class OAuthSelectorComponent extends OverlayPanel {
596
634
  else if (ev.act === "clear") {
597
635
  this.#search.setValue("");
598
636
  this.#syncSearchQuery();
599
- } else if (ev.act === "close" || ev.act === "cancel") {
637
+ } else if (ev.act === "close" || ev.act === "cancel" || ev.act === this.#sheet?.leave.id) {
600
638
  this.stopValidation();
601
639
  this.#onCancelCallback();
602
640
  }
@@ -256,6 +256,8 @@ export interface PlanReviewOverlayOptions {
256
256
  slider?: HookSelectorSlider;
257
257
  /** Display label for the external-editor key, surfaced in the footer help. */
258
258
  externalEditorLabel?: string;
259
+ /** The external editor's name ("Notepad", "nvim") for the native Edit button; `$EDITOR` when unset. */
260
+ externalEditorName?: string;
259
261
  /** Serializable annotations restored into this overlay instance. */
260
262
  annotationState?: PlanReviewAnnotationState;
261
263
  }
@@ -286,6 +288,7 @@ export class PlanReviewOverlay implements Component {
286
288
  #disabled: Set<number>;
287
289
  #helpSuffix: string | undefined;
288
290
  #externalEditorLabel: string | undefined;
291
+ #externalEditorName: string | undefined;
289
292
  #promptTitle: string | undefined;
290
293
  #selectedIndex: number;
291
294
  #slider: HookSelectorSlider | undefined;
@@ -354,6 +357,7 @@ export class PlanReviewOverlay implements Component {
354
357
  );
355
358
  this.#helpSuffix = options.helpText;
356
359
  this.#externalEditorLabel = options.externalEditorLabel;
360
+ this.#externalEditorName = options.externalEditorName;
357
361
  this.#promptTitle = options.promptTitle;
358
362
  this.#selectedIndex = this.#coerceIndex(options.initialIndex ?? 0);
359
363
  if (options.slider && options.slider.segments.length > 0) {
@@ -1632,7 +1636,13 @@ export class PlanReviewOverlay implements Component {
1632
1636
  // Unbound: the button still mirrors the callback, without a keycap.
1633
1637
  const editorKeyId = getKeybindings().getKeys("app.editor.external")[0];
1634
1638
  if (this.callbacks.onExternalEditor) {
1635
- buttons.push(actionButton("Edit in $EDITOR", "externalEditor", editorKeyId ? { keys: editorKeyId } : {}));
1639
+ buttons.push(
1640
+ actionButton(
1641
+ `Edit in ${this.#externalEditorName ?? "$EDITOR"}`,
1642
+ "externalEditor",
1643
+ editorKeyId ? { keys: editorKeyId } : {},
1644
+ ),
1645
+ );
1636
1646
  }
1637
1647
  // Cancel the review the way the cancel key does; while annotating that key
1638
1648
  // only leaves the draft/chooser, so the button is hidden there.
@@ -1754,7 +1764,8 @@ export class PlanReviewOverlay implements Component {
1754
1764
  "tabs",
1755
1765
  );
1756
1766
  const track: NativeChild[] = slider.caption ? [text([span(slider.caption, "dim")]), tabs] : [tabs];
1757
- const out = [keyed(rowNode(track, { gap: "sm", align: "center" }), "slider")];
1767
+ // A role of its own: Tern's sheet lets only the role-less split row (Contents + body) shrink and scroll.
1768
+ const out = [keyed(rowNode(track, { role: "omp.plan.slider", gap: "sm", align: "center" }), "slider")];
1758
1769
  const detail = slider.segments[this.#sliderIndex]?.detail;
1759
1770
  if (detail) out.push(keyed(text([span("↳ ", "dim"), span(detail, "muted")], { wrap: "word" }), "sliderDetail"));
1760
1771
  return out;
@@ -46,7 +46,11 @@ import { recentTranscriptEntries, type TranscriptEntryLike as TranscriptEntry }
46
46
  import { theme } from "../theme/theme";
47
47
  import { matchesAppToolsExpand, matchesSelectCancel, matchesSelectDown, matchesSelectUp } from "../keybinding-matchers";
48
48
  import { ChatTranscriptBuilder } from "../chat/chat-transcript-builder";
49
- import { TranscriptBrowser, type TranscriptBrowserFrame } from "../chat/transcript-browser";
49
+ import {
50
+ nearestVisibleOutlineTarget,
51
+ TranscriptBrowser,
52
+ type TranscriptBrowserFrame,
53
+ } from "../chat/transcript-browser";
50
54
  import { padToWidth } from "../render/utils";
51
55
  import { expandKeyHint } from "../render/render-utils";
52
56
  import { formatKeyHint, formatKeyHints } from "../app-keybindings";
@@ -55,6 +59,7 @@ import {
55
59
  appendOutlineEntries,
56
60
  type ComposedColumn,
57
61
  composeOutlineColumn,
62
+ describedVisibility,
58
63
  type OutlineTarget,
59
64
  isUserTurnEntry,
60
65
  outlineVisibility,
@@ -682,6 +687,21 @@ export class RewindSelectorComponent implements Component {
682
687
  return this.#mainVisible?.[index] ?? true;
683
688
  }
684
689
 
690
+ /**
691
+ * Adopt the main path's per-target visibility. A selection on a target
692
+ * that shows nothing (the empty turn an interrupt leaves last, a turn the
693
+ * expansion toggle collapsed) moves to the nearest one that shows and
694
+ * leaves the strip.
695
+ */
696
+ #adoptVisibility(visible: boolean[]): void {
697
+ this.#mainVisible = visible;
698
+ const selected = nearestVisibleOutlineTarget(visible, this.#selected);
699
+ if (selected === this.#selected) return;
700
+ this.#selected = selected;
701
+ this.#activeVariant = 0;
702
+ this.#siblingSelected = 0;
703
+ }
704
+
685
705
  // ========================================================================
686
706
  // Native: the transcript as a page
687
707
  // ========================================================================
@@ -694,12 +714,13 @@ export class RewindSelectorComponent implements Component {
694
714
  * of branch columns, the current path first. The bar is this component's
695
715
  * own node, docked under the page, so the filter field keeps the caret.
696
716
  */
697
- describeScreen(_cx: DescribeContext): NativeScreen {
698
- return { role: "omp.rewind", main: this.#page(), dock: [this] };
717
+ describeScreen(cx: DescribeContext): NativeScreen {
718
+ this.#adoptVisibility(describedVisibility(this.#builder.container.children, this.#targets, cx));
719
+ return { role: "omp.rewind", main: this.#page(cx), dock: [this] };
699
720
  }
700
721
 
701
722
  /** The page's blocks, marked for the current outline. */
702
- #page(): NativeChild[] {
723
+ #page(cx: DescribeContext): NativeChild[] {
703
724
  const blocks = this.#builder.container.children;
704
725
  const page: NativeChild[] = [];
705
726
  const filter = this.#filter;
@@ -741,6 +762,9 @@ export class RewindSelectorComponent implements Component {
741
762
  for (let index = 0; index < columns.length; index++) {
742
763
  const column = columns[index]!;
743
764
  const active = this.#activeVariant === index + 1;
765
+ if (active) {
766
+ this.#siblingVisible = describedVisibility(column.builder.container.children, column.targets, cx);
767
+ }
744
768
  const children: NativeChild[] = [this.#columnHead(index + 1, count, column.label)];
745
769
  const picked = active ? this.#siblingSelected : -1;
746
770
  this.#markRun(children, column.builder.container.children, 0, column.targets, picked, column.rootId);
@@ -900,15 +924,8 @@ export class RewindSelectorComponent implements Component {
900
924
  // rows a matching two-column left gutter so blocks never shift while stepping.
901
925
  const children = this.#builder.container.children;
902
926
  const prepared = this.#browser.prepareOutline(children, this.#targets, this.#selected, contentWidth);
903
- this.#mainVisible = prepared.visible;
904
927
  this.#mainRows = prepared.childRows;
905
- if (prepared.selected !== this.#selected) {
906
- // The current target collapsed (e.g. expansion toggle): rest on the
907
- // nearest visible one and leave the strip.
908
- this.#selected = prepared.selected;
909
- this.#activeVariant = 0;
910
- this.#siblingSelected = 0;
911
- }
928
+ this.#adoptVisibility(prepared.visible);
912
929
 
913
930
  if (this.#filter !== undefined) return this.#filterFrame(this.#filter, prepared.childRows, contentWidth);
914
931