koneck 2.128.56 → 2.128.57

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/ink-chat.js CHANGED
@@ -46,6 +46,7 @@ import { SessionChanges, readForDiff as readForDiffAt, totalsOf } from './sessio
46
46
  import os from 'node:os';
47
47
  import { modeLine, nextMode, statusFacts, barCells, PLACEHOLDER, contextSplit, contextSplitLines } from './status-line.js';
48
48
  import { KONECK_VERSION } from './version.js';
49
+ import { fileListView, listLength, diffRows, PANEL_CONTEXT, PANEL_DIFF_LINES } from './diff-layout.js';
49
50
  import { savePlan, plansDir, PLAN_CHOICES, PROCEED_PROMPT } from './plan-file.js';
50
51
  import { logoFor } from './logo.js';
51
52
  import { describeActivity } from './activity.js';
@@ -793,7 +794,7 @@ export function expandPastes(draft, pastes) {
793
794
  * capped: a 4,000-line rewrite scrolled past is no more readable in a panel than in a transcript,
794
795
  * and the count of what was left out is shown instead.
795
796
  */
796
- const PANEL_DIFF_LINES = 400;
797
+ // PANEL_DIFF_LINES lives in diff-layout.ts, so the browser's Changes view caps at the same point.
797
798
  /** The tabs of the usage pane, in the order they are shown. */
798
799
  const USAGE_TABS = ['settings', 'session', 'context', 'models', 'limits'];
799
800
  const MIN_REPLY_WIDTH = 24; // below this a table cannot render legibly anyway
@@ -1562,96 +1563,71 @@ function renderCodeLine(text, kind, width, lang) {
1562
1563
  /**
1563
1564
  * The live diff panel — the work as it happens, beside the conversation.
1564
1565
  *
1565
- * Asked for: the same running view the web has, in the terminal. It shows every file the session
1566
- * has changed, newest first, with the counts in the header, so a person watching can see the edit
1567
- * land rather than reading a description of it afterwards. Bounded on both axes: the newest files
1568
- * fill the height available and the rest are counted, since a panel that scrolls past what you
1569
- * wanted is no better than no panel.
1566
+ * Laid out the way Claude Code's is, which was the picture it was asked for with: the changed files
1567
+ * listed at the top with their own counts, tests and generated files folded into one line, and
1568
+ * below a rule the one file being read. Its changed lines sit on a band the whole width of the
1569
+ * panel, and a line too long for it wraps, carrying its + or - onto each piece.
1570
+ *
1571
+ * It used to stack every file's diff in one column, so the fifteenth file was a scroll through
1572
+ * fourteen others, and a long line was cut at the panel's edge. What is listed, in what order,
1573
+ * what is folded and how a line wraps are decided in diff-layout.ts, which the browser's Changes
1574
+ * view uses too.
1575
+ *
1576
+ * Alt+↑/↓ (or Alt+P/N, for Windows Terminal, which keeps Alt+arrows for its own panes) moves
1577
+ * through the list, Alt+PgUp/PgDn and the wheel scroll the file, Alt+T opens the fold.
1570
1578
  */
1571
- export function DiffPanel({ files, width, height, activity, activityRows = 0, scroll = 0 }) {
1572
- const added = files.reduce((n, d) => n + d.added, 0);
1573
- const removed = files.reduce((n, d) => n + d.removed, 0);
1574
- // The persistent panel needs a visible boundary even before an edit arrives. Without one, its
1575
- // empty state was a single easy-to-miss line and looked as if `/diff` had done nothing.
1576
- const inner = Math.max(20, width - 4);
1577
- const bodyHeight = Math.max(1, height - 4 - activityRows);
1578
- const rows = [];
1579
- let shownFiles = 0;
1580
- for (const d of [...files].reverse()) {
1581
- /*
1582
- * A rule between files, so the panel reads as sections rather than one column of lines.
1583
- *
1584
- * Asked for, against Claude Code's panel as the reference: "it is clean and well structured".
1585
- * The difference was structural, not chromatic — the colours were already right, but with a
1586
- * blank line as the only separation two files' diffs ran together and the filename headers had
1587
- * nothing to sit under. Drawn only between files, never above the first.
1588
- */
1589
- if (shownFiles > 0)
1590
- rows.push({ t: 'rule', key: `r-${d.path}` });
1591
- const numbers = d.hunks.flatMap(h => h.lines.map(l => l.after ?? l.before ?? 0));
1592
- const gutter = Math.max(3, String(Math.max(0, ...numbers)).length);
1593
- const codeWidth = Math.max(12, inner - gutter - 2);
1594
- const lang = spanLangFor(d.path);
1595
- const tag = d.created ? 'new' : d.deleted ? 'deleted' : `+${d.added}${d.removed ? ` -${d.removed}` : ''}`;
1596
- rows.push({ t: 'head', key: `h-${d.path}`, path: d.path, tag, deleted: d.deleted });
1597
- if (d.binary) {
1598
- rows.push({ t: 'binary', key: `b-${d.path}` });
1599
- shownFiles++;
1600
- continue;
1601
- }
1602
- for (const [hi, hunk] of d.hunks.entries()) {
1603
- if (hi > 0)
1604
- rows.push({ t: 'gap', key: `g-${d.path}-${hi}`, gutter });
1605
- for (const [li, l] of hunk.lines.entries()) {
1606
- rows.push({
1607
- t: 'line', key: `l-${d.path}-${hi}-${li}`, gutter, codeWidth, lang,
1608
- shown: (l.kind === 'del' ? l.before : l.after) ?? undefined,
1609
- sign: l.kind === 'add' ? '+' : l.kind === 'del' ? '-' : ' ',
1610
- kind: l.kind, text: l.text,
1611
- });
1612
- }
1613
- }
1614
- if (d.hiddenLines > 0) {
1615
- rows.push({ t: 'more', key: `m-${d.path}`, gutter, count: d.hiddenLines });
1616
- }
1617
- shownFiles++;
1618
- rows.push({ t: 'spacer', key: `s-${d.path}` });
1579
+ export function DiffPanel({ files, width, height, activity, activityRows = 0, scroll = 0, selected = 0, showGenerated = false }) {
1580
+ // A rule down the left instead of a box round it: two rows more for the diff, and the panel
1581
+ // reads as a pane beside the conversation, as in the panel it was modelled on.
1582
+ const inner = Math.max(20, width - 3);
1583
+ const room = Math.max(4, height - activityRows);
1584
+ const listLen = listLength(files, showGenerated);
1585
+ const listRows = Math.max(1, Math.min(listLen, 8, Math.max(2, Math.floor(room * 0.3))));
1586
+ const view = fileListView(files, { selected, rows: listRows, showGenerated });
1587
+ const d = view.selected >= 0 ? files[view.selected] : undefined;
1588
+ const counts = (added, removed, tag) => (_jsxs(_Fragment, { children: [tag !== '' && added === 0 && removed === 0 && _jsx(Text, { color: DIM, children: tag }), added > 0 && _jsxs(Text, { color: DIFF_ADD, children: ["+", added] }), removed > 0 && _jsxs(Text, { color: DIFF_DEL, children: [added > 0 ? ' ' : '', "-", removed] })] }));
1589
+ const list = view.rows.map(f => (_jsxs(Box, { justifyContent: "space-between", width: inner, children: [_jsxs(Text, { color: f.selected ? INK : MUTED, bold: f.selected, wrap: "truncate-start", children: [f.selected ? '› ' : ' ', f.path] }), _jsxs(Text, { children: [" ", counts(f.added, f.removed, f.tag)] })] }, `f-${f.path}`)));
1590
+ const hints = [];
1591
+ if (view.above > 0 || view.below > 0) {
1592
+ hints.push(_jsxs(Text, { color: DIM, wrap: "truncate", children: [view.above > 0 ? ` ↑ ${view.above} more above` : '', view.above > 0 && view.below > 0 ? ' ·' : '', view.below > 0 ? ` ↓ ${view.below} more below` : '', " (alt+\u2193 to move)"] }, "more"));
1619
1593
  }
1594
+ if (view.folded.count > 0) {
1595
+ hints.push(_jsx(Text, { color: DIM, wrap: "truncate", children: ` ${view.folded.count} tests/generated (alt+t to ${view.folded.open ? 'hide' : 'show'})` }, "fold"));
1596
+ }
1597
+ /*
1598
+ * Only the rows on screen are built into elements.
1599
+ *
1600
+ * Measured on a Raspberry Pi 5 with two 200-line diffs: building every row's elements on every
1601
+ * frame cost 281ms a repaint, against 1.09ms with the panel shut. A row object is cheap; a React
1602
+ * element per row is not. So the rows are described in full — the scroll needs the count — and
1603
+ * drawn only for the window.
1604
+ */
1605
+ const fixedRows = 2 + list.length + hints.length + (d ? 4 : 0) + (activity ? activityRows : 0);
1606
+ const bodyHeight = Math.max(1, height - fixedRows);
1607
+ const numbers = d ? d.hunks.flatMap(h => h.lines.map(l => l.after ?? l.before ?? 0)) : [];
1608
+ const gutter = Math.max(3, String(Math.max(0, ...numbers)).length);
1609
+ const codeWidth = Math.max(8, inner - gutter - 3);
1610
+ const rows = d ? diffRows(d, codeWidth) : [];
1620
1611
  const maxScroll = Math.max(0, rows.length - bodyHeight);
1621
1612
  const from = Math.max(0, Math.min(scroll, maxScroll));
1622
- const body = rows.slice(from, from + bodyHeight).map(r => {
1623
- switch (r.t) {
1624
- case 'rule':
1625
- return _jsx(Text, { color: DIM, children: '─'.repeat(inner) }, r.key);
1626
- case 'head':
1627
- return (_jsxs(Box, { justifyContent: "space-between", width: inner, children: [_jsx(Text, { color: CYAN, wrap: "truncate-start", children: r.path }), _jsxs(Text, { color: r.deleted ? DIFF_DEL : DIFF_ADD, children: [" ", r.tag] })] }, r.key));
1628
- case 'binary':
1629
- return _jsx(Text, { color: DIM, children: " binary \u2014 not shown" }, r.key);
1630
- case 'gap':
1631
- return _jsx(Text, { color: DIM, children: `${' '.repeat(r.gutter)} ⋯` }, r.key);
1632
- case 'more':
1633
- return _jsx(Text, { color: DIM, children: `${' '.repeat(r.gutter)} ⋯ ${r.count} more` }, r.key);
1634
- case 'spacer':
1635
- return _jsx(Text, { children: " " }, r.key);
1636
- default:
1637
- /*
1638
- * One Text per line, with the pieces nested inside it, rather than a Box of siblings.
1639
- *
1640
- * A Box is a layout node: the engine measures and places it and every child. A Text with
1641
- * Texts inside it is one node holding spans. With forty lines on screen and half a dozen
1642
- * pieces each that is the difference between laying out two hundred and fifty nodes a
1643
- * frame and laying out forty — and it is the same mistake that stranded the composer's
1644
- * cursor on the first row, in a place where it costs time rather than correctness.
1645
- */
1646
- return (_jsxs(Text, { wrap: "truncate", children: [_jsx(Text, { color: DIM, children: String(r.shown ?? '').padStart(r.gutter) }), _jsx(Text, { color: r.kind === 'add' ? DIFF_ADD : r.kind === 'del' ? DIFF_DEL : DIM, ...(r.kind !== 'ctx'
1647
- ? { backgroundColor: r.kind === 'add' ? DIFF_ADD_BG : DIFF_DEL_BG } : {}), children: r.sign }), renderCodeLine(r.text, r.kind, r.codeWidth, r.lang)] }, r.key));
1648
- }
1613
+ const lang = d ? spanLangFor(d.path) : undefined;
1614
+ const body = rows.slice(from, from + bodyHeight).map((r, i) => {
1615
+ const key = `d-${from + i}`;
1616
+ if (r.t === 'binary')
1617
+ return _jsx(Text, { color: DIM, children: " binary \u2014 not shown" }, key);
1618
+ if (r.t === 'more')
1619
+ return _jsx(Text, { color: DIM, children: `${' '.repeat(gutter)} ⋯ ${r.count} more lines not shown` }, key);
1620
+ const bg = r.kind === 'add' ? DIFF_ADD_BG : r.kind === 'del' ? DIFF_DEL_BG : undefined;
1621
+ const fg = r.kind === 'add' ? DIFF_ADD : r.kind === 'del' ? DIFF_DEL : DIM;
1622
+ const tint = bg ? { backgroundColor: bg } : {};
1623
+ // One Text with the pieces nested: a Box of siblings is a layout node per piece, forty lines
1624
+ // times half a dozen pieces a frame.
1625
+ return (_jsxs(Text, { wrap: "truncate", children: [_jsxs(Text, { color: r.kind === 'ctx' ? DIM : fg, ...tint, children: [(r.first && r.number !== null ? String(r.number) : '').padStart(gutter), ' ', r.sign, ' '] }), renderCodeLine(r.text, r.kind, codeWidth, lang)] }, key));
1649
1626
  });
1650
- const summary = files.length === 0
1651
- ? 'no changes yet'
1652
- : `${files.length} file${files.length === 1 ? '' : 's'} changed`
1653
- + (maxScroll > 0 ? ` ${from > 0 ? `↑${from}` : ''}${from < maxScroll ? ` ↓${maxScroll - from}` : ''}`.trimEnd() : '');
1654
- return (_jsxs(Box, { flexDirection: "column", width: width, height: height, borderStyle: "round", borderColor: CYAN, paddingX: 1, flexShrink: 0, overflowY: "hidden", children: [_jsxs(Box, { justifyContent: "space-between", width: inner, children: [_jsxs(Box, { children: [_jsx(Text, { color: INK, bold: true, children: summary }), added > 0 && _jsxs(Text, { color: DIFF_ADD, children: [" +", added] }), removed > 0 && _jsxs(Text, { color: DIFF_DEL, children: [" -", removed] })] }), _jsx(Text, { color: DIM, children: "/diff" })] }), activity && _jsxs(_Fragment, { children: [_jsx(Text, { color: DIM, children: '─'.repeat(inner) }), _jsx(Box, { flexDirection: "column", height: Math.max(1, activityRows - 1), overflowY: "hidden", children: activity })] }), _jsx(Box, { flexDirection: "column", flexGrow: 1, flexShrink: 1, overflowY: "hidden", children: body }), shownFiles < files.length && (_jsx(Text, { color: DIM, children: `… ${files.length - shownFiles} more file(s) changed` }))] }));
1627
+ const scrolled = maxScroll > 0
1628
+ ? `${from > 0 ? `↑${from}` : ''}${from < maxScroll ? ` ↓${maxScroll - from}` : ''}`.trim()
1629
+ : '';
1630
+ return (_jsxs(Box, { flexDirection: "column", width: width, height: height, borderStyle: "single", borderColor: DIM, borderTop: false, borderRight: false, borderBottom: false, paddingLeft: 1, flexShrink: 0, overflowY: "hidden", children: [_jsxs(Box, { justifyContent: "space-between", width: inner, children: [_jsxs(Box, { children: [_jsx(Text, { color: INK, bold: true, children: files.length === 0 ? 'no changes yet' : `${files.length} file${files.length === 1 ? '' : 's'} changed` }), view.added > 0 && _jsxs(Text, { color: DIFF_ADD, children: [" +", view.added] }), view.removed > 0 && _jsxs(Text, { color: DIFF_DEL, children: [" -", view.removed] })] }), _jsx(Text, { color: DIM, children: "/diff off" })] }), activity && _jsxs(_Fragment, { children: [_jsx(Text, { color: DIM, children: '─'.repeat(inner) }), _jsx(Box, { flexDirection: "column", height: Math.max(1, activityRows - 1), overflowY: "hidden", children: activity })] }), _jsx(Text, { children: " " }), list, hints, d && _jsxs(_Fragment, { children: [_jsx(Text, { children: " " }), _jsx(Text, { color: DIM, children: '─'.repeat(inner) }), _jsxs(Box, { justifyContent: "space-between", width: inner, children: [_jsx(Text, { color: INK, bold: true, wrap: "truncate-start", children: d.path }), _jsxs(Text, { color: DIM, children: [d.created && d.removed === 0 ? ' new' : d.deleted ? ' deleted' : '', scrolled ? ` ${scrolled}` : ''] })] }), _jsx(Text, { color: DIM, children: '─'.repeat(inner) })] }), _jsx(Box, { flexDirection: "column", flexGrow: 1, flexShrink: 1, overflowY: "hidden", children: body })] }));
1655
1631
  }
1656
1632
  /**
1657
1633
  * Exported for one reason: so a test can mount it.
@@ -1978,7 +1954,7 @@ export function App({ config: initialConfig, clearFrame, synchronizedFrames = fa
1978
1954
  * the truth about what the change currently is.
1979
1955
  */
1980
1956
  if (changesRef.current.size) {
1981
- const all = changesRef.current.diffs(PANEL_DIFF_LINES);
1957
+ const all = changesRef.current.diffs(PANEL_DIFF_LINES, PANEL_CONTEXT);
1982
1958
  setLiveDiffs(all);
1983
1959
  /*
1984
1960
  * Open itself on the session's first change, once, unless the person has already decided.
@@ -2400,6 +2376,9 @@ export function App({ config: initialConfig, clearFrame, synchronizedFrames = fa
2400
2376
  * studying a diff should not cost your place in the conversation.
2401
2377
  */
2402
2378
  const [panelScroll, setPanelScroll] = useState(0);
2379
+ /** Which file of the panel's list is being read, and whether tests and generated files are listed. */
2380
+ const [panelFile, setPanelFile] = useState(0);
2381
+ const [panelGenerated, setPanelGenerated] = useState(false);
2403
2382
  /**
2404
2383
  * The layout the wheel listener needs, in refs.
2405
2384
  *
@@ -5887,6 +5866,24 @@ export function App({ config: initialConfig, clearFrame, synchronizedFrames = fa
5887
5866
  * needs `/mouse on`. This is the keyboard equivalent, and it is deliberately a modifier
5888
5867
  * nothing else in the composer uses, so it cannot be reached by accident mid-sentence.
5889
5868
  */
5869
+ /*
5870
+ * Alt+↑/↓ moves through the panel's list of files, as in the panel it was modelled on; the
5871
+ * file itself scrolls with Alt+PgUp/PgDn and the wheel. Alt+P/N do the same for Windows
5872
+ * Terminal, which keeps Alt+arrows for moving between its own panes. Alt+T opens the fold
5873
+ * of tests and generated files.
5874
+ */
5875
+ const prevFile = key.meta && (key.upArrow || input === 'p');
5876
+ const nextFile = key.meta && (key.downArrow || input === 'n');
5877
+ if (diffPanelOpenRef.current && (prevFile || nextFile)) {
5878
+ const last = Math.max(0, listLength(liveDiffs, panelGenerated) - 1);
5879
+ setPanelFile(v => Math.max(0, Math.min(last, v + (prevFile ? -1 : 1))));
5880
+ setPanelScroll(0);
5881
+ return;
5882
+ }
5883
+ if (diffPanelOpenRef.current && key.meta && input === 't') {
5884
+ setPanelGenerated(v => !v);
5885
+ return;
5886
+ }
5890
5887
  if (key.meta && (key.upArrow || key.downArrow)) {
5891
5888
  setPanelScroll(v => Math.max(0, Math.min(2_000, v + (key.upArrow ? -3 : 3))));
5892
5889
  return;
@@ -6884,34 +6881,25 @@ export function App({ config: initialConfig, clearFrame, synchronizedFrames = fa
6884
6881
  const gutter = Math.max(2, String(Math.max(0, ...numbers)).length);
6885
6882
  const width = Math.max(24, barWidth - gutter - 8);
6886
6883
  const header = d.created ? 'new file' : d.deleted ? 'deleted' : diffSummary(d);
6887
- const all = [];
6888
- for (const [hi, hunk] of d.hunks.entries()) {
6889
- if (hi > 0)
6890
- all.push({ key: `g${hi}`, gap: true });
6891
- for (const [li, l] of hunk.lines.entries()) {
6892
- all.push({ key: `${hi}-${li}`, gap: false, kind: l.kind,
6893
- shown: (l.kind === 'del' ? l.before : l.after) ?? undefined,
6894
- sign: l.kind === 'add' ? '+' : l.kind === 'del' ? '-' : ' ',
6895
- text: l.text });
6896
- }
6897
- }
6884
+ const lang = spanLangFor(d.path);
6885
+ /*
6886
+ * Rows from diff-layout.ts, the same the panel draws: a long line wraps with its sign on each
6887
+ * piece rather than being cut at the edge, and the band behind a changed line covers its
6888
+ * number too. The ceiling counts rows as drawn, so a wrapped line costs what it takes.
6889
+ */
6890
+ const all = diffRows({ ...d, hiddenLines: 0 }, width);
6898
6891
  // A budget of zero is meaningful: show the file and what happened to it, and none of the
6899
6892
  // body. On a short terminal those lines are the difference between a calm screen and one
6900
6893
  // that wipes itself on every keystroke.
6901
6894
  const shownLines = all.slice(0, Math.max(0, maxLines));
6902
6895
  const clipped = all.length - shownLines.length;
6903
- return (_jsxs(Box, { flexDirection: "column", paddingLeft: 2, children: [_jsxs(Box, { gap: 1, children: [_jsx(Text, { color: DIM, children: "\u2514" }), _jsx(Text, { color: CYAN, children: d.path }), _jsx(Text, { color: d.created ? DIFF_ADD : d.deleted ? DIFF_DEL : MUTED, children: header })] }), d.binary
6904
- ? _jsx(Text, { color: DIM, children: ' binary file - not shown' })
6905
- : shownLines.map(l => l.gap
6906
- // A gap between hunks stands for the untouched lines that were skipped.
6907
- ? _jsxs(Text, { color: DIM, children: [' '.repeat(gutter), " ..."] }, l.key)
6908
- : (() => {
6909
- const fg = l.kind === 'add' ? DIFF_ADD : l.kind === 'del' ? DIFF_DEL : MUTED;
6910
- const bg = l.kind === 'add' ? DIFF_ADD_BG : l.kind === 'del' ? DIFF_DEL_BG : undefined;
6911
- // Tabs would make the tinted block a ragged width, so they are expanded.
6912
- const body = `${l.sign} ${l.text}`.replace(/\t/g, ' ');
6913
- return (_jsxs(Box, { children: [_jsxs(Text, { color: DIM, children: [String(l.shown ?? '').padStart(gutter), " "] }), _jsx(Text, { color: fg, ...(bg ? { backgroundColor: bg } : {}), children: fitCells(body, width, true) })] }, l.key));
6914
- })()), clipped > 0 && (_jsxs(Text, { color: DIM, children: [' '.repeat(gutter), " ", shownLines.length === 0 ? '' : '... ', clipped, " ", shownLines.length === 0 ? 'changed lines' : 'more lines', ", shown in full when this finishes"] })), d.hiddenLines > 0 && (_jsxs(Text, { color: DIM, children: [' '.repeat(gutter), " ... ", d.hiddenLines, " more changed lines"] }))] }, key));
6896
+ return (_jsxs(Box, { flexDirection: "column", paddingLeft: 2, children: [_jsxs(Box, { gap: 1, children: [_jsx(Text, { color: DIM, children: "\u2514" }), _jsx(Text, { color: CYAN, children: d.path }), _jsx(Text, { color: d.created ? DIFF_ADD : d.deleted ? DIFF_DEL : MUTED, children: header })] }), shownLines.map((r, i) => {
6897
+ if (r.t !== 'line')
6898
+ return _jsx(Text, { color: DIM, children: ' binary file - not shown' }, i);
6899
+ const fg = r.kind === 'add' ? DIFF_ADD : r.kind === 'del' ? DIFF_DEL : DIM;
6900
+ const bg = r.kind === 'add' ? DIFF_ADD_BG : r.kind === 'del' ? DIFF_DEL_BG : undefined;
6901
+ return (_jsxs(Text, { wrap: "truncate", children: [_jsxs(Text, { color: fg, ...(bg ? { backgroundColor: bg } : {}), children: [(r.first && r.number !== null ? String(r.number) : '').padStart(gutter), ' ', r.sign, ' '] }), renderCodeLine(r.text, r.kind, width, lang)] }, i));
6902
+ }), clipped > 0 && (_jsxs(Text, { color: DIM, children: [' '.repeat(gutter), " ", shownLines.length === 0 ? '' : '... ', clipped, " ", shownLines.length === 0 ? 'changed lines' : 'more lines', ", shown in full when this finishes"] })), d.hiddenLines > 0 && (_jsxs(Text, { color: DIM, children: [' '.repeat(gutter), " ... ", d.hiddenLines, " more changed lines"] }))] }, key));
6915
6903
  };
6916
6904
  const renderStep = (step, key) => {
6917
6905
  if (step.kind === 'say') {
@@ -7533,7 +7521,7 @@ export function App({ config: initialConfig, clearFrame, synchronizedFrames = fa
7533
7521
  : '*type the number, or arrows and enter · esc refuses*',
7534
7522
  ];
7535
7523
  return (_jsx(Box, { marginTop: 1, children: _jsx(Panel, { width: panelWidth(lines, barWidth, 46), color: AMBER, title: `Permission — ${modeSpec(mode).label} mode`, children: lines }) }));
7536
- })()] }), sidePanelOpen && (_jsx(Box, { flexDirection: "column", width: diffPanelWidth, flexShrink: 0, children: diffPanelOpen && _jsx(DiffPanel, { files: liveDiffs, width: diffPanelWidth, scroll: panelScroll, height: Math.max(6, frameRows - 6), activityRows: activityPanelOpen ? 4 : 0, activity: activityPanelOpen ? _jsxs(_Fragment, { children: [_jsxs(Text, { color: CYAN, bold: true, children: ["Activity \u00B7 ", fmtElapsed(busyStart.current ? Date.now() - busyStart.current : 0)] }), _jsx(Text, { color: AMBER, wrap: "truncate", children: ask ? 'Waiting for your approval' : liveToolRef.current
7524
+ })()] }), sidePanelOpen && (_jsx(Box, { flexDirection: "column", width: diffPanelWidth, flexShrink: 0, children: diffPanelOpen && _jsx(DiffPanel, { files: liveDiffs, width: diffPanelWidth, scroll: panelScroll, selected: panelFile, showGenerated: panelGenerated, height: Math.max(6, frameRows - 6), activityRows: activityPanelOpen ? 4 : 0, activity: activityPanelOpen ? _jsxs(_Fragment, { children: [_jsxs(Text, { color: CYAN, bold: true, children: ["Activity \u00B7 ", fmtElapsed(busyStart.current ? Date.now() - busyStart.current : 0)] }), _jsx(Text, { color: AMBER, wrap: "truncate", children: ask ? 'Waiting for your approval' : liveToolRef.current
7537
7525
  ? 'Running ' + liveToolRef.current.name : thinkingRef.current.chars > 0
7538
7526
  ? 'Waiting for model · reasoning received' : 'Waiting for model' }), _jsx(Text, { color: MUTED, wrap: "truncate", children: liveToolRef.current?.detail || plan?.steps.find(s => s.state !== 'done')?.text || 'esc to interrupt' })] }) : undefined }) }))] }), picker && (() => {
7539
7527
  const list = visiblePickerItems;