@kenkaiiii/ggcoder 4.8.2 → 4.8.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/dist/core/prompt-commands.d.ts.map +1 -1
  2. package/dist/core/prompt-commands.js +20 -7
  3. package/dist/core/prompt-commands.js.map +1 -1
  4. package/dist/ui/App.d.ts +16 -0
  5. package/dist/ui/App.d.ts.map +1 -1
  6. package/dist/ui/App.js +53 -16
  7. package/dist/ui/App.js.map +1 -1
  8. package/dist/ui/atomic-flush-regression.test.d.ts +2 -0
  9. package/dist/ui/atomic-flush-regression.test.d.ts.map +1 -0
  10. package/dist/ui/atomic-flush-regression.test.js +218 -0
  11. package/dist/ui/atomic-flush-regression.test.js.map +1 -0
  12. package/dist/ui/components/AssistantMessage.test.js +25 -0
  13. package/dist/ui/components/AssistantMessage.test.js.map +1 -1
  14. package/dist/ui/components/Markdown.d.ts.map +1 -1
  15. package/dist/ui/components/Markdown.js +9 -1
  16. package/dist/ui/components/Markdown.js.map +1 -1
  17. package/dist/ui/footer-jump-regression.test.js +56 -9
  18. package/dist/ui/footer-jump-regression.test.js.map +1 -1
  19. package/dist/ui/frame-clip-regression.test.d.ts +2 -0
  20. package/dist/ui/frame-clip-regression.test.d.ts.map +1 -0
  21. package/dist/ui/frame-clip-regression.test.js +215 -0
  22. package/dist/ui/frame-clip-regression.test.js.map +1 -0
  23. package/dist/ui/hooks/useTranscriptHistory.d.ts +9 -1
  24. package/dist/ui/hooks/useTranscriptHistory.d.ts.map +1 -1
  25. package/dist/ui/hooks/useTranscriptHistory.js +34 -19
  26. package/dist/ui/hooks/useTranscriptHistory.js.map +1 -1
  27. package/dist/ui/live-item-flush.d.ts +21 -0
  28. package/dist/ui/live-item-flush.d.ts.map +1 -1
  29. package/dist/ui/live-item-flush.js +35 -0
  30. package/dist/ui/live-item-flush.js.map +1 -1
  31. package/dist/ui/live-item-flush.test.js +72 -1
  32. package/dist/ui/live-item-flush.test.js.map +1 -1
  33. package/dist/ui/render.d.ts.map +1 -1
  34. package/dist/ui/render.js +126 -1
  35. package/dist/ui/render.js.map +1 -1
  36. package/dist/ui/startup-banner-order.test.d.ts +2 -0
  37. package/dist/ui/startup-banner-order.test.d.ts.map +1 -0
  38. package/dist/ui/startup-banner-order.test.js +105 -0
  39. package/dist/ui/startup-banner-order.test.js.map +1 -0
  40. package/dist/ui/streaming-flush-bounce.test.js +41 -20
  41. package/dist/ui/streaming-flush-bounce.test.js.map +1 -1
  42. package/dist/ui/testing/screen-recorder.d.ts.map +1 -1
  43. package/dist/ui/testing/screen-recorder.js +11 -1
  44. package/dist/ui/testing/screen-recorder.js.map +1 -1
  45. package/package.json +4 -4
