@vincemakes/kiso-tui 0.13.0 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -472,9 +472,6 @@ export class Body {
472
472
  return;
473
473
  }
474
474
  this.#endMd();
475
- const last = this.#cells[this.#cells.length - 1];
476
- if (last !== undefined && last.kind === "text" && !last.done)
477
- last.done = true;
478
475
  this.#mark();
479
476
  }
480
477
  /** W14 — the turn boundary's END: the CLI calls this at the run's
@@ -573,7 +570,10 @@ export class Body {
573
570
  const turn = this.#turns.length - 1;
574
571
  const last = this.#cells[this.#cells.length - 1];
575
572
  if (last !== undefined && last.kind === "checklist" && !last.done && last.turn === turn) {
576
- if (!sameTask(last.items, items)) {
573
+ // TV-1B: the header participates in the change detection — the
574
+ // settle verdict is a header-only update over the same items,
575
+ // and an items-only guard would swallow it silently.
576
+ if (last.header !== header || !sameTask(last.items, items)) {
577
577
  Object.assign(last, { header, items });
578
578
  this.#mark();
579
579
  }
@@ -1685,40 +1685,67 @@ export class Body {
1685
1685
  // content loss).
1686
1686
  if (skip > 0 && !overlay) {
1687
1687
  const leaving = Math.max(0, skip - this.#lastSkip);
1688
- // A8b (the fresh leaving share): a leaving row whose old-screen
1689
- // copy is stalethe committed-this-frame lines (their old rows
1690
- // held the previous live/chrome) is pre-painted at its OLD row
1691
- // so the LF scroll carries it into the scrollback; the frozen
1692
- // leaving rows are already on screen and scroll as-is. The first
1693
- // overflow frame of a batch: the window's top row is the freshly
1694
- // committed line, NEVER on the old screen without the
1695
- // pre-paint the scroll pushes a blank and the line's only paint
1696
- // (the clamped march at row 1) is overwritten by its neighbor.
1697
- if (skip > frozen.length) {
1698
- const fromIdx = Math.max(0, this.#lastSkip - frozen.length);
1699
- const top = Math.min(skip - frozen.length, all.length - frozen.length);
1700
- for (let i = fromIdx; i < top; i += 1) {
1701
- out.push(`\x1b[${Math.max(1, frozen.length + i - this.#lastSkip + 1)};1H\x1b[0K${this.#checked(all[frozen.length + i], W)}`);
1688
+ // TT-1B (finding TUI2-MD-1): a burst taller than the screen can
1689
+ // NOT be staged in one pass rows placed past H are clamped by
1690
+ // the terminal itself (CUP pins to the last row), the pile keeps
1691
+ // only its last member, and the scroll pushes the wreckage: the
1692
+ // short-terminal row loss the finding measured. The transit is
1693
+ // chunked instead: each pass paints <= H leaving rows at rows
1694
+ // 1..chunk and scrolls exactly that many LFs, so every row is ON
1695
+ // the screen when its scroll slot comes up. The <= H path below
1696
+ // keeps its exact pre-round bytes (its staging never exceeds H
1697
+ // position <= skip - lastSkip = leaving).
1698
+ if (leaving > H) {
1699
+ let p = this.#lastSkip;
1700
+ while (p < skip) {
1701
+ const chunk = Math.min(skip - p, H);
1702
+ for (let k = 0; k < chunk; k += 1) {
1703
+ out.push(`\x1b[${k + 1};1H\x1b[0K${this.#checked(all[p + k], W)}`);
1704
+ }
1705
+ if (chunk < H)
1706
+ out.push(`\x1b[${chunk + 1};1H\x1b[0J`);
1707
+ out.push(`\x1b[${H};1H`);
1708
+ for (let k = 0; k < chunk; k += 1)
1709
+ out.push("\n");
1710
+ p += chunk;
1702
1711
  }
1703
1712
  }
1704
- if (leaving < skip)
1705
- out.push(`\x1b[${leaving + 1};1H\x1b[0J`);
1706
- out.push(`\x1b[${H};1H`);
1707
- // TUI2-R2pre scroll the rows that LEFT THE WINDOW SINCE THE
1708
- // LAST FRAME (`leaving`), never `skip`, which is the window's
1709
- // ABSOLUTE top. Scrolling the absolute top re-pushed the whole
1710
- // history's worth of rows on EVERY full redraw and a live-region
1711
- // shrink takes this path, so that was most frames of a real
1712
- // session. The ED above had just blanked everything below row
1713
- // `leaving`, so what those surplus LFs carried into the terminal's
1714
- // scrollback was blank rows: the large blank bands mid-history of
1715
- // the owner's field report. The SCREEN never showed it because the
1716
- // repaint below covers every row 1..H (the V6-1 rule), and the
1717
- // house emulator drops scrolled rows on the floor so no gate
1718
- // could see it either. Measured on the 5-turn 80x24 repro: the
1719
- // scrollback went from 162 blank rows of 168 to 14 of 52.
1720
- for (let i = 0; i < leaving; i += 1)
1721
- out.push("\n");
1713
+ else {
1714
+ // A8b (the fresh leaving share): a leaving row whose old-screen
1715
+ // copy is stale — the committed-this-frame lines (their old rows
1716
+ // held the previous live/chrome) is pre-painted at its OLD row
1717
+ // so the LF scroll carries it into the scrollback; the frozen
1718
+ // leaving rows are already on screen and scroll as-is. The first
1719
+ // overflow frame of a batch: the window's top row is the freshly
1720
+ // committed line, NEVER on the old screen without the
1721
+ // pre-paint the scroll pushes a blank and the line's only paint
1722
+ // (the clamped march at row 1) is overwritten by its neighbor.
1723
+ if (skip > frozen.length) {
1724
+ const fromIdx = Math.max(0, this.#lastSkip - frozen.length);
1725
+ const top = Math.min(skip - frozen.length, all.length - frozen.length);
1726
+ for (let i = fromIdx; i < top; i += 1) {
1727
+ out.push(`\x1b[${Math.max(1, frozen.length + i - this.#lastSkip + 1)};1H\x1b[0K${this.#checked(all[frozen.length + i], W)}`);
1728
+ }
1729
+ }
1730
+ if (leaving < skip)
1731
+ out.push(`\x1b[${leaving + 1};1H\x1b[0J`);
1732
+ out.push(`\x1b[${H};1H`);
1733
+ // TUI2-R2pre ② — scroll the rows that LEFT THE WINDOW SINCE THE
1734
+ // LAST FRAME (`leaving`), never `skip`, which is the window's
1735
+ // ABSOLUTE top. Scrolling the absolute top re-pushed the whole
1736
+ // history's worth of rows on EVERY full redraw — and a live-region
1737
+ // shrink takes this path, so that was most frames of a real
1738
+ // session. The ED above had just blanked everything below row
1739
+ // `leaving`, so what those surplus LFs carried into the terminal's
1740
+ // scrollback was blank rows: the large blank bands mid-history of
1741
+ // the owner's field report. The SCREEN never showed it because the
1742
+ // repaint below covers every row 1..H (the V6-1 rule), and the
1743
+ // house emulator drops scrolled rows on the floor — so no gate
1744
+ // could see it either. Measured on the 5-turn 80x24 repro: the
1745
+ // scrollback went from 162 blank rows of 168 to 14 of 52.
1746
+ for (let i = 0; i < leaving; i += 1)
1747
+ out.push("\n");
1748
+ }
1722
1749
  }
1723
1750
  if (!overlay)
1724
1751
  this.#lastSkip = skip;
@@ -1785,6 +1812,15 @@ export class Body {
1785
1812
  const overlay = this.#overlayFrame;
1786
1813
  const skip = overlay ? this.#lastSkip : Math.max(0, this.#committedLines + liveLines.length + CHROME_ROWS + inputExtra + queueRows.length + menuRows.length - H);
1787
1814
  const leaving = overlay ? 0 : Math.max(0, skip - this.#lastSkip);
1815
+ // TT-1B (finding TUI2-MD-1): the steady staging has the same
1816
+ // past-H clamp as the full path had — a window that moved more than
1817
+ // a screenful in one frame cannot transit on this path at all.
1818
+ // Delegate the oversized frame to #drawFull's chunked emission (the
1819
+ // full path repaints every row, so the hand-off is self-contained).
1820
+ if (leaving > H) {
1821
+ this.#drawFull(out, W, H, liveTop, liveLines, queueRows, menuRows, editor);
1822
+ return;
1823
+ }
1788
1824
  // the jump to the bottom row H, then N real LFs scroll the screen
1789
1825
  // exactly N rows — ONE per committed line (the bookkeeping; the
1790
1826
  // stale 1B anchor jumped to H−1 and the N LFs scrolled only N−1 —
@@ -1996,9 +2032,6 @@ export class Body {
1996
2032
  * tail block closes and its cell becomes commit-eligible. */
