@vincemakes/kiso-tui 0.40.0 → 0.40.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.d.ts +10 -0
- package/dist/compositor.js +19 -1
- package/dist/editor.d.ts +17 -5
- package/dist/editor.js +42 -31
- package/dist/panel-input.d.ts +8 -0
- package/dist/panel-input.js +17 -4
- package/package.json +2 -2
package/dist/compositor.d.ts
CHANGED
|
@@ -235,6 +235,10 @@ export declare class Body {
|
|
|
235
235
|
count: number;
|
|
236
236
|
first: number;
|
|
237
237
|
} | null;
|
|
238
|
+
visiblePickWindow(): {
|
|
239
|
+
first: number;
|
|
240
|
+
size: number;
|
|
241
|
+
} | null;
|
|
238
242
|
/** SIGWINCH: clear the OLD live area (recorded geometry, ED only —
|
|
239
243
|
* zero LF, zero \x1b[3J — the shell history untouched), then the
|
|
240
244
|
* full-redraw path at the NEW geometry (O(height), zero replay).
|
|
@@ -395,6 +399,12 @@ export declare class Dock {
|
|
|
395
399
|
count: number;
|
|
396
400
|
first: number;
|
|
397
401
|
} | null;
|
|
402
|
+
/** B (review of this round): the pick panel's window for the frame last
|
|
403
|
+
* drawn — what the digit keys must name. */
|
|
404
|
+
visiblePickWindow(): {
|
|
405
|
+
first: number;
|
|
406
|
+
size: number;
|
|
407
|
+
} | null;
|
|
398
408
|
setStatus(text: string, hint?: string | null): void;
|
|
399
409
|
setTail(tail: string): void;
|
|
400
410
|
/** W21: bind the editor's panel state — the PanelSelect slot
|
package/dist/compositor.js
CHANGED
|
@@ -52,6 +52,7 @@ import { leadWidth } from "./width.js"; // W23: the ONE width authority (the edi
|
|
|
52
52
|
// for four reads, so an ask can never render half as an approval.
|
|
53
53
|
import { panelFrameOf, panelLeadOf, panelStatusOf } from "./ask-panel.js";
|
|
54
54
|
import { MOUSE_OFF } from "./editor.js";
|
|
55
|
+
import { pickWindowOf } from "./approval-panel.js";
|
|
55
56
|
import { atPanelRows, bandHeader } from "./at-picker.js";
|
|
56
57
|
// TUI2-R2 ②: the session picker's rows — the band's third occupant.
|
|
57
58
|
import { sessionPickerRows } from "./session-picker.js";
|
|
@@ -1241,6 +1242,13 @@ export class Body {
|
|
|
1241
1242
|
panelOptionRows() {
|
|
1242
1243
|
return this.#panelRowSpan;
|
|
1243
1244
|
}
|
|
1245
|
+
/** B (review of this round): the pick panel's window for the frame last
|
|
1246
|
+
* drawn — the renderer's own value, not a second derivation. `null` when
|
|
1247
|
+
* no pick panel was on screen. */
|
|
1248
|
+
#pickWin = null;
|
|
1249
|
+
visiblePickWindow() {
|
|
1250
|
+
return this.#pickWin;
|
|
1251
|
+
}
|
|
1244
1252
|
/** SIGWINCH: clear the OLD live area (recorded geometry, ED only —
|
|
1245
1253
|
* zero LF, zero \x1b[3J — the shell history untouched), then the
|
|
1246
1254
|
* full-redraw path at the NEW geometry (O(height), zero replay).
|
|
@@ -1853,6 +1861,7 @@ export class Body {
|
|
|
1853
1861
|
// at when they clicked, which is this one.
|
|
1854
1862
|
this.#panelRowSpan =
|
|
1855
1863
|
live.panelSpan === null ? null : { top: liveTop + live.panelSpan.offset, count: live.panelSpan.count, first: live.panelSpan.first };
|
|
1864
|
+
this.#pickWin = live.pickWin ?? null;
|
|
1856
1865
|
// 5. the frame bytes.
|
|
1857
1866
|
this.#paint(W, H, liveTop, liveLines, liveRowsTotal, chrome);
|
|
1858
1867
|
}
|
|
@@ -1953,7 +1962,11 @@ export class Body {
|
|
|
1953
1962
|
// fires on it. W22: the queue band sits below the panel — the
|
|
1954
1963
|
// cap shrinks by it.
|
|
1955
1964
|
const frame = panelFrameOf(panel, W, capped);
|
|
1956
|
-
|
|
1965
|
+
// B (review of this round): the pick window THIS frame draws. The size
|
|
1966
|
+
// depends on the budget, so the renderer is the only producer — the
|
|
1967
|
+
// digit keys are handed this value instead of deriving a second one.
|
|
1968
|
+
const pick = panel.pick ?? null;
|
|
1969
|
+
return { lines: frame.rows, panelSpan: frame.options, pickWin: pick === null ? null : pickWindowOf(panel.view, pick.cursor, pick.phase, capped) };
|
|
1957
1970
|
}
|
|
1958
1971
|
return { lines: this.#liveProjection(W, ctx, cap), panelSpan: null };
|
|
1959
1972
|
}
|
|
@@ -2869,6 +2882,11 @@ export class Dock {
|
|
|
2869
2882
|
panelOptionRows() {
|
|
2870
2883
|
return compositorRef?.panelOptionRows() ?? null;
|
|
2871
2884
|
}
|
|
2885
|
+
/** B (review of this round): the pick panel's window for the frame last
|
|
2886
|
+
* drawn — what the digit keys must name. */
|
|
2887
|
+
visiblePickWindow() {
|
|
2888
|
+
return compositorRef?.visiblePickWindow() ?? null;
|
|
2889
|
+
}
|
|
2872
2890
|
setStatus(text, hint) {
|
|
2873
2891
|
compositorRef?.setStatus(text, hint ?? null);
|
|
2874
2892
|
}
|
package/dist/editor.d.ts
CHANGED
|
@@ -27,13 +27,20 @@ import { type PanelState, type PanelVerdict, type PanelView, type SaferAnswer }
|
|
|
27
27
|
import { type AtItem, type AtMatch } from "./at-picker.js";
|
|
28
28
|
import { type SessionCardView, type SessionPickState } from "./session-picker.js";
|
|
29
29
|
/**
|
|
30
|
-
* TUI2-R3v2
|
|
30
|
+
* TUI2-R3v2 ②, corrected by the owner's ruling of 2026-09-21 (finding
|
|
31
|
+
* DC-56) — the mouse-mode bytes, stated once.
|
|
31
32
|
*
|
|
32
33
|
* ?1000 is the button-event report and ?1006 is the SGR encoding that
|
|
33
34
|
* makes it parseable past column 95 (the legacy X10 encoding packs the
|
|
34
|
-
* coordinate into one byte and simply breaks on a wide terminal).
|
|
35
|
-
*
|
|
36
|
-
*
|
|
35
|
+
* coordinate into one byte and simply breaks on a wide terminal). kiso
|
|
36
|
+
* still RESETS them — on entry (a previous process may have died with
|
|
37
|
+
* reporting left on) and on exit — but it no longer ENABLES them, because
|
|
38
|
+
* a terminal that is reporting stops scrolling its own scrollback: a
|
|
39
|
+
* surface that takes the mouse takes the history with it. kiso's
|
|
40
|
+
* transcript lives in that scrollback by design, and the first version of
|
|
41
|
+
* this feature locked it exactly where a person most wants to read back —
|
|
42
|
+
* under a blocking question. The history wins: no surface claims the
|
|
43
|
+
* mouse, and every surface keeps its keys.
|
|
37
44
|
*/
|
|
38
45
|
/**
|
|
39
46
|
* R5 — the viewer's key table, as a pure function of the input chunk.
|
|
@@ -45,7 +52,6 @@ import { type SessionCardView, type SessionPickState } from "./session-picker.js
|
|
|
45
52
|
* different route.
|
|
46
53
|
*/
|
|
47
54
|
export declare function viewerCommand(text: string): "up" | "down" | "toggle" | "all" | "pageUp" | "pageDown" | "home" | "end" | "close" | null;
|
|
48
|
-
export declare const MOUSE_ON = "\u001B[?1000h\u001B[?1006h";
|
|
49
55
|
export declare const MOUSE_OFF = "\u001B[?1000l\u001B[?1006l";
|
|
50
56
|
export declare const PROMPT = "\u258C ";
|
|
51
57
|
export declare const PROMPT_WIDTH: number;
|
|
@@ -221,6 +227,12 @@ export declare class Editor {
|
|
|
221
227
|
count: number;
|
|
222
228
|
first?: number;
|
|
223
229
|
} | null) | null): void;
|
|
230
|
+
/** B: the pick window the last frame drew — the Dock's value, not the
|
|
231
|
+
* editor's guess. Same pull-on-demand shape as `bindPanelRows`. */
|
|
232
|
+
bindPickWindow(fn: (() => {
|
|
233
|
+
first: number;
|
|
234
|
+
size: number;
|
|
235
|
+
} | null) | null): void;
|
|
224
236
|
/** R3a — cross-session input history: seed the recall buffer and
|
|
225
237
|
* register the append sink. The cap and the adjacent-duplicate
|
|
226
238
|
* collapse are unchanged; the seed takes the TAIL of what the CLI
|
package/dist/editor.js
CHANGED
|
@@ -46,13 +46,20 @@ import { PickInput } from "./pick-input.js";
|
|
|
46
46
|
// "you>" text is gone (the brick IS the prompt; the pipe path's readline
|
|
47
47
|
// prompt keeps its own "you> " — v2a line mode, byte-for-byte).
|
|
48
48
|
/**
|
|
49
|
-
* TUI2-R3v2
|
|
49
|
+
* TUI2-R3v2 ②, corrected by the owner's ruling of 2026-09-21 (finding
|
|
50
|
+
* DC-56) — the mouse-mode bytes, stated once.
|
|
50
51
|
*
|
|
51
52
|
* ?1000 is the button-event report and ?1006 is the SGR encoding that
|
|
52
53
|
* makes it parseable past column 95 (the legacy X10 encoding packs the
|
|
53
|
-
* coordinate into one byte and simply breaks on a wide terminal).
|
|
54
|
-
*
|
|
55
|
-
*
|
|
54
|
+
* coordinate into one byte and simply breaks on a wide terminal). kiso
|
|
55
|
+
* still RESETS them — on entry (a previous process may have died with
|
|
56
|
+
* reporting left on) and on exit — but it no longer ENABLES them, because
|
|
57
|
+
* a terminal that is reporting stops scrolling its own scrollback: a
|
|
58
|
+
* surface that takes the mouse takes the history with it. kiso's
|
|
59
|
+
* transcript lives in that scrollback by design, and the first version of
|
|
60
|
+
* this feature locked it exactly where a person most wants to read back —
|
|
61
|
+
* under a blocking question. The history wins: no surface claims the
|
|
62
|
+
* mouse, and every surface keeps its keys.
|
|
56
63
|
*/
|
|
57
64
|
/**
|
|
58
65
|
* R5 — the viewer's key table, as a pure function of the input chunk.
|
|
@@ -105,7 +112,6 @@ const ALT_WORD = new Map([
|
|
|
105
112
|
["\x7f", "killBack"],
|
|
106
113
|
["\x08", "killBack"],
|
|
107
114
|
]);
|
|
108
|
-
export const MOUSE_ON = "\x1b[?1000h\x1b[?1006h";
|
|
109
115
|
export const MOUSE_OFF = "\x1b[?1000l\x1b[?1006l";
|
|
110
116
|
export const PROMPT = "▌ ";
|
|
111
117
|
export const PROMPT_WIDTH = displayWidth(PROMPT);
|
|
@@ -314,12 +320,16 @@ export class Editor {
|
|
|
314
320
|
/** TUI2-R3v2 ①: one-shot — a panel that just closed swallows the
|
|
315
321
|
* habitual trailing enter rather than submitting the restored draft. */
|
|
316
322
|
#swallowEnter = false;
|
|
317
|
-
/** TUI2-R3v2 ②: whether SGR 1006 reporting is currently enabled. */
|
|
318
|
-
#mouseOn = false;
|
|
319
323
|
/** TUI2-R3v2 ②: where the compositor put the panel's option rows this
|
|
320
324
|
* frame (absolute 1-based screen rows). The editor owns no geometry —
|
|
321
325
|
* it asks the surface that placed them. */
|
|
322
326
|
#panelRows = null;
|
|
327
|
+
/** B (review of this round): the pick panel's window, published by the
|
|
328
|
+
* surface that DREW it — the frame's budget decides the size, so the
|
|
329
|
+
* editor PULLS it here instead of letting the input layer re-derive one
|
|
330
|
+
* (two derivations are two sizes, and on a short terminal the digits named
|
|
331
|
+
* rows nobody could see). */
|
|
332
|
+
#pickWin = null;
|
|
323
333
|
#lineCb = null;
|
|
324
334
|
#pendingLines = []; // submits before onLine is wired (startup) — never dropped
|
|
325
335
|
#sigintCb = null;
|
|
@@ -525,7 +535,6 @@ export class Editor {
|
|
|
525
535
|
// the human has no way to ask for it back.
|
|
526
536
|
process.stdin.setRawMode(true);
|
|
527
537
|
process.stdout.write(MOUSE_OFF);
|
|
528
|
-
this.#mouseOn = false;
|
|
529
538
|
process.stdout.write("\x1b[?2004h");
|
|
530
539
|
process.stdin.on("data", this.#onData);
|
|
531
540
|
}
|
|
@@ -1038,7 +1047,6 @@ export class Editor {
|
|
|
1038
1047
|
// chance this process gets, and emitting six harmless bytes twice
|
|
1039
1048
|
// is not a cost worth reasoning about.
|
|
1040
1049
|
process.stdout.write(MOUSE_OFF);
|
|
1041
|
-
this.#mouseOn = false;
|
|
1042
1050
|
process.stdout.write("\x1b[?2004l"); // bracketed paste OFF
|
|
1043
1051
|
// REL-0161: the hardware cursor was hidden for the session's whole
|
|
1044
1052
|
// life (the compositor's entry reset); this is the one place kiso
|
|
@@ -1050,30 +1058,20 @@ export class Editor {
|
|
|
1050
1058
|
this.#closedResolve();
|
|
1051
1059
|
}
|
|
1052
1060
|
/**
|
|
1053
|
-
* TUI2-R3v2
|
|
1054
|
-
*
|
|
1061
|
+
* TUI2-R3v2 ②, corrected by the owner's ruling of 2026-09-21 (finding
|
|
1062
|
+
* DC-56) — the bands still tell their host when a selection surface
|
|
1063
|
+
* opens or closes, and the host's answer is now "leave the terminal
|
|
1064
|
+
* alone".
|
|
1055
1065
|
*
|
|
1056
|
-
*
|
|
1057
|
-
*
|
|
1058
|
-
*
|
|
1059
|
-
*
|
|
1060
|
-
*
|
|
1061
|
-
*
|
|
1062
|
-
*
|
|
1063
|
-
* not depend on the order they unwind in.
|
|
1066
|
+
* The bands' contract is unchanged — a band reports that it has taken
|
|
1067
|
+
* the composer — but the mouse is not a thing kiso hands out: reporting
|
|
1068
|
+
* on means the terminal stops scrolling its own scrollback, and every
|
|
1069
|
+
* band kiso has (the approval/ask/pick panel, the session picker, the @
|
|
1070
|
+
* picker) is a keyboard surface — digits, arrows, enter, esc, a filter.
|
|
1071
|
+
* The call sites stay so a future surface that DOES want the mouse has
|
|
1072
|
+
* one place to ask, and the ruling is written at that place.
|
|
1064
1073
|
*/
|
|
1065
|
-
#
|
|
1066
|
-
if (this.#mouseOn === on)
|
|
1067
|
-
return;
|
|
1068
|
-
this.#mouseOn = on;
|
|
1069
|
-
if (this.#entered)
|
|
1070
|
-
process.stdout.write(on ? MOUSE_ON : MOUSE_OFF);
|
|
1071
|
-
}
|
|
1072
|
-
/** The surfaces that own a selection — the approval/ask/pick panel, the
|
|
1073
|
-
* session picker and the @ picker. Any one of them up = reporting on. */
|
|
1074
|
-
#syncMouse() {
|
|
1075
|
-
this.#setMouse(this.#panelInput.up() || this.#pickInput.up() || this.#atUp());
|
|
1076
|
-
}
|
|
1074
|
+
#syncMouse() { }
|
|
1077
1075
|
/** OR-11 (a) — the lead the composer's row is DRAWN with, which is not
|
|
1078
1076
|
* always the brick. The CLI binds the compositor's input lead as `""`
|
|
1079
1077
|
* (a prompt character is a third thing saying "input lives here", and
|
|
@@ -1751,6 +1749,13 @@ export class Editor {
|
|
|
1751
1749
|
* and context-menu everywhere else and would mean "approve" here.
|
|
1752
1750
|
* The stakes are a side effect the human did not ask for, and an
|
|
1753
1751
|
* ambiguous mouse event is not consent.
|
|
1752
|
+
*
|
|
1753
|
+
* 2026-09-21 (finding DC-56): kiso no longer ENABLES reporting, so on a
|
|
1754
|
+
* terminal this path is reached only when something else left the mouse
|
|
1755
|
+
* on — and then a click still answers the panel. The wheel stays
|
|
1756
|
+
* dropped either way (a scroll past a panel is not a choice), which is
|
|
1757
|
+
* the other half of why the settled rule is "no surface takes the
|
|
1758
|
+
* mouse": a terminal that is reporting cannot scroll its own history.
|
|
1754
1759
|
*/
|
|
1755
1760
|
#mouseEvent(params, press) {
|
|
1756
1761
|
if (!press)
|
|
@@ -1771,6 +1776,11 @@ export class Editor {
|
|
|
1771
1776
|
bindPanelRows(fn) {
|
|
1772
1777
|
this.#panelRows = fn;
|
|
1773
1778
|
}
|
|
1779
|
+
/** B: the pick window the last frame drew — the Dock's value, not the
|
|
1780
|
+
* editor's guess. Same pull-on-demand shape as `bindPanelRows`. */
|
|
1781
|
+
bindPickWindow(fn) {
|
|
1782
|
+
this.#pickWin = fn;
|
|
1783
|
+
}
|
|
1774
1784
|
/** TMUX-F1 ②: `run` identical arrow sequences arrived in ONE read. Every
|
|
1775
1785
|
* surface but one gets every press — a wheel over the transcript viewer,
|
|
1776
1786
|
* a panel, a multi-row draft scrolls, as a wheel should. The HISTORY
|
|
@@ -2000,6 +2010,7 @@ export class Editor {
|
|
|
2000
2010
|
reflow: () => this.#reflow(),
|
|
2001
2011
|
render: () => this.#onRender(),
|
|
2002
2012
|
syncMouse: () => this.#syncMouse(),
|
|
2013
|
+
pickWindow: () => this.#pickWin?.() ?? null,
|
|
2003
2014
|
closeBands: () => {
|
|
2004
2015
|
this.#menuOpen = false;
|
|
2005
2016
|
this.#menuSel = 0;
|
package/dist/panel-input.d.ts
CHANGED
|
@@ -32,6 +32,14 @@ export interface BandHost {
|
|
|
32
32
|
line(): string;
|
|
33
33
|
/** The buffer with its paste capsules expanded — the text that would leave the editor. */
|
|
34
34
|
expandPastes(line: string): string;
|
|
35
|
+
/** The pick panel's window as the RENDERER drew it (review of this round):
|
|
36
|
+
* the size depends on the frame's budget, so the digit keys must be handed
|
|
37
|
+
* the value instead of deriving it again from PICK_MAX. Absent → the input
|
|
38
|
+
* layer falls back to its own derivation. */
|
|
39
|
+
pickWindow?(): {
|
|
40
|
+
first: number;
|
|
41
|
+
size: number;
|
|
42
|
+
} | null;
|
|
35
43
|
/** Empty the buffer: chars, cursor and the ↑↓ goal column. */
|
|
36
44
|
clear(): void;
|
|
37
45
|
/** Type one code point at the cursor. */
|
package/dist/panel-input.js
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* and the mouse hit. The bodies are the same; only the buffer access
|
|
17
17
|
* goes through the host.
|
|
18
18
|
*/
|
|
19
|
-
import { PICK_MAX, panelOptions, saferDegradedNote, startLevel, stepLevel, } from "./approval-panel.js";
|
|
19
|
+
import { PICK_MAX, panelOptions, pickWindow, saferDegradedNote, startLevel, stepLevel, } from "./approval-panel.js";
|
|
20
20
|
import { askCommitCustom, askKey, askOnCustomRow, askStart } from "./ask-panel.js";
|
|
21
21
|
export class PanelInput {
|
|
22
22
|
host;
|
|
@@ -206,7 +206,11 @@ export class PanelInput {
|
|
|
206
206
|
if (panel === null)
|
|
207
207
|
return false;
|
|
208
208
|
if (panel.pick !== null && panel.pick.phase === "options") {
|
|
209
|
-
|
|
209
|
+
// DC-58: EVERY option is reachable by ↑↓ — the window follows the
|
|
210
|
+
// cursor (`pickWindow`) and PICK_MAX is only how many rows one
|
|
211
|
+
// screen holds. This bound used to be `min(count, PICK_MAX)`, which
|
|
212
|
+
// is what left a 60-profile config with nine reachable rows.
|
|
213
|
+
const n = panel.view.pick.options.length;
|
|
210
214
|
const cur = panel.pick.cursor;
|
|
211
215
|
const next = dir === "up" ? Math.max(0, cur - 1) : Math.min(Math.max(0, n - 1), cur + 1);
|
|
212
216
|
// OR-7: the second axis belongs to the highlighted option, so it
|
|
@@ -448,9 +452,18 @@ export class PanelInput {
|
|
|
448
452
|
const panel = this.#panel;
|
|
449
453
|
if (panel === null || panel.pick === null)
|
|
450
454
|
return;
|
|
451
|
-
|
|
455
|
+
const count = panel.view.pick.options.length;
|
|
456
|
+
// DC-58: a digit names the row ON SCREEN. The window's origin is what
|
|
457
|
+
// turns a visible position into an option index — the same
|
|
458
|
+
// `pickWindow` the renderer drew the rows with, so what the row says
|
|
459
|
+
// and what the key does cannot drift apart.
|
|
460
|
+
const { first, size } = this.host.pickWindow?.() ?? pickWindow(panel.pick.cursor, count, PICK_MAX);
|
|
461
|
+
if (index < 0 || index >= size)
|
|
452
462
|
return;
|
|
453
|
-
|
|
463
|
+
const target = first + index;
|
|
464
|
+
if (target >= count)
|
|
465
|
+
return;
|
|
466
|
+
panel.pick = { cursor: target, phase: "options", level: startLevel(panel.view.pick.options[target]) };
|
|
454
467
|
this.host.render();
|
|
455
468
|
}
|
|
456
469
|
#pickPanelEnter() {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vincemakes/kiso-tui",
|
|
3
|
-
"version": "0.40.
|
|
3
|
+
"version": "0.40.2",
|
|
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.40.
|
|
38
|
+
"@vincemakes/kiso-tui-cells": "0.40.2"
|
|
39
39
|
}
|
|
40
40
|
}
|