@@ -0,0 +1,215 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // Regression for the mid-screen footer "strand" in scrollback mode.
3
+ //
4
+ // If any single frame reaches terminal height (the app's estimate-based
5
+ // live-area clamp can undercount — emoji widths, wrapped footer lines,
6
+ // transient tool-panel rows), Ink records lastOutputHeight >= rows. On the
7
+ // next SHORTER frame, log-update's eraseLines clamps at the screen top and
8
+ // rewrites the frame top-anchored: the footer lands mid-screen with blank
9
+ // rows below it and stays there. The patched ink's clipFrameToTerminalHeight
10
+ // option (set by ggcoder's scrollback-mode INK_OPTIONS) clips frames to
11
+ // rows - 2 so that poisoned state can never be entered. This test renders a
12
+ // frame that overflows the terminal, then shrinks it, and asserts the footer
13
+ // ends at the bottom of the written content — not stranded above blank rows.
14
+ import React from "react";
15
+ import { render, Text, Box } from "ink";
16
+ import { describe, expect, it } from "vitest";
17
+ import stripAnsi from "strip-ansi";
18
+ import { ScreenRecorder, makeRecordingStdout } from "./testing/screen-recorder.js";
19
+ const COLUMNS = 80;
20
+ const ROWS = 24;
21
+ const FOOTER = "SIM_FOOTER_BOTTOM";
22
+ function Frame({ bodyRows }) {
23
+ return (_jsxs(Box, { flexDirection: "column", width: COLUMNS, children: [Array.from({ length: bodyRows }, (_, i) => (_jsx(Text, { children: `BODY_${String(i + 1).padStart(2, "0")}` }, i))), _jsx(Text, { children: "SIM_INPUT" }), _jsx(Text, { children: FOOTER })] }));
24
+ }
25
+ async function tick() {
26
+ await new Promise((resolve) => setTimeout(resolve, 45));
27
+ }
28
+ describe("scrollback frame clip (clipFrameToTerminalHeight)", () => {
29
+ it("clips overflow frames and never strands the footer above blank rows", async () => {
30
+ const recorder = new ScreenRecorder({ columns: COLUMNS, rows: ROWS });
31
+ const stdout = makeRecordingStdout(recorder);
32
+ // Overflow: 30 body rows + 2 control rows > 24 terminal rows.
33
+ const mounted = render(_jsx(Frame, { bodyRows: 30 }), {
34
+ stdout,
35
+ columns: COLUMNS,
36
+ rows: ROWS,
37
+ patchConsole: false,
38
+ maxFps: 1000,
39
+ // Mirrors ggcoder's scrollback-mode INK_OPTIONS.
40
+ clipFrameToTerminalHeight: true,
41
+ });
42
+ await tick();
43
+ // The clip must keep the frame under terminal height: with rows - 2 = 22
44
+ // frame rows, the oldest body rows are dropped (BODY_01..BODY_10 clipped,
45
+ // mirroring the app's bottom-anchored live-area clamp).
46
+ const tallLines = recorder.viewportLines().map((line) => stripAnsi(line));
47
+ expect(tallLines.some((line) => line.includes("BODY_01"))).toBe(false);
48
+ expect(tallLines.some((line) => line.includes("BODY_30"))).toBe(true);
49
+ expect(tallLines.some((line) => line.includes(FOOTER))).toBe(true);
50
+ // Shrink: the turn ends and the frame collapses to controls + done row.
51
+ // Without the clip this is the poisoned transition (eraseLines clamps at
52
+ // the screen top, frame rewrites top-anchored, footer stranded mid-screen
53
+ // above a block of blank rows).
54
+ mounted.rerender(_jsx(Frame, { bodyRows: 3 }));
55
+ await tick();
56
+ mounted.unmount();
57
+ const lines = recorder.viewportLines().map((line) => stripAnsi(line));
58
+ const footerIdx = lines.findIndex((line) => line.includes(FOOTER));
59
+ const lastNonBlank = lines.reduce((acc, line, i) => (line.trim().length > 0 ? i : acc), -1);
60
+ expect(footerIdx, "footer present after shrink").toBeGreaterThanOrEqual(0);
61
+ expect(lastNonBlank, "no blank rows below the footer (no strand)").toBe(footerIdx);
62
+ });
63
+ });
64
+ describe("scrollback bottom anchoring (anchorFrameToBottom)", () => {
65
+ it("keeps the footer's absolute row fixed when the frame shrinks", async () => {
66
+ const recorder = new ScreenRecorder({ columns: COLUMNS, rows: ROWS });
67
+ const stdout = makeRecordingStdout(recorder);
68
+ // A frame that fits comfortably (12 body + 2 control rows).
69
+ const mounted = render(_jsx(Frame, { bodyRows: 12 }), {
70
+ stdout,
71
+ columns: COLUMNS,
72
+ rows: ROWS,
73
+ patchConsole: false,
74
+ maxFps: 1000,
75
+ // Mirrors ggcoder's scrollback-mode INK_OPTIONS.
76
+ clipFrameToTerminalHeight: true,
77
+ anchorFrameToBottom: true,
78
+ });
79
+ await tick();
80
+ const footerRowAt = () => stripAnsi(recorder.fullText())
81
+ .split("\n")
82
+ .findIndex((line) => line.includes(FOOTER));
83
+ const tallFooterRow = footerRowAt();
84
+ expect(tallFooterRow, "footer present while tall").toBeGreaterThanOrEqual(0);
85
+ // The frame shrinks by 9 rows with no compensating scrollback bytes — the
86
+ // tool-panel-hide / status-swap / finalize scenario. Without anchoring,
87
+ // the footer would move UP by 9 rows; with it, blank pad lines land above
88
+ // the frame and the footer's absolute row stays exactly where it was.
89
+ mounted.rerender(_jsx(Frame, { bodyRows: 3 }));
90
+ await tick();
91
+ expect(footerRowAt(), "footer row unchanged after shrink").toBe(tallFooterRow);
92
+ // Growing again must CONSUME the pad debt: the growth overwrites the
93
+ // blank pad rows above the frame (cursor-up) instead of scrolling new
94
+ // rows, so the footer stays exactly put — not pushed further down — and
95
+ // the gap doesn't survive as permanent whitespace in the transcript.
96
+ mounted.rerender(_jsx(Frame, { bodyRows: 8 }));
97
+ await tick();
98
+ expect(footerRowAt(), "footer row unchanged on regrow (pad consumed)").toBe(tallFooterRow);
99
+ // No stranded blank gap: between the first body row and the footer, every
100
+ // row is real content (the regrown frame reclaimed the pad rows).
101
+ const lines = stripAnsi(recorder.fullText()).split("\n");
102
+ const firstBody = lines.findIndex((line) => line.includes("BODY_01"));
103
+ const footerIdx = lines.findIndex((line) => line.includes(FOOTER));
104
+ const blanksInside = lines
105
+ .slice(firstBody, footerIdx)
106
+ .filter((line) => line.trim().length === 0).length;
107
+ expect(blanksInside, "no permanent blank gap inside the frame region").toBe(0);
108
+ mounted.unmount();
109
+ });
110
+ it("lets the footer return up at idle (anchor inactive — slash menu close)", async () => {
111
+ const recorder = new ScreenRecorder({ columns: COLUMNS, rows: ROWS });
112
+ const stdout = makeRecordingStdout(recorder);
113
+ const mounted = render(_jsx(Frame, { bodyRows: 4 }), {
114
+ stdout,
115
+ columns: COLUMNS,
116
+ rows: ROWS,
117
+ patchConsole: false,
118
+ maxFps: 1000,
119
+ clipFrameToTerminalHeight: true,
120
+ anchorFrameToBottom: true,
121
+ });
122
+ const patched = mounted;
123
+ expect(typeof patched.setFrameAnchorActive, "runtime toggle exposed").toBe("function");
124
+ // Idle: App turns pad creation off (agent not running).
125
+ patched.setFrameAnchorActive(false);
126
+ await tick();
127
+ const footerRowAt = () => stripAnsi(recorder.fullText())
128
+ .split("\n")
129
+ .findIndex((line) => line.includes(FOOTER));
130
+ const baseFooterRow = footerRowAt();
131
+ // Slash menu opens: frame grows by 8 rows — footer moves down.
132
+ mounted.rerender(_jsx(Frame, { bodyRows: 12 }));
133
+ await tick();
134
+ const openFooterRow = footerRowAt();
135
+ expect(openFooterRow, "footer moved down while menu open").toBeGreaterThan(baseFooterRow);
136
+ // Slash menu closes: with the anchor INACTIVE the shrink is symmetric —
137
+ // the footer returns to its pre-menu row and NO pad rows are created.
138
+ mounted.rerender(_jsx(Frame, { bodyRows: 4 }));
139
+ await tick();
140
+ expect(footerRowAt(), "footer returned up after menu close").toBe(baseFooterRow);
141
+ // No whitespace was injected above the frame: the row above BODY_01 in
142
+ // the buffer is not a blank pad line.
143
+ const lines = stripAnsi(recorder.fullText()).split("\n");
144
+ const firstBody = lines.findIndex((line) => line.includes("BODY_01"));
145
+ const blanksInside = lines
146
+ .slice(firstBody, footerRowAt())
147
+ .filter((line) => line.trim().length === 0).length;
148
+ expect(blanksInside, "no pad rows created at idle").toBe(0);
149
+ mounted.unmount();
150
+ });
151
+ it("backfills a bottom-pinned idle shrink with the transcript tail", async () => {
152
+ const recorder = new ScreenRecorder({ columns: COLUMNS, rows: ROWS });
153
+ const stdout = makeRecordingStdout(recorder);
154
+ const mounted = render(_jsx(Frame, { bodyRows: 4 }), {
155
+ stdout,
156
+ columns: COLUMNS,
157
+ rows: ROWS,
158
+ patchConsole: false,
159
+ maxFps: 1000,
160
+ clipFrameToTerminalHeight: true,
161
+ anchorFrameToBottom: true,
162
+ });
163
+ const patched = mounted;
164
+ patched.setFrameAnchorActive(false);
165
+ patched.setFrameShrinkBackfill((rows) => Array.from({ length: rows }, (_, i) => `BACKFILL_${String(i + 1).padStart(2, "0")}`).join("\n"));
166
+ await tick();
167
+ // Fill the screen above the frame with transcript so the frame bottom is
168
+ // pinned at the screen bottom — the chat-in-progress shape.
169
+ patched.insertBeforeFrame(Array.from({ length: ROWS }, (_, i) => `HIST_${String(i + 1).padStart(2, "0")}`).join("\n") +
170
+ "\n");
171
+ // The insert's forced-render fallback fires after 100ms — wait it out so
172
+ // the pinned state is committed before the menu opens.
173
+ await tick();
174
+ await tick();
175
+ await tick();
176
+ const footerRowAt = () => stripAnsi(recorder.fullText())
177
+ .split("\n")
178
+ .findIndex((line) => line.includes(FOOTER));
179
+ const pinnedFooterRow = footerRowAt();
180
+ // Slash menu opens: bottom-pinned idle growth must NOT scroll the
181
+ // terminal — ink repaints the screen in place (cursor home + erase-down +
182
+ // shorter tail + taller frame). A scroll pushes a screenful into native
183
+ // scrollback, and every open/close cycle would deposit one more duplicate
184
+ // copy of those rows (the repeated half-banners). In-place repaint
185
+ // appends NOTHING to the recorder's cumulative buffer — a scroll would
186
+ // grow it by the frame-height delta.
187
+ void pinnedFooterRow;
188
+ const bufferLenBeforeOpen = stripAnsi(recorder.fullText()).split("\n").length;
189
+ mounted.rerender(_jsx(Frame, { bodyRows: 12 }));
190
+ await tick();
191
+ const openLines = stripAnsi(recorder.fullText()).split("\n");
192
+ expect(openLines.length, "menu open repainted in place (no scroll)").toBe(bufferLenBeforeOpen);
193
+ expect(openLines.some((line) => line.includes("BACKFILL_")), "tail visible above the open menu").toBe(true);
194
+ // Slash menu closes: same in-place repaint — taller tail + shorter frame.
195
+ mounted.rerender(_jsx(Frame, { bodyRows: 4 }));
196
+ await tick();
197
+ expect(stripAnsi(recorder.fullText()).split("\n").length, "menu close repainted in place (no scroll)").toBe(bufferLenBeforeOpen);
198
+ // Inspect the visible screen (bottom ROWS of the buffer): footer at the
199
+ // bottom, tail above the frame, no blanks, no duplicated rows.
200
+ const screen = recorder.viewportLines().map((line) => stripAnsi(line));
201
+ const screenFooterIdx = screen.findIndex((line) => line.includes(FOOTER));
202
+ expect(screenFooterIdx, "footer present after repaint").toBeGreaterThanOrEqual(0);
203
+ const lastNonBlank = screen.reduce((acc, line, i) => (line.trim().length > 0 ? i : acc), -1);
204
+ expect(lastNonBlank, "footer is the last content row").toBe(screenFooterIdx);
205
+ // Everything above the frame is transcript tail — each row appears
206
+ // exactly once (no duplicated banner/prompt rows) and none are blank.
207
+ const frameTop = screenFooterIdx - 6; // 4 body + 2 control rows
208
+ const aboveFrame = screen.slice(0, frameTop);
209
+ expect(aboveFrame.every((line) => line.includes("BACKFILL_")), "all rows above the frame are backfill transcript").toBe(true);
210
+ const uniqueRows = new Set(aboveFrame);
211
+ expect(uniqueRows.size, "no duplicated rows above the frame").toBe(aboveFrame.length);
212
+ mounted.unmount();
213
+ });
214
+ });
215
+ //# sourceMappingURL=frame-clip-regression.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"frame-clip-regression.test.js","sourceRoot":"","sources":["../../src/ui/frame-clip-regression.test.tsx"],"names":[],"mappings":"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"}
@@ -20,6 +20,14 @@ interface UseTranscriptHistoryOptions<TItem extends TranscriptHistoryItem> {
20
20
  terminalHistoryPrinter?: TranscriptHistoryPrinter<TItem>;
21
21
  terminalHistoryContext: TerminalHistoryContext;
22
22
  writeStdout: (data: string) => void;
23
+ /**
24
+ * Atomic scrollback enqueue (patched Ink `insertBeforeFrame`). Bytes passed
25
+ * here are NOT written immediately — they are folded into the next Ink frame
26
+ * write, so the live-frame shrink and the scrollback insert reach the
27
+ * terminal in ONE write and the footer never jumps. Falls back to
28
+ * `writeStdout` (erase frame → write → restore frame) when absent.
29
+ */
30
+ enqueueStdout?: (data: string) => void;
23
31
  sessionPathRef: React.RefObject<string | undefined>;
24
32
  sessionManagerRef: React.RefObject<SessionManager | null>;
25
33
  sessionStore?: SessionStoreLike<TItem>;
@@ -43,6 +51,6 @@ export interface UseTranscriptHistoryResult<TItem extends TranscriptHistoryItem>
43
51
  finalizeSubmittedUserItem: (item: TItem, deferredLiveItems?: readonly TItem[]) => void;
44
52
  clearPendingHistory: () => void;
45
53
  }
