@kenkaiiii/ggcoder 4.8.3 → 4.8.5
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/ui/App.d.ts +16 -0
- package/dist/ui/App.d.ts.map +1 -1
- package/dist/ui/App.js +28 -14
- package/dist/ui/App.js.map +1 -1
- package/dist/ui/atomic-flush-regression.test.d.ts +2 -0
- package/dist/ui/atomic-flush-regression.test.d.ts.map +1 -0
- package/dist/ui/atomic-flush-regression.test.js +218 -0
- package/dist/ui/atomic-flush-regression.test.js.map +1 -0
- package/dist/ui/footer-jump-regression.test.js +56 -9
- package/dist/ui/footer-jump-regression.test.js.map +1 -1
- package/dist/ui/frame-clip-regression.test.d.ts +2 -0
- package/dist/ui/frame-clip-regression.test.d.ts.map +1 -0
- package/dist/ui/frame-clip-regression.test.js +215 -0
- package/dist/ui/frame-clip-regression.test.js.map +1 -0
- package/dist/ui/hooks/useTranscriptHistory.d.ts +9 -1
- package/dist/ui/hooks/useTranscriptHistory.d.ts.map +1 -1
- package/dist/ui/hooks/useTranscriptHistory.js +34 -19
- package/dist/ui/hooks/useTranscriptHistory.js.map +1 -1
- package/dist/ui/render.d.ts.map +1 -1
- package/dist/ui/render.js +126 -1
- package/dist/ui/render.js.map +1 -1
- package/dist/ui/startup-banner-order.test.d.ts +2 -0
- package/dist/ui/startup-banner-order.test.d.ts.map +1 -0
- package/dist/ui/startup-banner-order.test.js +105 -0
- package/dist/ui/startup-banner-order.test.js.map +1 -0
- package/dist/ui/streaming-flush-bounce.test.js +41 -20
- package/dist/ui/streaming-flush-bounce.test.js.map +1 -1
- package/dist/ui/testing/screen-recorder.d.ts.map +1 -1
- package/dist/ui/testing/screen-recorder.js +11 -1
- package/dist/ui/testing/screen-recorder.js.map +1 -1
- package/package.json +5 -5
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
// Full-lifecycle regression for the atomic transcript flush.
|
|
3
|
+
//
|
|
4
|
+
// Models a complete turn on the native-scrollback default: idle → tall
|
|
5
|
+
// streaming response → live tool panel grows under the controls → done. The
|
|
6
|
+
// done transition finalizes the turn the way App does: queueFlush renders the
|
|
7
|
+
// transcript to ANSI and enqueues it through the patched Ink
|
|
8
|
+
// `insertBeforeFrame` while — in the SAME React batch — the live frame shrinks
|
|
9
|
+
// (streamed text flushed, tool panel hidden, activity → done swap). The
|
|
10
|
+
// resulting single frame write is `erase tall frame + scrollback bytes +
|
|
11
|
+
// shorter frame`, so:
|
|
12
|
+
// (a) Ink's full-screen repaint (eraseScreen + cursorTo top) never fires,
|
|
13
|
+
// (b) the footer never moves UP in the terminal buffer, and
|
|
14
|
+
// (c) the controls sit directly under the last transcript row after done —
|
|
15
|
+
// no stranded blank gap.
|
|
16
|
+
import React, { useEffect, useRef, useState } from "react";
|
|
17
|
+
import { render, Text, useStdout } from "ink";
|
|
18
|
+
import { describe, expect, it } from "vitest";
|
|
19
|
+
import stripAnsi from "strip-ansi";
|
|
20
|
+
import { ChatControls, ChatLayout } from "./components/ChatLayout.js";
|
|
21
|
+
import { ChatLivePane } from "./components/ChatLivePane.js";
|
|
22
|
+
import { TerminalSizeProvider } from "./hooks/useTerminalSize.js";
|
|
23
|
+
import { renderTranscriptItem } from "./transcript/TranscriptRenderer.js";
|
|
24
|
+
import { loadTheme, ThemeContext } from "./theme/theme.js";
|
|
25
|
+
import { createTerminalHistoryPrinter } from "./terminal-history.js";
|
|
26
|
+
import { useTranscriptHistory } from "./hooks/useTranscriptHistory.js";
|
|
27
|
+
import { ScreenRecorder, makeRecordingStdout } from "./testing/screen-recorder.js";
|
|
28
|
+
const COLUMNS = 80;
|
|
29
|
+
const ROWS = 24;
|
|
30
|
+
const theme = loadTheme("dark");
|
|
31
|
+
const terminalContext = {
|
|
32
|
+
theme,
|
|
33
|
+
columns: COLUMNS,
|
|
34
|
+
version: "sim",
|
|
35
|
+
model: "sim-model",
|
|
36
|
+
provider: "anthropic",
|
|
37
|
+
cwd: "/tmp/sim-project",
|
|
38
|
+
};
|
|
39
|
+
const FOOTER_BOTTOM = "SIM_ACTIVITY_BAR";
|
|
40
|
+
const TOOL_PANEL_ROWS = 4;
|
|
41
|
+
// Live-area budget: terminal rows minus the worst-case controls block (tool
|
|
42
|
+
// panel + status + input + footer rows). Mirrors App's layout measurement —
|
|
43
|
+
// the clamp is what keeps Ink out of its full-screen tall-frame repaint path.
|
|
44
|
+
const LIVE_AREA_BUDGET = ROWS - (TOOL_PANEL_ROWS + 4) - 2;
|
|
45
|
+
// Taller than the live-area budget, so streaming engages the clamp and the
|
|
46
|
+
// final flush writes more scrollback rows than the live region ever showed.
|
|
47
|
+
const longResponse = Array.from({ length: ROWS - 6 }, (_, i) => `SIM_LINE_${String(i + 1).padStart(2, "0")}`).join("\n");
|
|
48
|
+
function SimulatedControls({ running, toolRows }) {
|
|
49
|
+
return (_jsxs(ChatControls, { controlsRef: () => { }, children: [Array.from({ length: toolRows }, (_, i) => (_jsx(Text, { children: `SIM_TOOL_${i + 1}` }, `tool-${i}`))), _jsx(Text, { children: running ? "SIM_STREAMING" : "SIM_DONE" }), _jsx(Text, { children: "SIM_INPUT" }), _jsx(Text, { children: "SIM_FOOTER" }), _jsx(Text, { children: FOOTER_BOTTOM })] }));
|
|
50
|
+
}
|
|
51
|
+
function Driver({ phase, enqueueStdout }) {
|
|
52
|
+
const { write: writeStdout } = useStdout();
|
|
53
|
+
const [history, setHistory] = useState([]);
|
|
54
|
+
const [liveItems, setLiveItems] = useState([]);
|
|
55
|
+
// All visuals derive from internal state so the done transition can swap
|
|
56
|
+
// EVERYTHING (flush + streamed text removal + panel hide + activity → done)
|
|
57
|
+
// in one effect — i.e. one React batch — exactly like useAgentLoop's onDone.
|
|
58
|
+
const [view, setView] = useState({ running: false, streamingText: "", toolRows: 0 });
|
|
59
|
+
const committedRef = useRef(false);
|
|
60
|
+
// Stable printer across renders — mirrors renderApp, where one printer
|
|
61
|
+
// instance (and its printed-id dedup set) lives for the whole app lifetime.
|
|
62
|
+
const printerRef = useRef(null);
|
|
63
|
+
printerRef.current ??= createTerminalHistoryPrinter();
|
|
64
|
+
const { queueFlush } = useTranscriptHistory({
|
|
65
|
+
terminalHistoryPrinter: printerRef.current,
|
|
66
|
+
terminalHistoryContext: terminalContext,
|
|
67
|
+
writeStdout,
|
|
68
|
+
enqueueStdout,
|
|
69
|
+
sessionPathRef: { current: undefined },
|
|
70
|
+
sessionManagerRef: { current: null },
|
|
71
|
+
history,
|
|
72
|
+
setHistory,
|
|
73
|
+
setLiveItems,
|
|
74
|
+
});
|
|
75
|
+
// Idempotent setter: queueFlush is referentially unstable across renders
|
|
76
|
+
// (it lives in the hook's render scope), so this effect re-runs every
|
|
77
|
+
// commit. Returning the previous object when nothing changed keeps React
|
|
78
|
+
// from looping ("Maximum update depth exceeded") on the stream/tools
|
|
79
|
+
// phases, which set the same view on every run.
|
|
80
|
+
const applyView = (next) => {
|
|
81
|
+
setView((prev) => prev.running === next.running &&
|
|
82
|
+
prev.streamingText === next.streamingText &&
|
|
83
|
+
prev.toolRows === next.toolRows
|
|
84
|
+
? prev
|
|
85
|
+
: next);
|
|
86
|
+
};
|
|
87
|
+
const applyViewRef = useRef(applyView);
|
|
88
|
+
applyViewRef.current = applyView;
|
|
89
|
+
useEffect(() => {
|
|
90
|
+
if (phase === "idle")
|
|
91
|
+
return;
|
|
92
|
+
if (phase === "stream") {
|
|
93
|
+
applyViewRef.current({ running: true, streamingText: longResponse, toolRows: 0 });
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
if (phase === "tools") {
|
|
97
|
+
applyViewRef.current({
|
|
98
|
+
running: true,
|
|
99
|
+
streamingText: longResponse,
|
|
100
|
+
toolRows: TOOL_PANEL_ROWS,
|
|
101
|
+
});
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
if (committedRef.current)
|
|
105
|
+
return;
|
|
106
|
+
committedRef.current = true;
|
|
107
|
+
// Finalize the turn: enqueue the transcript bytes (passive) and shrink the
|
|
108
|
+
// live frame in the same batch. The next frame write carries both.
|
|
109
|
+
queueFlush([{ kind: "assistant", id: "final", text: longResponse }]);
|
|
110
|
+
setLiveItems([]);
|
|
111
|
+
applyViewRef.current({ running: false, streamingText: "", toolRows: 0 });
|
|
112
|
+
}, [phase, queueFlush]);
|
|
113
|
+
const renderItem = (item, index, items) => renderTranscriptItem({
|
|
114
|
+
item,
|
|
115
|
+
index,
|
|
116
|
+
items,
|
|
117
|
+
version: "sim",
|
|
118
|
+
currentModel: "sim-model",
|
|
119
|
+
currentProvider: "anthropic",
|
|
120
|
+
displayedCwd: "/tmp/sim-project",
|
|
121
|
+
columns: COLUMNS,
|
|
122
|
+
theme,
|
|
123
|
+
renderMarkdown: true,
|
|
124
|
+
measuredLiveAreaRows: LIVE_AREA_BUDGET,
|
|
125
|
+
});
|
|
126
|
+
return (_jsx(ThemeContext.Provider, { value: theme, children: _jsx(TerminalSizeProvider, { children: _jsxs(ChatLayout, { columns: COLUMNS, children: [_jsx(ChatLivePane, { liveItems: liveItems, renderItem: renderItem, isRunning: view.running, visibleStreamingText: view.streamingText, streamingThinking: "", thinkingMs: 0, reserveStreamingSpacing: false, renderMarkdown: true, measuredLiveAreaRows: LIVE_AREA_BUDGET, assistantMarginTop: 0, streamingContinuation: false }), _jsx(SimulatedControls, { running: view.running, toolRows: view.toolRows })] }) }) }));
|
|
127
|
+
}
|
|
128
|
+
const ERASE_SCREEN = "\u001B[2J";
|
|
129
|
+
async function tick() {
|
|
130
|
+
await new Promise((resolve) => setTimeout(resolve, 45));
|
|
131
|
+
}
|
|
132
|
+
describe("atomic transcript flush lifecycle", () => {
|
|
133
|
+
it("finalizes a tool-heavy turn without repaints, footer rises, or gaps", async () => {
|
|
134
|
+
const recorder = new ScreenRecorder({ columns: COLUMNS, rows: ROWS });
|
|
135
|
+
const baseStdout = makeRecordingStdout(recorder);
|
|
136
|
+
const sink = { raw: "" };
|
|
137
|
+
const stdout = new Proxy(baseStdout, {
|
|
138
|
+
get(target, prop, receiver) {
|
|
139
|
+
if (prop === "write") {
|
|
140
|
+
return (chunk, cb) => {
|
|
141
|
+
sink.raw += chunk;
|
|
142
|
+
return target.write(chunk, cb);
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
return Reflect.get(target, prop, receiver);
|
|
146
|
+
},
|
|
147
|
+
});
|
|
148
|
+
// Mirrors renderApp's enqueueHistoryWrite: read the (patched) instance at
|
|
149
|
+
// call time, fall back to a raw write when the API is unavailable.
|
|
150
|
+
const instanceRef = {
|
|
151
|
+
current: null,
|
|
152
|
+
};
|
|
153
|
+
const enqueueStdout = (data) => {
|
|
154
|
+
if (instanceRef.current?.insertBeforeFrame) {
|
|
155
|
+
instanceRef.current.insertBeforeFrame(data);
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
stdout.write(data);
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
const frames = [];
|
|
162
|
+
const capture = (label) => {
|
|
163
|
+
frames.push({ label, full: stripAnsi(recorder.fullText()) });
|
|
164
|
+
};
|
|
165
|
+
const mounted = render(_jsx(Driver, { phase: "idle", enqueueStdout: enqueueStdout }), {
|
|
166
|
+
stdout,
|
|
167
|
+
columns: COLUMNS,
|
|
168
|
+
rows: ROWS,
|
|
169
|
+
patchConsole: false,
|
|
170
|
+
maxFps: 1000,
|
|
171
|
+
});
|
|
172
|
+
instanceRef.current = mounted;
|
|
173
|
+
await tick();
|
|
174
|
+
capture("idle");
|
|
175
|
+
mounted.rerender(_jsx(Driver, { phase: "stream", enqueueStdout: enqueueStdout }));
|
|
176
|
+
await tick();
|
|
177
|
+
capture("tall streaming");
|
|
178
|
+
mounted.rerender(_jsx(Driver, { phase: "tools", enqueueStdout: enqueueStdout }));
|
|
179
|
+
await tick();
|
|
180
|
+
capture("tool panel grows");
|
|
181
|
+
mounted.rerender(_jsx(Driver, { phase: "done", enqueueStdout: enqueueStdout }));
|
|
182
|
+
await tick();
|
|
183
|
+
capture("done, first settle");
|
|
184
|
+
await tick();
|
|
185
|
+
capture("done, settled");
|
|
186
|
+
mounted.unmount();
|
|
187
|
+
// (a) Zero full-screen repaints (eraseScreen + cursorTo top) across the
|
|
188
|
+
// whole lifecycle — the clamp keeps Ink out of its tall-frame path, and
|
|
189
|
+
// the atomic insert keeps the shrink from ever needing a repaint.
|
|
190
|
+
expect(sink.raw.split(ERASE_SCREEN).length - 1, "full-screen repaints").toBe(0);
|
|
191
|
+
// (b) The footer's absolute row in the terminal buffer never moves UP.
|
|
192
|
+
// Scrollback growth pushes it down; an upward move means a shrink-write
|
|
193
|
+
// landed without its compensating scrollback bytes.
|
|
194
|
+
let previousFooterRow = -1;
|
|
195
|
+
for (const f of frames) {
|
|
196
|
+
const footerRow = f.full.split("\n").findIndex((line) => line.includes(FOOTER_BOTTOM));
|
|
197
|
+
expect(footerRow, `${f.label}: footer present`).toBeGreaterThanOrEqual(0);
|
|
198
|
+
expect(footerRow, `${f.label}: footer buffer row never decreases (was ${previousFooterRow})`).toBeGreaterThanOrEqual(previousFooterRow);
|
|
199
|
+
previousFooterRow = footerRow;
|
|
200
|
+
}
|
|
201
|
+
// (c) After done: the controls sit directly under the last transcript row —
|
|
202
|
+
// no stranded blank rows between the flushed response and the controls.
|
|
203
|
+
const settled = frames.at(-1);
|
|
204
|
+
const lines = settled.full.split("\n");
|
|
205
|
+
const lastTranscriptRow = lines.reduce((acc, line, i) => (line.includes("SIM_LINE_") ? i : acc), -1);
|
|
206
|
+
const controlsRow = lines.findIndex((line) => line.includes("SIM_DONE"));
|
|
207
|
+
expect(lastTranscriptRow, "flushed transcript present after done").toBeGreaterThanOrEqual(0);
|
|
208
|
+
expect(controlsRow, "controls present after done").toBeGreaterThan(lastTranscriptRow);
|
|
209
|
+
const between = lines.slice(lastTranscriptRow + 1, controlsRow);
|
|
210
|
+
expect(between.filter((line) => line.trim().length === 0).length, "no blank gap between transcript and controls").toBe(0);
|
|
211
|
+
// And the flushed response must never be duplicated (scrollback + live).
|
|
212
|
+
for (const f of frames) {
|
|
213
|
+
const occurrences = f.full.split("SIM_LINE_01").length - 1;
|
|
214
|
+
expect(occurrences, `${f.label}: flushed content not duplicated`).toBeLessThanOrEqual(1);
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
//# sourceMappingURL=atomic-flush-regression.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"atomic-flush-regression.test.js","sourceRoot":"","sources":["../../src/ui/atomic-flush-regression.test.tsx"],"names":[],"mappings":"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"}
|
|
@@ -53,15 +53,20 @@ function SimTui({ liveItems, streamingText, controlsLabel, }) {
|
|
|
53
53
|
});
|
|
54
54
|
return (_jsx(ThemeContext.Provider, { value: theme, children: _jsx(TerminalSizeProvider, { children: _jsxs(ChatLayout, { columns: COLUMNS, children: [_jsx(ChatLivePane, { liveItems: liveItems, renderItem: renderItem, isRunning: controlsLabel !== "SIM_DONE", visibleStreamingText: streamingText, streamingThinking: "", thinkingMs: 0, reserveStreamingSpacing: false, renderMarkdown: true, measuredLiveAreaRows: ROWS, assistantMarginTop: 0, streamingContinuation: false }), _jsx(SimulatedControls, { label: controlsLabel })] }) }) }));
|
|
55
55
|
}
|
|
56
|
-
function Driver({ phase }) {
|
|
56
|
+
function Driver({ phase, enqueueStdout }) {
|
|
57
57
|
const { write: writeStdout } = useStdout();
|
|
58
58
|
const [history, setHistory] = useState([]);
|
|
59
59
|
const [liveItems, setLiveItems] = useState([]);
|
|
60
60
|
const committedRef = useRef(false);
|
|
61
|
+
// Stable printer across renders — mirrors renderApp, where one printer
|
|
62
|
+
// instance (and its printed-id dedup set) lives for the whole app lifetime.
|
|
63
|
+
const printerRef = useRef(null);
|
|
64
|
+
printerRef.current ??= createTerminalHistoryPrinter();
|
|
61
65
|
const { queueFlush } = useTranscriptHistory({
|
|
62
|
-
terminalHistoryPrinter:
|
|
66
|
+
terminalHistoryPrinter: printerRef.current,
|
|
63
67
|
terminalHistoryContext: terminalContext,
|
|
64
68
|
writeStdout,
|
|
69
|
+
enqueueStdout,
|
|
65
70
|
sessionPathRef: { current: undefined },
|
|
66
71
|
sessionManagerRef: { current: null },
|
|
67
72
|
history,
|
|
@@ -130,25 +135,45 @@ describe("footer jump reproduction", () => {
|
|
|
130
135
|
const capture = (label) => {
|
|
131
136
|
const slice = sink.raw.slice(lastRawLen);
|
|
132
137
|
lastRawLen = sink.raw.length;
|
|
133
|
-
frames.push({
|
|
138
|
+
frames.push({
|
|
139
|
+
label,
|
|
140
|
+
lines: viewport(recorder),
|
|
141
|
+
full: stripAnsi(recorder.fullText()),
|
|
142
|
+
repaints: countFullRepaints(slice),
|
|
143
|
+
});
|
|
134
144
|
};
|
|
135
|
-
|
|
145
|
+
// Mirrors renderApp's enqueueHistoryWrite: read the (patched) instance at
|
|
146
|
+
// call time, fall back to a raw write when the API is unavailable.
|
|
147
|
+
const instanceRef = {
|
|
148
|
+
current: null,
|
|
149
|
+
};
|
|
150
|
+
const enqueueStdout = (data) => {
|
|
151
|
+
if (instanceRef.current?.insertBeforeFrame) {
|
|
152
|
+
instanceRef.current.insertBeforeFrame(data);
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
stdout.write(data);
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
const mounted = render(_jsx(Driver, { phase: "idle", enqueueStdout: enqueueStdout }), {
|
|
136
159
|
stdout,
|
|
137
160
|
columns: COLUMNS,
|
|
138
161
|
rows: ROWS,
|
|
139
162
|
patchConsole: false,
|
|
140
163
|
maxFps: 1000,
|
|
141
164
|
});
|
|
165
|
+
instanceRef.current = mounted;
|
|
142
166
|
await tick();
|
|
143
167
|
capture("Z: idle (before first turn)");
|
|
144
|
-
mounted.rerender(_jsx(Driver, { phase: "stream-short" }));
|
|
168
|
+
mounted.rerender(_jsx(Driver, { phase: "stream-short", enqueueStdout: enqueueStdout }));
|
|
145
169
|
await tick();
|
|
146
170
|
capture("A: short streaming");
|
|
147
|
-
mounted.rerender(_jsx(Driver, { phase: "stream-fill" }));
|
|
171
|
+
mounted.rerender(_jsx(Driver, { phase: "stream-fill", enqueueStdout: enqueueStdout }));
|
|
148
172
|
await tick();
|
|
149
173
|
capture("B: streaming fills the screen");
|
|
150
|
-
mounted.rerender(_jsx(Driver, { phase: "done" }));
|
|
174
|
+
mounted.rerender(_jsx(Driver, { phase: "done", enqueueStdout: enqueueStdout }));
|
|
151
175
|
await tick();
|
|
176
|
+
capture("C1: done, first settle");
|
|
152
177
|
await tick();
|
|
153
178
|
capture("C: done + committed final response");
|
|
154
179
|
mounted.unmount();
|
|
@@ -164,8 +189,30 @@ describe("footer jump reproduction", () => {
|
|
|
164
189
|
// repaint (eraseScreen + cursorTo top), which is what visibly shoves the
|
|
165
190
|
// footer. Some repaints are unavoidable during clamp engagement while
|
|
166
191
|
// streaming, but committing the finalized response must not repaint.
|
|
167
|
-
const
|
|
168
|
-
|
|
192
|
+
for (const label of ["C1:", "C:"]) {
|
|
193
|
+
const doneFrame = frames.find((f) => f.label.startsWith(label));
|
|
194
|
+
expect(doneFrame?.repaints, `${label} done transition full-screen repaints`).toBe(0);
|
|
195
|
+
}
|
|
196
|
+
// THE STRAND: the footer's absolute row in the terminal buffer must never
|
|
197
|
+
// move UP across frames. Scrollback growth pushes it down; an upward move
|
|
198
|
+
// means a shrink-write landed without its compensating scrollback bytes —
|
|
199
|
+
// the footer gets stranded mid-screen with blank rows below it. (Measured
|
|
200
|
+
// in the cumulative buffer, not the viewport, so Ink's trailing-newline
|
|
201
|
+
// convention — omitted only for exactly-fullscreen frames — can't skew it.)
|
|
202
|
+
let previousFooterRow = -1;
|
|
203
|
+
for (const f of frames) {
|
|
204
|
+
const bufferLines = f.full.split("\n");
|
|
205
|
+
const footerRow = bufferLines.findIndex((line) => line.includes(FOOTER_BOTTOM));
|
|
206
|
+
expect(footerRow, `${f.label}: footer buffer row never decreases (was ${previousFooterRow})`).toBeGreaterThanOrEqual(previousFooterRow);
|
|
207
|
+
previousFooterRow = footerRow;
|
|
208
|
+
}
|
|
209
|
+
// NO DUPLICATION: the flushed response must never be visible twice (once in
|
|
210
|
+
// scrollback AND still in the live frame). The atomic insert erases the
|
|
211
|
+
// live copy in the same write that adds the scrollback copy.
|
|
212
|
+
for (const f of frames) {
|
|
213
|
+
const occurrences = f.full.split("SIM_LINE_01").length - 1;
|
|
214
|
+
expect(occurrences, `${f.label}: flushed content not duplicated`).toBeLessThanOrEqual(1);
|
|
215
|
+
}
|
|
169
216
|
// THE START JUMP: with the status slot reserved unconditionally and the
|
|
170
217
|
// controls height held constant, starting the first turn (idle → first
|
|
171
218
|
// stream) must not trigger a full-screen repaint either — the footer stays
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"footer-jump-regression.test.js","sourceRoot":"","sources":["../../src/ui/footer-jump-regression.test.tsx"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"footer-jump-regression.test.js","sourceRoot":"","sources":["../../src/ui/footer-jump-regression.test.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"frame-clip-regression.test.d.ts","sourceRoot":"","sources":["../../src/ui/frame-clip-regression.test.tsx"],"names":[],"mappings":""}
|
|
@@ -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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"}
|