acuvo-code 0.4.0 → 0.4.2
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/lib/input-box.mjs +57 -5
- package/lib/turn.mjs +5716 -5706
- package/package.json +1 -1
package/lib/input-box.mjs
CHANGED
|
@@ -67,7 +67,20 @@ export function visibleWidth(s) {
|
|
|
67
67
|
* @returns {{lines: string[], cursorColumn: number}} 1-based cursor column
|
|
68
68
|
*/
|
|
69
69
|
export function renderBox({ value = '', cursor = 0, columns = 80, prompt = '› ' } = {}) {
|
|
70
|
-
|
|
70
|
+
/**
|
|
71
|
+
* ── ⚠️ FULL WIDTH. THE 100-COLUMN CAP WAS WRONG AND IT LOOKED WRONG ────────
|
|
72
|
+
*
|
|
73
|
+
* Roman, from a screenshot: *"ours isn't the entire width."* In a ~200-column
|
|
74
|
+
* terminal a 100-column box reads as a half-finished element rather than as a
|
|
75
|
+
* deliberate measure — it is the input, and the input should be as wide as the
|
|
76
|
+
* place you are typing.
|
|
77
|
+
*
|
|
78
|
+
* ⚠️ `columns - 1`, NOT `columns`. A box drawn to the very last column makes
|
|
79
|
+
* many terminals wrap to the next row the moment the final border character is
|
|
80
|
+
* written, which pushes everything down by one and breaks the cursor
|
|
81
|
+
* arithmetic for the rest of the session.
|
|
82
|
+
*/
|
|
83
|
+
const width = Math.max(20, columns - 1);
|
|
71
84
|
const inner = width - 2;
|
|
72
85
|
const promptWidth = visibleWidth(prompt);
|
|
73
86
|
|
|
@@ -213,10 +226,37 @@ export function splitKeys(chunk) {
|
|
|
213
226
|
*/
|
|
214
227
|
export function paint(output, state, { first = false } = {}) {
|
|
215
228
|
const { lines, cursorColumn } = renderBox(state);
|
|
216
|
-
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* ── ⚠️⚠️ THE CURSOR MATH, AND MY FIRST VERSION ATE THE SCREEN ──────────────
|
|
232
|
+
*
|
|
233
|
+
* Roman: *"it moves upwards every time you type a character then deletes the
|
|
234
|
+
* design you did."* Exactly right, and the arithmetic says why.
|
|
235
|
+
*
|
|
236
|
+
* After a paint the cursor rests on the INPUT line — line 2 of 3, not below
|
|
237
|
+
* the box. The first version began each repaint with `ESC[3A`, which is where
|
|
238
|
+
* it would be if the cursor were below. From line 2, moving up 3 lands ONE
|
|
239
|
+
* LINE ABOVE the top border — and the `ESC[0J` that follows clears from there
|
|
240
|
+
* to the bottom of the screen. So every keystroke crept upward and erased
|
|
241
|
+
* another line of the banner.
|
|
242
|
+
*
|
|
243
|
+
* ⭐ THE INVARIANT, WRITTEN DOWN BECAUSE IT IS THE WHOLE FUNCTION: this
|
|
244
|
+
* routine ENTERS with the cursor on the input line and LEAVES it there. So a
|
|
245
|
+
* repaint moves up exactly ONE line to reach the top border, and the final
|
|
246
|
+
* reposition moves up exactly one from the last line written.
|
|
247
|
+
*
|
|
248
|
+
* ╭────────╮ <- line 1 ESC[1A from the input line reaches here
|
|
249
|
+
* │› … │ <- line 2 cursor lives here, in and out
|
|
250
|
+
* ╰────────╯ <- line 3 cursor is here after writing; ESC[1A returns
|
|
251
|
+
*
|
|
252
|
+
* ⚠️ NO TRAILING NEWLINE. Writing one after the last border scrolls the
|
|
253
|
+
* viewport when the box is at the bottom of the screen, and every subsequent
|
|
254
|
+
* `up` is then off by a row for the rest of the session.
|
|
255
|
+
*/
|
|
256
|
+
const home = first ? '' : `\r${CSI}1A`;
|
|
217
257
|
output.write(
|
|
218
|
-
`${CSI}?25l${
|
|
219
|
-
`${CSI}
|
|
258
|
+
`${CSI}?25l${home}${CSI}0J${lines.join('\n')}` +
|
|
259
|
+
`${CSI}1A\r${CSI}${cursorColumn}G${CSI}?25h`,
|
|
220
260
|
);
|
|
221
261
|
}
|
|
222
262
|
|
|
@@ -247,7 +287,19 @@ export function readBoxedLine({ input, output, history = [], onInterrupt = null,
|
|
|
247
287
|
input.off('data', onData);
|
|
248
288
|
input.off('end', onEnd);
|
|
249
289
|
try { input.setRawMode?.(false); } catch { /* not a TTY any more */ }
|
|
250
|
-
|
|
290
|
+
/**
|
|
291
|
+
* ── ⚠️⚠️ MOVE BELOW THE BOX BEFORE ANYTHING ELSE WRITES ─────────────────
|
|
292
|
+
*
|
|
293
|
+
* Seen in a screenshot: an MCP warning printed straight ON TOP of the
|
|
294
|
+
* bottom border, which was left visible only where the message was
|
|
295
|
+
* shorter than the box.
|
|
296
|
+
*
|
|
297
|
+
* The cursor rests on the INPUT line — line 2 of 3. A bare `\n` moves it
|
|
298
|
+
* to line 3, which IS the bottom border, so the very next thing anyone
|
|
299
|
+
* writes overwrites it. Down one FIRST, then a newline, so the cursor
|
|
300
|
+
* lands on fresh ground below a box that stays intact.
|
|
301
|
+
*/
|
|
302
|
+
output.write(`${CSI}1B\n`);
|
|
251
303
|
resolve({ value, reason });
|
|
252
304
|
};
|
|
253
305
|
|