@crouton-kit/humanloop 0.3.21 → 0.3.22
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/inbox/tui.js +10 -3
- package/dist/tui/app.js +16 -2
- package/dist/tui/render.d.ts +9 -1
- package/dist/tui/render.js +62 -9
- package/package.json +1 -1
package/dist/inbox/tui.js
CHANGED
|
@@ -113,17 +113,24 @@ export function pickFromInbox(items, opts) {
|
|
|
113
113
|
const flush = () => {
|
|
114
114
|
const { cols: currentCols, rows: currentRows } = getTerminalSize();
|
|
115
115
|
const lines = buildInboxLines(items, currentCols, selectedIndex);
|
|
116
|
-
const { writes, nextPrevFrame } = diffFrame(prevFrame, lines, currentRows);
|
|
116
|
+
const { writes, nextPrevFrame } = diffFrame(prevFrame, lines, currentRows, currentCols);
|
|
117
117
|
process.stdout.write('\x1b[?2026h');
|
|
118
118
|
for (const w of writes)
|
|
119
119
|
process.stdout.write(w);
|
|
120
120
|
process.stdout.write('\x1b[?2026l');
|
|
121
121
|
prevFrame = nextPrevFrame;
|
|
122
122
|
};
|
|
123
|
+
// Resize reflows/scrolls what's already on screen, invalidating the diff
|
|
124
|
+
// model — clear and redraw from scratch at the new size.
|
|
125
|
+
const onResize = () => {
|
|
126
|
+
prevFrame = [];
|
|
127
|
+
process.stdout.write('\x1b[2J\x1b[H');
|
|
128
|
+
flush();
|
|
129
|
+
};
|
|
123
130
|
const done = (result) => {
|
|
124
131
|
restoreTerminal();
|
|
125
132
|
process.stdin.removeListener('data', onData);
|
|
126
|
-
process.stdout.removeListener('resize',
|
|
133
|
+
process.stdout.removeListener('resize', onResize);
|
|
127
134
|
resolve(result);
|
|
128
135
|
};
|
|
129
136
|
setupTerminal();
|
|
@@ -151,6 +158,6 @@ export function pickFromInbox(items, opts) {
|
|
|
151
158
|
}
|
|
152
159
|
};
|
|
153
160
|
process.stdin.on('data', onData);
|
|
154
|
-
process.stdout.on('resize',
|
|
161
|
+
process.stdout.on('resize', onResize);
|
|
155
162
|
});
|
|
156
163
|
}
|
package/dist/tui/app.js
CHANGED
|
@@ -254,14 +254,26 @@ export async function resolveInteractionDir(dir, deck, opts = {}) {
|
|
|
254
254
|
let currentDeck = deck;
|
|
255
255
|
let deckWatch = null;
|
|
256
256
|
const flushHost = (lines) => {
|
|
257
|
-
const { rows: currentRows } = getTerminalSize();
|
|
258
|
-
const { writes, nextPrevFrame } = diffFrame(prevFrameLocal, lines, currentRows);
|
|
257
|
+
const { cols: currentCols, rows: currentRows } = getTerminalSize();
|
|
258
|
+
const { writes, nextPrevFrame } = diffFrame(prevFrameLocal, lines, currentRows, currentCols);
|
|
259
259
|
process.stdout.write('\x1b[?2026h');
|
|
260
260
|
for (const w of writes)
|
|
261
261
|
process.stdout.write(w);
|
|
262
262
|
process.stdout.write('\x1b[?2026l');
|
|
263
263
|
prevFrameLocal = nextPrevFrame;
|
|
264
264
|
};
|
|
265
|
+
// On resize the terminal reflows/scrolls existing content, so the diff
|
|
266
|
+
// model no longer matches the screen: re-layout at the new size, clear
|
|
267
|
+
// everything, and redraw from scratch.
|
|
268
|
+
const onResize = () => {
|
|
269
|
+
if (panel === null)
|
|
270
|
+
return;
|
|
271
|
+
const { cols: c, rows: r } = getTerminalSize();
|
|
272
|
+
const lines = panel.handleResize(c, r);
|
|
273
|
+
prevFrameLocal = [];
|
|
274
|
+
process.stdout.write('\x1b[2J\x1b[H');
|
|
275
|
+
flushHost(lines);
|
|
276
|
+
};
|
|
265
277
|
const finalize = (responses) => {
|
|
266
278
|
if (deckWatch !== null) {
|
|
267
279
|
clearInterval(deckWatch);
|
|
@@ -269,6 +281,7 @@ export async function resolveInteractionDir(dir, deck, opts = {}) {
|
|
|
269
281
|
}
|
|
270
282
|
restoreTerminal();
|
|
271
283
|
process.stdin.removeListener('data', onData);
|
|
284
|
+
process.stdout.removeListener('resize', onResize);
|
|
272
285
|
panel?.unmount();
|
|
273
286
|
const completedAt = new Date().toISOString();
|
|
274
287
|
// Resolved supersedes in-progress: write response.json, drop progress.json.
|
|
@@ -342,6 +355,7 @@ export async function resolveInteractionDir(dir, deck, opts = {}) {
|
|
|
342
355
|
flushHost(panel.render());
|
|
343
356
|
};
|
|
344
357
|
process.stdin.on('data', onData);
|
|
358
|
+
process.stdout.on('resize', onResize);
|
|
345
359
|
});
|
|
346
360
|
}
|
|
347
361
|
// ── launchTui — file-path entry over the dir resolver (a kept public export
|
package/dist/tui/render.d.ts
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
import type { TuiState, Interaction, InteractionResponse } from '../types.js';
|
|
2
2
|
export declare function sanitize(text: string): string;
|
|
3
|
-
|
|
3
|
+
/**
|
|
4
|
+
* ANSI-aware clip: truncate a line's *visible* width to `maxWidth`, passing
|
|
5
|
+
* escape sequences through untouched. Every line written to the terminal must
|
|
6
|
+
* fit within the columns, or it physically wraps onto the next row — which
|
|
7
|
+
* breaks diffFrame's one-logical-line-per-row model and strands spillover text
|
|
8
|
+
* on rows the differ believes are empty (so it never erases them).
|
|
9
|
+
*/
|
|
10
|
+
export declare function clipLine(line: string, maxWidth: number): string;
|
|
11
|
+
export declare function diffFrame(prevFrame: string[], nextLines: string[], rows: number, cols?: number): {
|
|
4
12
|
writes: string[];
|
|
5
13
|
nextPrevFrame: string[];
|
|
6
14
|
};
|
package/dist/tui/render.js
CHANGED
|
@@ -143,15 +143,54 @@ function centerHorizontal(lines, cols, contentWidth) {
|
|
|
143
143
|
return lines.map((line) => (line === '' ? '' : pad + line));
|
|
144
144
|
}
|
|
145
145
|
// ── Frame buffer ─────────────────────────────────────────────────────────────
|
|
146
|
-
|
|
146
|
+
/**
|
|
147
|
+
* ANSI-aware clip: truncate a line's *visible* width to `maxWidth`, passing
|
|
148
|
+
* escape sequences through untouched. Every line written to the terminal must
|
|
149
|
+
* fit within the columns, or it physically wraps onto the next row — which
|
|
150
|
+
* breaks diffFrame's one-logical-line-per-row model and strands spillover text
|
|
151
|
+
* on rows the differ believes are empty (so it never erases them).
|
|
152
|
+
*/
|
|
153
|
+
export function clipLine(line, maxWidth) {
|
|
154
|
+
if (maxWidth < 1)
|
|
155
|
+
return '';
|
|
156
|
+
if (stringWidth(line) <= maxWidth)
|
|
157
|
+
return line; // string-width ignores ANSI
|
|
158
|
+
let out = '';
|
|
159
|
+
let w = 0;
|
|
160
|
+
let i = 0;
|
|
161
|
+
let sawAnsi = false;
|
|
162
|
+
while (i < line.length) {
|
|
163
|
+
if (line[i] === '\x1b') {
|
|
164
|
+
const m = /^\x1b\[[0-9;?]*[a-zA-Z]|^\x1b[@-_]/.exec(line.slice(i));
|
|
165
|
+
if (m !== null) {
|
|
166
|
+
out += m[0];
|
|
167
|
+
i += m[0].length;
|
|
168
|
+
sawAnsi = true;
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
const ch = String.fromCodePoint(line.codePointAt(i));
|
|
173
|
+
const cw = stringWidth(ch);
|
|
174
|
+
if (w + cw > maxWidth)
|
|
175
|
+
break;
|
|
176
|
+
out += ch;
|
|
177
|
+
w += cw;
|
|
178
|
+
i += ch.length;
|
|
179
|
+
}
|
|
180
|
+
return sawAnsi ? out + RESET : out;
|
|
181
|
+
}
|
|
182
|
+
export function diffFrame(prevFrame, nextLines, rows, cols) {
|
|
183
|
+
const clipped = cols !== undefined
|
|
184
|
+
? nextLines.map((l) => clipLine(l, cols))
|
|
185
|
+
: nextLines;
|
|
147
186
|
const writes = [];
|
|
148
187
|
for (let i = 0; i < rows; i++) {
|
|
149
|
-
const line = i <
|
|
188
|
+
const line = i < clipped.length ? clipped[i] : '';
|
|
150
189
|
if (prevFrame[i] !== line) {
|
|
151
190
|
writes.push(`${ESC}${i + 1};1H${ESC}2K${line}`);
|
|
152
191
|
}
|
|
153
192
|
}
|
|
154
|
-
return { writes, nextPrevFrame: [...
|
|
193
|
+
return { writes, nextPrevFrame: [...clipped] };
|
|
155
194
|
}
|
|
156
195
|
// ── Renderers ────────────────────────────────────────────────────────────────
|
|
157
196
|
export function renderOverview(state, cols, rows) {
|
|
@@ -301,12 +340,14 @@ export function renderItemReview(state, cols, rows) {
|
|
|
301
340
|
? opts.find((o) => o.id === attachedId)
|
|
302
341
|
: undefined;
|
|
303
342
|
const valueText = attached !== undefined
|
|
304
|
-
? `${CYAN}${singleLine(attached.label)}${RESET}`
|
|
343
|
+
? `${CYAN}${truncate(singleLine(attached.label), Math.max(10, maxW - 28))}${RESET}`
|
|
305
344
|
: `${DIM}none (overall)${RESET}`;
|
|
306
345
|
attachedLine = ` ${DIM}attached:${RESET} ${valueText} ${DIM}[tab to cycle]${RESET}`;
|
|
307
346
|
}
|
|
308
347
|
}
|
|
309
|
-
|
|
348
|
+
for (const labelLine of wrap(`${singleLine(label)}:`, maxW)) {
|
|
349
|
+
postLines.push(` ${YELLOW}${labelLine}${RESET}`);
|
|
350
|
+
}
|
|
310
351
|
const bufLines = hardWrap(state.inputMode.buffer, maxW - 1);
|
|
311
352
|
for (let i = 0; i < bufLines.length; i++) {
|
|
312
353
|
const isLast = i === bufLines.length - 1;
|
|
@@ -326,7 +367,9 @@ export function renderItemReview(state, cols, rows) {
|
|
|
326
367
|
// just above the footer; cleared on the next keypress.
|
|
327
368
|
if (state.hint !== undefined && state.hint.length > 0) {
|
|
328
369
|
postLines.push('');
|
|
329
|
-
|
|
370
|
+
for (const hintLine of wrap(sanitize(state.hint), maxW)) {
|
|
371
|
+
postLines.push(` ${YELLOW}${hintLine}${RESET}`);
|
|
372
|
+
}
|
|
330
373
|
}
|
|
331
374
|
// Window the body
|
|
332
375
|
const reservedRows = preLines.length + postLines.length + 1; // +1 for footer
|
|
@@ -428,15 +471,25 @@ function renderActions(interaction, selectedAction, maxW, existing) {
|
|
|
428
471
|
label = 'Add overall comment (c on an option for per-option)';
|
|
429
472
|
else
|
|
430
473
|
label = 'Add comment';
|
|
431
|
-
|
|
474
|
+
const ftLines = wrap(sanitize(label), contentMax);
|
|
475
|
+
for (let j = 0; j < ftLines.length; j++) {
|
|
476
|
+
const prefix = j === 0 ? ` ${cursor} ${DIM}[c]${RESET} ` : ' '.repeat(8);
|
|
477
|
+
lines.push(`${prefix}${ftLines[j]}`);
|
|
478
|
+
}
|
|
432
479
|
}
|
|
433
480
|
else if (interaction.allowFreetext && opts.length === 0) {
|
|
434
481
|
const ftLabel = interaction.freetextLabel !== undefined ? interaction.freetextLabel : 'Enter response';
|
|
435
|
-
|
|
482
|
+
const ftLines = wrap(sanitize(ftLabel), contentMax);
|
|
483
|
+
for (let j = 0; j < ftLines.length; j++) {
|
|
484
|
+
const prefix = j === 0 ? ` ${DIM}[r]${RESET} ` : ' '.repeat(6);
|
|
485
|
+
lines.push(`${prefix}${ftLines[j]}`);
|
|
486
|
+
}
|
|
436
487
|
}
|
|
437
488
|
if (existing) {
|
|
438
489
|
lines.push('');
|
|
439
|
-
|
|
490
|
+
for (const curLine of wrap(`Current: ${responseSummary(existing, interaction)}`, maxW)) {
|
|
491
|
+
lines.push(` ${GREEN}${curLine}${RESET}`);
|
|
492
|
+
}
|
|
440
493
|
}
|
|
441
494
|
return lines;
|
|
442
495
|
}
|