@vincemakes/kiso-tui 0.15.5 → 0.15.7

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.
@@ -803,8 +803,14 @@ export class Body {
803
803
  // "[?1000l[?1006l" into piped stdout and four gates caught it
804
804
  // (compact-cli, tui-modes, tui-v7-planmode, tui2-r2-resume-picker):
805
805
  // the invariant is about terminals, and a pipe is not one.
806
+ // REL-0152-D14: autowrap goes back on with the mouse, and for the
807
+ // same reason — the terminal outlives kiso, and a mode kiso turned
808
+ // off inside a frame must never be what the shell inherits. The
809
+ // frame restores it itself; this is the second belt, on the same
810
+ // TTY gate, because a half-torn-down compositor is exactly where a
811
+ // mode leak survives.
806
812
  if (process.stdout.isTTY === true)
807
- this.#write(MOUSE_OFF);
813
+ this.#write(`${MOUSE_OFF}\x1b[?7h`);
808
814
  if (!this.#docked) {
809
815
  // TUI2-R2pre ③: an un-docked compositor can still hold a listener
810
816
  // (it was superseded, or enter() ran and the dock was torn down by
@@ -1180,6 +1186,36 @@ export class Body {
1180
1186
  panelSpan === null ? null : { top: liveTop + panelSpan.offset, count: panelSpan.count, first: panelSpan.first };
1181
1187
  // 5. the frame bytes.
1182
1188
  const out = [];
1189
+ // REL-0152-D14 — AUTOWRAP OFF for the frame's duration.
1190
+ //
1191
+ // Found in the owner's own capture: 97 of the rows kiso emitted at
1192
+ // 80 columns are EXACTLY 80 cells wide — the box top, the box
1193
+ // bottom, the composer row, every gap row the chrome pads out. A
1194
+ // character printed into the last column does not move the cursor
1195
+ // past it; it sets the terminal's PENDING WRAP flag, and the next
1196
+ // printed character goes to column 1 of the following row. What
1197
+ // terminals do with that flag when a cursor MOVE arrives instead
1198
+ // of a character is not agreed on — some clear it, some keep it —
1199
+ // and this frame then makes 65 relative cursor-ups through exactly
1200
+ // that state.
1201
+ //
1202
+ // So the frame's layout depended on a behaviour terminals disagree
1203
+ // about, on every frame, at every width where a row happens to
1204
+ // fill the screen. That is enough to explain a layout that is
1205
+ // correct on one terminal and damaged on another, and damaged only
1206
+ // while frames are being painted.
1207
+ //
1208
+ // With autowrap off, a character in the last column simply stays
1209
+ // there: no pending flag, no disagreement, and relative moves mean
1210
+ // what they say. kiso can never lose content to it either, because
1211
+ // #checked already refuses to emit a row wider than the screen —
1212
+ // the invariant that makes turning wrapping off safe was in place
1213
+ // long before this.
1214
+ //
1215
+ // Restored at the end of every frame: prose written OUTSIDE a
1216
+ // frame (bodyLog, an error) is ordinary output and must still
1217
+ // wrap, and the shell inherits the terminal when kiso exits.
1218
+ out.push("\x1b[?7l");
1183
1219
  out.push(this.#conservative ? "\x1b[?25l" : "\x1b[?2026h"); // D1: sync ON, or cursor-hide where 2026 is dead bytes
1184
1220
  // A8: the bottom-anchored window (the model's last H rows) shifts
1185
1221
  // DOWN when the live region SHRINKS — the done-fold, the fold-hold
@@ -1202,6 +1238,7 @@ export class Body {
1202
1238
  this.#drawSteady(out, W, H, liveTop, liveLines, queueRows, menuRows, editor);
1203
1239
  }
1204
1240
  out.push(this.#conservative ? "\x1b[?25h" : "\x1b[?2026l");
1241
+ out.push("\x1b[?7h"); // REL-0152-D14: autowrap back on for everything outside the frame
1205
1242
  this.#write(out.join(""));
1206
1243
  this.#lastLiveTop = liveTop;
1207
1244
  this.#lastLiveRows = liveRowsTotal;
package/dist/editor.d.ts CHANGED
@@ -68,6 +68,10 @@ export declare class Editor {
68
68
  * over while the run is told to stop. Mirrors onEscape (a list, so
69
69
  * listeners can coexist); the line arrives already gone from the
70
70
  * composer, exactly as a submit's does. */
71
+ /** REL-0152-D11: what to do when a paste arrives empty. See
72
+ * #onEmptyPaste — the CLI owns the platform, the editor owns the
73
+ * moment. */
74
+ onEmptyPaste(cb: () => string | null): void;
71
75
  onRedirect(cb: (line: string) => void): void;
72
76
  /** W22: bind the pending-turn queue — the CLI's live slots. The ↑
73
77
  * pop walks them (each pop leaves the queue, cancelling the turn);
package/dist/editor.js CHANGED
@@ -153,6 +153,23 @@ export class Editor {
153
153
  * reads, and this is a field, not a local.
154
154
  */
155
155
  #pasteRun = null;
156
+ /**
157
+ * REL-0152-D11 — what an EMPTY paste means.
158
+ *
159
+ * Pasting an image sends nothing: a terminal has no way to put binary
160
+ * into a byte stream, so the bracketed paste arrives with no content.
161
+ * That emptiness is the signal — the user pressed paste and the
162
+ * terminal had nothing to hand over — and whatever they meant has to
163
+ * be fetched from the clipboard instead.
164
+ *
165
+ * The editor does not know what a clipboard is and must not: this
166
+ * package renders and reads keys, and reaching into the operating
167
+ * system from it would put a platform dependency under every gate in
168
+ * the suite. The CLI supplies the hook; the editor supplies the
169
+ * moment. It returns the text to insert (a path, for the CLI's
170
+ * attachment scan to pick up) or null when there was nothing.
171
+ */
172
+ #onEmptyPaste = null;
156
173
  /** TUI2-R3v2 ①: one-shot — a panel that just closed swallows the
157
174
  * habitual trailing enter rather than submitting the restored draft. */
158
175
  #swallowEnter = false;
@@ -282,6 +299,12 @@ export class Editor {
282
299
  * over while the run is told to stop. Mirrors onEscape (a list, so
283
300
  * listeners can coexist); the line arrives already gone from the
284
301
  * composer, exactly as a submit's does. */
302
+ /** REL-0152-D11: what to do when a paste arrives empty. See
303
+ * #onEmptyPaste — the CLI owns the platform, the editor owns the
304
+ * moment. */
305
+ onEmptyPaste(cb) {
306
+ this.#onEmptyPaste = cb;
307
+ }
285
308
  onRedirect(cb) {
286
309
  this.#redirectCbs.push(cb);
287
310
  }
@@ -1902,12 +1925,19 @@ export class Editor {
1902
1925
  * operation in this file works on it without knowing it exists.
1903
1926
  */
1904
1927
  #commitPaste() {
1905
- const run = this.#pasteRun ?? [];
1928
+ let run = this.#pasteRun ?? [];
1906
1929
  const start = this.#pasteAt ?? this.#cursor;
1907
1930
  this.#pasteRun = null;
1908
1931
  this.#pasteAt = null;
1909
- if (run.length === 0)
1910
- return;
1932
+ if (run.length === 0) {
1933
+ // REL-0152-D11: an empty paste is the image case — see
1934
+ // #onEmptyPaste. Anything it returns is ordinary text from here
1935
+ // on and takes the same route as if it had been typed.
1936
+ const substitute = this.#onEmptyPaste?.() ?? null;
1937
+ if (substitute === null || substitute === "")
1938
+ return;
1939
+ run = [...substitute].map((ch) => ch.codePointAt(0));
1940
+ }
1911
1941
  if (this.#historyIdx !== null)
1912
1942
  this.#historyIdx = null;
1913
1943
  this.#queuePopMode = false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vincemakes/kiso-tui",
3
- "version": "0.15.5",
3
+ "version": "0.15.7",
4
4
  "description": "kiso tui \u2014 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.15.5"
38
+ "@vincemakes/kiso-tui-cells": "0.15.7"
39
39
  }
40
40
  }