46
- export declare function useTranscriptHistory<TItem extends TranscriptHistoryItem = CompletedItem>({ terminalHistoryPrinter, terminalHistoryContext, writeStdout, sessionPathRef, sessionManagerRef, sessionStore, history, setHistory, setLiveItems, compactHistoryItems, persistDisplayItem, trimFlushItems, }: UseTranscriptHistoryOptions<TItem>): UseTranscriptHistoryResult<TItem>;
54
+ export declare function useTranscriptHistory<TItem extends TranscriptHistoryItem = CompletedItem>({ terminalHistoryPrinter, terminalHistoryContext, writeStdout, enqueueStdout, sessionPathRef, sessionManagerRef, sessionStore, history, setHistory, setLiveItems, compactHistoryItems, persistDisplayItem, trimFlushItems, }: UseTranscriptHistoryOptions<TItem>): UseTranscriptHistoryResult<TItem>;
47
55
  export {};
48
56
  //# sourceMappingURL=useTranscriptHistory.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"useTranscriptHistory.d.ts","sourceRoot":"","sources":["../../../src/ui/hooks/useTranscriptHistory.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"useTranscriptHistory.d.ts","sourceRoot":"","sources":["../../../src/ui/hooks/useTranscriptHistory.ts"],"names":[],"mappings":"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"}
