@vincemakes/kiso-tui 0.15.8 → 0.15.9

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.
@@ -867,7 +867,7 @@ export class Body {
867
867
  if (process.stdout.isTTY !== true || palette().bold === "" || rows < 4)
868
868
  return;
869
869
  this.#docked = true;
870
- this.#guardStdout();
870
+ this.#guardOutput();
871
871
  this.#attachResize();
872
872
  this.#fullRedraw = true;
873
873
  this.#dirty = true;
@@ -936,26 +936,56 @@ export class Body {
936
936
  * change, and the cursor parks on the wrong row. Three PTY gates
937
937
  * caught it.
938
938
  *
939
- * The fix is not a list of writers to remember. stdout is wrapped
940
- * while the dock is up: anything written that is not this frame's own
941
- * bytes forgets the screen, and the next frame repaints. New callers
942
- * cannot get it wrong because they are not asked to get it right.
939
+ * REL-0152-D17: BOTH descriptors, and the second one is the one that
940
+ * mattered. The first version of this guard wrapped stdout alone,
941
+ * while kiso's degradation notices go to stderr through
942
+ * `console.error` and several of them BEGIN with `[` and contain
943
+ * `]`:
944
+ *
945
+ * [extensions] … [project .kiso] …
946
+ * [KISO_FAUX_SCRIPT] … [run failed] …
947
+ *
948
+ * Those land on the tty wherever the cursor is, and a renderer that
949
+ * does not know they were printed skips exactly the rows that would
950
+ * repair them. The residue survives on the rows whose desired content
951
+ * NEVER changes — the composer box's own edges — which is a stray `[`
952
+ * at the left and `]` at the right for the rest of the session,
953
+ * clearing only on a resize and returning on the next launch.
954
+ *
955
+ * The reasoning that missed it was mine, and it is worth keeping: the
956
+ * renderer emits no OSC and no bare `]`, so a `]` on screen CANNOT
957
+ * have come from its stream. I had that fact and concluded "therefore
958
+ * the terminal invents it" when the only sound conclusion is
959
+ * "therefore something else wrote it".
960
+ *
961
+ * The fix is not a list of writers to remember, and not silencing the
962
+ * loggers. A compositor cannot hold a belief about a terminal it does
963
+ * not own: any write it did not make forgets the screen, whichever
964
+ * descriptor carried it. New callers cannot get it wrong because they
965
+ * are not asked to get it right.
943
966
  */
944
- #guardStdout() {
967
+ #guardOutput() {
945
968
  if (this.#unguard !== null || this.#opts.write !== undefined)
946
969
  return;
947
- const real = process.stdout.write.bind(process.stdout);
948
- const patched = ((chunk, ...rest) => {
949
- if (!this.#inFrame)
950
- this.#screen = [];
951
- return real(chunk, ...rest);
952
- });
953
- process.stdout.write = patched;
970
+ const restores = [];
971
+ for (const stream of [process.stdout, process.stderr]) {
972
+ const real = stream.write.bind(stream);
973
+ const patched = ((chunk, ...rest) => {
974
+ if (!this.#inFrame)
975
+ this.#screen = [];
976
+ return real(chunk, ...rest);
977
+ });
978
+ stream.write = patched;
979
+ restores.push(() => {
980
+ // only restore what we installed — another wrapper may have
981
+ // been layered on top since (the byte trace does exactly that)
982
+ if (stream.write === patched)
983
+ stream.write = real;
984
+ });
985
+ }
954
986
  this.#unguard = () => {
955
- // only restore what we installed — another wrapper may have
956
- // been layered on top since (the byte trace does exactly that)
957
- if (process.stdout.write === patched)
958
- process.stdout.write = real;
987
+ for (const r of restores)
988
+ r();
959
989
  this.#unguard = null;
960
990
  };
961
991
  }
package/dist/editor.d.ts CHANGED
@@ -72,6 +72,10 @@ export declare class Editor {
72
72
  * human asks for them — ctrl+V, or an empty paste. The CLI owns the
73
73
  * platform, the editor owns the moment. */
74
74
  onClipboardPaste(cb: () => string | null): void;
75
+ /** REL-0152-D16: the files this line's `[Image #N]` capsules stand
76
+ * for, by their number. The CLI resolves them when it builds the
77
+ * turn; a capsule the human deleted is simply never looked up. */
78
+ attachments(): Map<number, string>;
75
79
  onRedirect(cb: (line: string) => void): void;
76
80
  /** W22: bind the pending-turn queue — the CLI's live slots. The ↑
77
81
  * pop walks them (each pop leaves the queue, cancelling the turn);
package/dist/editor.js CHANGED
@@ -131,6 +131,24 @@ export class Editor {
131
131
  * every entry is text they chose to paste and may still submit.
132
132
  */
133
133
  #pastes = new Map();
134
+ /**
135
+ * REL-0152-D16 — which file each `[Image #N]` capsule stands for.
136
+ *
137
+ * D15 made ctrl+V fetch the clipboard and it worked; then it inserted
138
+ * the PATH, the path began with `/`, and the composer handed it to
139
+ * the slash-command dispatcher. A feature that reaches the last step
140
+ * and gives the result to the wrong parser has not shipped.
141
+ *
142
+ * So the buffer carries a token and this carries the file. The token
143
+ * is what the LINE is — the dispatcher sees `[Image #1]`, which is
144
+ * not a command and never could be — and the CLI reads this map when
145
+ * it builds the turn. Unlike the text capsule, the image is NOT
146
+ * expanded into the line on the way out: a path is not something the
147
+ * model should be sent, and a transcript full of temp-file names is
148
+ * not something the human should have to read.
149
+ */
150
+ #attachments = new Map();
151
+ #attachSeq = 0;
134
152
  #pasteSeq = 0;
135
153
  /** The buffer index where the in-flight paste began; null outside one. */
136
154
  #pasteAt = null;
@@ -311,6 +329,12 @@ export class Editor {
311
329
  onClipboardPaste(cb) {
312
330
  this.#onClipboardPaste = cb;
313
331
  }
332
+ /** REL-0152-D16: the files this line's `[Image #N]` capsules stand
333
+ * for, by their number. The CLI resolves them when it builds the
334
+ * turn; a capsule the human deleted is simply never looked up. */
335
+ attachments() {
336
+ return new Map(this.#attachments);
337
+ }
314
338
  onRedirect(cb) {
315
339
  this.#redirectCbs.push(cb);
316
340
  }
@@ -1205,9 +1229,13 @@ export class Editor {
1205
1229
  // REL-0152-D15: ctrl+V asks for the clipboard. Inert with no
1206
1230
  // hook wired, and 0x16 must NEVER reach the buffer — an
1207
1231
  // unhandled control byte in a prompt is a corrupt prompt.
1208
- const text = this.#onClipboardPaste?.() ?? null;
1209
- if (text !== null && text !== "") {
1210
- for (const ch of text)
1232
+ const file = this.#onClipboardPaste?.() ?? null;
1233
+ if (file !== null && file !== "") {
1234
+ // REL-0152-D16: the capsule goes in the buffer, the file
1235
+ // goes beside it. See #attachments.
1236
+ this.#attachSeq += 1;
1237
+ this.#attachments.set(this.#attachSeq, file);
1238
+ for (const ch of `[Image #${this.#attachSeq}]`)
1211
1239
  this.#insert(ch.codePointAt(0));
1212
1240
  this.#onRender();
1213
1241
  }
@@ -1951,10 +1979,12 @@ export class Editor {
1951
1979
  // REL-0152-D11: an empty paste is the image case — see
1952
1980
  // #onEmptyPaste. Anything it returns is ordinary text from here
1953
1981
  // on and takes the same route as if it had been typed.
1954
- const substitute = this.#onClipboardPaste?.() ?? null;
1955
- if (substitute === null || substitute === "")
1982
+ const file = this.#onClipboardPaste?.() ?? null;
1983
+ if (file === null || file === "")
1956
1984
  return;
1957
- run = [...substitute].map((ch) => ch.codePointAt(0));
1985
+ this.#attachSeq += 1;
1986
+ this.#attachments.set(this.#attachSeq, file);
1987
+ run = [...`[Image #${this.#attachSeq}]`].map((ch) => ch.codePointAt(0));
1958
1988
  }
1959
1989
  if (this.#historyIdx !== null)
1960
1990
  this.#historyIdx = null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vincemakes/kiso-tui",
3
- "version": "0.15.8",
3
+ "version": "0.15.9",
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.8"
38
+ "@vincemakes/kiso-tui-cells": "0.15.9"
39
39
  }
40
40
  }