1997
2033
  #closeOpenText() {
1998
2034
  this.#endMd();
1999
- const last = this.#cells[this.#cells.length - 1];
2000
- if (last !== undefined && last.kind === "text" && !last.done)
2001
- last.done = true;
2002
2035
  }
2003
2036
  /** Close an open thinking cell when a new cell starts. */
2004
2037
  #closeOpenThinking() {
package/dist/index.d.ts CHANGED
@@ -14,7 +14,7 @@ export { bannerLines, COLOR_OFF, COLOR_ON, escapeTerminal, foldResult, foldThink
14
14
  export { editFileDiff, truncateDiff, writeFileDiff, type DiffLine, type DiffResult } from "./diff.js";
15
15
  export { STATUS_GLYPHS, cacheHitPct, idleStatus, runningStatus, type StatusMeter } from "./status.js";
16
16
  export { contextRows, contextUnavailableRows, type ContextLedger } from "./context-ledger.js";
17
- export { interactivePrompt, projectTrustRows, projectTrustView, projectUntrustedNote, uncertainView, type TrustArtifact } from "./strings.js";
17
+ export { interactivePrompt, projectTrustRows, projectTrustView, projectUntrustedNote, uncertainView, verifyOfferView, type TrustArtifact } from "./strings.js";
18
18
  export { AT_CAP, AT_SKIP, AT_VISIBLE, atEmbed, atFilter, atPanelRows, atWindow, bandHeader, longestRun, type AtItem, type AtMatch } from "./at-picker.js";
19
19
  export { BADGE_GLYPH, idColumn, sessionAge, sessionBadge, sessionCounterRow, sessionFilter, sessionListFooter, sessionListRow, sessionNote, sessionPickerRows, sessionRow, type SessionCardView, type SessionPickState, } from "./session-picker.js";
20
20
  export { ASK_HEADER_CAP, ASK_MAX_OPTIONS, ASK_MAX_QUESTIONS, ASK_MIN_OPTIONS, askAffordance, askAnswers, askBlockRows, askCommitCustom, askDeclineAll, askDeclineList, askKey, askLeadPlain, askStart, askStatus, askView, type AskAnswer, type AskOption, type AskQuestion, type AskResult, type AskRuntime, type AskSpec, type AskStep, } from "./ask-panel.js";
package/dist/index.js CHANGED
@@ -26,7 +26,7 @@ export { contextRows, contextUnavailableRows } from "./context-ledger.js";
26
26
  // KC3 §1 (the extraction): the human-facing strings — the prompt, the
27
27
  // project-trust listing/view/note, the uncertain execution's view. The
28
28
  // FLOW (who is asked, what a verdict means) stays in the cli.
29
- export { interactivePrompt, projectTrustRows, projectTrustView, projectUntrustedNote, uncertainView } from "./strings.js";
29
+ export { interactivePrompt, projectTrustRows, projectTrustView, projectUntrustedNote, uncertainView, verifyOfferView } from "./strings.js";
30
30
  // KC3 §3/§5: the @ file picker's pure half — the subsequence filter, the
31
31
  // deterministic rank, and the ONE cap the CLI's file source shares.
32
32
  export { AT_CAP, AT_SKIP, AT_VISIBLE, atEmbed, atFilter, atPanelRows, atWindow, bandHeader, longestRun } from "./at-picker.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vincemakes/kiso-tui",
3
- "version": "0.13.0",
3
+ "version": "0.15.0",
4
4
  "description": "kiso tui — the pure terminal layer (cell renderer, dock, raw editor, diff, palette). Zero runtime dependencies: input is data, output is bytes.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -35,6 +35,6 @@
35
35
  },
36
36
  "homepage": "https://github.com/vincemakes/kiso/tree/main/packages/tui#readme",
37
37
  "dependencies": {
38
- "@vincemakes/kiso-tui-cells": "0.13.0"
38
+ "@vincemakes/kiso-tui-cells": "0.15.0"
39
39
  }
40
40
  }