@@ -2,7 +2,7 @@ import { useCallback, useEffect, useLayoutEffect, useRef, useState } from "react
2
2
  import { DISPLAY_ITEM_CUSTOM_KIND } from "../../core/session-manager.js";
3
3
  import { compactHistory } from "../item-helpers.js";
4
4
  import { trimFlushedItems } from "../live-item-flush.js";
5
- export function useTranscriptHistory({ terminalHistoryPrinter, terminalHistoryContext, writeStdout, sessionPathRef, sessionManagerRef, sessionStore, history, setHistory, setLiveItems, compactHistoryItems = (items) => compactHistory(items), persistDisplayItem, trimFlushItems = (items) => trimFlushedItems(items), }) {
5
+ export function useTranscriptHistory({ terminalHistoryPrinter, terminalHistoryContext, writeStdout, enqueueStdout, sessionPathRef, sessionManagerRef, sessionStore, history, setHistory, setLiveItems, compactHistoryItems = (items) => compactHistory(items), persistDisplayItem, trimFlushItems = (items) => trimFlushedItems(items), }) {
6
6
  const terminalHistoryContextRef = useRef(terminalHistoryContext);
7
7
  const pendingHistoryFlushRef = useRef([]);
8
8
  const drainedHistoryFlushRef = useRef([]);
@@ -20,14 +20,20 @@ export function useTranscriptHistory({ terminalHistoryPrinter, terminalHistoryCo
20
20
  return;
21
21
  terminalHistoryPrinter.print(items, terminalHistoryContextRef.current, {
22
22
  ...options,
23
- write: writeStdout,
23
+ write: enqueueStdout ?? writeStdout,
24
24
  });
25
- }, [terminalHistoryPrinter, writeStdout]);
25
+ }, [enqueueStdout, terminalHistoryPrinter, writeStdout]);
26
26
  const queueFlush = useCallback((items) => {
27
27
  const flushed = trimFlushItems(items);
28
28
  if (flushed.length === 0)
29
29
  return;
30
30
  pendingHistoryFlushRef.current = [...pendingHistoryFlushRef.current, ...flushed];
31
+ // Render the rows to ANSI and enqueue the bytes NOW, before any state
32
+ // update. With the patched `insertBeforeFrame` the enqueue is passive (no
33
+ // terminal write), so the bytes ride the very next frame write — the one
34
+ // produced by the batched setLiveItems/generation updates below, which is
35
+ // also the write that shrinks the live frame. One write = no footer jump.
36
+ printHistoryItems(flushed);
31
37
  const sessionPath = sessionPathRef.current;
32
38
  const sessionManager = sessionManagerRef.current;
33
39
  if (sessionPath && sessionManager) {
@@ -49,26 +55,37 @@ export function useTranscriptHistory({ terminalHistoryPrinter, terminalHistoryCo
49
55
  const queuedIds = new Set(items.map((item) => item.id));
50
56
  sessionStore.liveItems = (sessionStore.liveItems ?? []).filter((item) => !queuedIds.has(item.id));
51
57
  }
58
+ // Remove the flushed rows from the live frame in the SAME React batch as
59
+ // the generation bump. Ink emits one frame write per commit (throttled,
60
+ // leading edge), so deferring this shrink to a later effect would split
61
+ // enqueue and shrink across two writes — the second one (shrink without
62
+ // compensating scrollback bytes) is exactly what strands the footer.
63
+ const flushedIds = new Set(flushed.map((item) => item.id));
64
+ setLiveItems((prev) => prev.filter((item) => !flushedIds.has(item.id)));
52
65
  setHistoryFlushGeneration((generation) => generation + 1);
53
- }, [persistDisplayItem, sessionManagerRef, sessionPathRef, sessionStore, trimFlushItems]);
66
+ }, [
67
+ persistDisplayItem,
68
+ printHistoryItems,
69
+ sessionManagerRef,
70
+ sessionPathRef,
71
+ sessionStore,
72
+ setLiveItems,
73
+ trimFlushItems,
74
+ ]);
54
75
  useEffect(() => {
55
76
  printHistoryItems(history);
56
77
  }, [history, printHistoryItems]);
