sisyphi 1.1.8 → 1.1.10
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.
- package/dist/cli.js +840 -165
- package/dist/cli.js.map +1 -1
- package/dist/templates/nvim-tutorial.txt +68 -0
- package/dist/templates/orchestrator-base.md +4 -2
- package/dist/templates/tutorial-demo/CLAUDE.md +14 -0
- package/dist/templates/tutorial-demo/package.json +10 -0
- package/dist/templates/tutorial-demo/server.js +76 -0
- package/dist/templates/tutorial-demo/test.js +48 -0
- package/dist/templates/tutorial-demo/todo.js +35 -0
- package/dist/tui.js +479 -214
- package/dist/tui.js.map +1 -1
- package/package.json +1 -1
- package/templates/nvim-tutorial.txt +68 -0
- package/templates/orchestrator-base.md +4 -2
- package/templates/tutorial-demo/CLAUDE.md +14 -0
- package/templates/tutorial-demo/package.json +10 -0
- package/templates/tutorial-demo/server.js +76 -0
- package/templates/tutorial-demo/test.js +48 -0
- package/templates/tutorial-demo/todo.js +35 -0
package/dist/tui.js
CHANGED
|
@@ -333,6 +333,12 @@ function createAppState(cwd2) {
|
|
|
333
333
|
cachedReportBlocks: /* @__PURE__ */ new Map(),
|
|
334
334
|
cachedTreeNodes: null,
|
|
335
335
|
treeCacheKey: "",
|
|
336
|
+
cachedDetailLines: null,
|
|
337
|
+
detailCacheKey: "",
|
|
338
|
+
detailRenderedCache: { lines: [], ansi: [] },
|
|
339
|
+
cachedLogsLines: null,
|
|
340
|
+
logsCacheKey: "",
|
|
341
|
+
logsRenderedCache: { lines: [], ansi: [] },
|
|
336
342
|
cwd: cwd2
|
|
337
343
|
};
|
|
338
344
|
}
|
|
@@ -1651,13 +1657,21 @@ function renderLine(segs) {
|
|
|
1651
1657
|
}
|
|
1652
1658
|
return out;
|
|
1653
1659
|
}
|
|
1660
|
+
var cachedBlank = "";
|
|
1661
|
+
var cachedBlankWidth = 0;
|
|
1654
1662
|
function createFrameBuffer(width, height) {
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1663
|
+
if (width !== cachedBlankWidth) {
|
|
1664
|
+
cachedBlank = " ".repeat(width);
|
|
1665
|
+
cachedBlankWidth = width;
|
|
1666
|
+
}
|
|
1667
|
+
const lines = new Array(height);
|
|
1668
|
+
for (let i = 0; i < height; i++) lines[i] = cachedBlank;
|
|
1669
|
+
return { lines, width, height };
|
|
1670
|
+
}
|
|
1671
|
+
function copyRows(buf, src, startRow, count) {
|
|
1672
|
+
for (let i = 0; i < count && startRow + i < buf.height; i++) {
|
|
1673
|
+
buf.lines[startRow + i] = src[startRow + i];
|
|
1674
|
+
}
|
|
1661
1675
|
}
|
|
1662
1676
|
function flushFrame(frame, prevFrame2) {
|
|
1663
1677
|
let out = "\x1B[?2026h";
|
|
@@ -1672,6 +1686,71 @@ function flushFrame(frame, prevFrame2) {
|
|
|
1672
1686
|
return out;
|
|
1673
1687
|
}
|
|
1674
1688
|
var ANSI_RE = /\x1b\[[0-9;]*[a-zA-Z]/g;
|
|
1689
|
+
function clipAnsi(content, maxWidth) {
|
|
1690
|
+
let out = "";
|
|
1691
|
+
let displayWidth = 0;
|
|
1692
|
+
let i = 0;
|
|
1693
|
+
while (i < content.length) {
|
|
1694
|
+
if (content[i] === "\x1B" && content[i + 1] === "[") {
|
|
1695
|
+
const seqLen = ansiLen(content, i);
|
|
1696
|
+
if (seqLen > 0) {
|
|
1697
|
+
out += content.substring(i, i + seqLen);
|
|
1698
|
+
i += seqLen;
|
|
1699
|
+
continue;
|
|
1700
|
+
}
|
|
1701
|
+
}
|
|
1702
|
+
const cp = content.codePointAt(i);
|
|
1703
|
+
const ch = String.fromCodePoint(cp);
|
|
1704
|
+
const chWidth = cp < 128 ? 1 : stringWidth2(ch);
|
|
1705
|
+
if (displayWidth + chWidth > maxWidth) break;
|
|
1706
|
+
out += ch;
|
|
1707
|
+
displayWidth += chWidth;
|
|
1708
|
+
i += ch.length;
|
|
1709
|
+
}
|
|
1710
|
+
if (out.includes("\x1B[") && !out.endsWith("\x1B[0m")) {
|
|
1711
|
+
out += "\x1B[0m";
|
|
1712
|
+
}
|
|
1713
|
+
const remaining = maxWidth - displayWidth;
|
|
1714
|
+
if (remaining > 0) out += " ".repeat(remaining);
|
|
1715
|
+
return out;
|
|
1716
|
+
}
|
|
1717
|
+
function displayWidthFast(s) {
|
|
1718
|
+
let w = 0;
|
|
1719
|
+
let i = 0;
|
|
1720
|
+
while (i < s.length) {
|
|
1721
|
+
if (s[i] === "\x1B" && s[i + 1] === "[") {
|
|
1722
|
+
const len = ansiLen(s, i);
|
|
1723
|
+
if (len > 0) {
|
|
1724
|
+
i += len;
|
|
1725
|
+
continue;
|
|
1726
|
+
}
|
|
1727
|
+
}
|
|
1728
|
+
const cp = s.codePointAt(i);
|
|
1729
|
+
const ch = String.fromCodePoint(cp);
|
|
1730
|
+
w += cp < 128 ? 1 : stringWidth2(ch);
|
|
1731
|
+
i += ch.length;
|
|
1732
|
+
}
|
|
1733
|
+
return w;
|
|
1734
|
+
}
|
|
1735
|
+
function ansiLen(s, i) {
|
|
1736
|
+
let j = i + 2;
|
|
1737
|
+
const len = s.length;
|
|
1738
|
+
while (j < len) {
|
|
1739
|
+
const c = s.charCodeAt(j);
|
|
1740
|
+
if (c >= 48 && c <= 57 || c === 59) {
|
|
1741
|
+
j++;
|
|
1742
|
+
} else {
|
|
1743
|
+
break;
|
|
1744
|
+
}
|
|
1745
|
+
}
|
|
1746
|
+
if (j < len) {
|
|
1747
|
+
const c = s.charCodeAt(j);
|
|
1748
|
+
if (c >= 65 && c <= 90 || c >= 97 && c <= 122) {
|
|
1749
|
+
return j + 1 - i;
|
|
1750
|
+
}
|
|
1751
|
+
}
|
|
1752
|
+
return 0;
|
|
1753
|
+
}
|
|
1675
1754
|
function writeAt(buf, x, y, content) {
|
|
1676
1755
|
if (y < 0 || y >= buf.height) return;
|
|
1677
1756
|
if (x < 0 || x >= buf.width) return;
|
|
@@ -1691,16 +1770,16 @@ function writeClipped(buf, x, y, content, maxWidth) {
|
|
|
1691
1770
|
let i = 0;
|
|
1692
1771
|
while (i < content.length) {
|
|
1693
1772
|
if (content[i] === "\x1B" && content[i + 1] === "[") {
|
|
1694
|
-
const
|
|
1695
|
-
if (
|
|
1696
|
-
out +=
|
|
1697
|
-
i +=
|
|
1773
|
+
const seqLen = ansiLen(content, i);
|
|
1774
|
+
if (seqLen > 0) {
|
|
1775
|
+
out += content.substring(i, i + seqLen);
|
|
1776
|
+
i += seqLen;
|
|
1698
1777
|
continue;
|
|
1699
1778
|
}
|
|
1700
1779
|
}
|
|
1701
1780
|
const cp = content.codePointAt(i);
|
|
1702
1781
|
const ch = String.fromCodePoint(cp);
|
|
1703
|
-
const chWidth = stringWidth2(ch);
|
|
1782
|
+
const chWidth = cp < 128 ? 1 : stringWidth2(ch);
|
|
1704
1783
|
if (displayWidth + chWidth > maxWidth) break;
|
|
1705
1784
|
out += ch;
|
|
1706
1785
|
displayWidth += chWidth;
|
|
@@ -1713,7 +1792,12 @@ function writeClipped(buf, x, y, content, maxWidth) {
|
|
|
1713
1792
|
if (remaining > 0) {
|
|
1714
1793
|
out += " ".repeat(remaining);
|
|
1715
1794
|
}
|
|
1716
|
-
|
|
1795
|
+
const existing = buf.lines[y];
|
|
1796
|
+
const prefix = sliceDisplayCols(existing, 0, x);
|
|
1797
|
+
const suffix = sliceDisplayCols(existing, x + maxWidth, buf.width);
|
|
1798
|
+
const prefixDisplayW = displayWidthFast(prefix);
|
|
1799
|
+
const paddedPrefix = prefixDisplayW < x ? prefix + " ".repeat(x - prefixDisplayW) : prefix;
|
|
1800
|
+
buf.lines[y] = paddedPrefix + out + suffix;
|
|
1717
1801
|
}
|
|
1718
1802
|
function writeCenter(buf, row, content) {
|
|
1719
1803
|
const textWidth = stringWidth2(content.replace(ANSI_RE, ""));
|
|
@@ -1730,28 +1814,75 @@ function drawBorder(buf, x, y, w, h, color) {
|
|
|
1730
1814
|
writeAt(buf, x + w - 1, row, sgr + "\u2502" + reset);
|
|
1731
1815
|
}
|
|
1732
1816
|
}
|
|
1733
|
-
function
|
|
1734
|
-
const {
|
|
1735
|
-
|
|
1736
|
-
const
|
|
1817
|
+
function buildPanelRows(rect, lines, scrollOffset, focused, borderColor, renderedCache) {
|
|
1818
|
+
const { w, h } = rect;
|
|
1819
|
+
const rows = new Array(h);
|
|
1820
|
+
const color = focused ? "blue" : borderColor;
|
|
1821
|
+
const sgr = `\x1B[${colorToSGR(color)}m`;
|
|
1822
|
+
const reset = "\x1B[0m";
|
|
1737
1823
|
const innerW = w - 4;
|
|
1738
|
-
const innerY = y + 1;
|
|
1739
1824
|
const innerH = h - 2;
|
|
1740
|
-
|
|
1825
|
+
const blankInner = " ".repeat(innerW);
|
|
1826
|
+
rows[0] = sgr + "\u256D" + "\u2500".repeat(w - 2) + "\u256E" + reset;
|
|
1827
|
+
rows[h - 1] = sgr + "\u2570" + "\u2500".repeat(w - 2) + "\u256F" + reset;
|
|
1828
|
+
const borderL = sgr + "\u2502" + reset + " ";
|
|
1829
|
+
const borderR = " " + sgr + "\u2502" + reset;
|
|
1830
|
+
const emptyRow = borderL + blankInner + borderR;
|
|
1831
|
+
for (let i = 1; i < h - 1; i++) rows[i] = emptyRow;
|
|
1832
|
+
if (innerW <= 0 || innerH <= 0) return rows;
|
|
1833
|
+
let ansiLines;
|
|
1834
|
+
if (renderedCache && renderedCache.lines === lines) {
|
|
1835
|
+
ansiLines = renderedCache.ansi;
|
|
1836
|
+
} else {
|
|
1837
|
+
ansiLines = new Array(lines.length);
|
|
1838
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1839
|
+
ansiLines[i] = renderLine(lines[i]);
|
|
1840
|
+
}
|
|
1841
|
+
if (renderedCache) {
|
|
1842
|
+
renderedCache.lines = lines;
|
|
1843
|
+
renderedCache.ansi = ansiLines;
|
|
1844
|
+
}
|
|
1845
|
+
}
|
|
1741
1846
|
const hasOverflow = lines.length > innerH;
|
|
1742
1847
|
const viewableH = hasOverflow ? innerH - 1 : innerH;
|
|
1743
1848
|
const maxScroll = Math.max(0, lines.length - viewableH);
|
|
1744
1849
|
const effectiveOffset = Math.min(scrollOffset, maxScroll);
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
writeClipped(buf, innerX, innerY + i, ansi, innerW);
|
|
1850
|
+
for (let i = 0; i < viewableH && effectiveOffset + i < ansiLines.length; i++) {
|
|
1851
|
+
const clipped = clipAnsi(ansiLines[effectiveOffset + i], innerW);
|
|
1852
|
+
rows[1 + i] = borderL + clipped + borderR;
|
|
1749
1853
|
}
|
|
1750
1854
|
if (hasOverflow) {
|
|
1751
1855
|
const scrollPct = maxScroll > 0 ? Math.round(effectiveOffset / maxScroll * 100) : 100;
|
|
1752
1856
|
const indicator = ` \u2195 ${scrollPct}% \xB7 ${lines.length} lines`;
|
|
1753
|
-
|
|
1857
|
+
const clipped = clipAnsi(`\x1B[2m${indicator}\x1B[0m`, innerW);
|
|
1858
|
+
rows[1 + viewableH] = borderL + clipped + borderR;
|
|
1754
1859
|
}
|
|
1860
|
+
return rows;
|
|
1861
|
+
}
|
|
1862
|
+
function buildEmptyPanelRows(rect, focused, borderColor, centerText) {
|
|
1863
|
+
const { w, h } = rect;
|
|
1864
|
+
const rows = new Array(h);
|
|
1865
|
+
const color = focused ? "blue" : borderColor;
|
|
1866
|
+
const sgr = `\x1B[${colorToSGR(color)}m`;
|
|
1867
|
+
const reset = "\x1B[0m";
|
|
1868
|
+
const innerW = w - 4;
|
|
1869
|
+
const borderL = sgr + "\u2502" + reset + " ";
|
|
1870
|
+
const borderR = " " + sgr + "\u2502" + reset;
|
|
1871
|
+
const emptyRow = borderL + " ".repeat(innerW) + borderR;
|
|
1872
|
+
rows[0] = sgr + "\u256D" + "\u2500".repeat(w - 2) + "\u256E" + reset;
|
|
1873
|
+
rows[h - 1] = sgr + "\u2570" + "\u2500".repeat(w - 2) + "\u256F" + reset;
|
|
1874
|
+
for (let i = 1; i < h - 1; i++) rows[i] = emptyRow;
|
|
1875
|
+
if (centerText) {
|
|
1876
|
+
const midRow = Math.floor(h / 2);
|
|
1877
|
+
if (midRow > 0 && midRow < h - 1) {
|
|
1878
|
+
const clipped = clipAnsi(centerText, innerW);
|
|
1879
|
+
const textW = displayWidthFast(centerText);
|
|
1880
|
+
const pad = Math.max(0, Math.floor((innerW - textW) / 2));
|
|
1881
|
+
const centered = " ".repeat(pad) + clipped;
|
|
1882
|
+
rows[midRow] = borderL + clipAnsi(centered, innerW) + borderR;
|
|
1883
|
+
}
|
|
1884
|
+
}
|
|
1885
|
+
return rows;
|
|
1755
1886
|
}
|
|
1756
1887
|
function sliceDisplayCols(s, start, end) {
|
|
1757
1888
|
let out = "";
|
|
@@ -1761,19 +1892,20 @@ function sliceDisplayCols(s, start, end) {
|
|
|
1761
1892
|
let hasOpenSGR = false;
|
|
1762
1893
|
while (i < s.length && col < end) {
|
|
1763
1894
|
if (s[i] === "\x1B" && s[i + 1] === "[") {
|
|
1764
|
-
const
|
|
1765
|
-
if (
|
|
1895
|
+
const seqLen = ansiLen(s, i);
|
|
1896
|
+
if (seqLen > 0) {
|
|
1766
1897
|
if (col >= start) {
|
|
1767
|
-
|
|
1768
|
-
|
|
1898
|
+
const seq = s.substring(i, i + seqLen);
|
|
1899
|
+
out += seq;
|
|
1900
|
+
hasOpenSGR = seq !== "\x1B[0m" && seq !== "\x1B[m";
|
|
1769
1901
|
}
|
|
1770
|
-
i +=
|
|
1902
|
+
i += seqLen;
|
|
1771
1903
|
continue;
|
|
1772
1904
|
}
|
|
1773
1905
|
}
|
|
1774
1906
|
const cp = s.codePointAt(i);
|
|
1775
1907
|
const ch = String.fromCodePoint(cp);
|
|
1776
|
-
const chWidth = stringWidth2(ch);
|
|
1908
|
+
const chWidth = cp < 128 ? 1 : stringWidth2(ch);
|
|
1777
1909
|
if (col >= start) {
|
|
1778
1910
|
inSlice = true;
|
|
1779
1911
|
if (col + chWidth > end) break;
|
|
@@ -1788,6 +1920,76 @@ function sliceDisplayCols(s, start, end) {
|
|
|
1788
1920
|
return out;
|
|
1789
1921
|
}
|
|
1790
1922
|
|
|
1923
|
+
// src/tui/lib/tree-render.ts
|
|
1924
|
+
function renderTreePrefix(node, nodes, index) {
|
|
1925
|
+
if (node.depth === 0) {
|
|
1926
|
+
return node.expandable ? node.expanded ? "\u25BC " : "\u25B8 " : " ";
|
|
1927
|
+
}
|
|
1928
|
+
const parts = [];
|
|
1929
|
+
for (let d = 1; d < node.depth; d++) {
|
|
1930
|
+
parts.push(isAncestorLastSibling(nodes, index, d) ? " " : "\u2502 ");
|
|
1931
|
+
}
|
|
1932
|
+
parts.push(isLastSibling(nodes, index) ? "\u2514\u2500" : "\u251C\u2500");
|
|
1933
|
+
if (node.expandable) {
|
|
1934
|
+
parts.push(node.expanded ? "\u25BC " : "\u25B8 ");
|
|
1935
|
+
} else {
|
|
1936
|
+
parts.push(" ");
|
|
1937
|
+
}
|
|
1938
|
+
return parts.join("");
|
|
1939
|
+
}
|
|
1940
|
+
function isLastSibling(nodes, index) {
|
|
1941
|
+
const depth = nodes[index].depth;
|
|
1942
|
+
for (let i = index + 1; i < nodes.length; i++) {
|
|
1943
|
+
if (nodes[i].depth === depth) return false;
|
|
1944
|
+
if (nodes[i].depth < depth) return true;
|
|
1945
|
+
}
|
|
1946
|
+
return true;
|
|
1947
|
+
}
|
|
1948
|
+
function isAncestorLastSibling(nodes, index, depth) {
|
|
1949
|
+
for (let i = index - 1; i >= 0; i--) {
|
|
1950
|
+
if (nodes[i].depth === depth) {
|
|
1951
|
+
return isLastSibling(nodes, i);
|
|
1952
|
+
}
|
|
1953
|
+
if (nodes[i].depth < depth) return true;
|
|
1954
|
+
}
|
|
1955
|
+
return true;
|
|
1956
|
+
}
|
|
1957
|
+
function precomputePrefixes(nodes) {
|
|
1958
|
+
const len = nodes.length;
|
|
1959
|
+
if (len === 0) return;
|
|
1960
|
+
const isLast = new Array(len);
|
|
1961
|
+
const lastSeenAtDepth = /* @__PURE__ */ new Map();
|
|
1962
|
+
for (let i = len - 1; i >= 0; i--) {
|
|
1963
|
+
const depth = nodes[i].depth;
|
|
1964
|
+
isLast[i] = !lastSeenAtDepth.has(depth);
|
|
1965
|
+
lastSeenAtDepth.set(depth, i);
|
|
1966
|
+
for (const [d] of lastSeenAtDepth) {
|
|
1967
|
+
if (d > depth) lastSeenAtDepth.delete(d);
|
|
1968
|
+
}
|
|
1969
|
+
}
|
|
1970
|
+
const ancestorIsLast = [];
|
|
1971
|
+
for (let i = 0; i < len; i++) {
|
|
1972
|
+
const node = nodes[i];
|
|
1973
|
+
if (node.depth === 0) {
|
|
1974
|
+
node.prefix = node.expandable ? node.expanded ? "\u25BC " : "\u25B8 " : " ";
|
|
1975
|
+
ancestorIsLast[0] = isLast[i];
|
|
1976
|
+
continue;
|
|
1977
|
+
}
|
|
1978
|
+
ancestorIsLast[node.depth] = isLast[i];
|
|
1979
|
+
const parts = [];
|
|
1980
|
+
for (let d = 1; d < node.depth; d++) {
|
|
1981
|
+
parts.push(ancestorIsLast[d] ? " " : "\u2502 ");
|
|
1982
|
+
}
|
|
1983
|
+
parts.push(isLast[i] ? "\u2514\u2500" : "\u251C\u2500");
|
|
1984
|
+
if (node.expandable) {
|
|
1985
|
+
parts.push(node.expanded ? "\u25BC " : "\u25B8 ");
|
|
1986
|
+
} else {
|
|
1987
|
+
parts.push(" ");
|
|
1988
|
+
}
|
|
1989
|
+
node.prefix = parts.join("");
|
|
1990
|
+
}
|
|
1991
|
+
}
|
|
1992
|
+
|
|
1791
1993
|
// src/tui/lib/client.ts
|
|
1792
1994
|
function send(request) {
|
|
1793
1995
|
return rawSend(request, 5e3);
|
|
@@ -1804,8 +2006,13 @@ function selectWindow(windowId) {
|
|
|
1804
2006
|
function selectPane(paneId) {
|
|
1805
2007
|
execSafe(`tmux select-pane -t "${paneId}"`);
|
|
1806
2008
|
}
|
|
1807
|
-
function
|
|
1808
|
-
|
|
2009
|
+
function listAllWindowIds() {
|
|
2010
|
+
try {
|
|
2011
|
+
const output = execSync('tmux list-windows -a -F "#{window_id}"', { encoding: "utf-8", env: EXEC_ENV });
|
|
2012
|
+
return new Set(output.trim().split("\n").filter(Boolean));
|
|
2013
|
+
} catch {
|
|
2014
|
+
return /* @__PURE__ */ new Set();
|
|
2015
|
+
}
|
|
1809
2016
|
}
|
|
1810
2017
|
var companionPaneId = null;
|
|
1811
2018
|
function setupCompanionPlugin() {
|
|
@@ -1901,41 +2108,6 @@ function copyToClipboard(text) {
|
|
|
1901
2108
|
execSync2("pbcopy", { input: text });
|
|
1902
2109
|
}
|
|
1903
2110
|
|
|
1904
|
-
// src/tui/lib/tree-render.ts
|
|
1905
|
-
function renderTreePrefix(node, nodes, index) {
|
|
1906
|
-
if (node.depth === 0) {
|
|
1907
|
-
return node.expandable ? node.expanded ? "\u25BC " : "\u25B8 " : " ";
|
|
1908
|
-
}
|
|
1909
|
-
const parts = [];
|
|
1910
|
-
for (let d = 1; d < node.depth; d++) {
|
|
1911
|
-
parts.push(isAncestorLastSibling(nodes, index, d) ? " " : "\u2502 ");
|
|
1912
|
-
}
|
|
1913
|
-
parts.push(isLastSibling(nodes, index) ? "\u2514\u2500" : "\u251C\u2500");
|
|
1914
|
-
if (node.expandable) {
|
|
1915
|
-
parts.push(node.expanded ? "\u25BC " : "\u25B8 ");
|
|
1916
|
-
} else {
|
|
1917
|
-
parts.push(" ");
|
|
1918
|
-
}
|
|
1919
|
-
return parts.join("");
|
|
1920
|
-
}
|
|
1921
|
-
function isLastSibling(nodes, index) {
|
|
1922
|
-
const depth = nodes[index].depth;
|
|
1923
|
-
for (let i = index + 1; i < nodes.length; i++) {
|
|
1924
|
-
if (nodes[i].depth === depth) return false;
|
|
1925
|
-
if (nodes[i].depth < depth) return true;
|
|
1926
|
-
}
|
|
1927
|
-
return true;
|
|
1928
|
-
}
|
|
1929
|
-
function isAncestorLastSibling(nodes, index, depth) {
|
|
1930
|
-
for (let i = index - 1; i >= 0; i--) {
|
|
1931
|
-
if (nodes[i].depth === depth) {
|
|
1932
|
-
return isLastSibling(nodes, i);
|
|
1933
|
-
}
|
|
1934
|
-
if (nodes[i].depth < depth) return true;
|
|
1935
|
-
}
|
|
1936
|
-
return true;
|
|
1937
|
-
}
|
|
1938
|
-
|
|
1939
2111
|
// src/tui/panels/tree.ts
|
|
1940
2112
|
function renderNodeContent(node, maxWidth) {
|
|
1941
2113
|
switch (node.type) {
|
|
@@ -2075,7 +2247,7 @@ function renderTreePanel(buf, rect, nodes, cursorIndex, focused) {
|
|
|
2075
2247
|
const node = visible[i];
|
|
2076
2248
|
const realIdx = scrollOffset + i;
|
|
2077
2249
|
const isSelected = realIdx === cursorIndex;
|
|
2078
|
-
const prefix = renderTreePrefix(node, nodes, realIdx);
|
|
2250
|
+
const prefix = node.prefix ?? renderTreePrefix(node, nodes, realIdx);
|
|
2079
2251
|
const contentWidth = innerW;
|
|
2080
2252
|
const { icon, label, meta, color, dim, metaColor, suffix, suffixColor } = renderNodeContent(
|
|
2081
2253
|
node,
|
|
@@ -2562,7 +2734,7 @@ function buildLogsLines(cycleLogs, width) {
|
|
|
2562
2734
|
}
|
|
2563
2735
|
return lines;
|
|
2564
2736
|
}
|
|
2565
|
-
function
|
|
2737
|
+
function renderDetailRows(rect, state2, detailCtx) {
|
|
2566
2738
|
const { session, agents, reportBlocks, detailReportBlocks, contextFileContent } = detailCtx;
|
|
2567
2739
|
const scrollOffset = state2.detailScroll.offset;
|
|
2568
2740
|
const focused = state2.focusPane === "detail";
|
|
@@ -2570,157 +2742,203 @@ function renderDetailContent(buf, rect, state2, detailCtx) {
|
|
|
2570
2742
|
const reportAgent = agents.find((a) => a.id === state2.targetAgentId);
|
|
2571
2743
|
if (reportAgent) {
|
|
2572
2744
|
const lines2 = buildReportViewLines(reportAgent, reportBlocks, rect.w);
|
|
2573
|
-
|
|
2574
|
-
return;
|
|
2745
|
+
return buildPanelRows(rect, lines2, scrollOffset, focused, "cyan");
|
|
2575
2746
|
}
|
|
2576
2747
|
}
|
|
2577
2748
|
const cursorNode = detailCtx.nodes[state2.cursorIndex];
|
|
2578
2749
|
if (!cursorNode || !session) {
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
return;
|
|
2583
|
-
}
|
|
2750
|
+
return buildEmptyPanelRows(rect, false, "gray", "\x1B[2mSelect a session to view details\x1B[0m");
|
|
2751
|
+
}
|
|
2752
|
+
if (cursorNode.sessionId !== session.id) {
|
|
2753
|
+
return buildEmptyPanelRows(rect, false, "gray");
|
|
2754
|
+
}
|
|
2755
|
+
const lastCycle = session.orchestratorCycles[session.orchestratorCycles.length - 1];
|
|
2756
|
+
const cacheKey = [
|
|
2757
|
+
cursorNode.id,
|
|
2758
|
+
cursorNode.type,
|
|
2759
|
+
state2.mode,
|
|
2760
|
+
state2.targetAgentId,
|
|
2761
|
+
state2.showStrategy,
|
|
2762
|
+
rect.w,
|
|
2763
|
+
session.id,
|
|
2764
|
+
session.agents.length,
|
|
2765
|
+
session.orchestratorCycles.length,
|
|
2766
|
+
lastCycle?.completedAt ?? "",
|
|
2767
|
+
lastCycle?.agentsSpawned.length ?? 0,
|
|
2768
|
+
state2.planContent.length,
|
|
2769
|
+
state2.goalContent.length,
|
|
2770
|
+
state2.strategyContent.length,
|
|
2771
|
+
state2.paneAlive,
|
|
2772
|
+
detailReportBlocks.length,
|
|
2773
|
+
session.messages.length,
|
|
2774
|
+
state2.contextFiles.length,
|
|
2775
|
+
contextFileContent?.length ?? -1
|
|
2776
|
+
].join(":");
|
|
2584
2777
|
let lines;
|
|
2585
2778
|
let borderColor = "gray";
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
case "cycle": {
|
|
2592
|
-
const cycleNode = cursorNode;
|
|
2593
|
-
const cycle = session.orchestratorCycles.find((c) => c.cycle === cycleNode.cycleNumber);
|
|
2594
|
-
if (!cycle) {
|
|
2779
|
+
if (cacheKey === state2.detailCacheKey && state2.cachedDetailLines !== null) {
|
|
2780
|
+
lines = state2.cachedDetailLines;
|
|
2781
|
+
} else {
|
|
2782
|
+
switch (cursorNode.type) {
|
|
2783
|
+
case "session": {
|
|
2595
2784
|
lines = buildSessionLines(session, state2.planContent, state2.goalContent, rect.w, state2.paneAlive, state2.strategyContent, state2.showStrategy);
|
|
2596
|
-
|
|
2597
|
-
lines = buildCycleLines(cycle, session.agents, rect.w);
|
|
2785
|
+
break;
|
|
2598
2786
|
}
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2787
|
+
case "cycle": {
|
|
2788
|
+
const cycleNode = cursorNode;
|
|
2789
|
+
const cycle = session.orchestratorCycles.find((c) => c.cycle === cycleNode.cycleNumber);
|
|
2790
|
+
if (!cycle) {
|
|
2791
|
+
lines = buildSessionLines(session, state2.planContent, state2.goalContent, rect.w, state2.paneAlive, state2.strategyContent, state2.showStrategy);
|
|
2792
|
+
} else {
|
|
2793
|
+
lines = buildCycleLines(cycle, session.agents, rect.w);
|
|
2794
|
+
}
|
|
2795
|
+
break;
|
|
2608
2796
|
}
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2797
|
+
case "agent": {
|
|
2798
|
+
const agentNode = cursorNode;
|
|
2799
|
+
const agent = agents.find((a) => a.id === agentNode.agentId);
|
|
2800
|
+
if (!agent) {
|
|
2801
|
+
lines = buildSessionLines(session, state2.planContent, state2.goalContent, rect.w, state2.paneAlive, state2.strategyContent, state2.showStrategy);
|
|
2802
|
+
} else {
|
|
2803
|
+
lines = buildAgentLines(agent, detailReportBlocks, rect.w);
|
|
2804
|
+
}
|
|
2616
2805
|
break;
|
|
2617
2806
|
}
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
const
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2807
|
+
case "report": {
|
|
2808
|
+
const reportNode = cursorNode;
|
|
2809
|
+
const agent = agents.find((a) => a.id === reportNode.agentId);
|
|
2810
|
+
if (!agent) {
|
|
2811
|
+
lines = buildSessionLines(session, state2.planContent, state2.goalContent, rect.w, state2.paneAlive, state2.strategyContent, state2.showStrategy);
|
|
2812
|
+
break;
|
|
2813
|
+
}
|
|
2814
|
+
const reportIdx = reportNode.reportIndex;
|
|
2815
|
+
const specificBlock = detailReportBlocks.find((_b, i) => {
|
|
2816
|
+
const originalIdx = agent.reports.length - 1 - i;
|
|
2817
|
+
return originalIdx === reportIdx;
|
|
2818
|
+
});
|
|
2819
|
+
if (specificBlock) {
|
|
2820
|
+
const { label: badge, color: badgeColor } = reportBadge(specificBlock.type);
|
|
2821
|
+
lines = [
|
|
2822
|
+
[seg(" "), seg(badge, { color: badgeColor }), seg(` ${agent.id} \xB7 ${agentDisplayName(agent)}`, { bold: true })],
|
|
2823
|
+
singleLine(` ${formatTime(specificBlock.timestamp)}`, { dim: true }),
|
|
2824
|
+
singleLine(" "),
|
|
2825
|
+
[seg(" \u258E CONTENT", { color: badgeColor, bold: true })],
|
|
2826
|
+
...wrapText(specificBlock.content.trim(), rect.w - 8).map((l) => singleLine(` ${l}`))
|
|
2827
|
+
];
|
|
2828
|
+
borderColor = badgeColor;
|
|
2829
|
+
} else {
|
|
2830
|
+
lines = buildAgentLines(agent, detailReportBlocks, rect.w);
|
|
2831
|
+
}
|
|
2832
|
+
break;
|
|
2635
2833
|
}
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
]);
|
|
2834
|
+
case "messages": {
|
|
2835
|
+
lines = [singleLine(` Messages (${session.messages.length})`, { bold: true })];
|
|
2836
|
+
if (session.messages.length === 0) {
|
|
2837
|
+
lines.push(singleLine(" No messages", { dim: true, italic: true }));
|
|
2838
|
+
} else {
|
|
2839
|
+
for (const msg of session.messages) {
|
|
2840
|
+
const time = formatTime(msg.timestamp);
|
|
2841
|
+
const agentId = msg.source.type === "agent" ? msg.source.agentId : void 0;
|
|
2842
|
+
const label = messageSourceLabel(msg.source.type, agentId);
|
|
2843
|
+
const labelColor = messageSourceColor(msg.source.type);
|
|
2844
|
+
const maxContent = Math.max(10, rect.w - label.length - 20);
|
|
2845
|
+
lines.push([
|
|
2846
|
+
seg(` [${time}] `, { dim: true }),
|
|
2847
|
+
seg(`${label}: `, { color: labelColor, bold: true }),
|
|
2848
|
+
seg(wrapText(msg.summary.length > 0 ? msg.summary : msg.content, maxContent)[0], {})
|
|
2849
|
+
]);
|
|
2850
|
+
}
|
|
2654
2851
|
}
|
|
2852
|
+
break;
|
|
2655
2853
|
}
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2854
|
+
case "message": {
|
|
2855
|
+
const msgNode = cursorNode;
|
|
2856
|
+
const msg = session.messages.find((m) => m.id === msgNode.messageId);
|
|
2857
|
+
lines = [singleLine(" Message", { bold: true })];
|
|
2858
|
+
if (msg) {
|
|
2859
|
+
lines.push(singleLine(` ${msgNode.source} \xB7 ${msgNode.timestamp}`, { dim: true }));
|
|
2860
|
+
for (const l of wrapText(msg.content, rect.w - 8)) {
|
|
2861
|
+
lines.push(singleLine(` ${l}`));
|
|
2862
|
+
}
|
|
2863
|
+
} else {
|
|
2864
|
+
lines.push(singleLine(" Message not found", { dim: true }));
|
|
2666
2865
|
}
|
|
2667
|
-
|
|
2668
|
-
lines.push(singleLine(" Message not found", { dim: true }));
|
|
2866
|
+
break;
|
|
2669
2867
|
}
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
lines.push(singleLine(` \xB7 ${f}`, { dim: true }));
|
|
2868
|
+
case "context": {
|
|
2869
|
+
lines = [
|
|
2870
|
+
[seg(" "), seg("\u229E", { color: "white" }), seg(` Context (${state2.contextFiles.length})`, { bold: true })]
|
|
2871
|
+
];
|
|
2872
|
+
if (state2.contextFiles.length === 0) {
|
|
2873
|
+
lines.push(singleLine(" No context files found.", { dim: true }));
|
|
2874
|
+
} else {
|
|
2875
|
+
for (const f of state2.contextFiles) {
|
|
2876
|
+
lines.push(singleLine(` \xB7 ${f}`, { dim: true }));
|
|
2877
|
+
}
|
|
2681
2878
|
}
|
|
2879
|
+
break;
|
|
2682
2880
|
}
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
if (contextFileContent == null) {
|
|
2692
|
-
lines.push(singleLine(" File not found or unreadable.", { dim: true }));
|
|
2693
|
-
} else {
|
|
2694
|
-
const wrapped = wrapText(stripFrontmatter(contextFileContent), rect.w - 8);
|
|
2695
|
-
if (wrapped.length === 0) {
|
|
2696
|
-
lines.push(singleLine(" (empty)", { dim: true }));
|
|
2881
|
+
case "context-file": {
|
|
2882
|
+
const ctxFileNode = cursorNode;
|
|
2883
|
+
lines = [
|
|
2884
|
+
[seg(" "), seg("\u229E", { color: "white" }), seg(` ${ctxFileNode.label}`, { bold: true })],
|
|
2885
|
+
singleLine(" ")
|
|
2886
|
+
];
|
|
2887
|
+
if (contextFileContent == null) {
|
|
2888
|
+
lines.push(singleLine(" File not found or unreadable.", { dim: true }));
|
|
2697
2889
|
} else {
|
|
2698
|
-
|
|
2699
|
-
|
|
2890
|
+
const wrapped = wrapText(stripFrontmatter(contextFileContent), rect.w - 8);
|
|
2891
|
+
if (wrapped.length === 0) {
|
|
2892
|
+
lines.push(singleLine(" (empty)", { dim: true }));
|
|
2893
|
+
} else {
|
|
2894
|
+
for (const l of wrapped) {
|
|
2895
|
+
lines.push(singleLine(` ${l}`));
|
|
2896
|
+
}
|
|
2700
2897
|
}
|
|
2701
2898
|
}
|
|
2899
|
+
borderColor = "white";
|
|
2900
|
+
break;
|
|
2901
|
+
}
|
|
2902
|
+
default: {
|
|
2903
|
+
lines = buildSessionLines(session, state2.planContent, state2.goalContent, rect.w, state2.paneAlive, state2.strategyContent, state2.showStrategy);
|
|
2904
|
+
break;
|
|
2702
2905
|
}
|
|
2703
|
-
borderColor = "white";
|
|
2704
|
-
break;
|
|
2705
2906
|
}
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2907
|
+
state2.cachedDetailLines = lines;
|
|
2908
|
+
state2.detailCacheKey = cacheKey;
|
|
2909
|
+
}
|
|
2910
|
+
if (cursorNode.type === "context-file") {
|
|
2911
|
+
borderColor = "white";
|
|
2912
|
+
} else if (cursorNode.type === "report") {
|
|
2913
|
+
const reportNode = cursorNode;
|
|
2914
|
+
const agent = agents.find((a) => a.id === reportNode.agentId);
|
|
2915
|
+
if (agent) {
|
|
2916
|
+
const reportIdx = reportNode.reportIndex;
|
|
2917
|
+
const specificBlock = detailReportBlocks.find((_b, i) => {
|
|
2918
|
+
const originalIdx = agent.reports.length - 1 - i;
|
|
2919
|
+
return originalIdx === reportIdx;
|
|
2920
|
+
});
|
|
2921
|
+
if (specificBlock) borderColor = reportBadge(specificBlock.type).color;
|
|
2709
2922
|
}
|
|
2710
2923
|
}
|
|
2711
|
-
|
|
2924
|
+
return buildPanelRows(rect, lines, scrollOffset, focused, borderColor, state2.detailRenderedCache);
|
|
2712
2925
|
}
|
|
2713
|
-
function
|
|
2926
|
+
function renderLogsRows(rect, state2) {
|
|
2714
2927
|
const focused = state2.focusPane === "logs";
|
|
2715
2928
|
const scrollOffset = state2.logsScroll.offset;
|
|
2716
2929
|
if (state2.logsCycles.length === 0) {
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2930
|
+
return buildEmptyPanelRows(rect, focused, "gray", "\x1B[2mNo logs\x1B[0m");
|
|
2931
|
+
}
|
|
2932
|
+
const logsCacheKey = `${state2.logsCycles.length}:${rect.w}:${state2.logsCycles.map((c) => c.cycle).join(",")}`;
|
|
2933
|
+
let lines;
|
|
2934
|
+
if (logsCacheKey === state2.logsCacheKey && state2.cachedLogsLines !== null) {
|
|
2935
|
+
lines = state2.cachedLogsLines;
|
|
2936
|
+
} else {
|
|
2937
|
+
lines = buildLogsLines(state2.logsCycles, rect.w);
|
|
2938
|
+
state2.cachedLogsLines = lines;
|
|
2939
|
+
state2.logsCacheKey = logsCacheKey;
|
|
2721
2940
|
}
|
|
2722
|
-
|
|
2723
|
-
renderPanel(buf, rect, lines, scrollOffset, focused, "gray");
|
|
2941
|
+
return buildPanelRows(rect, lines, scrollOffset, focused, "gray", state2.logsRenderedCache);
|
|
2724
2942
|
}
|
|
2725
2943
|
|
|
2726
2944
|
// src/tui/panels/bottom.ts
|
|
@@ -2889,6 +3107,10 @@ var latestNodes = [];
|
|
|
2889
3107
|
var cachedContextFilePath = null;
|
|
2890
3108
|
var cachedContextFileContent = null;
|
|
2891
3109
|
var prevFrame = [];
|
|
3110
|
+
var prevTreeInputs = "";
|
|
3111
|
+
var prevBottomInputs = "";
|
|
3112
|
+
var prevOverlayMode = "";
|
|
3113
|
+
var cachedTreeRows = [];
|
|
2892
3114
|
var cachedLogSessionId = null;
|
|
2893
3115
|
var cachedLogFiles = /* @__PURE__ */ new Map();
|
|
2894
3116
|
function getAgentForNode(node, agents) {
|
|
@@ -2901,6 +3123,7 @@ function getAgentForNode(node, agents) {
|
|
|
2901
3123
|
function startApp(state2, cleanup2) {
|
|
2902
3124
|
const config = loadConfig(state2.cwd);
|
|
2903
3125
|
let prevSelectedSessionId = void 0;
|
|
3126
|
+
let debouncedPollTimer = null;
|
|
2904
3127
|
async function poll() {
|
|
2905
3128
|
try {
|
|
2906
3129
|
let selectedSession = null;
|
|
@@ -2918,13 +3141,10 @@ function startApp(state2, cleanup2) {
|
|
|
2918
3141
|
statusPromise ?? Promise.resolve(null)
|
|
2919
3142
|
]);
|
|
2920
3143
|
const sessions = listRes.ok ? listRes.data?.sessions ?? [] : [];
|
|
3144
|
+
const aliveWindows = listAllWindowIds();
|
|
2921
3145
|
for (const s of sessions) {
|
|
2922
3146
|
if (s.status !== "completed" && s.tmuxWindowId) {
|
|
2923
|
-
|
|
2924
|
-
s.windowAlive = windowExists(s.tmuxWindowId);
|
|
2925
|
-
} catch {
|
|
2926
|
-
s.windowAlive = false;
|
|
2927
|
-
}
|
|
3147
|
+
s.windowAlive = aliveWindows.has(s.tmuxWindowId);
|
|
2928
3148
|
}
|
|
2929
3149
|
}
|
|
2930
3150
|
if (state2.selectedSessionId) {
|
|
@@ -3027,7 +3247,7 @@ function startApp(state2, cleanup2) {
|
|
|
3027
3247
|
writeCenter(buf, Math.floor(state2.rows / 2), "Terminal too small \u2014 resize to continue");
|
|
3028
3248
|
const out2 = flushFrame(buf.lines, prevFrame);
|
|
3029
3249
|
writeToStdout(out2);
|
|
3030
|
-
prevFrame =
|
|
3250
|
+
prevFrame = buf.lines;
|
|
3031
3251
|
return;
|
|
3032
3252
|
}
|
|
3033
3253
|
const treeWidth = 36;
|
|
@@ -3055,6 +3275,7 @@ function startApp(state2, cleanup2) {
|
|
|
3055
3275
|
state2.cwd,
|
|
3056
3276
|
state2.contextFiles
|
|
3057
3277
|
);
|
|
3278
|
+
precomputePrefixes(nodes);
|
|
3058
3279
|
state2.cachedTreeNodes = nodes;
|
|
3059
3280
|
state2.treeCacheKey = cacheKey;
|
|
3060
3281
|
}
|
|
@@ -3067,11 +3288,19 @@ function startApp(state2, cleanup2) {
|
|
|
3067
3288
|
state2.selectedSessionId = newSessionId;
|
|
3068
3289
|
state2.detailScroll.reset();
|
|
3069
3290
|
state2.logsScroll.reset();
|
|
3291
|
+
state2.cachedDetailLines = null;
|
|
3292
|
+
state2.detailCacheKey = "";
|
|
3293
|
+
state2.cachedLogsLines = null;
|
|
3294
|
+
state2.logsCacheKey = "";
|
|
3070
3295
|
}
|
|
3071
3296
|
if (state2.selectedSessionId !== prevSelectedSessionId) {
|
|
3072
3297
|
prevSelectedSessionId = state2.selectedSessionId;
|
|
3298
|
+
if (debouncedPollTimer !== null) clearTimeout(debouncedPollTimer);
|
|
3073
3299
|
if (state2.selectedSessionId !== null) {
|
|
3074
|
-
|
|
3300
|
+
debouncedPollTimer = setTimeout(() => {
|
|
3301
|
+
debouncedPollTimer = null;
|
|
3302
|
+
void poll();
|
|
3303
|
+
}, 80);
|
|
3075
3304
|
}
|
|
3076
3305
|
}
|
|
3077
3306
|
autoExpandCycle(state2);
|
|
@@ -3099,13 +3328,37 @@ function startApp(state2, cleanup2) {
|
|
|
3099
3328
|
cachedContextFilePath = null;
|
|
3100
3329
|
cachedContextFileContent = null;
|
|
3101
3330
|
}
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
|
|
3331
|
+
const treeFocused = state2.mode === "navigate" && state2.focusPane === "tree";
|
|
3332
|
+
const treeInputs = `${state2.treeCacheKey}:${state2.cursorIndex}:${treeFocused}`;
|
|
3333
|
+
const bottomInputs = `${state2.notification}:${state2.error}:${state2.mode}:${state2.inputText}:${state2.inputCursorPos}:${cursorNode?.type}`;
|
|
3334
|
+
const overlayMode = state2.mode === "leader" || state2.mode === "copy-menu" || state2.mode === "help" ? state2.mode : "";
|
|
3335
|
+
const hasPrev = prevFrame.length === buf.height;
|
|
3336
|
+
const treeDirty = !hasPrev || treeInputs !== prevTreeInputs;
|
|
3337
|
+
const bottomDirty = !hasPrev || bottomInputs !== prevBottomInputs;
|
|
3338
|
+
const overlayDirty = !hasPrev || overlayMode !== prevOverlayMode;
|
|
3339
|
+
prevTreeInputs = treeInputs;
|
|
3340
|
+
prevBottomInputs = bottomInputs;
|
|
3341
|
+
prevOverlayMode = overlayMode;
|
|
3342
|
+
let treeRows;
|
|
3343
|
+
if (treeDirty) {
|
|
3344
|
+
const treeBlank = " ".repeat(treeWidth);
|
|
3345
|
+
const treeBuf = {
|
|
3346
|
+
lines: Array.from({ length: contentHeight }, () => treeBlank),
|
|
3347
|
+
width: treeWidth,
|
|
3348
|
+
height: contentHeight
|
|
3349
|
+
};
|
|
3350
|
+
renderTreePanel(
|
|
3351
|
+
treeBuf,
|
|
3352
|
+
{ x: 0, y: 0, w: treeWidth, h: contentHeight },
|
|
3353
|
+
nodes,
|
|
3354
|
+
state2.cursorIndex,
|
|
3355
|
+
treeFocused
|
|
3356
|
+
);
|
|
3357
|
+
cachedTreeRows = treeBuf.lines;
|
|
3358
|
+
treeRows = treeBuf.lines;
|
|
3359
|
+
} else {
|
|
3360
|
+
treeRows = cachedTreeRows;
|
|
3361
|
+
}
|
|
3109
3362
|
const detailCtx = {
|
|
3110
3363
|
nodes,
|
|
3111
3364
|
session: state2.selectedSession,
|
|
@@ -3114,19 +3367,30 @@ function startApp(state2, cleanup2) {
|
|
|
3114
3367
|
detailReportBlocks,
|
|
3115
3368
|
contextFileContent
|
|
3116
3369
|
};
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
if (
|
|
3370
|
+
const detailRows = renderDetailRows(detailRect, state2, detailCtx);
|
|
3371
|
+
const logsRows = logsRect ? renderLogsRows(logsRect, state2) : null;
|
|
3372
|
+
for (let i = 0; i < contentHeight; i++) {
|
|
3373
|
+
if (logsRows) {
|
|
3374
|
+
buf.lines[i] = treeRows[i] + detailRows[i] + logsRows[i];
|
|
3375
|
+
} else {
|
|
3376
|
+
buf.lines[i] = treeRows[i] + detailRows[i];
|
|
3377
|
+
}
|
|
3378
|
+
}
|
|
3379
|
+
if (bottomDirty || overlayDirty) {
|
|
3380
|
+
renderNotificationRow(buf, bottomY, state2.notification, state2.error);
|
|
3381
|
+
renderInputBar(buf, bottomY + 1, state2);
|
|
3382
|
+
renderStatusLine(buf, bottomY + 2, state2, cursorNode?.type);
|
|
3383
|
+
} else {
|
|
3384
|
+
copyRows(buf, prevFrame, bottomY, 3);
|
|
3385
|
+
}
|
|
3386
|
+
if (overlayMode) {
|
|
3387
|
+
if (state2.mode === "leader") renderLeaderOverlay(buf, state2.rows, state2.cols);
|
|
3388
|
+
if (state2.mode === "copy-menu") renderCopyMenuOverlay(buf, state2.rows, state2.cols);
|
|
3389
|
+
if (state2.mode === "help") renderHelpOverlay(buf, state2.rows, state2.cols);
|
|
3390
|
+
}
|
|
3127
3391
|
const out = flushFrame(buf.lines, prevFrame);
|
|
3128
3392
|
writeToStdout(out);
|
|
3129
|
-
prevFrame =
|
|
3393
|
+
prevFrame = buf.lines;
|
|
3130
3394
|
}
|
|
3131
3395
|
const inputActions = {
|
|
3132
3396
|
getNodes: () => latestNodes,
|
|
@@ -3187,6 +3451,7 @@ function startApp(state2, cleanup2) {
|
|
|
3187
3451
|
const origCleanup = inputActions.cleanup;
|
|
3188
3452
|
inputActions.cleanup = () => {
|
|
3189
3453
|
clearInterval(pollInterval);
|
|
3454
|
+
if (debouncedPollTimer !== null) clearTimeout(debouncedPollTimer);
|
|
3190
3455
|
stopKeypress();
|
|
3191
3456
|
stopResize();
|
|
3192
3457
|
state2.detailScroll.destroy();
|