@vincemakes/kiso-tui 0.32.0 → 0.32.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/dist/compositor.js +20 -1
- package/dist/editor.js +143 -1
- package/dist/lines.js +2 -2
- package/package.json +2 -2
package/dist/compositor.js
CHANGED
|
@@ -57,6 +57,7 @@ import { atPanelRows, bandHeader } from "./at-picker.js";
|
|
|
57
57
|
import { sessionPickerRows } from "./session-picker.js";
|
|
58
58
|
import { CAP_PREVIEW, thinkingRow, Container, MOTION_FRAMES, MdStream, bodySpacing, boxBottom, boxTop, cellComponent, gutterCut, cutLine, pendingQueueRows, statusLine, visibleWidth, breathFrame, } from "./components.js";
|
|
59
59
|
import { bannerLines, escapeTerminal, foldResult, foldThinking, palette, renderTerminalGap, renderToolSummary } from "./lines.js";
|
|
60
|
+
import { oneRow } from "@vincemakes/kiso-tui-cells/render";
|
|
60
61
|
import { displayVerb, keysSheetRows } from "./strings.js";
|
|
61
62
|
/** DC-56: does a rendered row carry any visible text once its SGR is
|
|
62
63
|
* stripped? A washed pad row is spaces under a background colour — width
|
|
@@ -2638,6 +2639,8 @@ export class Body {
|
|
|
2638
2639
|
* CRASH with the diagnostic, never a silent truncate. */
|
|
2639
2640
|
/** DC-48 — the field cut is announced ONCE per session. */
|
|
2640
2641
|
#cutOnce = false;
|
|
2642
|
+
/** HF-1: the ①b notice, once per session (the same discipline as #cutOnce). */
|
|
2643
|
+
#breakOnce = false;
|
|
2641
2644
|
#checked(line, W) {
|
|
2642
2645
|
// Invariant ①b (R3f): a ROW IS ONE PHYSICAL ROW.
|
|
2643
2646
|
//
|
|
@@ -2663,7 +2666,23 @@ export class Body {
|
|
|
2663
2666
|
// (it moves the cursor to column 1).
|
|
2664
2667
|
const bad = /[\n\r]/.exec(line);
|
|
2665
2668
|
if (bad !== null) {
|
|
2666
|
-
|
|
2669
|
+
// HF-1 (0.32.1): the SAME ruling as the width half below. This
|
|
2670
|
+
// threw in the field on a heredoc shell command (the running
|
|
2671
|
+
// card's head carried the model's newlines) and the owner's
|
|
2672
|
+
// session died with it. Under test the crash keeps its teeth;
|
|
2673
|
+
// in the field the break is projected to ⏎ — the row goes out
|
|
2674
|
+
// as one row — and a notice says so once. The builders project
|
|
2675
|
+
// first (tui-cells `oneRow`), so this branch is the net under
|
|
2676
|
+
// the net: a builder nobody has found yet costs a mark, not a
|
|
2677
|
+
// session.
|
|
2678
|
+
const why = `kiso-tui invariant ①b violated: a row containing ${JSON.stringify(bad[0])} was about to be emitted — a row must be ONE physical row, and the width check cannot see this (charWidth counts a newline as one cell) — ${JSON.stringify(line.slice(0, 80))}`;
|
|
2679
|
+
if (process.env.KISO_INVARIANTS === "throw")
|
|
2680
|
+
throw new Error(why);
|
|
2681
|
+
if (!this.#breakOnce) {
|
|
2682
|
+
this.#breakOnce = true;
|
|
2683
|
+
queueMicrotask(() => this.notice("kiso-tui: a row carried a line break and was shown as one row (invariant ①b) · please report"));
|
|
2684
|
+
}
|
|
2685
|
+
line = oneRow(line);
|
|
2667
2686
|
}
|
|
2668
2687
|
const w = visibleWidth(line);
|
|
2669
2688
|
if (w > W) {
|
package/dist/editor.js
CHANGED
|
@@ -1107,6 +1107,37 @@ export class Editor {
|
|
|
1107
1107
|
continue;
|
|
1108
1108
|
}
|
|
1109
1109
|
}
|
|
1110
|
+
// DC-62 — ONE RULE, at the top of the chain: inside a paste a
|
|
1111
|
+
// control byte is CONTENT OR NOTHING, never a gesture.
|
|
1112
|
+
//
|
|
1113
|
+
// DC-58 guarded three branches (0x07, 0x14, 0x0f) and stated the
|
|
1114
|
+
// rule generally; the rule was right and the placement was not.
|
|
1115
|
+
// Every other claimed control byte still ran: a pasted backspace
|
|
1116
|
+
// ate the text typed BEFORE the paste, a pasted 0x15 emptied the
|
|
1117
|
+
// buffer, a pasted 0x1a rewound it — and a pasted 0x03 or 0x04
|
|
1118
|
+
// fired the exit callbacks, so a paste could end the session.
|
|
1119
|
+
// `#insert` only COLLECTS what reaches it, so none of these ever
|
|
1120
|
+
// reached the collection; they acted instead.
|
|
1121
|
+
//
|
|
1122
|
+
// Nothing rather than content, because that is what the chain
|
|
1123
|
+
// already does with an UNCLAIMED control byte (`c < " "` discards
|
|
1124
|
+
// it), and a paste should not be the one place where 0x15 becomes
|
|
1125
|
+
// visible text. Three bytes are let through to branches that
|
|
1126
|
+
// already know about `#pasting` and treat them as content: CR and
|
|
1127
|
+
// LF become a newline, TAB stays a tab (DC-55). ESC is let
|
|
1128
|
+
// through because the parser must still see `ESC[201~` to END the
|
|
1129
|
+
// paste — swallow that and the editor never leaves paste mode.
|
|
1130
|
+
//
|
|
1131
|
+
// The three DC-58 guards are retired into this: one rule, not
|
|
1132
|
+
// twelve, and the next control byte someone claims is covered
|
|
1133
|
+
// without anyone remembering to guard it.
|
|
1134
|
+
if (this.#pasting && c !== undefined && c !== "\x1b" && c !== "\x0d" && c !== "\x0a" && c !== "\t") {
|
|
1135
|
+
const code = c.codePointAt(0);
|
|
1136
|
+
if (code < 0x20 || code === 0x7f) {
|
|
1137
|
+
i += 1;
|
|
1138
|
+
continue;
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1110
1141
|
if (this.#panelInput.up()) {
|
|
1111
1142
|
// S5: the panel answers PARSED keys — a bare Esc, an Enter, a
|
|
1112
1143
|
// Tab, a character — and says how many bytes it took, or null
|
|
@@ -1172,6 +1203,19 @@ export class Editor {
|
|
|
1172
1203
|
i += seqLen; // intermediates: a report kiso did not ask for — skipped whole
|
|
1173
1204
|
continue;
|
|
1174
1205
|
}
|
|
1206
|
+
// DC-62b: inside a paste the ONLY ESC-led gesture is the paste
|
|
1207
|
+
// end. DC-62's rule passes ESC through so the parser can still
|
|
1208
|
+
// see `ESC[201~`, and everything else ESC-led rode through with
|
|
1209
|
+
// it: a pasted `ESC[3~` forward-deleted the text after the
|
|
1210
|
+
// cursor. A complete CSI that is not the paste end is skipped
|
|
1211
|
+
// WHOLE — neither content nor gesture, the same answer the C0
|
|
1212
|
+
// rule gives. The incomplete case above is untouched: a CSI
|
|
1213
|
+
// split across two feeds still parks, or a paste arriving in
|
|
1214
|
+
// pieces would lose its end.
|
|
1215
|
+
if (this.#pasting && !(m[1] === "201" && m[3] === "~")) {
|
|
1216
|
+
i += seqLen;
|
|
1217
|
+
continue;
|
|
1218
|
+
}
|
|
1175
1219
|
// TMUX-F1 ②: a BURST of identical arrows in ONE read is a wheel,
|
|
1176
1220
|
// not a hand. Apple Terminal turns wheel and trackpad scrolling
|
|
1177
1221
|
// into arrow keys for an alternate-screen app (tmux's client is
|
|
@@ -1211,9 +1255,74 @@ export class Editor {
|
|
|
1211
1255
|
this.#pending = tail; // incomplete OSC — wait for more
|
|
1212
1256
|
break;
|
|
1213
1257
|
}
|
|
1214
|
-
|
|
1258
|
+
// DC-62b: a pasted OSC is not a terminal report. A shell's
|
|
1259
|
+
// PROMPT_COMMAND writes `ESC]0;title BEL` on every prompt, so
|
|
1260
|
+
// it is in any captured log a human might paste — and it was
|
|
1261
|
+
// reaching the ground-probe reply handler, which is DC-7's
|
|
1262
|
+
// rule inverted: kiso must not read the human's data as the
|
|
1263
|
+
// terminal's answer either. Skipped to its terminator.
|
|
1264
|
+
if (!this.#pasting)
|
|
1265
|
+
this.#oscCb?.(rest.slice(1, end.index));
|
|
1215
1266
|
i += 1 + end.index + end[0].length;
|
|
1216
1267
|
}
|
|
1268
|
+
else if (this.#pasting && rest === "") {
|
|
1269
|
+
// DC-62c: a lone trailing ESC inside a paste is HELD, not
|
|
1270
|
+
// consumed — parked in `#pending` the way an incomplete CSI
|
|
1271
|
+
// parks, so the next feed resolves it.
|
|
1272
|
+
//
|
|
1273
|
+
// Measured: with the paste-end marker split immediately after
|
|
1274
|
+
// its ESC byte (the chunk ends on the bare ESC, the next
|
|
1275
|
+
// begins `[201~`), the marker was missed, `#pasting` never
|
|
1276
|
+
// cleared, and THE COMPOSER WENT DEAF — every later keystroke
|
|
1277
|
+
// collected, nothing on screen, until some intact `ESC[201~`
|
|
1278
|
+
// arrived and flushed the lot. The TMUX-F1 shape, and it
|
|
1279
|
+
// predates DC-62b: byte-identical on the editor before it.
|
|
1280
|
+
//
|
|
1281
|
+
// The hold is free HERE and nowhere else. CA-4 rules that a
|
|
1282
|
+
// bare Esc fires at once rather than waiting for a possible
|
|
1283
|
+
// CSI, because its immediacy is what a hold would spend — esc
|
|
1284
|
+
// interrupts a run. Inside a paste, after DC-62b, a bare ESC
|
|
1285
|
+
// already does nothing at all, so there is no promptness to
|
|
1286
|
+
// spend and nothing to protect. Outside a paste CA-4 is
|
|
1287
|
+
// untouched, and a gate pins that so this is not widened.
|
|
1288
|
+
//
|
|
1289
|
+
// KNOWN RESIDUAL, the mirror at the paste START: a lone
|
|
1290
|
+
// trailing ESC OUTSIDE a paste followed by `[200~body` in the
|
|
1291
|
+
// next chunk fires the bare esc, and the body arrives as
|
|
1292
|
+
// keystrokes, control bytes and all. That one cannot be fixed
|
|
1293
|
+
// without holding the ESC exactly where CA-4 says not to, so
|
|
1294
|
+
// it stays — same rarity argument, a terminal writes each
|
|
1295
|
+
// marker in one write. A real log showing it makes it a
|
|
1296
|
+
// finding of its own.
|
|
1297
|
+
this.#pending = text.slice(i);
|
|
1298
|
+
break;
|
|
1299
|
+
}
|
|
1300
|
+
else if (this.#pasting) {
|
|
1301
|
+
// DC-62b: inside a paste, the CSI arm above has already let
|
|
1302
|
+
// `ESC[201~` through and skipped every other complete CSI, and
|
|
1303
|
+
// the OSC arm has skipped its report. Everything ESC-led that
|
|
1304
|
+
// reaches here is a gesture the human did not make: a bare ESC
|
|
1305
|
+
// fires the escape callbacks (interrupting a running turn), a
|
|
1306
|
+
// double ESC is the redirect, and `ESC(B` — a charset reset,
|
|
1307
|
+
// in any captured terminal log — took the same road.
|
|
1308
|
+
//
|
|
1309
|
+
// ONE BYTE: the ESC is nothing and what follows is content.
|
|
1310
|
+
//
|
|
1311
|
+
// Not because the rest cannot be parsed — an ECMA-48 nF
|
|
1312
|
+
// escape is deterministic (ESC, intermediates 0x20–0x2F, one
|
|
1313
|
+
// final 0x30–0x7E), so `ESC(B` and `ESC=` COULD be consumed
|
|
1314
|
+
// whole. The reason is smaller and still enough: beside CSI
|
|
1315
|
+
// and OSC these forms are rare in pasted logs, the C0 rule
|
|
1316
|
+
// already gives the shape "nothing, not content", and the two
|
|
1317
|
+
// failure modes are not equal — `(B` left visible is
|
|
1318
|
+
// recoverable by the human, text eaten by a misread is not.
|
|
1319
|
+
//
|
|
1320
|
+
// KNOWN RESIDUAL: a pasted nF escape leaves its intermediates
|
|
1321
|
+
// and its final as text. Named here rather than implied,
|
|
1322
|
+
// because the next reader deserves the cost and not only the
|
|
1323
|
+
// choice.
|
|
1324
|
+
i += 1;
|
|
1325
|
+
}
|
|
1217
1326
|
else if (rest.startsWith("O")) {
|
|
1218
1327
|
i += 3; // SS3 (function keys) — ignored
|
|
1219
1328
|
}
|
|
@@ -1411,6 +1520,11 @@ export class Editor {
|
|
|
1411
1520
|
if (file !== null && file !== "") {
|
|
1412
1521
|
// REL-0152-D16: the capsule goes in the buffer, the file
|
|
1413
1522
|
// goes beside it. See #attachments.
|
|
1523
|
+
//
|
|
1524
|
+
// DC-61: and it is an archive point, for the same reason
|
|
1525
|
+
// the bracketed-paste path is — this inserts a token the
|
|
1526
|
+
// human did not type, so one ctrl+z has to take it back.
|
|
1527
|
+
this.#checkpoint();
|
|
1414
1528
|
this.#attachSeq += 1;
|
|
1415
1529
|
this.#attachments.set(this.#attachSeq, file);
|
|
1416
1530
|
for (const ch of `[Image #${this.#attachSeq}]`)
|
|
@@ -1420,6 +1534,21 @@ export class Editor {
|
|
|
1420
1534
|
i += 1;
|
|
1421
1535
|
}
|
|
1422
1536
|
else if (c === "\x0f") {
|
|
1537
|
+
// DC-58 (0.32.1) guarded THIS branch and the two below with
|
|
1538
|
+
// `!#pasting`, the guard tab and CR already had: unguarded, a
|
|
1539
|
+
// pasted BEL opened $VISUAL mid-paste and the rest of the body
|
|
1540
|
+
// went to that child's stdin — DC-7's rule from the third side (a
|
|
1541
|
+
// byte from a paste is data the human handed over, as a reply's
|
|
1542
|
+
// byte is the terminal's).
|
|
1543
|
+
//
|
|
1544
|
+
// DC-62 (0.32.2) RETIRED those three guards into one rule at the
|
|
1545
|
+
// top of the byte loop, because the same question asked of every
|
|
1546
|
+
// other branch found the same answer: a pasted backspace ate the
|
|
1547
|
+
// text typed before the paste, and a pasted 0x03 fired the exit
|
|
1548
|
+
// callback. There is no per-branch guard here any more — look at
|
|
1549
|
+
// the top of the loop, not at this line, for why a control byte
|
|
1550
|
+
// inside a paste reaches nothing.
|
|
1551
|
+
//
|
|
1423
1552
|
// W15: the expand key — rides the chain like a command, the
|
|
1424
1553
|
// editor just forwards it.
|
|
1425
1554
|
//
|
|
@@ -2137,6 +2266,19 @@ export class Editor {
|
|
|
2137
2266
|
if (this.#historyIdx !== null)
|
|
2138
2267
|
this.#historyIdx = null;
|
|
2139
2268
|
this.#queuePopMode = false;
|
|
2269
|
+
// DC-61: a paste is an ARCHIVE POINT. UD-1's invariant was written
|
|
2270
|
+
// around destructive gestures — no gesture may discard more than one
|
|
2271
|
+
// code point without a checkpoint — and a paste discards nothing, so
|
|
2272
|
+
// it never took one. The loss arrived from the other side: ctrl+z
|
|
2273
|
+
// after a paste did nothing, or reached an OLDER checkpoint and threw
|
|
2274
|
+
// away the paste plus everything typed since it in one press.
|
|
2275
|
+
//
|
|
2276
|
+
// It sits here, after the image branch has either returned or filled
|
|
2277
|
+
// `run`, so both shapes are covered by one call and the
|
|
2278
|
+
// nothing-happened case (an empty paste with no clipboard image)
|
|
2279
|
+
// still takes no checkpoint — a phantom entry would make ctrl+z a
|
|
2280
|
+
// press the human has to repeat.
|
|
2281
|
+
this.#checkpoint();
|
|
2140
2282
|
const pasted = _a.#textOf(run);
|
|
2141
2283
|
const lines = pasted.split("\n").length;
|
|
2142
2284
|
const small = lines < _a.#PASTE_LINES && run.length < _a.#PASTE_CHARS;
|
package/dist/lines.js
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* the event renderers (renderEvent, the status line, the recap, the
|
|
14
14
|
* session line).
|
|
15
15
|
*/
|
|
16
|
-
import { escapeTerminal, foldResult, foldThinking, kUnit, palette } from "@vincemakes/kiso-tui-cells/render";
|
|
16
|
+
import { echoText, escapeTerminal, foldResult, foldThinking, kUnit, palette } from "@vincemakes/kiso-tui-cells/render";
|
|
17
17
|
import { foldTerms, widthCut } from "@vincemakes/kiso-tui-cells/components";
|
|
18
18
|
import { visibleWidth } from "@vincemakes/kiso-tui-cells/width";
|
|
19
19
|
export * from "@vincemakes/kiso-tui-cells/render";
|
|
@@ -60,7 +60,7 @@ export function renderEvent(ev, prevThinking = false, resolvePath = (p) => p) {
|
|
|
60
60
|
case "tool_execution_failed":
|
|
61
61
|
return { text: `${p.red} failed: ${escapeTerminal(ev.error.slice(0, 160))}${p.reset}\n`, newline: true, prompt: false };
|
|
62
62
|
case "tool_result": {
|
|
63
|
-
const content =
|
|
63
|
+
const content = echoText(ev.content); // DC-60: one projection, shared with the live echo
|
|
64
64
|
return {
|
|
65
65
|
// v2b: the echo truncates at 160 chars + a /last hint — the
|
|
66
66
|
// full content stays in the event stream.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vincemakes/kiso-tui",
|
|
3
|
-
"version": "0.32.
|
|
3
|
+
"version": "0.32.2",
|
|
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.32.
|
|
38
|
+
"@vincemakes/kiso-tui-cells": "0.32.2"
|
|
39
39
|
}
|
|
40
40
|
}
|