57
78
  useLayoutEffect(() => {
79
+ // Printing and live-row removal already happened synchronously inside
80
+ // queueFlush (they must batch into the commit that carries the enqueued
81
+ // scrollback bytes). This effect only moves the queue along so the
82
+ // follow-up effect below can fold the flushed rows into React history.
58
83
  const flushed = pendingHistoryFlushRef.current;
59
84
  if (flushed.length === 0)
60
85
  return;
61
- // Keep the visible row continuous during finalization: write the completed
62
- // transcript into terminal history while Ink still knows about the current
63
- // live frame, then remove those live rows. Doing this in the opposite order
64
- // collapses the live frame to the controls first, so a large final history
65
- // write scrolls underneath the footer/input and visibly pushes them upward.
66
86
  pendingHistoryFlushRef.current = [];
67
87
  drainedHistoryFlushRef.current = flushed;
68
- printHistoryItems(flushed);
69
- const flushedIds = new Set(flushed.map((item) => item.id));
70
- setLiveItems((prev) => prev.filter((item) => !flushedIds.has(item.id)));
71
- }, [historyFlushGeneration, printHistoryItems, setLiveItems]);
88
+ }, [historyFlushGeneration]);
72
89
  useEffect(() => {
73
90
  const flushed = drainedHistoryFlushRef.current;
74
91
  if (flushed.length === 0)
@@ -89,14 +106,12 @@ export function useTranscriptHistory({ terminalHistoryPrinter, terminalHistoryCo
89
106
  streamedAssistantFlushRef.current = { flushedChars: 0, text: "" };
90
107
  const priorLiveItems = trimFlushItems([...deferredLiveItems]);
91
108
  const finalizedItems = [...priorLiveItems, item];
109
+ // queueFlush renders + enqueues the rows synchronously (the printer
110
+ // dedupes by id), so the deferred final assistant output is anchored in
111
+ // scrollback before the next prompt clears the live frame.
92
112
  queueFlush(finalizedItems);
93
- // Print synchronously so deferred final assistant output is anchored in
94
- // scrollback before the next prompt clears the live frame. The queued flush
95
- // still persists and updates React history; the printer dedupes by id when
96
- // the effect drains the queue.
97
- printHistoryItems(finalizedItems);
98
113
  setLiveItems([]);
99
- }, [printHistoryItems, queueFlush, setLiveItems, trimFlushItems]);
114
+ }, [queueFlush, setLiveItems, trimFlushItems]);
100
115
  const clearPendingHistory = useCallback(() => {
101
116
  pendingHistoryFlushRef.current = [];
102
117
  terminalHistoryPrinter?.clear();
@@ -1 +1 @@
1
- {"version":3,"file":"useTranscriptHistory.js","sourceRoot":"","sources":["../../../src/ui/hooks/useTranscriptHistory.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"useTranscriptHistory.js","sourceRoot":"","sources":["../../../src/ui/hooks/useTranscriptHistory.ts"],"names":[],"mappings":"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"}
@@ -48,6 +48,27 @@ export declare function flushOverflow<T extends FlushableItem>(liveItems: T[]):
48
48
  flushed: T[];
49
49
  remaining: T[];
50
50
  };
51
+ /**
52
+ * When a turn finalizes, decide how many leading items must be flushed to
53
+ * scrollback immediately because the pinned frame is too tall for the live
54
+ * area. Finalized items lose the streaming-time table/code-block clamp, so a
55
+ * too-tall pinned frame exceeds the terminal and Ink can only paint its
56
+ * bottom rows — the top of the response (e.g. a table's header) is never
57
+ * painted anywhere until the next turn's flush finally writes it to
58
+ * scrollback.
59
+ *
60
+ * The budget is CUMULATIVE: a turn segmented on [DONE:N] markers pins several
61
+ * assistant items whose individual heights fit while their sum overflows.
62
+ * Walking from the end, keep the largest suffix that fits within
63
+ * `liveAreaRows` and flush the leading complement — a contiguous prefix, so
64
+ * transcript order is preserved. An item taller than the whole budget flushes
65
+ * itself and everything before it. Returns 0 when everything fits or the
66
+ * live area hasn't been measured yet.
67
+ */
68
+ export declare function countOversizedFlushItems(items: readonly {
69
+ kind: string;
70
+ text?: string;
71
+ }[], estimateRows: (text: string) => number, liveAreaRows: number): number;
51
72
  /**
52
73
  * Called when `onTurnEnd` fires with a tool_use stop reason (LLM responded
53
74
  * with only tool calls, no text). Flushes all items IF none are still pending
@@ -1 +1 @@
1
- {"version":3,"file":"live-item-flush.d.ts","sourceRoot":"","sources":["../../src/ui/live-item-flush.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,iDAAiD;AACjD,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;CACZ;AAED;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,MAAM,CAAC;AAUrC;;;GAGG;AACH,wBAAgB,YAAY,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,GAAG,CAAC,EAAE,CAG/C;AAQD;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,aAAa,EAAE,KAAK,EAAE,CAAC,EAAE,GAAG,CAAC,EAAE,CA0DzE;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,CAAC,SAAS,aAAa,EAAE,SAAS,EAAE,CAAC,EAAE,GAAG,CAAC,EAAE,CAE5E;AAaD,wBAAgB,aAAa,CAAC,CAAC,SAAS,aAAa,EACnD,SAAS,EAAE,CAAC,EAAE,GACb;IAAE,OAAO,EAAE,CAAC,EAAE,CAAC;IAAC,SAAS,EAAE,CAAC,EAAE,CAAA;CAAE,CAmClC;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,CAAC,SAAS,aAAa,EACpD,SAAS,EAAE,CAAC,EAAE,EACd,UAAU,EAAE,MAAM,GACjB;IAAE,OAAO,EAAE,CAAC,EAAE,CAAC;IAAC,SAAS,EAAE,CAAC,EAAE,CAAA;CAAE,CAuBlC"}
1
+ {"version":3,"file":"live-item-flush.d.ts","sourceRoot":"","sources":["../../src/ui/live-item-flush.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,iDAAiD;AACjD,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;CACZ;AAED;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,MAAM,CAAC;AAUrC;;;GAGG;AACH,wBAAgB,YAAY,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,GAAG,CAAC,EAAE,CAG/C;AAQD;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,aAAa,EAAE,KAAK,EAAE,CAAC,EAAE,GAAG,CAAC,EAAE,CA0DzE;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,CAAC,SAAS,aAAa,EAAE,SAAS,EAAE,CAAC,EAAE,GAAG,CAAC,EAAE,CAE5E;AAaD,wBAAgB,aAAa,CAAC,CAAC,SAAS,aAAa,EACnD,SAAS,EAAE,CAAC,EAAE,GACb;IAAE,OAAO,EAAE,CAAC,EAAE,CAAC;IAAC,SAAS,EAAE,CAAC,EAAE,CAAA;CAAE,CAmClC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,SAAS;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,EAAE,EACjD,YAAY,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,EACtC,YAAY,EAAE,MAAM,GACnB,MAAM,CAeR;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,CAAC,SAAS,aAAa,EACpD,SAAS,EAAE,CAAC,EAAE,EACd,UAAU,EAAE,MAAM,GACjB;IAAE,OAAO,EAAE,CAAC,EAAE,CAAC;IAAC,SAAS,EAAE,CAAC,EAAE,CAAA;CAAE,CAuBlC"}
@@ -148,6 +148,41 @@ export function flushOverflow(liveItems) {
148
148
  const candidates = liveItems.slice(0, splitAt);
149
149
  return { flushed: candidates, remaining: liveItems.slice(splitAt) };
150
150
  }
151
+ /**
152
+ * When a turn finalizes, decide how many leading items must be flushed to
153
+ * scrollback immediately because the pinned frame is too tall for the live
154
+ * area. Finalized items lose the streaming-time table/code-block clamp, so a
155
+ * too-tall pinned frame exceeds the terminal and Ink can only paint its
156
+ * bottom rows — the top of the response (e.g. a table's header) is never
157
+ * painted anywhere until the next turn's flush finally writes it to
158
+ * scrollback.
159
+ *
160
+ * The budget is CUMULATIVE: a turn segmented on [DONE:N] markers pins several
161
+ * assistant items whose individual heights fit while their sum overflows.
162
+ * Walking from the end, keep the largest suffix that fits within
163
+ * `liveAreaRows` and flush the leading complement — a contiguous prefix, so
164
+ * transcript order is preserved. An item taller than the whole budget flushes
165
+ * itself and everything before it. Returns 0 when everything fits or the
166
+ * live area hasn't been measured yet.
167
+ */
168
+ export function countOversizedFlushItems(items, estimateRows, liveAreaRows) {
169
+ if (liveAreaRows <= 0)
170
+ return 0;
171
+ let rows = 0;
172
+ for (let i = items.length - 1; i >= 0; i--) {
173
+ const item = items[i];
174
+ if (item === undefined)
175
+ continue;
176
+ // Non-assistant rows (step_done, etc.) are single-line; assistant items
177
+ // are estimated from their text. +1 approximates the transcript margin
178
+ // between items — over-counting just flushes marginally earlier.
179
+ const itemRows = item.kind === "assistant" && typeof item.text === "string" ? estimateRows(item.text) : 1;
180
+ rows += itemRows + (i < items.length - 1 ? 1 : 0);
181
+ if (rows > liveAreaRows)
182
+ return i + 1;
183
+ }
184
+ return 0;
185
+ }
151
186
  /**
152
187
  * Called when `onTurnEnd` fires with a tool_use stop reason (LLM responded
153
188
  * with only tool calls, no text). Flushes all items IF none are still pending
@@ -1 +1 @@
1
- {"version":3,"file":"live-item-flush.js","sourceRoot":"","sources":["../../src/ui/live-item-flush.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"live-item-flush.js","sourceRoot":"","sources":["../../src/ui/live-item-flush.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,6 @@
1
1
  import { describe, it, expect } from "vitest";
2
- import { pruneHistory, flushOnTurnText, flushOnTurnEnd, MAX_HISTORY_ITEMS, } from "./live-item-flush.js";
2
+ import { pruneHistory, flushOnTurnText, flushOnTurnEnd, countOversizedFlushItems, MAX_HISTORY_ITEMS, } from "./live-item-flush.js";
3
+ import { estimateRenderedRows } from "./utils/assistant-stream-split.js";
3
4
  // ── Test helpers ──────────────────────────────────────────
4
5
  let idCounter = 0;
5
6
  function id() {
@@ -103,6 +104,76 @@ describe("flushOnTurnText", () => {
103
104
  expect(flushed).toHaveLength(2);
104
105
  });
105
106
  });
107
+ // ── countOversizedFlushItems ──────────────────────────────
108
+ describe("countOversizedFlushItems", () => {
109
+ const LIVE_ROWS = 20;
110
+ const COLUMNS = 80;
111
+ const estimate = (text) => estimateRenderedRows(text, COLUMNS);
112
+ const shortText = { kind: "assistant", text: "A short answer.", id: "short" };
113
+ const stepDone = { kind: "step_done", id: "step" };
114
+ // A markdown table tall enough to exceed the live area: header + separator
115
+ // + 30 rows, double-counted by the table heuristic.
116
+ const tallTable = {
117
+ kind: "assistant",
118
+ text: [
119
+ "| Feature | Detail |",
120
+ "| --- | --- |",
121
+ ...Array.from({ length: 30 }, (_, i) => `| Row ${i} | some detail text |`),
122
+ ].join("\n"),
123
+ id: "table",
124
+ };
125
+ it("returns 0 when every assistant item fits the live area", () => {
126
+ expect(countOversizedFlushItems([shortText, stepDone], estimate, LIVE_ROWS)).toBe(0);
127
+ });
128
+ it("flushes through the oversized assistant item (the table-cut-off bug)", () => {
129
+ expect(countOversizedFlushItems([tallTable], estimate, LIVE_ROWS)).toBe(1);
130
+ });
131
+ it("flushes earlier siblings too, preserving transcript order", () => {
132
+ // [short, step_done, tallTable, short] — flushing only the table would
133
+ // print it to scrollback BEFORE the pinned earlier items get flushed on
134
+ // the next turn, corrupting order. Cut must include everything up to and
135
+ // including the last oversized item.
136
+ const items = [shortText, stepDone, tallTable, { ...shortText, id: "tail" }];
137
+ expect(countOversizedFlushItems(items, estimate, LIVE_ROWS)).toBe(3);
138
+ });
139
+ it("ignores non-assistant items even with long text", () => {
140
+ const longError = { kind: "error", text: "x".repeat(10_000), id: "err" };
141
+ expect(countOversizedFlushItems([longError], estimate, LIVE_ROWS)).toBe(0);
142
+ });
143
+ it("returns 0 when the live area has not been measured yet", () => {
144
+ expect(countOversizedFlushItems([tallTable], estimate, 0)).toBe(0);
145
+ });
146
+ it("returns 0 for an empty item list", () => {
147
+ expect(countOversizedFlushItems([], estimate, LIVE_ROWS)).toBe(0);
148
+ });
149
+ it("flushes a tall plain-text response too (no table required)", () => {
150
+ const tallText = {
151
+ kind: "assistant",
152
+ text: Array.from({ length: 40 }, (_, i) => `line ${i}`).join("\n"),
153
+ id: "tall-text",
154
+ };
155
+ expect(countOversizedFlushItems([tallText], estimate, LIVE_ROWS)).toBe(1);
156
+ });
157
+ it("CUMULATIVE: several medium items that fit individually but overflow together", () => {
158
+ // [DONE:N] segmentation pins multiple assistant items per turn. Three
159
+ // 10-row items each fit a 20-row live area alone, but the pinned set is
160
+ // ~32 rows — same top-rows-never-painted bug as a single tall table.
161
+ const medium = (id) => ({
162
+ kind: "assistant",
163
+ text: Array.from({ length: 10 }, (_, i) => `para line ${i}`).join("\n"),
164
+ id,
165
+ });
166
+ const items = [medium("a"), { kind: "step_done", id: "s" }, medium("b"), medium("c")];
167
+ // Suffix that fits: [medium c] (10+0) + [medium b] (10+1) = 21 > 20, so
168
+ // only "c" survives — flush the first three.
169
+ expect(countOversizedFlushItems(items, estimate, LIVE_ROWS)).toBe(3);
170
+ });
171
+ it("CUMULATIVE: keeps the whole set when the sum fits the budget", () => {
172
+ const small = (id) => ({ kind: "assistant", text: "one line", id });
173
+ const items = [small("a"), { kind: "step_done", id: "s" }, small("b")];
174
+ expect(countOversizedFlushItems(items, estimate, LIVE_ROWS)).toBe(0);
175
+ });
176
+ });
106
177
  // ── flushOnTurnEnd ────────────────────────────────────────
107
178
  describe("flushOnTurnEnd", () => {
108
179
  describe("with tool_use stop reason", () => {