omnius 1.0.878 → 1.0.879

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.
@@ -2,12 +2,12 @@
2
2
  import { createWriteStream } from "node:fs";
3
3
  import { createInterface } from "node:readline";
4
4
  import { PassThrough } from "node:stream";
5
- import { CliRenderEvents as CliRenderEvents9, NativeSession, RGBA as RGBA3, createCliRenderer } from "@opentui/core";
5
+ import { CliRenderEvents as CliRenderEvents10, NativeSession, RGBA as RGBA3, createCliRenderer } from "@opentui/core";
6
6
 
7
7
  // packages/cli/src/tui/native/components/frontend.ts
8
8
  import {
9
9
  BoxRenderable as BoxRenderable11,
10
- CliRenderEvents as CliRenderEvents7,
10
+ CliRenderEvents as CliRenderEvents8,
11
11
  EmbeddedTerminalRenderable as EmbeddedTerminalRenderable2,
12
12
  ScrollBoxRenderable as ScrollBoxRenderable8,
13
13
  SyntaxStyle as SyntaxStyle2,
@@ -1396,6 +1396,7 @@ var NativeBlockList = class {
1396
1396
  // packages/cli/src/tui/native/components/modals.ts
1397
1397
  import {
1398
1398
  BoxRenderable as BoxRenderable4,
1399
+ CliRenderEvents as CliRenderEvents3,
1399
1400
  InputRenderable,
1400
1401
  ScrollBoxRenderable as ScrollBoxRenderable2,
1401
1402
  TextareaRenderable,
@@ -1715,6 +1716,123 @@ var NativeCornerFrame = class {
1715
1716
  };
1716
1717
  };
1717
1718
 
1719
+ // packages/cli/src/tui/terminal-cells.ts
1720
+ var graphemes = new Intl.Segmenter(void 0, { granularity: "grapheme" });
1721
+ var extendedPictographic = /\p{Extended_Pictographic}/u;
1722
+ var emojiPresentation = /\p{Emoji_Presentation}/u;
1723
+ var zeroWidthGrapheme = /^[\p{Mark}\p{Cf}\u200c\u200d\ufe0e\ufe0f]+$/u;
1724
+ function escapeEnd(value, start) {
1725
+ if (value[start] !== "\x1B") return start + 1;
1726
+ const kind = value[start + 1];
1727
+ if (kind === "[") {
1728
+ for (let i = start + 2; i < value.length; i++) {
1729
+ const code = value.charCodeAt(i);
1730
+ if (code >= 64 && code <= 126) return i + 1;
1731
+ }
1732
+ return value.length;
1733
+ }
1734
+ if (kind === "]") {
1735
+ for (let i = start + 2; i < value.length; i++) {
1736
+ if (value[i] === "\x07") return i + 1;
1737
+ if (value[i] === "\x1B" && value[i + 1] === "\\") return i + 2;
1738
+ }
1739
+ return value.length;
1740
+ }
1741
+ if (kind === "P" || kind === "X" || kind === "^" || kind === "_") {
1742
+ for (let i = start + 2; i < value.length; i++) {
1743
+ if (value[i] === "\x1B" && value[i + 1] === "\\") return i + 2;
1744
+ }
1745
+ return value.length;
1746
+ }
1747
+ return Math.min(value.length, start + 2);
1748
+ }
1749
+ function stripTerminalSequences(value) {
1750
+ let out = "";
1751
+ for (let i = 0; i < value.length; ) {
1752
+ if (value[i] === "\x1B") {
1753
+ i = escapeEnd(value, i);
1754
+ continue;
1755
+ }
1756
+ const code = value.charCodeAt(i);
1757
+ if (code >= 0 && code < 32 || code === 127) {
1758
+ i++;
1759
+ continue;
1760
+ }
1761
+ const cp = value.codePointAt(i);
1762
+ out += String.fromCodePoint(cp);
1763
+ i += cp > 65535 ? 2 : 1;
1764
+ }
1765
+ return out;
1766
+ }
1767
+ function normalizeTerminalLine(value) {
1768
+ return value.replace(/\r\n|\r|\n/g, " ").replace(/\t/g, " ");
1769
+ }
1770
+ function isWideCodePoint(cp) {
1771
+ return cp >= 4352 && cp <= 4447 || cp === 9001 || cp === 9002 || cp >= 11904 && cp <= 12350 || cp >= 12352 && cp <= 42191 || cp >= 44032 && cp <= 55203 || cp >= 63744 && cp <= 64255 || cp >= 65040 && cp <= 65049 || cp >= 65072 && cp <= 65135 || cp >= 65280 && cp <= 65376 || cp >= 65504 && cp <= 65510 || cp >= 110592 && cp <= 111359 || cp >= 131072 && cp <= 262141;
1772
+ }
1773
+ function graphemeCellWidth(value) {
1774
+ if (!value || zeroWidthGrapheme.test(value)) return 0;
1775
+ const cp = value.codePointAt(0);
1776
+ if (cp < 32 || cp >= 127 && cp < 160) return 0;
1777
+ if (emojiPresentation.test(value) || value.includes("️") || value.includes("‍") && extendedPictographic.test(value) || cp >= 127462 && cp <= 127487) {
1778
+ return 2;
1779
+ }
1780
+ return isWideCodePoint(cp) ? 2 : 1;
1781
+ }
1782
+ function plainGraphemes(value) {
1783
+ return Array.from(
1784
+ graphemes.segment(stripTerminalSequences(value)),
1785
+ (part) => part.segment
1786
+ );
1787
+ }
1788
+ function terminalCellWidth(value) {
1789
+ let width = 0;
1790
+ for (const grapheme of plainGraphemes(normalizeTerminalLine(value))) {
1791
+ width += graphemeCellWidth(grapheme);
1792
+ }
1793
+ return width;
1794
+ }
1795
+
1796
+ // packages/cli/src/tui/native/components/modal-fit.ts
1797
+ function nativeModalWidth(spec, columns) {
1798
+ const limit = Math.max(1, Math.min(spec.kind === "document" ? Infinity : 100, columns - (columns >= 12 ? 4 : 0)));
1799
+ const longest = (text) => {
1800
+ let width = 0;
1801
+ for (const line of text.split(/\r\n|\r|\n/)) {
1802
+ width = Math.max(width, terminalCellWidth(line));
1803
+ if (width >= limit) break;
1804
+ }
1805
+ return Math.min(limit, width);
1806
+ };
1807
+ const actions = (items = spec.actions) => (items ?? []).reduce((width, item, index) => width + longest(item.label) + (index ? 1 : 0), 0);
1808
+ const blockWidth = (block, depth) => {
1809
+ if (depth > 16) return 0;
1810
+ const children = (block.children ?? []).slice(0, 256).map((child) => blockWidth(child, depth + 1));
1811
+ return Math.min(limit, Math.max(
1812
+ longest(plainText(block.text)),
1813
+ longest(block.title ?? ""),
1814
+ actions(block.actions),
1815
+ block.kind === "row" ? children.reduce((total, width) => total + width + 1, 0) : Math.max(0, ...children)
1816
+ ) + (block.kind === "card" ? 2 : 0));
1817
+ };
1818
+ let content = Math.max(
1819
+ spec.kind === "input" || spec.kind === "progress" ? 28 : 18,
1820
+ longest(spec.title) + actions(spec.headerActions) + (spec.headerActions?.length ? 1 : 0),
1821
+ longest(plainText(spec.description)),
1822
+ longest((spec.breadcrumbs ?? []).join(" › ")),
1823
+ longest(spec.validationError ?? ""),
1824
+ longest(spec.placeholder ?? ""),
1825
+ actions()
1826
+ );
1827
+ for (const item of spec.items ?? []) content = Math.max(content, longest(plainText(item.label)) + 4, Math.min(72, longest(plainText(item.description))) + 4);
1828
+ for (const block of (spec.blocks ?? []).slice(0, 256)) content = Math.max(content, blockWidth(block, 0));
1829
+ return Math.min(limit, content + 5);
1830
+ }
1831
+ function nativeModalTextRows(value, width) {
1832
+ if (!value) return 0;
1833
+ return value.split(/\r\n|\r|\n/).reduce((rows, line) => rows + Math.max(1, Math.ceil(terminalCellWidth(line) / Math.max(1, width))), 0);
1834
+ }
1835
+
1718
1836
  // packages/cli/src/tui/native/components/modals.ts
1719
1837
  var NativeModalView = class {
1720
1838
  constructor(context, spec, close2) {
@@ -1747,7 +1865,7 @@ var NativeModalView = class {
1747
1865
  this.panel = new BoxRenderable4(renderer2, {
1748
1866
  id: `modal-panel:${spec.id}`,
1749
1867
  flexDirection: "column",
1750
- width: "94%",
1868
+ width: nativeModalWidth(spec, renderer2.terminalWidth),
1751
1869
  maxHeight: "94%",
1752
1870
  border: false,
1753
1871
  backgroundColor: colors.background,
@@ -1759,29 +1877,36 @@ var NativeModalView = class {
1759
1877
  });
1760
1878
  this.root.add(this.panel);
1761
1879
  this.frame = new NativeCornerFrame(this.panel, colors, renderer2, this.root);
1880
+ this.body = new ScrollBoxRenderable2(renderer2, {
1881
+ id: spec.kind === "document" ? `modal-document:${spec.id}` : `modal-body:${spec.id}`,
1882
+ width: "100%",
1883
+ height: 1,
1884
+ minHeight: 1,
1885
+ flexShrink: 0,
1886
+ scrollY: true,
1887
+ scrollX: false,
1888
+ viewportCulling: true,
1889
+ horizontalScrollbarOptions: { visible: false },
1890
+ contentOptions: { flexDirection: "column", minHeight: 0 }
1891
+ });
1892
+ this.panel.add(this.body);
1893
+ styleNativeScrollbars(this.body, colors);
1762
1894
  this.breadcrumb = this.addText(`modal-breadcrumb:${spec.id}`);
1763
1895
  const header = new BoxRenderable4(renderer2, { id: `modal-heading:${spec.id}`, width: "100%", flexDirection: "row", gap: 1, flexShrink: 0 });
1764
1896
  this.heading = new TextRenderable3(renderer2, { id: `modal-title:${spec.id}`, content: "", width: 0, flexGrow: 1, flexShrink: 1, wrapMode: "word", fg: colors.foreground });
1765
- this.headerActions = new BoxRenderable4(renderer2, { id: `modal-header-actions:${spec.id}`, flexDirection: "row", gap: 1, flexShrink: 0 });
1897
+ this.heading.maxHeight = 2;
1898
+ this.headerActions = new BoxRenderable4(renderer2, { id: `modal-header-actions:${spec.id}`, flexDirection: "row", gap: 1, flexShrink: 1, minWidth: 0, maxWidth: "60%" });
1766
1899
  header.add(this.heading);
1767
1900
  header.add(this.headerActions);
1768
- this.panel.add(header);
1901
+ this.panel.add(header, 0);
1769
1902
  this.description = this.addText(`modal-description:${spec.id}`);
1770
1903
  if (spec.kind === "select") this.buildSelect();
1771
1904
  else if (spec.kind === "input") this.buildInput();
1772
1905
  else if (spec.kind === "document") {
1773
- this.document = new ScrollBoxRenderable2(renderer2, {
1774
- id: `modal-document:${spec.id}`,
1775
- width: "100%",
1776
- height: Math.max(5, Math.floor(renderer2.terminalHeight * 0.6)),
1777
- scrollY: true,
1778
- scrollX: false,
1779
- viewportCulling: true,
1780
- contentOptions: { flexDirection: "column" }
1781
- });
1782
- styleNativeScrollbars(this.document, colors);
1783
- this.blocks = new NativeBlockList(this.modalContext(), this.document);
1784
- this.panel.add(this.document);
1906
+ this.document = this.body;
1907
+ const documentBlocks = new BoxRenderable4(renderer2, { id: `modal-document-blocks:${spec.id}`, width: "100%", flexDirection: "column", flexShrink: 0 });
1908
+ this.body.add(documentBlocks);
1909
+ this.blocks = new NativeBlockList(this.modalContext(), documentBlocks);
1785
1910
  } else {
1786
1911
  this.progress = new TextRenderable3(renderer2, {
1787
1912
  id: `modal-progress:${spec.id}`,
@@ -1794,16 +1919,21 @@ var NativeModalView = class {
1794
1919
  wrapMode: "none",
1795
1920
  selectable: false
1796
1921
  });
1797
- this.panel.add(this.progress);
1922
+ this.body.add(this.progress);
1798
1923
  const progressBlocks = new BoxRenderable4(renderer2, { id: `modal-progress-blocks:${spec.id}`, flexDirection: "column", width: "100%" });
1799
- this.panel.add(progressBlocks);
1924
+ this.body.add(progressBlocks);
1800
1925
  this.blocks = new NativeBlockList(this.modalContext(), progressBlocks);
1801
1926
  }
1802
1927
  this.error = this.addText(`modal-error:${spec.id}`);
1803
- this.hints = this.addText(`modal-hints:${spec.id}`);
1804
- this.actions = new BoxRenderable4(renderer2, { id: `modal-actions:${spec.id}`, flexDirection: "row", gap: 1, width: "100%", marginTop: 0 });
1928
+ this.hints = this.addText(`modal-hints:${spec.id}`, this.panel);
1929
+ this.hints.maxHeight = 2;
1930
+ this.actions = new BoxRenderable4(renderer2, { id: `modal-actions:${spec.id}`, flexDirection: "row", flexWrap: "wrap", flexShrink: 0, gap: 1, width: "100%", marginTop: 0 });
1805
1931
  this.panel.add(this.actions);
1806
1932
  this.update(spec);
1933
+ renderer2.on(CliRenderEvents3.RESIZE, this.fit);
1934
+ this.panel.on("resize", this.fit);
1935
+ for (const child of this.panel.getChildren()) child.on("resize", this.fit);
1936
+ this.body.content.on("resize", this.fit);
1807
1937
  }
1808
1938
  context;
1809
1939
  close;
@@ -1822,6 +1952,8 @@ var NativeModalView = class {
1822
1952
  editor;
1823
1953
  secretMask;
1824
1954
  select;
1955
+ choiceDescription;
1956
+ body;
1825
1957
  document;
1826
1958
  blocks;
1827
1959
  progress;
@@ -1830,9 +1962,14 @@ var NativeModalView = class {
1830
1962
  changing = false;
1831
1963
  actionsSignature = "";
1832
1964
  headerActionsSignature = "";
1833
- addText(id) {
1965
+ sizing = false;
1966
+ sizeSignature = "";
1967
+ optionSignature = "";
1968
+ disposed = false;
1969
+ revealFocused = false;
1970
+ addText(id, parent = this.body) {
1834
1971
  const text = new TextRenderable3(this.context.renderer, { id, content: "", width: "100%", wrapMode: "word", flexShrink: 0, fg: this.context.colors.foreground, selectable: true });
1835
- this.panel.add(text);
1972
+ parent.add(text);
1836
1973
  return text;
1837
1974
  }
1838
1975
  replaceActions(container, actions, prefix = "") {
@@ -1841,6 +1978,10 @@ var NativeModalView = class {
1841
1978
  const button = new TextRenderable3(this.context.renderer, {
1842
1979
  id: `modal-action:${this.spec.id}:${prefix}${action.id}`,
1843
1980
  content: plainText(action.label),
1981
+ minWidth: 0,
1982
+ flexShrink: 1,
1983
+ maxHeight: 2,
1984
+ wrapMode: "word",
1844
1985
  fg: this.context.colors.foreground,
1845
1986
  bg: this.context.colors.background,
1846
1987
  attributes: action.disabled ? 2 : action.active ? 1 : 0,
@@ -1882,7 +2023,7 @@ var NativeModalView = class {
1882
2023
  }
1883
2024
  });
1884
2025
  this.filter = this.spec.filter ?? "";
1885
- this.panel.add(this.filterInput);
2026
+ this.body.add(this.filterInput);
1886
2027
  this.select = new NativeSelectRenderable(renderer2, {
1887
2028
  id: `modal-select:${this.spec.id}`,
1888
2029
  width: "100%",
@@ -1901,10 +2042,16 @@ var NativeModalView = class {
1901
2042
  });
1902
2043
  this.select.onActivate = () => this.confirm();
1903
2044
  this.select.on("selectionChanged", () => {
1904
- if (!this.changing) this.emitAction("focus");
2045
+ if (!this.changing) {
2046
+ this.fit();
2047
+ this.body.scrollChildIntoView(this.select.id);
2048
+ this.emitAction("focus");
2049
+ }
1905
2050
  });
1906
2051
  this.select.on("itemSelected", () => this.confirm());
1907
- this.panel.add(this.select);
2052
+ this.body.add(this.select);
2053
+ this.choiceDescription = this.addText(`modal-choice-description:${this.spec.id}`);
2054
+ this.choiceDescription.visible = false;
1908
2055
  }
1909
2056
  buildInput() {
1910
2057
  const { renderer: renderer2, colors } = this.context;
@@ -1921,6 +2068,7 @@ var NativeModalView = class {
1921
2068
  onContentChange: () => {
1922
2069
  if (!this.editor || this.changing) return;
1923
2070
  this.refreshMask();
2071
+ if (!this.spec.secret) this.fit();
1924
2072
  if (!this.spec.secret) this.emitAction("change", this.editor.plainText);
1925
2073
  },
1926
2074
  onSubmit: () => this.confirm()
@@ -1935,8 +2083,11 @@ var NativeModalView = class {
1935
2083
  if (this.spec.secret) {
1936
2084
  this.secretMask = this.addText(`modal-secret:${this.spec.id}`);
1937
2085
  this.secretMask.selectable = false;
2086
+ this.secretMask.height = 1;
2087
+ this.secretMask.wrapMode = "none";
2088
+ this.secretMask.truncate = true;
1938
2089
  this.editor.showCursor = false;
1939
- } else this.panel.add(this.editor);
2090
+ } else this.body.add(this.editor);
1940
2091
  }
1941
2092
  refreshMask() {
1942
2093
  if (this.secretMask && this.editor) this.secretMask.content = "•".repeat(Array.from(this.editor.plainText).length);
@@ -1977,6 +2128,7 @@ var NativeModalView = class {
1977
2128
  } finally {
1978
2129
  this.changing = false;
1979
2130
  }
2131
+ this.fit();
1980
2132
  this.applyTheme();
1981
2133
  }
1982
2134
  refreshOptions(selectedIndex) {
@@ -1990,12 +2142,88 @@ var NativeModalView = class {
1990
2142
  value: { key: item.key, index, disabled: item.disabled || item.heading }
1991
2143
  }));
1992
2144
  this.select.showDescription = this.filtered.some(({ item }) => Boolean(plainText(item.description)));
1993
- const rowCount = this.select.showDescription ? 2 : 1;
1994
- this.select.height = Math.max(1, Math.min(Math.max(3, Math.floor(this.context.renderer.terminalHeight * 0.55)), this.filtered.length * rowCount));
2145
+ this.optionSignature = "";
1995
2146
  const preferred = selectedIndex !== void 0 ? this.filtered.findIndex((entry) => entry.index === selectedIndex) : this.filtered.findIndex((entry) => entry.item.key === previousKey);
1996
2147
  const firstEnabled = this.filtered.findIndex((entry) => !entry.item.disabled && !entry.item.heading);
1997
2148
  this.select.setSelectedIndex(preferred >= 0 && !this.filtered[preferred].item.disabled && !this.filtered[preferred].item.heading ? preferred : Math.max(0, firstEnabled));
1998
- }
2149
+ this.fit();
2150
+ }
2151
+ /** Geometry writes are idempotent: native resize observes wrapping without
2152
+ * creating a render loop through unchanged flex/min/max setters. */
2153
+ fit = () => {
2154
+ if (this.disposed || this.sizing || !this.actions) return;
2155
+ this.sizing = true;
2156
+ const wasChanging = this.changing;
2157
+ this.changing = true;
2158
+ try {
2159
+ const renderer2 = this.context.renderer, spec = this.spec;
2160
+ const width = nativeModalWidth({ ...spec, validationError: this.error.visible ? this.error.plainText : void 0 }, renderer2.terminalWidth), padding = width >= 8 ? 2 : 0;
2161
+ const inner = Math.max(1, width - padding * 2), bodyWidth = Math.max(1, inner - 1), maximum = Math.max(1, renderer2.terminalHeight - (renderer2.terminalHeight >= 8 ? 2 : 0));
2162
+ const signature = `${width}:${maximum}:${padding}`;
2163
+ if (signature !== this.sizeSignature) {
2164
+ this.sizeSignature = signature;
2165
+ this.panel.width = width;
2166
+ this.panel.maxHeight = maximum;
2167
+ this.panel.paddingX = padding;
2168
+ }
2169
+ if (this.select) {
2170
+ const descriptions = this.filtered.map(({ item }) => plainText(item.description));
2171
+ const long = descriptions.map((value) => /[\r\n]/.test(value) || nativeModalTextRows(value, Math.max(1, bodyWidth - 3)) > 1);
2172
+ const options = this.filtered.map(({ item, index: index2 }, position) => ({
2173
+ name: `${item.heading ? "── " : ""}${plainText(item.label)}`,
2174
+ description: long[position] ? "" : descriptions[position],
2175
+ value: { key: item.key, index: index2, disabled: item.disabled || item.heading }
2176
+ }));
2177
+ const optionSignature = JSON.stringify(options);
2178
+ if (optionSignature !== this.optionSignature) {
2179
+ this.optionSignature = optionSignature;
2180
+ const index2 = this.select.getSelectedIndex();
2181
+ this.select.options = options;
2182
+ this.select.setSelectedIndex(index2);
2183
+ }
2184
+ const show = options.some((item) => Boolean(item.description));
2185
+ if (this.select.showDescription !== show) this.select.showDescription = show;
2186
+ const index = this.select.getSelectedIndex(), detail = long[index] ? descriptions[index] ?? "" : "";
2187
+ if (this.choiceDescription.plainText !== detail) this.choiceDescription.content = detail;
2188
+ this.choiceDescription.visible = Boolean(detail);
2189
+ }
2190
+ const textRows = (node, text, availableWidth = bodyWidth) => node.visible ? node.width === availableWidth && node.height > 0 ? node.height : nativeModalTextRows(text, availableWidth) : 0;
2191
+ const headerActions = (spec.headerActions ?? []).reduce((total, action) => total + terminalCellWidth(plainText(action.label)) + 1, 0);
2192
+ const headingRows = Math.min(2, nativeModalTextRows(spec.title, Math.max(1, inner - Math.min(Math.floor(inner * 0.6), headerActions))));
2193
+ let fallbackActions = 0, rowWidth = 0, rowHeight = 0;
2194
+ for (const action of spec.actions ?? []) {
2195
+ const cells = Math.max(1, terminalCellWidth(plainText(action.label))), childWidth = Math.min(inner, cells);
2196
+ if (rowWidth && rowWidth + 1 + childWidth > inner) {
2197
+ fallbackActions += rowHeight + 1;
2198
+ rowWidth = 0;
2199
+ rowHeight = 0;
2200
+ }
2201
+ rowWidth += (rowWidth ? 1 : 0) + childWidth;
2202
+ rowHeight = Math.max(rowHeight, Math.min(2, Math.ceil(cells / inner)));
2203
+ }
2204
+ fallbackActions += rowHeight;
2205
+ const actionRows = this.actions.visible && this.actions.width === inner && this.actions.height > 0 ? this.actions.height : fallbackActions;
2206
+ const header = this.heading.parent;
2207
+ const headerRows = header.width === inner && header.height > 0 ? header.height : Math.max(1, headingRows);
2208
+ const chrome = 2 + headerRows + Math.min(2, textRows(this.hints, this.hints.plainText, inner)) + actionRows;
2209
+ const available = Math.max(1, maximum - chrome);
2210
+ if (this.select) {
2211
+ const details = textRows(this.breadcrumb, (spec.breadcrumbs ?? []).join(" › ")) + textRows(this.description, plainText(spec.description)) + textRows(this.error, spec.validationError ?? "") + textRows(this.choiceDescription, this.choiceDescription.plainText) + 1;
2212
+ this.select.height = Math.max(1, Math.min(Math.max(1, available - Math.min(details, Math.floor(available / 2))), this.filtered.length * (this.select.showDescription ? 2 : 1)));
2213
+ } else if (this.editor && spec.multiline && !spec.secret) {
2214
+ this.editor.height = Math.max(1, Math.min(available, Math.max(3, nativeModalTextRows(this.editor.plainText, bodyWidth))));
2215
+ }
2216
+ this.body.height = Math.max(1, Math.min(available, this.body.content.height || 1));
2217
+ const focused = renderer2.currentFocusedRenderable;
2218
+ if (this.revealFocused && focused && focused.width > 0 && this.body.viewport.width > 0) {
2219
+ this.revealFocused = false;
2220
+ this.body.scrollChildIntoView(focused.id);
2221
+ }
2222
+ } finally {
2223
+ this.changing = wasChanging;
2224
+ this.sizing = false;
2225
+ }
2226
+ };
1999
2227
  current() {
2000
2228
  return this.filtered[this.select?.getSelectedIndex() ?? -1];
2001
2229
  }
@@ -2036,10 +2264,12 @@ var NativeModalView = class {
2036
2264
  }
2037
2265
  focus() {
2038
2266
  if (this.frame.closing) return;
2267
+ this.revealFocused = true;
2039
2268
  if (this.filterInput) this.filterInput.focus();
2040
2269
  else if (this.editor && !this.spec.secret) this.editor.focus();
2041
2270
  else if (this.document) this.document.focus();
2042
2271
  else this.panel.focus();
2272
+ this.fit();
2043
2273
  }
2044
2274
  nextControl(direction) {
2045
2275
  const controls = [];
@@ -2057,7 +2287,7 @@ var NativeModalView = class {
2057
2287
  const current = focused ? controls.indexOf(focused) : -1;
2058
2288
  const next = controls[current < 0 ? direction > 0 ? 0 : controls.length - 1 : (current + direction + controls.length) % controls.length];
2059
2289
  next.focus();
2060
- this.document?.scrollChildIntoView(next.id);
2290
+ this.body.scrollChildIntoView(next.id);
2061
2291
  }
2062
2292
  handleKey(key) {
2063
2293
  const consume = () => {
@@ -2123,8 +2353,8 @@ var NativeModalView = class {
2123
2353
  this.confirm();
2124
2354
  return consume();
2125
2355
  }
2126
- if (this.document && (key.name === "pageup" || key.name === "pagedown" || key.name === "up" || key.name === "down")) {
2127
- this.document.scrollBy(key.name === "up" ? -1 : key.name === "down" ? 1 : key.name === "pageup" ? -1 : 1, key.name.startsWith("page") ? "viewport" : "absolute");
2356
+ if (key.name === "pageup" || key.name === "pagedown" || key.name === "up" || key.name === "down") {
2357
+ this.body.scrollBy(key.name === "up" ? -1 : key.name === "down" ? 1 : key.name === "pageup" ? -1 : 1, key.name.startsWith("page") ? "viewport" : "absolute");
2128
2358
  return consume();
2129
2359
  }
2130
2360
  if (key.sequence.length === 1 && !key.ctrl && !key.meta) {
@@ -2159,6 +2389,7 @@ var NativeModalView = class {
2159
2389
  if (Buffer.byteLength(text, "utf8") + Buffer.byteLength(current, "utf8") <= budget) return false;
2160
2390
  this.error.content = `Input exceeds the ${budget.toLocaleString()} byte limit.`;
2161
2391
  this.error.visible = true;
2392
+ this.fit();
2162
2393
  return true;
2163
2394
  }
2164
2395
  applyTheme() {
@@ -2168,6 +2399,7 @@ var NativeModalView = class {
2168
2399
  this.heading.fg = colors.foreground;
2169
2400
  this.heading.attributes = 1;
2170
2401
  this.description.fg = colors.foreground;
2402
+ if (this.choiceDescription) this.choiceDescription.fg = colors.dim;
2171
2403
  this.breadcrumb.fg = colors.dim;
2172
2404
  this.breadcrumb.attributes = 2;
2173
2405
  this.error.fg = colors.foreground;
@@ -2178,7 +2410,7 @@ var NativeModalView = class {
2178
2410
  this.progress.fg = colors.foreground;
2179
2411
  this.progress.bg = colors.background;
2180
2412
  }
2181
- if (this.document) styleNativeScrollbars(this.document, colors);
2413
+ styleNativeScrollbars(this.body, colors);
2182
2414
  if (this.select) {
2183
2415
  this.select.backgroundColor = this.select.focusedBackgroundColor = colors.background;
2184
2416
  this.select.textColor = this.select.focusedTextColor = colors.foreground;
@@ -2203,6 +2435,8 @@ var NativeModalView = class {
2203
2435
  this.blocks?.applyTheme();
2204
2436
  }
2205
2437
  destroy() {
2438
+ this.disposed = true;
2439
+ this.context.renderer.off(CliRenderEvents3.RESIZE, this.fit);
2206
2440
  this.frame.destroy();
2207
2441
  this.blocks?.destroy();
2208
2442
  if (this.spec.secret) this.editor?.destroy();
@@ -2343,90 +2577,13 @@ function deviceHistoryGraph(samples, field, width) {
2343
2577
  // packages/cli/src/tui/native/components/task-notes.ts
2344
2578
  import {
2345
2579
  BoxRenderable as BoxRenderable5,
2346
- CliRenderEvents as CliRenderEvents3,
2580
+ CliRenderEvents as CliRenderEvents4,
2347
2581
  RenderableEvents as RenderableEvents3,
2348
2582
  ScrollBoxRenderable as ScrollBoxRenderable3,
2349
2583
  TextAttributes as TextAttributes5,
2350
2584
  TextRenderable as TextRenderable4
2351
2585
  } from "@opentui/core";
2352
2586
 
2353
- // packages/cli/src/tui/terminal-cells.ts
2354
- var graphemes = new Intl.Segmenter(void 0, { granularity: "grapheme" });
2355
- var extendedPictographic = /\p{Extended_Pictographic}/u;
2356
- var emojiPresentation = /\p{Emoji_Presentation}/u;
2357
- var zeroWidthGrapheme = /^[\p{Mark}\p{Cf}\u200c\u200d\ufe0e\ufe0f]+$/u;
2358
- function escapeEnd(value, start) {
2359
- if (value[start] !== "\x1B") return start + 1;
2360
- const kind = value[start + 1];
2361
- if (kind === "[") {
2362
- for (let i = start + 2; i < value.length; i++) {
2363
- const code = value.charCodeAt(i);
2364
- if (code >= 64 && code <= 126) return i + 1;
2365
- }
2366
- return value.length;
2367
- }
2368
- if (kind === "]") {
2369
- for (let i = start + 2; i < value.length; i++) {
2370
- if (value[i] === "\x07") return i + 1;
2371
- if (value[i] === "\x1B" && value[i + 1] === "\\") return i + 2;
2372
- }
2373
- return value.length;
2374
- }
2375
- if (kind === "P" || kind === "X" || kind === "^" || kind === "_") {
2376
- for (let i = start + 2; i < value.length; i++) {
2377
- if (value[i] === "\x1B" && value[i + 1] === "\\") return i + 2;
2378
- }
2379
- return value.length;
2380
- }
2381
- return Math.min(value.length, start + 2);
2382
- }
2383
- function stripTerminalSequences(value) {
2384
- let out = "";
2385
- for (let i = 0; i < value.length; ) {
2386
- if (value[i] === "\x1B") {
2387
- i = escapeEnd(value, i);
2388
- continue;
2389
- }
2390
- const code = value.charCodeAt(i);
2391
- if (code >= 0 && code < 32 || code === 127) {
2392
- i++;
2393
- continue;
2394
- }
2395
- const cp = value.codePointAt(i);
2396
- out += String.fromCodePoint(cp);
2397
- i += cp > 65535 ? 2 : 1;
2398
- }
2399
- return out;
2400
- }
2401
- function normalizeTerminalLine(value) {
2402
- return value.replace(/\r\n|\r|\n/g, " ").replace(/\t/g, " ");
2403
- }
2404
- function isWideCodePoint(cp) {
2405
- return cp >= 4352 && cp <= 4447 || cp === 9001 || cp === 9002 || cp >= 11904 && cp <= 12350 || cp >= 12352 && cp <= 42191 || cp >= 44032 && cp <= 55203 || cp >= 63744 && cp <= 64255 || cp >= 65040 && cp <= 65049 || cp >= 65072 && cp <= 65135 || cp >= 65280 && cp <= 65376 || cp >= 65504 && cp <= 65510 || cp >= 110592 && cp <= 111359 || cp >= 131072 && cp <= 262141;
2406
- }
2407
- function graphemeCellWidth(value) {
2408
- if (!value || zeroWidthGrapheme.test(value)) return 0;
2409
- const cp = value.codePointAt(0);
2410
- if (cp < 32 || cp >= 127 && cp < 160) return 0;
2411
- if (emojiPresentation.test(value) || value.includes("️") || value.includes("‍") && extendedPictographic.test(value) || cp >= 127462 && cp <= 127487) {
2412
- return 2;
2413
- }
2414
- return isWideCodePoint(cp) ? 2 : 1;
2415
- }
2416
- function plainGraphemes(value) {
2417
- return Array.from(
2418
- graphemes.segment(stripTerminalSequences(value)),
2419
- (part) => part.segment
2420
- );
2421
- }
2422
- function terminalCellWidth(value) {
2423
- let width = 0;
2424
- for (const grapheme of plainGraphemes(normalizeTerminalLine(value))) {
2425
- width += graphemeCellWidth(grapheme);
2426
- }
2427
- return width;
2428
- }
2429
-
2430
2587
  // packages/cli/src/tui/native/components/instrument-format.ts
2431
2588
  var graphemes2 = new Intl.Segmenter(void 0, { granularity: "grapheme" });
2432
2589
  function formatSignalDivider(width, geometry = { direction: "top" }) {
@@ -2511,7 +2668,7 @@ var ActiveBlocks = class {
2511
2668
  this.renderer = renderer2;
2512
2669
  this.active = active;
2513
2670
  this.paint = paint;
2514
- renderer2.on(CliRenderEvents3.FRAME, this.sync);
2671
+ renderer2.on(CliRenderEvents4.FRAME, this.sync);
2515
2672
  }
2516
2673
  renderer;
2517
2674
  active;
@@ -2542,7 +2699,7 @@ var ActiveBlocks = class {
2542
2699
  this.destroyed = true;
2543
2700
  if (this.timer) clearInterval(this.timer);
2544
2701
  this.timer = void 0;
2545
- this.renderer.off(CliRenderEvents3.FRAME, this.sync);
2702
+ this.renderer.off(CliRenderEvents4.FRAME, this.sync);
2546
2703
  }
2547
2704
  };
2548
2705
  var Disclosure = class extends TextRenderable4 {
@@ -2676,7 +2833,7 @@ var NativeTaskNotesView = class extends BoxRenderable5 {
2676
2833
  this.frame = mark;
2677
2834
  for (const row of this.rows.values()) if (row.item.status === "in_progress" && visible(row.title, renderer2)) this.refreshRow(row);
2678
2835
  });
2679
- renderer2.on(CliRenderEvents3.FRAME, this.finishInspect);
2836
+ renderer2.on(CliRenderEvents4.FRAME, this.finishInspect);
2680
2837
  }
2681
2838
  renderer;
2682
2839
  colors;
@@ -2921,7 +3078,7 @@ var NativeTaskNotesView = class extends BoxRenderable5 {
2921
3078
  }
2922
3079
  destroySelf() {
2923
3080
  this.animation.destroy();
2924
- this.renderer.off(CliRenderEvents3.FRAME, this.finishInspect);
3081
+ this.renderer.off(CliRenderEvents4.FRAME, this.finishInspect);
2925
3082
  super.destroySelf();
2926
3083
  }
2927
3084
  };
@@ -3124,7 +3281,7 @@ var NativeTaskStrip = class extends BoxRenderable5 {
3124
3281
  // packages/cli/src/tui/native/components/work-queue.ts
3125
3282
  import {
3126
3283
  BoxRenderable as BoxRenderable6,
3127
- CliRenderEvents as CliRenderEvents4,
3284
+ CliRenderEvents as CliRenderEvents5,
3128
3285
  RenderableEvents as RenderableEvents4,
3129
3286
  ScrollBoxRenderable as ScrollBoxRenderable4,
3130
3287
  TextAttributes as TextAttributes6,
@@ -3242,7 +3399,7 @@ var NativeWorkQueueView = class extends BoxRenderable6 {
3242
3399
  this.body.add(this.empty);
3243
3400
  this.omitted = this.text("omnius-work-queue:omitted", TextAttributes6.DIM);
3244
3401
  this.body.add(this.omitted);
3245
- renderer2.on(CliRenderEvents4.FRAME, this.syncAnimation);
3402
+ renderer2.on(CliRenderEvents5.FRAME, this.syncAnimation);
3246
3403
  }
3247
3404
  renderer;
3248
3405
  colors;
@@ -3471,7 +3628,7 @@ RUN ${label(receipt.runId)}` : ""}`)
3471
3628
  this.disposed = true;
3472
3629
  if (this.timer) clearInterval(this.timer);
3473
3630
  this.timer = void 0;
3474
- this.renderer.off(CliRenderEvents4.FRAME, this.syncAnimation);
3631
+ this.renderer.off(CliRenderEvents5.FRAME, this.syncAnimation);
3475
3632
  super.destroySelf();
3476
3633
  }
3477
3634
  };
@@ -3558,7 +3715,7 @@ var NativeWorkspaceFilesView = class extends BoxRenderable7 {
3558
3715
  this.reveal = reveal;
3559
3716
  this.header = new FileControl(renderer2, "omnius-workspace-files:heading", colors, () => this.setOpen(!this.open), true);
3560
3717
  this.add(this.header);
3561
- this.body = new BoxRenderable7(renderer2, { id: "omnius-workspace-files:body", width: "100%", minWidth: 0, flexDirection: "column", flexShrink: 0, visible: false });
3718
+ this.body = new BoxRenderable7(renderer2, { id: "omnius-workspace-files:body", width: "100%", minWidth: 0, minHeight: 0, flexDirection: "column", flexGrow: 1, flexBasis: 0, flexShrink: 1, visible: false });
3562
3719
  this.add(this.body);
3563
3720
  const heading = new BoxRenderable7(renderer2, { id: "omnius-workspace-files:location", width: "100%", minWidth: 0, height: 1, flexDirection: "row", gap: 1, flexShrink: 0 });
3564
3721
  this.body.add(heading);
@@ -3581,14 +3738,17 @@ var NativeWorkspaceFilesView = class extends BoxRenderable7 {
3581
3738
  id: "omnius-workspace-files:scroll",
3582
3739
  width: "100%",
3583
3740
  minWidth: 0,
3584
- height: 1,
3585
- flexShrink: 0,
3741
+ height: "auto",
3742
+ minHeight: 1,
3743
+ flexGrow: 1,
3744
+ flexBasis: 0,
3745
+ flexShrink: 1,
3586
3746
  scrollY: true,
3587
3747
  scrollX: false,
3588
3748
  viewportCulling: true,
3589
3749
  horizontalScrollbarOptions: { visible: false },
3590
3750
  verticalScrollbarOptions: { showArrows: false },
3591
- contentOptions: { flexDirection: "column" }
3751
+ contentOptions: { flexDirection: "column", paddingRight: 1 }
3592
3752
  });
3593
3753
  this.body.add(this.scroll);
3594
3754
  this.rootPages = new BoxRenderable7(renderer2, { id: "omnius-workspace-files:pages", width: "100%", height: 1, flexDirection: "row", gap: 2, flexShrink: 0 });
@@ -3637,6 +3797,8 @@ var NativeWorkspaceFilesView = class extends BoxRenderable7 {
3637
3797
  selectedPath;
3638
3798
  lastClick;
3639
3799
  restoredTop;
3800
+ layoutMinimum = 0;
3801
+ layoutExpanded = false;
3640
3802
  text(id) {
3641
3803
  return new TextRenderable6(this.renderer, { id, content: "", width: "100%", minWidth: 0, flexShrink: 0, wrapMode: "word", fg: this.colors.foreground, bg: this.colors.background });
3642
3804
  }
@@ -3715,8 +3877,7 @@ var NativeWorkspaceFilesView = class extends BoxRenderable7 {
3715
3877
  page = { root, start, next };
3716
3878
  this.pagination.set(item.path, page);
3717
3879
  for (const target of [start, next]) target.on(RenderableEvents5.FOCUSED, () => {
3718
- this.scroll.scrollChildIntoView(root.id);
3719
- this.reveal?.(this.scroll);
3880
+ this.revealRow(root);
3720
3881
  });
3721
3882
  }
3722
3883
  page.start.visible = (item.page ?? 0) > 0;
@@ -3742,7 +3903,6 @@ var NativeWorkspaceFilesView = class extends BoxRenderable7 {
3742
3903
  this.rootNext.visible = state.hasMore === true;
3743
3904
  this.rootNext.focusable = this.rootNext.visible;
3744
3905
  this.rootPages.visible = this.rootStart.visible || this.rootNext.visible;
3745
- this.scroll.height = Math.max(1, Math.min(10, position));
3746
3906
  this.applyPresentation();
3747
3907
  this.updateDetail();
3748
3908
  if (this.restoredTop !== void 0) {
@@ -3790,8 +3950,7 @@ var NativeWorkspaceFilesView = class extends BoxRenderable7 {
3790
3950
  const row = { root, disclosure, name, item };
3791
3951
  name.on(RenderableEvents5.FOCUSED, () => {
3792
3952
  this.select(item.path);
3793
- this.scroll.scrollChildIntoView(root.id);
3794
- this.reveal?.(this.scroll);
3953
+ this.revealRow(root);
3795
3954
  this.updateRow(row);
3796
3955
  });
3797
3956
  name.on(RenderableEvents5.BLURRED, () => {
@@ -3801,6 +3960,19 @@ var NativeWorkspaceFilesView = class extends BoxRenderable7 {
3801
3960
  root.add(name);
3802
3961
  return row;
3803
3962
  }
3963
+ revealRow(target) {
3964
+ const viewport = this.scroll.viewport;
3965
+ if (target.y < viewport.y) this.scroll.scrollBy(target.y - viewport.y);
3966
+ else if (target.y + target.height > viewport.y + viewport.height) this.scroll.scrollBy(target.y + target.height - viewport.y - viewport.height);
3967
+ this.reveal?.(target);
3968
+ }
3969
+ revealFocused() {
3970
+ const focused = this.renderer.currentFocusedRenderable;
3971
+ for (let node = focused; node; node = node.parent) if (node === this.scroll) {
3972
+ this.revealRow(focused);
3973
+ break;
3974
+ }
3975
+ }
3804
3976
  updateRow(row) {
3805
3977
  const marker = activityLabel(row.item), text = label2(row.item.name) + (row.item.kind === "directory" ? "/" : "");
3806
3978
  const content = marker ? `${marker} ${text}` : text;
@@ -3861,9 +4033,30 @@ var NativeWorkspaceFilesView = class extends BoxRenderable7 {
3861
4033
  this.action("visibility", "");
3862
4034
  if (open) this.reveal?.(this.header);
3863
4035
  }
4036
+ get isOpen() {
4037
+ return this.open;
4038
+ }
4039
+ /** Natural chrome plus one usable tree row; independent of directory size. */
4040
+ get minimumRows() {
4041
+ if (!this.visible) return 0;
4042
+ if (!this.open) return 1;
4043
+ return 2 + this.body.getChildren().filter((node) => node !== this.scroll && node.visible).reduce((rows, node) => rows + Math.max(1, node.height), 0);
4044
+ }
4045
+ syncLayoutMinimum() {
4046
+ const minimum = this.minimumRows;
4047
+ if (minimum === this.layoutMinimum) return;
4048
+ this.layoutMinimum = minimum;
4049
+ this.minHeight = minimum;
4050
+ }
3864
4051
  applyPresentation() {
3865
4052
  this.header.setLabel("FILES / FOLDERS", this.open);
3866
4053
  this.body.visible = this.open;
4054
+ if (this.layoutExpanded !== this.open) {
4055
+ this.layoutExpanded = this.open;
4056
+ this.flexGrow = this.open ? 1 : 0;
4057
+ this.flexBasis = this.open ? 0 : "auto";
4058
+ }
4059
+ this.syncLayoutMinimum();
3867
4060
  }
3868
4061
  capturePresentation() {
3869
4062
  return { open: this.open, scrollTop: this.scroll.scrollTop, ...this.state ? { projectRoot: this.state.projectRoot, sessionId: this.state.sessionId, selectedPath: this.selectedPath, expandedPaths: (this.state.expandedPaths ?? this.state.entries.filter((item) => item.expanded).map((item) => item.path)).slice(0, 256) } : {} };
@@ -4160,7 +4353,7 @@ var InstrumentDivider = class extends TextRenderable7 {
4160
4353
  };
4161
4354
  var DeviceMonitor = class extends BoxRenderable8 {
4162
4355
  constructor(renderer2, action) {
4163
- super(renderer2, { id: "omnius-devices", width: "100%", height: 5, minHeight: 5, maxHeight: "60%", flexShrink: 1, flexDirection: "column", visible: false });
4356
+ super(renderer2, { id: "omnius-devices", width: "100%", height: 5, minHeight: 3, flexShrink: 0, flexDirection: "column", visible: false });
4164
4357
  this.action = action;
4165
4358
  this.heading = new SectionBanner(renderer2, "DEVICES", "omnius-devices:heading", () => this.toggle());
4166
4359
  this.heading.update("DEVICES", false);
@@ -4222,6 +4415,9 @@ var DeviceMonitor = class extends BoxRenderable8 {
4222
4415
  sectionState = { cpu: false, memory: false, disk: false, network: false, gpu: true };
4223
4416
  sections = /* @__PURE__ */ new Map();
4224
4417
  availableRows = 100;
4418
+ railRows = 102;
4419
+ reservedUpperRows = 2;
4420
+ allocation = "";
4225
4421
  snapshot;
4226
4422
  historySource = "";
4227
4423
  rows = /* @__PURE__ */ new Map();
@@ -4241,7 +4437,7 @@ var DeviceMonitor = class extends BoxRenderable8 {
4241
4437
  }
4242
4438
  });
4243
4439
  const header = new SectionBanner(renderer2, title, `${root.id}:heading`, () => this.toggleDeviceSection(key));
4244
- header.on(RenderableEvents6.FOCUSED, () => this.scroll.scrollChildIntoView(header.id));
4440
+ header.on(RenderableEvents6.FOCUSED, () => this.revealReading(header));
4245
4441
  const body = new BoxRenderable8(renderer2, { id: `${root.id}:body`, width: "100%", minWidth: 0, flexShrink: 0, flexDirection: "column", visible: this.sectionState[key] });
4246
4442
  root.add(header);
4247
4443
  root.add(body);
@@ -4412,7 +4608,7 @@ var DeviceMonitor = class extends BoxRenderable8 {
4412
4608
  if (current && this.canSelectDevice(current)) this.action?.(`/gpu select ${current.uuid}`);
4413
4609
  });
4414
4610
  title.width = "100%";
4415
- title.on(RenderableEvents6.FOCUSED, () => this.scroll.scrollChildIntoView(root.id));
4611
+ title.on(RenderableEvents6.FOCUSED, () => this.revealReading(title));
4416
4612
  const load = new TelemetryRow(this._ctx, "LOAD", `${root.id}:load`, 16);
4417
4613
  const memory = new TelemetryRow(this._ctx, "VRAM", `${root.id}:memory`, 16);
4418
4614
  const plot = (name) => new TextRenderable7(this._ctx, { id: `${root.id}:${name}-history`, content: "", width: "100%", height: 3, flexShrink: 0, fg: foreground, bg: background, wrapMode: "none", truncate: true, selectable: false });
@@ -4467,24 +4663,45 @@ var DeviceMonitor = class extends BoxRenderable8 {
4467
4663
  this.expanded = !this.expanded;
4468
4664
  this.scroll.visible = this.expanded;
4469
4665
  this.scroll.minHeight = this.expanded ? 1 : 0;
4470
- this.fitHeight(this.availableRows + 2);
4666
+ this.fitHeight(this.railRows, this.reservedUpperRows);
4471
4667
  this.refresh();
4472
4668
  }
4669
+ revealReading(target) {
4670
+ const viewport = this.scroll.viewport;
4671
+ if (target.y < viewport.y) this.scroll.scrollBy(target.y - viewport.y);
4672
+ else if (target.y + target.height > viewport.y + viewport.height) this.scroll.scrollBy(target.y + target.height - viewport.y - viewport.height);
4673
+ }
4674
+ revealFocused() {
4675
+ const focused = this._ctx.currentFocusedRenderable;
4676
+ for (let node = focused; node; node = node.parent) if (node === this.scroll) {
4677
+ this.revealReading(focused);
4678
+ break;
4679
+ }
4680
+ }
4473
4681
  get isExpanded() {
4474
4682
  return this.expanded;
4475
4683
  }
4476
4684
  setExpanded(expanded) {
4477
4685
  if (this.expanded !== expanded) this.toggle();
4478
4686
  }
4479
- fitHeight(railRows) {
4480
- this.availableRows = Math.max(0, railRows - 2);
4687
+ fitHeight(railRows, reservedUpperRows = this.reservedUpperRows) {
4688
+ this.railRows = railRows;
4689
+ this.reservedUpperRows = reservedUpperRows;
4690
+ this.availableRows = Math.max(0, railRows - reservedUpperRows);
4481
4691
  this.visible = Boolean(this.snapshot) && this.availableRows >= 3;
4482
- this.minHeight = Math.min(5, this.availableRows);
4483
- this.maxHeight = this.availableRows;
4484
4692
  this.summary.visible = this.availableRows >= 5;
4485
4693
  this.compactLoad.visible = !this.expanded;
4486
4694
  this.compactMemory.visible = !this.expanded && this.availableRows >= 4;
4487
- this.height = Math.min(this.availableRows, this.expanded ? Math.min(18, Math.max(6, Math.floor(railRows * 0.6))) : 5);
4695
+ const chrome = this.getChildren().filter((node) => node !== this.scroll && node.visible).reduce((rows, node) => rows + Math.max(1, node.height), 0);
4696
+ const content = this.scroll.getChildren().filter((node) => node.visible).reduce((rows, node) => rows + Math.max(1, node.height), 0);
4697
+ const demand = this.expanded ? chrome + Math.max(1, content) : 5;
4698
+ const height = Math.min(this.availableRows, demand), minimum = Math.min(this.expanded ? 3 : 5, this.availableRows);
4699
+ const signature = `${height}:${minimum}:${this.availableRows}`;
4700
+ if (signature === this.allocation) return;
4701
+ this.allocation = signature;
4702
+ this.minHeight = minimum;
4703
+ this.maxHeight = this.availableRows;
4704
+ this.height = height;
4488
4705
  }
4489
4706
  onResize(width, height) {
4490
4707
  super.onResize(width, height);
@@ -4657,9 +4874,11 @@ var InstrumentRail = class extends BoxRenderable8 {
4657
4874
  routeAttemptsHeader;
4658
4875
  routeAttempts;
4659
4876
  routeAttemptRows = [];
4877
+ layoutSignature = "";
4878
+ contentMinimum = 0;
4660
4879
  onResize(width, height) {
4661
4880
  super.onResize(width, height);
4662
- this.devices?.fitHeight(height);
4881
+ this.reconcileLayout();
4663
4882
  }
4664
4883
  constructor(renderer2, options = {}) {
4665
4884
  const id = options.id ?? "omnius-instruments";
@@ -4822,6 +5041,7 @@ var InstrumentRail = class extends BoxRenderable8 {
4822
5041
  this.scroll.add(this.workspaceFiles);
4823
5042
  this.devices = new DeviceMonitor(renderer2, options.onAction);
4824
5043
  this.add(this.devices);
5044
+ this.renderAfter = this.reconcileLayout;
4825
5045
  if (options.colors) this.applyTheme(options.colors);
4826
5046
  }
4827
5047
  update(state) {
@@ -4990,6 +5210,37 @@ var InstrumentRail = class extends BoxRenderable8 {
4990
5210
  target.focus();
4991
5211
  return true;
4992
5212
  }
5213
+ /** Read the small retained section list after native layout. Only changed
5214
+ * budgets stage styles, so a settled sidebar does not schedule idle frames. */
5215
+ reconcileLayout = () => {
5216
+ if (this.isDestroyed || !this.visible || !this.scroll || !this.devices || !this.workspaceFiles) return;
5217
+ this.workspaceFiles.syncLayoutMinimum();
5218
+ const upper = this.scroll.getChildren().filter((node) => node !== this.workspaceFiles && node.visible).reduce((rows, node) => rows + node.height, 0) + this.workspaceFiles.minimumRows;
5219
+ const contentMinimum = Math.max(this.scroll.viewport.height, upper);
5220
+ if (contentMinimum !== this.contentMinimum) {
5221
+ this.contentMinimum = contentMinimum;
5222
+ this.scroll.content.minHeight = contentMinimum;
5223
+ }
5224
+ const border = this.border === true ? 2 : Array.isArray(this.border) ? Number(this.border.includes("top")) + Number(this.border.includes("bottom")) : 0;
5225
+ const available = Math.max(0, this.height - border);
5226
+ const reserved = Math.min(upper, Math.max(1, available - 3));
5227
+ this.devices.fitHeight(available, reserved);
5228
+ const signature = `${this.width}:${available}:${upper}:${this.devices.getHeight()}:${this.scroll.content.height}:${this.scroll.viewport.height}`;
5229
+ if (signature === this.layoutSignature) return;
5230
+ this.layoutSignature = signature;
5231
+ const focused = this._ctx.currentFocusedRenderable;
5232
+ for (let node = focused; node; node = node.parent) {
5233
+ if (node === this.scroll) {
5234
+ this.workspaceFiles.revealFocused();
5235
+ this.reveal(focused);
5236
+ break;
5237
+ }
5238
+ if (node === this.devices) {
5239
+ this.devices.revealFocused();
5240
+ break;
5241
+ }
5242
+ }
5243
+ };
4993
5244
  reveal(target) {
4994
5245
  const viewport = this.scroll.viewport;
4995
5246
  if (target.y < viewport.y) this.scroll.scrollBy(target.y - viewport.y);
@@ -6440,14 +6691,14 @@ var VimComposer = class {
6440
6691
  };
6441
6692
 
6442
6693
  // packages/cli/src/tui/native/components/selection-menu.ts
6443
- import { BoxRenderable as BoxRenderable9, CliRenderEvents as CliRenderEvents5, TextAttributes as TextAttributes9, TextRenderable as TextRenderable8 } from "@opentui/core";
6694
+ import { BoxRenderable as BoxRenderable9, CliRenderEvents as CliRenderEvents6, TextAttributes as TextAttributes9, TextRenderable as TextRenderable8 } from "@opentui/core";
6444
6695
  var NativeSelectionMenu = class {
6445
6696
  constructor(renderer2, colors, copy, restoreFocus) {
6446
6697
  this.renderer = renderer2;
6447
6698
  this.colors = colors;
6448
6699
  this.copy = copy;
6449
6700
  this.restoreFocus = restoreFocus;
6450
- renderer2.on(CliRenderEvents5.RESIZE, this.position);
6701
+ renderer2.on(CliRenderEvents6.RESIZE, this.position);
6451
6702
  }
6452
6703
  renderer;
6453
6704
  colors;
@@ -6568,12 +6819,12 @@ var NativeSelectionMenu = class {
6568
6819
  }
6569
6820
  destroy() {
6570
6821
  this.close();
6571
- this.renderer.off(CliRenderEvents5.RESIZE, this.position);
6822
+ this.renderer.off(CliRenderEvents6.RESIZE, this.position);
6572
6823
  }
6573
6824
  };
6574
6825
 
6575
6826
  // packages/cli/src/tui/native/components/tab-strip.ts
6576
- import { BoxRenderable as BoxRenderable10, CliRenderEvents as CliRenderEvents6, ScrollBoxRenderable as ScrollBoxRenderable7, SliderRenderable, TextAttributes as TextAttributes10, TextRenderable as TextRenderable9 } from "@opentui/core";
6827
+ import { BoxRenderable as BoxRenderable10, CliRenderEvents as CliRenderEvents7, ScrollBoxRenderable as ScrollBoxRenderable7, SliderRenderable, TextAttributes as TextAttributes10, TextRenderable as TextRenderable9 } from "@opentui/core";
6577
6828
  var NativeTabStrip = class extends BoxRenderable10 {
6578
6829
  constructor(renderer2, options) {
6579
6830
  super(renderer2, {
@@ -6628,7 +6879,7 @@ var NativeTabStrip = class extends BoxRenderable10 {
6628
6879
  this.rail.focusable = false;
6629
6880
  this.add(this.rail);
6630
6881
  styleNativeSlider(this.rail, this.colors);
6631
- renderer2.on(CliRenderEvents6.FRAME, this.syncScroll);
6882
+ renderer2.on(CliRenderEvents7.FRAME, this.syncScroll);
6632
6883
  }
6633
6884
  renderer;
6634
6885
  options;
@@ -6791,7 +7042,7 @@ var NativeTabStrip = class extends BoxRenderable10 {
6791
7042
  this.scroll.scrollLeft = offset2;
6792
7043
  }
6793
7044
  destroySelf() {
6794
- this.renderer.off(CliRenderEvents6.FRAME, this.syncScroll);
7045
+ this.renderer.off(CliRenderEvents7.FRAME, this.syncScroll);
6795
7046
  super.destroySelf();
6796
7047
  }
6797
7048
  };
@@ -7304,8 +7555,8 @@ var NativeFrontend = class {
7304
7555
  this.modals = new NativeModalStack(this.context, () => this.restoreFocus());
7305
7556
  renderer2.keyInput.on("keypress", this.onKey);
7306
7557
  renderer2.keyInput.on("paste", this.onPaste);
7307
- renderer2.on(CliRenderEvents7.FRAME, this.onFrame);
7308
- renderer2.on(CliRenderEvents7.RESIZE, this.onResize);
7558
+ renderer2.on(CliRenderEvents8.FRAME, this.onFrame);
7559
+ renderer2.on(CliRenderEvents8.RESIZE, this.onResize);
7309
7560
  this.applyTheme(initialTheme);
7310
7561
  this.editor.focus();
7311
7562
  }
@@ -8235,8 +8486,8 @@ var NativeFrontend = class {
8235
8486
  this.disposeSelectionContrast();
8236
8487
  this.renderer.keyInput.off("keypress", this.onKey);
8237
8488
  this.renderer.keyInput.off("paste", this.onPaste);
8238
- this.renderer.off(CliRenderEvents7.FRAME, this.onFrame);
8239
- this.renderer.off(CliRenderEvents7.RESIZE, this.onResize);
8489
+ this.renderer.off(CliRenderEvents8.FRAME, this.onFrame);
8490
+ this.renderer.off(CliRenderEvents8.RESIZE, this.onResize);
8240
8491
  const mediaClosed = [...this.context.mediaViews ?? []].map((media) => {
8241
8492
  media.pausePlayback();
8242
8493
  return media.closed;
@@ -8252,7 +8503,7 @@ var NativeFrontend = class {
8252
8503
  };
8253
8504
 
8254
8505
  // packages/cli/src/tui/native/components/cursor.ts
8255
- import { CliRenderEvents as CliRenderEvents8 } from "@opentui/core";
8506
+ import { CliRenderEvents as CliRenderEvents9 } from "@opentui/core";
8256
8507
  function installTerminalCursorDefaults(renderer2, session) {
8257
8508
  const reset = new TextEncoder().encode("\x1B]112\x07");
8258
8509
  let disposed = false;
@@ -8302,11 +8553,11 @@ function installTerminalCursorDefaults(renderer2, session) {
8302
8553
  const dispose = () => {
8303
8554
  if (disposed) return;
8304
8555
  disposed = true;
8305
- renderer2.off(CliRenderEvents8.FRAME, afterFrame);
8306
- renderer2.off(CliRenderEvents8.DESTROY, dispose);
8556
+ renderer2.off(CliRenderEvents9.FRAME, afterFrame);
8557
+ renderer2.off(CliRenderEvents9.DESTROY, dispose);
8307
8558
  };
8308
- renderer2.on(CliRenderEvents8.FRAME, afterFrame);
8309
- renderer2.once(CliRenderEvents8.DESTROY, dispose);
8559
+ renderer2.on(CliRenderEvents9.FRAME, afterFrame);
8560
+ renderer2.once(CliRenderEvents9.DESTROY, dispose);
8310
8561
  return { restore, dispose };
8311
8562
  }
8312
8563
 
@@ -8395,10 +8646,10 @@ try {
8395
8646
  maxFps: 60,
8396
8647
  clearOnShutdown: false
8397
8648
  });
8398
- renderer.on(CliRenderEvents9.RENDER_ERROR, (event) => {
8649
+ renderer.on(CliRenderEvents10.RENDER_ERROR, (event) => {
8399
8650
  void fail(event.error);
8400
8651
  });
8401
- renderer.on(CliRenderEvents9.HANDLER_ERROR, (event) => {
8652
+ renderer.on(CliRenderEvents10.HANDLER_ERROR, (event) => {
8402
8653
  void fail(event.error);
8403
8654
  });
8404
8655
  renderer.on("error", (event) => {