@vincemakes/kiso-tui 0.11.0 → 0.13.0
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/ask-panel.d.ts +30 -6
- package/dist/ask-panel.js +40 -15
- package/dist/compositor.d.ts +15 -0
- package/dist/compositor.js +45 -2
- package/dist/editor.d.ts +25 -2
- package/dist/editor.js +456 -91
- package/dist/index.d.ts +3 -2
- package/dist/index.js +3 -2
- package/dist/status.d.ts +3 -1
- package/dist/status.js +3 -1
- package/package.json +2 -2
package/dist/ask-panel.d.ts
CHANGED
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
* durability would need a new durable mechanism), no "chat about this"
|
|
28
28
|
* hand-off, no timeout and no countdown.
|
|
29
29
|
*/
|
|
30
|
-
import { type AskAnswer, type AskOption, type AskQuestion, type AskResult, type AskRuntime, type AskSpec, type PanelPhase, type
|
|
30
|
+
import { type SaferRuntime, type AskAnswer, type AskOption, type AskQuestion, type AskResult, type AskRuntime, type AskSpec, type PanelPhase, type PanelState, type PanelView, type PickRuntime } from "./approval-panel.js";
|
|
31
31
|
/** The schema's own bounds — the registry refuses anything outside them
|
|
32
32
|
* (extensions/ask validates; these are the numbers it validates to). */
|
|
33
33
|
export declare const ASK_MAX_QUESTIONS = 4;
|
|
@@ -77,15 +77,39 @@ export declare function askStatus(view: PanelView, state: AskRuntime): string;
|
|
|
77
77
|
/** The input row's lead: the digit lead while picking, the typing lead
|
|
78
78
|
* in the custom phase (the rule-input phase's shape, reused). */
|
|
79
79
|
export declare function askLeadPlain(state: AskRuntime): string;
|
|
80
|
-
export declare function panelBlockRows(view: PanelView, phase: PanelPhase,
|
|
81
|
-
export declare function panelLead(view: PanelView, phase: PanelPhase,
|
|
82
|
-
export declare function panelLeadPlain(view: PanelView, phase: PanelPhase,
|
|
83
|
-
export declare function panelStatus(view: PanelView, phase: PanelPhase,
|
|
84
|
-
export declare function panelAffordance(view: PanelView, phase: PanelPhase,
|
|
80
|
+
export declare function panelBlockRows(view: PanelView, phase: PanelPhase, cursor: number, W: number, maxRows: number, ask?: AskRuntime, pick?: PickRuntime, note?: string, safer?: SaferRuntime): string[];
|
|
81
|
+
export declare function panelLead(view: PanelView, phase: PanelPhase, cursor: number, ask?: AskRuntime, pick?: PickRuntime): string;
|
|
82
|
+
export declare function panelLeadPlain(view: PanelView, phase: PanelPhase, cursor: number, ask?: AskRuntime, pick?: PickRuntime): string;
|
|
83
|
+
export declare function panelStatus(view: PanelView, phase: PanelPhase, cursor: number, ask?: AskRuntime, pick?: PickRuntime): string;
|
|
84
|
+
export declare function panelAffordance(view: PanelView, phase: PanelPhase, cursor: number, ask?: AskRuntime, pick?: PickRuntime, safer?: SaferRuntime): string;
|
|
85
85
|
/** The whole panel state in one call — the compositor's four reads share
|
|
86
86
|
* one source, so an ask can never render half as an approval (TUI2-R2
|
|
87
87
|
* ④: nor a pick as either). */
|
|
88
88
|
export declare const panelRowsOf: (s: PanelState, W: number, maxRows: number) => string[];
|
|
89
|
+
/**
|
|
90
|
+
* TUI2-R3v2 ② — the rows AND where the clickable ones are, from ONE
|
|
91
|
+
* call.
|
|
92
|
+
*
|
|
93
|
+
* The compositor needs both and must not compute the second from the
|
|
94
|
+
* first: the args cap, the note row and the option window all move the
|
|
95
|
+
* list, so a hit-test that re-derived the offset would drift from the
|
|
96
|
+
* picture exactly when the block is under pressure — which is when a
|
|
97
|
+
* misrouted click is most expensive.
|
|
98
|
+
*
|
|
99
|
+
* `options` is null for the ask and pick flavors: their rows are placed
|
|
100
|
+
* by their own renderers, and this round does not claim a click on them
|
|
101
|
+
* (the enable/disable invariant covers those surfaces; the gesture does
|
|
102
|
+
* not). A null here means every click is inert, which is the safe way
|
|
103
|
+
* to not-implement something.
|
|
104
|
+
*/
|
|
105
|
+
export declare const panelFrameOf: (s: PanelState, W: number, maxRows: number) => {
|
|
106
|
+
rows: string[];
|
|
107
|
+
options: {
|
|
108
|
+
offset: number;
|
|
109
|
+
count: number;
|
|
110
|
+
first: number;
|
|
111
|
+
} | null;
|
|
112
|
+
};
|
|
89
113
|
export declare const panelLeadOf: (s: PanelState) => string;
|
|
90
114
|
export declare const panelStatusOf: (s: PanelState) => string;
|
|
91
115
|
export declare const panelAffordanceOf: (s: PanelState) => string;
|
package/dist/ask-panel.js
CHANGED
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
* durability would need a new durable mechanism), no "chat about this"
|
|
28
28
|
* hand-off, no timeout and no countdown.
|
|
29
29
|
*/
|
|
30
|
-
import { panelAffordance as basePanelAffordance, panelBlockRows as basePanelBlockRows, panelLead as basePanelLead, panelLeadPlain as basePanelLeadPlain, panelStatus as basePanelStatus, pickAffordance, pickBlockRows, pickLead, pickLeadPlain, pickStatus, } from "./approval-panel.js";
|
|
30
|
+
import { panelBlockLayout, panelAffordance as basePanelAffordance, panelBlockRows as basePanelBlockRows, panelLead as basePanelLead, panelLeadPlain as basePanelLeadPlain, panelStatus as basePanelStatus, pickAffordance, pickBlockRows, pickLead, pickLeadPlain, pickStatus, } from "./approval-panel.js";
|
|
31
31
|
import { cutLine } from "@vincemakes/kiso-tui-cells/components";
|
|
32
32
|
import { escapeTerminal, palette } from "./render.js";
|
|
33
33
|
/** The schema's own bounds — the registry refuses anything outside them
|
|
@@ -225,49 +225,74 @@ export function askLeadPlain(state) {
|
|
|
225
225
|
return state.phase === "custom" ? "your answer: " : "1-4> ";
|
|
226
226
|
}
|
|
227
227
|
// ── the dispatchers: the panel slot, with the ask branch folded in ────
|
|
228
|
-
export function panelBlockRows(view, phase,
|
|
228
|
+
export function panelBlockRows(view, phase, cursor, W, maxRows, ask, pick, note, safer) {
|
|
229
229
|
if (view.pick !== undefined && pick !== undefined)
|
|
230
230
|
return pickBlockRows(view, pick, W, maxRows);
|
|
231
231
|
if (view.ask !== undefined && ask !== undefined)
|
|
232
232
|
return askBlockRows(view, ask, W, maxRows);
|
|
233
|
-
return basePanelBlockRows(view, phase,
|
|
233
|
+
return basePanelBlockRows(view, phase, cursor, W, maxRows, note, safer);
|
|
234
234
|
}
|
|
235
|
-
export function panelLead(view, phase,
|
|
235
|
+
export function panelLead(view, phase, cursor, ask, pick) {
|
|
236
236
|
const p = palette();
|
|
237
237
|
if (view.pick !== undefined && pick !== undefined)
|
|
238
238
|
return pickLead(view, pick);
|
|
239
239
|
if (view.ask !== undefined && ask !== undefined)
|
|
240
240
|
return `${p.bold}${askLeadPlain(ask)}${p.reset}`;
|
|
241
|
-
return basePanelLead(view, phase,
|
|
241
|
+
return basePanelLead(view, phase, cursor);
|
|
242
242
|
}
|
|
243
|
-
export function panelLeadPlain(view, phase,
|
|
243
|
+
export function panelLeadPlain(view, phase, cursor, ask, pick) {
|
|
244
244
|
if (view.pick !== undefined && pick !== undefined)
|
|
245
245
|
return pickLeadPlain(view, pick);
|
|
246
246
|
if (view.ask !== undefined && ask !== undefined)
|
|
247
247
|
return askLeadPlain(ask);
|
|
248
|
-
return basePanelLeadPlain(view, phase,
|
|
248
|
+
return basePanelLeadPlain(view, phase, cursor);
|
|
249
249
|
}
|
|
250
|
-
export function panelStatus(view, phase,
|
|
250
|
+
export function panelStatus(view, phase, cursor, ask, pick) {
|
|
251
251
|
if (view.pick !== undefined && pick !== undefined)
|
|
252
252
|
return pickStatus(view);
|
|
253
253
|
if (view.ask !== undefined && ask !== undefined)
|
|
254
254
|
return askStatus(view, ask);
|
|
255
|
-
return basePanelStatus(view, phase,
|
|
255
|
+
return basePanelStatus(view, phase, cursor);
|
|
256
256
|
}
|
|
257
|
-
export function panelAffordance(view, phase,
|
|
257
|
+
export function panelAffordance(view, phase, cursor, ask, pick, safer) {
|
|
258
258
|
if (view.pick !== undefined && pick !== undefined)
|
|
259
259
|
return pickAffordance(pick);
|
|
260
260
|
if (view.ask !== undefined && ask !== undefined)
|
|
261
261
|
return askAffordance(ask);
|
|
262
|
-
return basePanelAffordance(view, phase,
|
|
262
|
+
return basePanelAffordance(view, phase, cursor, safer);
|
|
263
263
|
}
|
|
264
264
|
/** The whole panel state in one call — the compositor's four reads share
|
|
265
265
|
* one source, so an ask can never render half as an approval (TUI2-R2
|
|
266
266
|
* ④: nor a pick as either). */
|
|
267
|
-
export const panelRowsOf = (s, W, maxRows) => panelBlockRows(s.view, s.phase, s.
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
267
|
+
export const panelRowsOf = (s, W, maxRows) => panelBlockRows(s.view, s.phase, s.cursor, W, maxRows, s.ask, s.pick, s.note, s.safer);
|
|
268
|
+
/**
|
|
269
|
+
* TUI2-R3v2 ② — the rows AND where the clickable ones are, from ONE
|
|
270
|
+
* call.
|
|
271
|
+
*
|
|
272
|
+
* The compositor needs both and must not compute the second from the
|
|
273
|
+
* first: the args cap, the note row and the option window all move the
|
|
274
|
+
* list, so a hit-test that re-derived the offset would drift from the
|
|
275
|
+
* picture exactly when the block is under pressure — which is when a
|
|
276
|
+
* misrouted click is most expensive.
|
|
277
|
+
*
|
|
278
|
+
* `options` is null for the ask and pick flavors: their rows are placed
|
|
279
|
+
* by their own renderers, and this round does not claim a click on them
|
|
280
|
+
* (the enable/disable invariant covers those surfaces; the gesture does
|
|
281
|
+
* not). A null here means every click is inert, which is the safe way
|
|
282
|
+
* to not-implement something.
|
|
283
|
+
*/
|
|
284
|
+
export const panelFrameOf = (s, W, maxRows) => {
|
|
285
|
+
if (s.view.pick !== undefined || s.view.ask !== undefined)
|
|
286
|
+
return { rows: panelRowsOf(s, W, maxRows), options: null };
|
|
287
|
+
const layout = panelBlockLayout(s.view, s.phase, s.cursor, W, maxRows, s.note, s.safer);
|
|
288
|
+
return {
|
|
289
|
+
rows: layout.rows,
|
|
290
|
+
options: layout.count === 0 ? null : { offset: layout.offset, count: layout.count, first: layout.first },
|
|
291
|
+
};
|
|
292
|
+
};
|
|
293
|
+
export const panelLeadOf = (s) => panelLead(s.view, s.phase, s.cursor, s.ask, s.pick);
|
|
294
|
+
export const panelStatusOf = (s) => panelStatus(s.view, s.phase, s.cursor, s.ask, s.pick);
|
|
295
|
+
export const panelAffordanceOf = (s) => panelAffordance(s.view, s.phase, s.cursor, s.ask, s.pick, s.safer);
|
|
271
296
|
// ── the view: what the human reads when the model asks ────────────────
|
|
272
297
|
/** The ask's PanelView. The dock-less fallback question is HONEST: a
|
|
273
298
|
* terminal without a panel cannot walk options, so it says the ask is
|
package/dist/compositor.d.ts
CHANGED
|
@@ -193,6 +193,13 @@ export declare class Body {
|
|
|
193
193
|
/** Teardown — CSI r (the "no broken terminal" contract byte), the
|
|
194
194
|
* chrome rows cleared, the cursor home at the input line. */
|
|
195
195
|
exit(): void;
|
|
196
|
+
/** TUI2-R3v2 ②: the panel's option rows as this frame placed them —
|
|
197
|
+
* absolute, 1-based. The click hit-test's single source. */
|
|
198
|
+
panelOptionRows(): {
|
|
199
|
+
top: number;
|
|
200
|
+
count: number;
|
|
201
|
+
first: number;
|
|
202
|
+
} | null;
|
|
196
203
|
/** SIGWINCH: clear the OLD live area (recorded geometry, ED only —
|
|
197
204
|
* zero LF, zero \x1b[3J — the shell history untouched), then the
|
|
198
205
|
* full-redraw path at the NEW geometry (O(height), zero replay).
|
|
@@ -257,6 +264,14 @@ export declare class Dock {
|
|
|
257
264
|
enter(): void;
|
|
258
265
|
exit(): void;
|
|
259
266
|
onResize(): void;
|
|
267
|
+
/** TUI2-R3v2 ②: where the last frame put the panel's clickable option
|
|
268
|
+
* rows (absolute screen rows). The editor binds this and does no row
|
|
269
|
+
* arithmetic of its own. */
|
|
270
|
+
panelOptionRows(): {
|
|
271
|
+
top: number;
|
|
272
|
+
count: number;
|
|
273
|
+
first: number;
|
|
274
|
+
} | null;
|
|
260
275
|
setStatus(text: string, hint?: string | null): void;
|
|
261
276
|
setTail(tail: string): void;
|
|
262
277
|
/** W21: bind the editor's panel state — the PanelSelect slot
|
package/dist/compositor.js
CHANGED
|
@@ -47,7 +47,8 @@ import { displayWidth } from "./editor.js";
|
|
|
47
47
|
import { leadWidth } from "./width.js"; // W23: the ONE width authority (the editor, #inputRow, and editCol share it)
|
|
48
48
|
// KC3.5: the panel-slot reads come from the DISPATCHERS — one source
|
|
49
49
|
// for four reads, so an ask can never render half as an approval.
|
|
50
|
-
import { panelAffordanceOf, panelLeadOf, panelRowsOf, panelStatusOf } from "./ask-panel.js";
|
|
50
|
+
import { panelAffordanceOf, panelFrameOf, panelLeadOf, panelRowsOf, panelStatusOf } from "./ask-panel.js";
|
|
51
|
+
import { MOUSE_OFF } from "./editor.js";
|
|
51
52
|
import { atPanelRows, bandHeader } from "./at-picker.js";
|
|
52
53
|
// TUI2-R2 ②: the session picker's rows — the band's third occupant.
|
|
53
54
|
import { sessionPickerRows } from "./session-picker.js";
|
|
@@ -117,6 +118,9 @@ export class Body {
|
|
|
117
118
|
#rolledHeads = new Set();
|
|
118
119
|
#write;
|
|
119
120
|
#resizeHandler = null;
|
|
121
|
+
/** TUI2-R3v2 ②: the panel option rows' absolute screen span, as of the
|
|
122
|
+
* last frame. Null whenever no clickable list is on screen. */
|
|
123
|
+
#panelRowSpan = null;
|
|
120
124
|
// the chrome state (the Dock façade)
|
|
121
125
|
#status = "";
|
|
122
126
|
#statusHint = null;
|
|
@@ -772,6 +776,20 @@ export class Body {
|
|
|
772
776
|
/** Teardown — CSI r (the "no broken terminal" contract byte), the
|
|
773
777
|
* chrome rows cleared, the cursor home at the input line. */
|
|
774
778
|
exit() {
|
|
779
|
+
// TUI2-R3v2 ②: the mouse disable rides the SAME teardown as CSI r,
|
|
780
|
+
// and rides it BEFORE the docked guard — an un-docked compositor is
|
|
781
|
+
// exactly the state a superseded or half-torn-down one is in, and
|
|
782
|
+
// that is when a leak survives. The editor disables it too; this is
|
|
783
|
+
// the second belt on the one contract the round calls blocker-class.
|
|
784
|
+
//
|
|
785
|
+
// TTY-GATED, and the gate is not a nicety. A pipe has no mouse mode
|
|
786
|
+
// to reset, so the bytes would be pure noise there — and the pipe
|
|
787
|
+
// path is byte-identical by ruling. The unguarded version put
|
|
788
|
+
// "[?1000l[?1006l" into piped stdout and four gates caught it
|
|
789
|
+
// (compact-cli, tui-modes, tui-v7-planmode, tui2-r2-resume-picker):
|
|
790
|
+
// the invariant is about terminals, and a pipe is not one.
|
|
791
|
+
if (process.stdout.isTTY === true)
|
|
792
|
+
this.#write(MOUSE_OFF);
|
|
775
793
|
if (!this.#docked) {
|
|
776
794
|
// TUI2-R2pre ③: an un-docked compositor can still hold a listener
|
|
777
795
|
// (it was superseded, or enter() ran and the dock was torn down by
|
|
@@ -793,6 +811,11 @@ export class Body {
|
|
|
793
811
|
out.push(`\x1b[${Math.max(1, H - 1)};1H`);
|
|
794
812
|
this.#write(out.join(""));
|
|
795
813
|
}
|
|
814
|
+
/** TUI2-R3v2 ②: the panel's option rows as this frame placed them —
|
|
815
|
+
* absolute, 1-based. The click hit-test's single source. */
|
|
816
|
+
panelOptionRows() {
|
|
817
|
+
return this.#panelRowSpan;
|
|
818
|
+
}
|
|
796
819
|
/** SIGWINCH: clear the OLD live area (recorded geometry, ED only —
|
|
797
820
|
* zero LF, zero \x1b[3J — the shell history untouched), then the
|
|
798
821
|
* full-redraw path at the NEW geometry (O(height), zero replay).
|
|
@@ -1051,6 +1074,10 @@ export class Body {
|
|
|
1051
1074
|
const inputExtra = editor.rows.length - 1;
|
|
1052
1075
|
const chromeRows = CHROME_ROWS + inputExtra + menuRows.length + queueRows.length;
|
|
1053
1076
|
let liveLines = [];
|
|
1077
|
+
// TUI2-R3v2 ②: where this frame put the panel's option rows, relative
|
|
1078
|
+
// to the live region's top. Resolved to ABSOLUTE screen rows once
|
|
1079
|
+
// liveTop is known, below.
|
|
1080
|
+
let panelSpan = null;
|
|
1054
1081
|
const panel = this.#panelState?.() ?? null;
|
|
1055
1082
|
// TUI2-R1.5 ⑦(a) (VD-8): the sheet is an OVERLAY, and the frame it
|
|
1056
1083
|
// opens on — and the one it closes on — take the full-redraw path.
|
|
@@ -1077,7 +1104,12 @@ export class Body {
|
|
|
1077
1104
|
// the W11 blank would separate it from the frozen content).
|
|
1078
1105
|
// The cap is exact, so the force-commit loop never fires. W22:
|
|
1079
1106
|
// the queue band sits below the panel — the cap shrinks by it.
|
|
1080
|
-
|
|
1107
|
+
// TUI2-R3v2 ②: the rows and the CLICKABLE span come from one
|
|
1108
|
+
// call, so the hit-test reads the arithmetic that placed the
|
|
1109
|
+
// rows rather than a second copy of it.
|
|
1110
|
+
const frame = panelFrameOf(panel, W, Math.max(1, H - 4 - inputExtra - queueRows.length));
|
|
1111
|
+
liveLines = frame.rows;
|
|
1112
|
+
panelSpan = frame.options;
|
|
1081
1113
|
}
|
|
1082
1114
|
else {
|
|
1083
1115
|
// TUI2-R2 ⑤ (D, candidate 1): the FOCUS — the cell the next ctrl+r
|
|
@@ -1126,6 +1158,11 @@ export class Body {
|
|
|
1126
1158
|
// shows the bottom H rows; the live region anchors to the bottom.
|
|
1127
1159
|
const liveRowsTotal = liveLines.length + chromeRows;
|
|
1128
1160
|
const liveTop = Math.min(this.#committedLines, H - liveRowsTotal) + 1;
|
|
1161
|
+
// TUI2-R3v2 ②: the option rows' ABSOLUTE screen rows, recorded per
|
|
1162
|
+
// frame. A click is answered against the frame the human was looking
|
|
1163
|
+
// at when they clicked, which is this one.
|
|
1164
|
+
this.#panelRowSpan =
|
|
1165
|
+
panelSpan === null ? null : { top: liveTop + panelSpan.offset, count: panelSpan.count, first: panelSpan.first };
|
|
1129
1166
|
// 5. the frame bytes.
|
|
1130
1167
|
const out = [];
|
|
1131
1168
|
out.push("\x1b[?2026h"); // synchronized output ON (DEC 2026)
|
|
@@ -1995,6 +2032,12 @@ export class Dock {
|
|
|
1995
2032
|
onResize() {
|
|
1996
2033
|
compositorRef?.onResize();
|
|
1997
2034
|
}
|
|
2035
|
+
/** TUI2-R3v2 ②: where the last frame put the panel's clickable option
|
|
2036
|
+
* rows (absolute screen rows). The editor binds this and does no row
|
|
2037
|
+
* arithmetic of its own. */
|
|
2038
|
+
panelOptionRows() {
|
|
2039
|
+
return compositorRef?.panelOptionRows() ?? null;
|
|
2040
|
+
}
|
|
1998
2041
|
setStatus(text, hint) {
|
|
1999
2042
|
compositorRef?.setStatus(text, hint ?? null);
|
|
2000
2043
|
}
|
package/dist/editor.d.ts
CHANGED
|
@@ -23,9 +23,20 @@
|
|
|
23
23
|
*/
|
|
24
24
|
import { charWidth, displayWidth, widthOf } from "./width.js";
|
|
25
25
|
export { charWidth, displayWidth, widthOf };
|
|
26
|
-
import { type PanelState, type PanelVerdict, type PanelView } from "./approval-panel.js";
|
|
26
|
+
import { type PanelState, type PanelVerdict, type PanelView, type SaferAnswer } from "./approval-panel.js";
|
|
27
27
|
import { type AtItem, type AtMatch } from "./at-picker.js";
|
|
28
28
|
import { type SessionCardView, type SessionPickState } from "./session-picker.js";
|
|
29
|
+
/**
|
|
30
|
+
* TUI2-R3v2 ② — the mouse-mode bytes, stated once.
|
|
31
|
+
*
|
|
32
|
+
* ?1000 is the button-event report and ?1006 is the SGR encoding that
|
|
33
|
+
* makes it parseable past column 95 (the legacy X10 encoding packs the
|
|
34
|
+
* coordinate into one byte and simply breaks on a wide terminal). Both
|
|
35
|
+
* go on together and come off together; a terminal left with either one
|
|
36
|
+
* set is a terminal that prints escape bytes at the shell prompt.
|
|
37
|
+
*/
|
|
38
|
+
export declare const MOUSE_ON = "\u001B[?1000h\u001B[?1006h";
|
|
39
|
+
export declare const MOUSE_OFF = "\u001B[?1000l\u001B[?1006l";
|
|
29
40
|
export declare const PROMPT = "\u258C ";
|
|
30
41
|
export declare const PROMPT_WIDTH: number;
|
|
31
42
|
/** v3 §04 — the slash-command menu's command table (English one-liners). */
|
|
@@ -116,7 +127,9 @@ export declare class Editor {
|
|
|
116
127
|
/** W21: open the approval panel. The current buffer is stashed
|
|
117
128
|
* (restored at close — commit AND cancel), the panel takes the
|
|
118
129
|
* keys and the input row's lead, the menu closes. */
|
|
119
|
-
beginPanel(view: PanelView, onCommit: (v: PanelVerdict) => void
|
|
130
|
+
beginPanel(view: PanelView, onCommit: (v: PanelVerdict) => void, opts?: {
|
|
131
|
+
safer?: () => Promise<SaferAnswer>;
|
|
132
|
+
}): void;
|
|
120
133
|
/** W21: cancel the panel — the SIGINT path's pair to beginPanel. */
|
|
121
134
|
cancelPanel(): void;
|
|
122
135
|
/** W21: the compositor's bound view — the phase/selection while the
|
|
@@ -130,4 +143,14 @@ export declare class Editor {
|
|
|
130
143
|
selfRender(): void;
|
|
131
144
|
/** Feed raw stdin bytes — the parser. Public for unit tests. */
|
|
132
145
|
feed(raw: Uint8Array): void;
|
|
146
|
+
/** TUI2-R3v2 ②: the compositor reports where it PUT the option rows.
|
|
147
|
+
* The editor does no row arithmetic of its own — the surface that
|
|
148
|
+
* placed them is the only thing that can say where they are, and a
|
|
149
|
+
* second copy of that sum is how a hit-test comes to disagree with
|
|
150
|
+
* the picture. */
|
|
151
|
+
bindPanelRows(fn: (() => {
|
|
152
|
+
top: number;
|
|
153
|
+
count: number;
|
|
154
|
+
first?: number;
|
|
155
|
+
} | null) | null): void;
|
|
133
156
|
}
|
package/dist/editor.js
CHANGED
|
@@ -26,7 +26,7 @@ import { charWidth, displayWidth, leadWidth, widthOf } from "./width.js";
|
|
|
26
26
|
// authority) — re-exported so the editor's public surface is unchanged.
|
|
27
27
|
export { charWidth, displayWidth, widthOf };
|
|
28
28
|
import { palette } from "./render.js";
|
|
29
|
-
import { PICK_MAX } from "./approval-panel.js";
|
|
29
|
+
import { PICK_MAX, panelOptions, saferDegradedNote, } from "./approval-panel.js";
|
|
30
30
|
// KC3.5: the panel-slot dispatchers — the ask branch folded into the
|
|
31
31
|
// W21 lead/rows, so this file keeps ONE panel and one key owner.
|
|
32
32
|
import { askCommitCustom, askKey, askStart, panelLead } from "./ask-panel.js";
|
|
@@ -38,6 +38,17 @@ import { sessionFilter } from "./session-picker.js";
|
|
|
38
38
|
// TUI v4 #16d: the input row is the blue brick + the edit area — the
|
|
39
39
|
// "you>" text is gone (the brick IS the prompt; the pipe path's readline
|
|
40
40
|
// prompt keeps its own "you> " — v2a line mode, byte-for-byte).
|
|
41
|
+
/**
|
|
42
|
+
* TUI2-R3v2 ② — the mouse-mode bytes, stated once.
|
|
43
|
+
*
|
|
44
|
+
* ?1000 is the button-event report and ?1006 is the SGR encoding that
|
|
45
|
+
* makes it parseable past column 95 (the legacy X10 encoding packs the
|
|
46
|
+
* coordinate into one byte and simply breaks on a wide terminal). Both
|
|
47
|
+
* go on together and come off together; a terminal left with either one
|
|
48
|
+
* set is a terminal that prints escape bytes at the shell prompt.
|
|
49
|
+
*/
|
|
50
|
+
export const MOUSE_ON = "\x1b[?1000h\x1b[?1006h";
|
|
51
|
+
export const MOUSE_OFF = "\x1b[?1000l\x1b[?1006l";
|
|
41
52
|
export const PROMPT = "▌ ";
|
|
42
53
|
export const PROMPT_WIDTH = displayWidth(PROMPT);
|
|
43
54
|
export const MENU_ITEMS = [
|
|
@@ -96,6 +107,18 @@ export class Editor {
|
|
|
96
107
|
// user's next turn.
|
|
97
108
|
#panel = null;
|
|
98
109
|
#pasting = false;
|
|
110
|
+
/** TUI2-R3v2 ①: one-shot — a panel that just closed swallows the
|
|
111
|
+
* habitual trailing enter rather than submitting the restored draft. */
|
|
112
|
+
#swallowEnter = false;
|
|
113
|
+
/** TUI2-R3v2 ②: whether SGR 1006 reporting is currently enabled. */
|
|
114
|
+
#mouseOn = false;
|
|
115
|
+
/** TUI2-R3v2 ③: the safer ask's generation. A panel the human escaped
|
|
116
|
+
* must not be resurrected by a promise nobody is waiting for. */
|
|
117
|
+
#saferToken = 0;
|
|
118
|
+
/** TUI2-R3v2 ②: where the compositor put the panel's option rows this
|
|
119
|
+
* frame (absolute 1-based screen rows). The editor owns no geometry —
|
|
120
|
+
* it asks the surface that placed them. */
|
|
121
|
+
#panelRows = null;
|
|
99
122
|
#lineCb = null;
|
|
100
123
|
#pendingLines = []; // submits before onLine is wired (startup) — never dropped
|
|
101
124
|
#sigintCb = null;
|
|
@@ -423,11 +446,13 @@ export class Editor {
|
|
|
423
446
|
this.#atOpen = true;
|
|
424
447
|
this.#atSel = 0;
|
|
425
448
|
this.#atList = this.#atItems(); // §5: listed per OPEN, never per keystroke
|
|
449
|
+
this.#syncMouse();
|
|
426
450
|
}
|
|
427
451
|
#atClose() {
|
|
428
452
|
this.#atOpen = false;
|
|
429
453
|
this.#atSel = 0;
|
|
430
454
|
this.#atList = null;
|
|
455
|
+
this.#syncMouse();
|
|
431
456
|
}
|
|
432
457
|
/**
|
|
433
458
|
* KC3 §3 — accept: the token becomes `@<path> `.
|
|
@@ -470,6 +495,7 @@ export class Editor {
|
|
|
470
495
|
this.#pickCards = cards;
|
|
471
496
|
this.#pickCommit = onPick;
|
|
472
497
|
this.#pickSel = 0;
|
|
498
|
+
this.#syncMouse();
|
|
473
499
|
this.#chars = [];
|
|
474
500
|
this.#cursor = 0;
|
|
475
501
|
this.#reflow();
|
|
@@ -507,6 +533,7 @@ export class Editor {
|
|
|
507
533
|
this.#pickCards = null;
|
|
508
534
|
this.#pickCommit = null;
|
|
509
535
|
this.#pickSel = 0;
|
|
536
|
+
this.#syncMouse();
|
|
510
537
|
this.#chars = [];
|
|
511
538
|
this.#cursor = 0;
|
|
512
539
|
this.#reflow();
|
|
@@ -538,16 +565,18 @@ export class Editor {
|
|
|
538
565
|
/** W21: open the approval panel. The current buffer is stashed
|
|
539
566
|
* (restored at close — commit AND cancel), the panel takes the
|
|
540
567
|
* keys and the input row's lead, the menu closes. */
|
|
541
|
-
beginPanel(view, onCommit) {
|
|
568
|
+
beginPanel(view, onCommit, opts) {
|
|
542
569
|
this.#panel = {
|
|
543
570
|
view,
|
|
544
571
|
phase: "options",
|
|
545
|
-
|
|
572
|
+
cursor: 0,
|
|
573
|
+
note: null,
|
|
574
|
+
safer: opts?.safer,
|
|
575
|
+
saferRun: null,
|
|
546
576
|
ask: view.ask === undefined ? null : askStart(view.ask),
|
|
547
577
|
// TUI2-R2 ④: the pick's walk — present exactly when the view is
|
|
548
578
|
// a pick, the same contract the ask's runtime has.
|
|
549
579
|
pick: view.pick === undefined ? null : { cursor: 0, phase: "options" },
|
|
550
|
-
amend: "yes",
|
|
551
580
|
onCommit,
|
|
552
581
|
stash: { chars: this.#chars, cursor: this.#cursor, scroll: this.#scroll },
|
|
553
582
|
};
|
|
@@ -559,6 +588,7 @@ export class Editor {
|
|
|
559
588
|
this.#menuSel = 0;
|
|
560
589
|
this.#queuePopMode = false; // W22: the panel owns the keys while up
|
|
561
590
|
this.#atClose(); // KC3 §3: and the picker closes with everything else
|
|
591
|
+
this.#syncMouse();
|
|
562
592
|
this.#onRender();
|
|
563
593
|
}
|
|
564
594
|
/** W21: cancel the panel — the SIGINT path's pair to beginPanel. */
|
|
@@ -574,7 +604,9 @@ export class Editor {
|
|
|
574
604
|
return {
|
|
575
605
|
view: panel.view,
|
|
576
606
|
phase: panel.phase,
|
|
577
|
-
|
|
607
|
+
cursor: panel.cursor,
|
|
608
|
+
...(panel.note === null ? {} : { note: panel.note }),
|
|
609
|
+
...(panel.saferRun === null ? {} : { safer: panel.saferRun }),
|
|
578
610
|
...(panel.ask === null ? {} : { ask: panel.ask }),
|
|
579
611
|
...(panel.pick === null ? {} : { pick: panel.pick }),
|
|
580
612
|
};
|
|
@@ -584,6 +616,15 @@ export class Editor {
|
|
|
584
616
|
return;
|
|
585
617
|
this.#entered = true;
|
|
586
618
|
process.stdin.setRawMode(true);
|
|
619
|
+
// TUI2-R3v2 ②: the DEFENSIVE reset, first byte out.
|
|
620
|
+
//
|
|
621
|
+
// Mouse reporting is process state the terminal keeps, not state we
|
|
622
|
+
// keep, so a previous kiso that died with a panel open (kill -9, a
|
|
623
|
+
// panic, a closed laptop) left the terminal reporting clicks to
|
|
624
|
+
// whatever ran next — and nothing in that dead process can ever
|
|
625
|
+
// clean up after it. A fresh process is the only thing left that
|
|
626
|
+
// can, so it does, unconditionally, before it draws anything.
|
|
627
|
+
process.stdout.write(MOUSE_OFF);
|
|
587
628
|
process.stdout.write("\x1b[?2004h"); // bracketed paste ON
|
|
588
629
|
process.stdin.on("data", this.#onData);
|
|
589
630
|
this.#onRender();
|
|
@@ -593,10 +634,43 @@ export class Editor {
|
|
|
593
634
|
return;
|
|
594
635
|
this.#entered = false;
|
|
595
636
|
process.stdin.off("data", this.#onData);
|
|
637
|
+
// TUI2-R3v2 ②: unconditional, and BEFORE raw mode goes away — a
|
|
638
|
+
// terminal left reporting mouse events prints escape bytes at the
|
|
639
|
+
// shell prompt on every click and every scroll, and the user's only
|
|
640
|
+
// fix is `reset`. The flag is not consulted: exit() is the last
|
|
641
|
+
// chance this process gets, and emitting six harmless bytes twice
|
|
642
|
+
// is not a cost worth reasoning about.
|
|
643
|
+
process.stdout.write(MOUSE_OFF);
|
|
644
|
+
this.#mouseOn = false;
|
|
596
645
|
process.stdout.write("\x1b[?2004l"); // bracketed paste OFF
|
|
597
646
|
process.stdin.setRawMode(false);
|
|
598
647
|
this.#closedResolve();
|
|
599
648
|
}
|
|
649
|
+
/**
|
|
650
|
+
* TUI2-R3v2 ② — mouse reporting follows the SELECTION SURFACES and
|
|
651
|
+
* nothing else.
|
|
652
|
+
*
|
|
653
|
+
* While it is on, the terminal's own text selection changes behaviour
|
|
654
|
+
* (shift+drag still selects on every terminal that matters, but plain
|
|
655
|
+
* drag-to-copy does not), so leaving it on for the whole session would
|
|
656
|
+
* tax every copy-paste in the product to pay for a gesture that only
|
|
657
|
+
* means something while a list is up. It goes on when one opens and
|
|
658
|
+
* off when it closes — and both calls are idempotent, because the
|
|
659
|
+
* surfaces nest (a panel can open over a picker) and the bytes must
|
|
660
|
+
* not depend on the order they unwind in.
|
|
661
|
+
*/
|
|
662
|
+
#setMouse(on) {
|
|
663
|
+
if (this.#mouseOn === on)
|
|
664
|
+
return;
|
|
665
|
+
this.#mouseOn = on;
|
|
666
|
+
if (this.#entered)
|
|
667
|
+
process.stdout.write(on ? MOUSE_ON : MOUSE_OFF);
|
|
668
|
+
}
|
|
669
|
+
/** The surfaces that own a selection — the approval/ask/pick panel, the
|
|
670
|
+
* session picker and the @ picker. Any one of them up = reporting on. */
|
|
671
|
+
#syncMouse() {
|
|
672
|
+
this.#setMouse(this.#panel !== null || this.#pickCards !== null || this.#atUp());
|
|
673
|
+
}
|
|
600
674
|
/** The row's own render when the dock is inactive (a TTY without a
|
|
601
675
|
* real size): \r + clear + blue brick prompt + visible + cursor
|
|
602
676
|
* column. */
|
|
@@ -607,7 +681,7 @@ export class Editor {
|
|
|
607
681
|
// W21: the panel's lead owns the row while up (the brick returns
|
|
608
682
|
// when the panel closes).
|
|
609
683
|
const panel = this.#panel;
|
|
610
|
-
const lead = panel !== null ? panelLead(panel.view, panel.phase, panel.
|
|
684
|
+
const lead = panel !== null ? panelLead(panel.view, panel.phase, panel.cursor, panel.ask ?? undefined) : `${p.bold}${PROMPT}${p.reset}`;
|
|
611
685
|
// W23: the ONE width authority — leadWidth(lead), the ANSI-stripped
|
|
612
686
|
// visible width (the styled panel lead / the styled brick measure
|
|
613
687
|
// the same as their plain text — a lead can never measure
|
|
@@ -635,15 +709,25 @@ export class Editor {
|
|
|
635
709
|
let i = 0;
|
|
636
710
|
while (i < text.length) {
|
|
637
711
|
const c = text[i];
|
|
712
|
+
// TUI2-R3v2 ①: the one-shot enter guard a just-closed panel arms
|
|
713
|
+
// (see #panelClose). It sits at the very top of the loop because
|
|
714
|
+
// the byte it must not let through is the FIRST byte after the
|
|
715
|
+
// close, and it disarms on anything else in the same breath.
|
|
716
|
+
if (this.#swallowEnter) {
|
|
717
|
+
this.#swallowEnter = false;
|
|
718
|
+
if (this.#panel === null && (c === "\x0d" || c === "\x0a")) {
|
|
719
|
+
i += 1;
|
|
720
|
+
continue;
|
|
721
|
+
}
|
|
722
|
+
}
|
|
638
723
|
if (this.#panel !== null) {
|
|
639
|
-
// W21: the panel owns the keys —
|
|
640
|
-
// the options phase
|
|
641
|
-
//
|
|
642
|
-
//
|
|
643
|
-
//
|
|
644
|
-
//
|
|
645
|
-
//
|
|
646
|
-
// cancels the panel).
|
|
724
|
+
// W21: the panel owns the keys — a digit CONFIRMS its row in
|
|
725
|
+
// the options phase, tab opens the amend (approval only), esc
|
|
726
|
+
// backs out (amend → options, options → cancel), enter takes
|
|
727
|
+
// the highlighted row. CSI/SS3 and the editing keys still ride
|
|
728
|
+
// the normal chain below (the amend line is free text);
|
|
729
|
+
// ctrl-c still rides the SIGINT handler (which cancels the
|
|
730
|
+
// panel).
|
|
647
731
|
const panel = this.#panel;
|
|
648
732
|
// KC3.5: an ASK panel routes its own keys — the digits pick
|
|
649
733
|
// (single-select advances, multi toggles), space toggles at
|
|
@@ -738,46 +822,76 @@ export class Editor {
|
|
|
738
822
|
i += 1;
|
|
739
823
|
continue;
|
|
740
824
|
}
|
|
741
|
-
// TUI2-
|
|
742
|
-
//
|
|
825
|
+
// TUI2-R3v2 ③: the safer list answers the SAME keys the approval
|
|
826
|
+
// list does — one interaction model means the new surface is not
|
|
827
|
+
// an exception to it. A digit takes its row (the way back
|
|
828
|
+
// included, as the last one).
|
|
829
|
+
if (panel.phase === "safer" && c !== undefined && c >= "1" && c <= "9") {
|
|
830
|
+
this.#saferConfirm(Number(c) - 1);
|
|
831
|
+
i += 1;
|
|
832
|
+
continue;
|
|
833
|
+
}
|
|
834
|
+
// while the ask is in flight the panel owns every printable key
|
|
835
|
+
// and answers to none of them — esc (above) is the only gesture
|
|
836
|
+
// with a meaning, and a stray letter must not reach the composer.
|
|
837
|
+
if (panel.phase === "asking" && c !== undefined && c >= " " && c !== "\x7f") {
|
|
838
|
+
i += 1;
|
|
839
|
+
continue;
|
|
840
|
+
}
|
|
841
|
+
// TUI2-R3v2 ① — the digit CONFIRMS, and it confirms on the
|
|
842
|
+
// keypress.
|
|
843
|
+
//
|
|
844
|
+
// The retired model made a digit a selection and Enter the
|
|
845
|
+
// commit, which meant the fastest path through an approval was
|
|
846
|
+
// two keys and the hint line had to teach both. The list makes
|
|
847
|
+
// the digit redundant as a selector — the bar is already showing
|
|
848
|
+
// what is selected — so the digit becomes what a human pressing
|
|
849
|
+
// a number on a numbered list means by it: THAT one.
|
|
850
|
+
//
|
|
851
|
+
// A digit past the list is INERT (the R2 pick panel's rule,
|
|
852
|
+
// inherited whole): an option nobody has is never taken, and a
|
|
853
|
+
// mistyped 7 must not fall through to the composer underneath.
|
|
854
|
+
//
|
|
855
|
+
// The guard is the options phase and nothing else. The typed
|
|
856
|
+
// phase is prose — that is the R2 slice-⑧ finding, and it is
|
|
857
|
+
// why this branch sits below the enter/esc/tab handlers and
|
|
858
|
+
// above nothing at all: "yes, run 13 of them" keeps its digits.
|
|
859
|
+
if (panel.phase === "options" && panel.ask === null && panel.pick === null && c !== undefined && c >= "1" && c <= "9") {
|
|
860
|
+
this.#panelConfirm(Number(c) - 1);
|
|
861
|
+
i += 1;
|
|
862
|
+
continue;
|
|
863
|
+
}
|
|
864
|
+
// TUI2-R2 ⑧, carried forward — the shortcut keys belong to the
|
|
865
|
+
// OPTIONS phase and to it alone.
|
|
743
866
|
//
|
|
744
|
-
// `
|
|
745
|
-
//
|
|
746
|
-
//
|
|
747
|
-
//
|
|
748
|
-
//
|
|
749
|
-
//
|
|
750
|
-
// silently, with no error and no visible cause. "yes, run it
|
|
751
|
-
// now 13" committed as "es, ru it ow ".
|
|
867
|
+
// `y`/`n` used to be applied in every phase of every flavour:
|
|
868
|
+
// the `i += 1; continue;` sat OUTSIDE the phase check, so a
|
|
869
|
+
// phase where the key meant nothing swallowed it anyway — and a
|
|
870
|
+
// phase where a letter means nothing is exactly a phase where a
|
|
871
|
+
// human is typing prose. Every y and n vanished from the line,
|
|
872
|
+
// silently. "yes, run it now" committed as "es, ru it ow".
|
|
752
873
|
//
|
|
753
|
-
//
|
|
754
|
-
//
|
|
755
|
-
//
|
|
756
|
-
// DURABLE don't-ask-again rule, so a dropped character persists
|
|
757
|
-
// a rule the human never typed.
|
|
874
|
+
// The guard survives the migration unchanged in spirit and
|
|
875
|
+
// simpler in fact: there is now ONE typed phase instead of
|
|
876
|
+
// three, and the letters reach only the list.
|
|
758
877
|
//
|
|
759
|
-
//
|
|
760
|
-
//
|
|
761
|
-
//
|
|
762
|
-
//
|
|
763
|
-
//
|
|
764
|
-
//
|
|
878
|
+
// The letters stay because they are the two answers this panel
|
|
879
|
+
// has always taken and a decade of muscle memory types them.
|
|
880
|
+
// They are ALIASES for rows, not a second model: `y` is the
|
|
881
|
+
// first option, `n` is the last, and on an approval the last
|
|
882
|
+
// option opens the composer — so the old "n then enter" still
|
|
883
|
+
// lands the same bare denial it always did.
|
|
765
884
|
const optionsPhase = panel.pick === null && // a pick has no yes and no no
|
|
766
|
-
panel.phase === "options" && // the
|
|
885
|
+
panel.phase === "options" && // the amend line is prose
|
|
767
886
|
(panel.ask === null || panel.ask.phase === "options"); // and so is a typed ask answer
|
|
768
|
-
if (optionsPhase) {
|
|
769
|
-
if (c === "
|
|
770
|
-
this.#
|
|
771
|
-
i += 1;
|
|
772
|
-
continue;
|
|
773
|
-
}
|
|
774
|
-
if (c === "2" && panel.view.flavor === "approval") {
|
|
775
|
-
this.#panelRule();
|
|
887
|
+
if (optionsPhase && panel.ask === null) {
|
|
888
|
+
if (c === "y" || c === "Y") {
|
|
889
|
+
this.#panelConfirm(0);
|
|
776
890
|
i += 1;
|
|
777
891
|
continue;
|
|
778
892
|
}
|
|
779
|
-
if (c === "
|
|
780
|
-
this.#
|
|
893
|
+
if (c === "n" || c === "N") {
|
|
894
|
+
this.#panelConfirm(panelOptions(panel.view).length - 1);
|
|
781
895
|
i += 1;
|
|
782
896
|
continue;
|
|
783
897
|
}
|
|
@@ -786,7 +900,17 @@ export class Editor {
|
|
|
786
900
|
if (c === "\x1b") {
|
|
787
901
|
const rest = text.slice(i + 1);
|
|
788
902
|
if (rest.startsWith("[")) {
|
|
789
|
-
|
|
903
|
+
// TUI2-R3v2 ②: `<` joins the parameter class.
|
|
904
|
+
//
|
|
905
|
+
// An SGR 1006 mouse report is `\x1b[<0;COL;ROWM`, and the
|
|
906
|
+
// retired character class ([0-9;?]) did not contain `<`. The
|
|
907
|
+
// match failed, the branch below PARKED the whole thing as an
|
|
908
|
+
// incomplete CSI, and #pending grew forever: every keystroke
|
|
909
|
+
// after the first click was appended to a sequence that could
|
|
910
|
+
// never complete. The editor went deaf. It never happened
|
|
911
|
+
// because nothing ever enabled reporting — which is exactly
|
|
912
|
+
// the kind of latent break turning a feature on discovers.
|
|
913
|
+
const m = rest.match(/^\[([0-9;?<]*)([A-Za-z~])/);
|
|
790
914
|
if (m === null) {
|
|
791
915
|
this.#pending = text.slice(i); // incomplete CSI — wait for more
|
|
792
916
|
break;
|
|
@@ -968,7 +1092,58 @@ export class Editor {
|
|
|
968
1092
|
}
|
|
969
1093
|
}
|
|
970
1094
|
}
|
|
1095
|
+
/**
|
|
1096
|
+
* TUI2-R3v2 ② — one gesture, and only one: a plain LEFT PRESS on an
|
|
1097
|
+
* option row is that row's digit.
|
|
1098
|
+
*
|
|
1099
|
+
* Everything else is dropped, and the list of everything else is the
|
|
1100
|
+
* point. A release (`m`) is not a second click. Button 64/65 is the
|
|
1101
|
+
* wheel — scrolling past a panel must not answer it. Bit 32 is a
|
|
1102
|
+
* motion report, so a drag over the list is a drag, not four
|
|
1103
|
+
* approvals. Buttons 1 and 2 are middle and right, which mean paste
|
|
1104
|
+
* and context-menu everywhere else and would mean "approve" here.
|
|
1105
|
+
* The stakes are a side effect the human did not ask for, and an
|
|
1106
|
+
* ambiguous mouse event is not consent.
|
|
1107
|
+
*/
|
|
1108
|
+
#mouseEvent(params, press) {
|
|
1109
|
+
if (!press)
|
|
1110
|
+
return; // the press already decided; the release is noise
|
|
1111
|
+
const [button, , row] = params.slice(1).split(";").map(Number);
|
|
1112
|
+
if (button !== 0)
|
|
1113
|
+
return; // wheel (64/65), motion (32+), middle/right
|
|
1114
|
+
// TUI2-R3v2 ③: a click works on BOTH lists — one interaction model
|
|
1115
|
+
// means the safer alternatives are clickable for the same reason the
|
|
1116
|
+
// original choices are.
|
|
1117
|
+
if (this.#panel === null || (this.#panel.phase !== "options" && this.#panel.phase !== "safer"))
|
|
1118
|
+
return;
|
|
1119
|
+
const span = this.#panelRows?.();
|
|
1120
|
+
if (span == null || row === undefined || !Number.isFinite(row))
|
|
1121
|
+
return;
|
|
1122
|
+
const offset = row - span.top;
|
|
1123
|
+
if (offset < 0 || offset >= span.count)
|
|
1124
|
+
return; // outside the list — inert
|
|
1125
|
+
if (this.#panel.phase === "safer")
|
|
1126
|
+
this.#saferConfirm(offset);
|
|
1127
|
+
else
|
|
1128
|
+
this.#panelConfirm((span.first ?? 0) + offset);
|
|
1129
|
+
}
|
|
1130
|
+
/** TUI2-R3v2 ②: the compositor reports where it PUT the option rows.
|
|
1131
|
+
* The editor does no row arithmetic of its own — the surface that
|
|
1132
|
+
* placed them is the only thing that can say where they are, and a
|
|
1133
|
+
* second copy of that sum is how a hit-test comes to disagree with
|
|
1134
|
+
* the picture. */
|
|
1135
|
+
bindPanelRows(fn) {
|
|
1136
|
+
this.#panelRows = fn;
|
|
1137
|
+
}
|
|
971
1138
|
#csi(params, final) {
|
|
1139
|
+
// TUI2-R3v2 ②: an SGR 1006 report — `\x1b[<b;col;rowM` (press) or
|
|
1140
|
+
// `...m` (release). It is routed FIRST because a `<` parameter is
|
|
1141
|
+
// never anything else, and because a mouse byte must never fall
|
|
1142
|
+
// through to a key handler.
|
|
1143
|
+
if (params.startsWith("<")) {
|
|
1144
|
+
this.#mouseEvent(params, final === "M");
|
|
1145
|
+
return;
|
|
1146
|
+
}
|
|
972
1147
|
// KC1 §4 — Shift+Enter WHERE THE TERMINAL ENCODES IT: kitty's
|
|
973
1148
|
// CSI-u (ESC [ 13;2 u) and xterm's modifyOtherKeys (ESC [ 27;2;13 ~).
|
|
974
1149
|
// Never claimed universal — Ctrl+J is the everywhere baseline; a
|
|
@@ -1020,6 +1195,16 @@ export class Editor {
|
|
|
1020
1195
|
}
|
|
1021
1196
|
else if (this.#panel.ask !== null && this.#panel.ask.phase === "options")
|
|
1022
1197
|
this.#askStep(final === "A" ? "up" : "down");
|
|
1198
|
+
// TUI2-R3v2 ①: the approval/simple panel joins them. It was the
|
|
1199
|
+
// one panel flavour with no ↑↓ role, because it had no cursor to
|
|
1200
|
+
// move; it has one now, and the gesture is the same one the
|
|
1201
|
+
// pick, the ask, the session picker and the @ picker already
|
|
1202
|
+
// answer to. ONE interaction model is the round's acceptance
|
|
1203
|
+
// criterion, and this branch is where it stops being four.
|
|
1204
|
+
else if (this.#panel.phase === "safer")
|
|
1205
|
+
this.#saferMove(final === "A" ? -1 : 1);
|
|
1206
|
+
else if (this.#panel.phase !== "asking")
|
|
1207
|
+
this.#panelMove(final === "A" ? -1 : 1);
|
|
1023
1208
|
}
|
|
1024
1209
|
else if (this.#pickUp()) {
|
|
1025
1210
|
// TUI2-R2 ②: the session picker owns ↑↓ while up — the
|
|
@@ -1103,35 +1288,173 @@ export class Editor {
|
|
|
1103
1288
|
this.#reflow();
|
|
1104
1289
|
this.#verticalGoalCol = goal; // the walk re-arms it (the reflow's reset is for every OTHER key)
|
|
1105
1290
|
}
|
|
1106
|
-
// ---- W21
|
|
1107
|
-
|
|
1291
|
+
// ---- W21 / TUI2-R3v2 ①: the panel state machine ----
|
|
1292
|
+
/** ↑↓ — the bar walks the list and STOPS at both ends. A list that
|
|
1293
|
+
* wraps makes the fastest gesture (hold ↓ to reach the bottom) into
|
|
1294
|
+
* a gamble about where you landed, and the bottom option here is the
|
|
1295
|
+
* denial. */
|
|
1296
|
+
#panelMove(delta) {
|
|
1108
1297
|
const panel = this.#panel;
|
|
1109
|
-
if (panel === null)
|
|
1298
|
+
if (panel === null || panel.phase !== "options")
|
|
1110
1299
|
return;
|
|
1111
|
-
|
|
1300
|
+
const n = panelOptions(panel.view).length;
|
|
1301
|
+
panel.cursor = Math.max(0, Math.min(n - 1, panel.cursor + delta));
|
|
1112
1302
|
this.#onRender();
|
|
1113
1303
|
}
|
|
1114
|
-
/**
|
|
1115
|
-
*
|
|
1116
|
-
|
|
1304
|
+
/**
|
|
1305
|
+
* Take the option at `index` — the ONE place a panel choice resolves,
|
|
1306
|
+
* whether the human pressed a digit, pressed ⏎ on the bar, typed the
|
|
1307
|
+
* y/n alias, or clicked the row (slice ②). Four gestures, one branch:
|
|
1308
|
+
* a click cannot mean something a digit does not.
|
|
1309
|
+
*
|
|
1310
|
+
* Every kind but `deny` on an approval resolves IMMEDIATELY. That is
|
|
1311
|
+
* the round's whole claim — the durable rule included, because the
|
|
1312
|
+
* rule the machinery supports is exactly "this tool", and asking the
|
|
1313
|
+
* human to confirm a value they cannot change was the old model's
|
|
1314
|
+
* ceremony, not a safeguard.
|
|
1315
|
+
*/
|
|
1316
|
+
#panelConfirm(index) {
|
|
1317
|
+
const panel = this.#panel;
|
|
1318
|
+
if (panel === null || panel.phase !== "options")
|
|
1319
|
+
return;
|
|
1320
|
+
const options = panelOptions(panel.view);
|
|
1321
|
+
const option = options[index];
|
|
1322
|
+
if (option === undefined)
|
|
1323
|
+
return; // a digit past the list is inert
|
|
1324
|
+
panel.cursor = index;
|
|
1325
|
+
switch (option.kind) {
|
|
1326
|
+
case "allow":
|
|
1327
|
+
this.#panelClose({ action: "allow", reason: "" });
|
|
1328
|
+
return;
|
|
1329
|
+
case "rule":
|
|
1330
|
+
this.#panelClose({ action: "allow-rule", rule: panel.view.name });
|
|
1331
|
+
return;
|
|
1332
|
+
case "safer":
|
|
1333
|
+
this.#panelSafer();
|
|
1334
|
+
return;
|
|
1335
|
+
case "deny":
|
|
1336
|
+
// the approval flavor's denial is "let me tell it what to do
|
|
1337
|
+
// instead", so it opens the composer; the simple flavors have
|
|
1338
|
+
// nothing to tell anyone and resolve on the spot.
|
|
1339
|
+
if (panel.view.flavor === "approval")
|
|
1340
|
+
this.#panelAmend();
|
|
1341
|
+
else
|
|
1342
|
+
this.#panelClose({ action: "deny", reason: "" });
|
|
1343
|
+
return;
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
/**
|
|
1347
|
+
* Option 3 — "show me safer ways to do this".
|
|
1348
|
+
*
|
|
1349
|
+
* The round's ONE new model request, and every branch here exists to
|
|
1350
|
+
* keep it honest.
|
|
1351
|
+
*
|
|
1352
|
+
* It fires ONLY from this method, which only this option reaches —
|
|
1353
|
+
* that is the entire mechanism behind the zero-ambient-rent claim,
|
|
1354
|
+
* and it is why the claim is checkable rather than asserted: a
|
|
1355
|
+
* session that never presses 3 never enters this branch, and the
|
|
1356
|
+
* trace shows no side-query line.
|
|
1357
|
+
*
|
|
1358
|
+
* The in-flight phase is VISIBLE because this is a network call: a
|
|
1359
|
+
* button that goes quiet for two seconds reads as broken, and the
|
|
1360
|
+
* human is standing in front of a paused run.
|
|
1361
|
+
*
|
|
1362
|
+
* Every failure — a throw, a null, an empty list, no provider bound
|
|
1363
|
+
* at all — lands on the SAME honest branch and puts back every
|
|
1364
|
+
* original choice. There is deliberately no retry and no partial
|
|
1365
|
+
* state: the alternative to "I could not get them" is either a lie or
|
|
1366
|
+
* a spinner that never ends, and both are worse than the sentence.
|
|
1367
|
+
*
|
|
1368
|
+
* R3v2-F1: one of those failures can now name its cause, and the
|
|
1369
|
+
* sentence says it. That is a widening of the COPY, not of the
|
|
1370
|
+
* branch — there is still exactly one failure path, it still restores
|
|
1371
|
+
* every original choice, and a provider that has nothing to add still
|
|
1372
|
+
* resolves `null` and still gets the line it always got. A cause is
|
|
1373
|
+
* only ever spoken when the caller could prove it; a diagnosis the
|
|
1374
|
+
* product cannot prove would be worse than the unqualified line it
|
|
1375
|
+
* replaced.
|
|
1376
|
+
*
|
|
1377
|
+
* The generation token is the guard against a late answer: a panel
|
|
1378
|
+
* the human escaped (or that a SIGINT cancelled) must not be
|
|
1379
|
+
* resurrected two seconds later by a promise nobody is waiting for.
|
|
1380
|
+
*/
|
|
1381
|
+
#panelSafer() {
|
|
1117
1382
|
const panel = this.#panel;
|
|
1118
1383
|
if (panel === null)
|
|
1119
1384
|
return;
|
|
1120
|
-
|
|
1121
|
-
panel.
|
|
1122
|
-
|
|
1123
|
-
this.#
|
|
1124
|
-
|
|
1125
|
-
|
|
1385
|
+
const ask = panel.safer;
|
|
1386
|
+
panel.phase = "asking";
|
|
1387
|
+
panel.note = null;
|
|
1388
|
+
this.#onRender();
|
|
1389
|
+
const token = ++this.#saferToken;
|
|
1390
|
+
const settle = (answer) => {
|
|
1391
|
+
// the panel that asked must still be the panel on screen
|
|
1392
|
+
if (this.#panel !== panel || token !== this.#saferToken)
|
|
1393
|
+
return;
|
|
1394
|
+
// R3v2-F1: a non-list answer is a failure, and it may name its
|
|
1395
|
+
// cause. Which sentence that earns is decided where the
|
|
1396
|
+
// sentences live; here we only route to the same one branch
|
|
1397
|
+
// every failure has always taken.
|
|
1398
|
+
const options = Array.isArray(answer) ? answer : null;
|
|
1399
|
+
if (options === null || options.length === 0) {
|
|
1400
|
+
panel.phase = "options";
|
|
1401
|
+
panel.note = saferDegradedNote(answer);
|
|
1402
|
+
panel.cursor = 0;
|
|
1403
|
+
this.#onRender();
|
|
1404
|
+
return;
|
|
1405
|
+
}
|
|
1406
|
+
panel.phase = "safer";
|
|
1407
|
+
panel.saferRun = { options, cursor: 0 };
|
|
1408
|
+
this.#onRender();
|
|
1409
|
+
};
|
|
1410
|
+
if (ask === undefined) {
|
|
1411
|
+
settle(null); // no provider bound — the button says so rather than lying
|
|
1412
|
+
return;
|
|
1413
|
+
}
|
|
1414
|
+
void Promise.resolve()
|
|
1415
|
+
.then(ask)
|
|
1416
|
+
.then(settle)
|
|
1417
|
+
.catch(() => settle(null));
|
|
1418
|
+
}
|
|
1419
|
+
/** Take a row of the SAFER list. The alternatives route through the
|
|
1420
|
+
* EXISTING amend channel — choosing a safer command is a denial with
|
|
1421
|
+
* instructions, which is a verdict the product already has; the last
|
|
1422
|
+
* row is the way back and decides nothing. */
|
|
1423
|
+
#saferConfirm(index) {
|
|
1424
|
+
const panel = this.#panel;
|
|
1425
|
+
if (panel === null || panel.saferRun === null)
|
|
1426
|
+
return;
|
|
1427
|
+
const { options } = panel.saferRun;
|
|
1428
|
+
if (index === options.length) {
|
|
1429
|
+
// "back to the original choices"
|
|
1430
|
+
panel.phase = "options";
|
|
1431
|
+
panel.saferRun = null;
|
|
1432
|
+
panel.cursor = 0;
|
|
1433
|
+
this.#onRender();
|
|
1434
|
+
return;
|
|
1435
|
+
}
|
|
1436
|
+
const chosen = options[index];
|
|
1437
|
+
if (chosen === undefined)
|
|
1438
|
+
return; // past the list — inert
|
|
1439
|
+
this.#panelClose({ action: "deny", reason: `run this instead: ${chosen.command}` });
|
|
1440
|
+
}
|
|
1441
|
+
/** ↑↓ inside the safer list — the way back is its last row, so the
|
|
1442
|
+
* bar reaches it like any other. */
|
|
1443
|
+
#saferMove(delta) {
|
|
1444
|
+
const panel = this.#panel;
|
|
1445
|
+
if (panel === null || panel.saferRun === null)
|
|
1446
|
+
return;
|
|
1447
|
+
const last = panel.saferRun.options.length; // + the way-back row
|
|
1448
|
+
panel.saferRun = { options: panel.saferRun.options, cursor: Math.max(0, Math.min(last, panel.saferRun.cursor + delta)) };
|
|
1126
1449
|
this.#onRender();
|
|
1127
1450
|
}
|
|
1128
|
-
/**
|
|
1129
|
-
*
|
|
1130
|
-
|
|
1451
|
+
/** The typed phase — the one place the panel takes prose. The buffer
|
|
1452
|
+
* starts empty and the cursor stays where the human left it, so esc
|
|
1453
|
+
* can put the bar back exactly where it was. */
|
|
1454
|
+
#panelAmend() {
|
|
1131
1455
|
const panel = this.#panel;
|
|
1132
|
-
if (panel === null
|
|
1456
|
+
if (panel === null)
|
|
1133
1457
|
return;
|
|
1134
|
-
panel.amend = panel.sel === 3 ? "no" : "yes";
|
|
1135
1458
|
panel.phase = "amend";
|
|
1136
1459
|
this.#chars = [];
|
|
1137
1460
|
this.#cursor = 0;
|
|
@@ -1139,15 +1462,39 @@ export class Editor {
|
|
|
1139
1462
|
this.#verticalGoalCol = null;
|
|
1140
1463
|
this.#onRender();
|
|
1141
1464
|
}
|
|
1142
|
-
/**
|
|
1143
|
-
*
|
|
1465
|
+
/** tab — the amend alias, unchanged as a GESTURE: it opens the same
|
|
1466
|
+
* typed phase the last option does, from anywhere in the list. The
|
|
1467
|
+
* simple flavors never had it and still do not. */
|
|
1468
|
+
#panelTab() {
|
|
1469
|
+
const panel = this.#panel;
|
|
1470
|
+
if (panel === null || panel.view.flavor !== "approval")
|
|
1471
|
+
return;
|
|
1472
|
+
panel.cursor = panelOptions(panel.view).length - 1;
|
|
1473
|
+
this.#panelAmend();
|
|
1474
|
+
}
|
|
1475
|
+
/** esc — back out of the typed phase to the list (the buffer clears,
|
|
1476
|
+
* the bar stays on the option that opened it), or cancel the panel.
|
|
1477
|
+
* The old model had a third step, the deselect, because a selection
|
|
1478
|
+
* could be "none"; a list always has a selection, so esc from the
|
|
1479
|
+
* list means what it means everywhere else in the product. */
|
|
1144
1480
|
#panelEsc() {
|
|
1145
1481
|
const panel = this.#panel;
|
|
1146
1482
|
if (panel === null)
|
|
1147
1483
|
return;
|
|
1484
|
+
// TUI2-R3v2 ③: esc out of the safer list — or out of the ask while
|
|
1485
|
+
// it is still in flight — returns to the original choices, exactly
|
|
1486
|
+
// as the way-back row does. The in-flight answer is orphaned by the
|
|
1487
|
+
// generation token; nothing it does can reopen this list.
|
|
1488
|
+
if (panel.phase === "safer" || panel.phase === "asking") {
|
|
1489
|
+
this.#saferToken += 1;
|
|
1490
|
+
panel.phase = "options";
|
|
1491
|
+
panel.saferRun = null;
|
|
1492
|
+
panel.cursor = 0;
|
|
1493
|
+
this.#onRender();
|
|
1494
|
+
return;
|
|
1495
|
+
}
|
|
1148
1496
|
if (panel.phase !== "options") {
|
|
1149
1497
|
panel.phase = "options";
|
|
1150
|
-
panel.sel = 0;
|
|
1151
1498
|
this.#chars = [];
|
|
1152
1499
|
this.#cursor = 0;
|
|
1153
1500
|
this.#scroll = 0;
|
|
@@ -1155,36 +1502,40 @@ export class Editor {
|
|
|
1155
1502
|
this.#onRender();
|
|
1156
1503
|
return;
|
|
1157
1504
|
}
|
|
1158
|
-
if (panel.sel !== 0) {
|
|
1159
|
-
panel.sel = 0;
|
|
1160
|
-
this.#onRender();
|
|
1161
|
-
return;
|
|
1162
|
-
}
|
|
1163
1505
|
this.#panelClose({ action: "cancel" });
|
|
1164
1506
|
}
|
|
1165
|
-
/**
|
|
1166
|
-
*
|
|
1167
|
-
*
|
|
1168
|
-
*
|
|
1507
|
+
/**
|
|
1508
|
+
* enter — send the typed note, or TAKE THE HIGHLIGHTED OPTION.
|
|
1509
|
+
*
|
|
1510
|
+
* The second half is the round. The retired model's enter-at-rest did
|
|
1511
|
+
* nothing at all, on the theory that an accidental return must never
|
|
1512
|
+
* approve; what it actually produced was a panel that ignored the key
|
|
1513
|
+
* every human presses first. The safeguard is real but it belongs on
|
|
1514
|
+
* WHERE THE BAR STARTS, not on whether the key works: the bar opens on
|
|
1515
|
+
* the option whose blast radius is one tool call the human is looking
|
|
1516
|
+
* at, and every irreversible-er choice is a deliberate ↑↓ away.
|
|
1517
|
+
*
|
|
1518
|
+
* An empty note in the typed phase is the bare denial — the W21
|
|
1519
|
+
* mapping, untouched: no words means the run aborts, words mean the
|
|
1520
|
+
* model gets them and proposes a new call.
|
|
1521
|
+
*/
|
|
1169
1522
|
#panelEnter() {
|
|
1170
1523
|
const panel = this.#panel;
|
|
1171
1524
|
if (panel === null)
|
|
1172
1525
|
return;
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
this.#panelClose({ action: "allow-rule", rule: line === "" ? panel.view.name : line });
|
|
1526
|
+
if (panel.phase === "amend") {
|
|
1527
|
+
this.#panelClose({ action: "deny", reason: this.line() });
|
|
1176
1528
|
return;
|
|
1177
1529
|
}
|
|
1178
|
-
|
|
1179
|
-
|
|
1530
|
+
// TUI2-R3v2 ③: in the safer list, enter takes the highlighted
|
|
1531
|
+
// alternative — the same gesture, one surface over.
|
|
1532
|
+
if (panel.phase === "safer" && panel.saferRun !== null) {
|
|
1533
|
+
this.#saferConfirm(panel.saferRun.cursor);
|
|
1180
1534
|
return;
|
|
1181
1535
|
}
|
|
1182
|
-
if (panel.
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
this.#panelRule();
|
|
1186
|
-
else if (panel.sel === 3)
|
|
1187
|
-
this.#panelClose({ action: "deny", reason: "" });
|
|
1536
|
+
if (panel.phase === "asking")
|
|
1537
|
+
return; // nothing to confirm yet
|
|
1538
|
+
this.#panelConfirm(panel.cursor);
|
|
1188
1539
|
}
|
|
1189
1540
|
/**
|
|
1190
1541
|
* KC3.5 — one ask key: the pure reducer decides, this method applies.
|
|
@@ -1272,8 +1623,22 @@ export class Editor {
|
|
|
1272
1623
|
if (panel === null)
|
|
1273
1624
|
return;
|
|
1274
1625
|
this.#panel = null;
|
|
1275
|
-
|
|
1276
|
-
//
|
|
1626
|
+
this.#syncMouse();
|
|
1627
|
+
// TUI2-R3v2 ①: swallow ONE bare enter after the panel goes away.
|
|
1628
|
+
//
|
|
1629
|
+
// This is the hazard the instant confirm creates and it is not
|
|
1630
|
+
// hypothetical: "y⏎" and "1⏎" are what a decade of y/n prompts
|
|
1631
|
+
// taught everyone's fingers, and the panel used to need both bytes.
|
|
1632
|
+
// It needs one now — so the second one lands in a composer that has
|
|
1633
|
+
// just had the user's PRE-PANEL DRAFT restored into it, and submits
|
|
1634
|
+
// it. Answering an approval would send a half-written message.
|
|
1635
|
+
//
|
|
1636
|
+
// The guard is one-shot and expires on any other key, so it can
|
|
1637
|
+
// never eat an enter the user meant: by the time they have typed
|
|
1638
|
+
// anything at all, it is gone.
|
|
1639
|
+
this.#swallowEnter = true;
|
|
1640
|
+
// the pre-panel buffer returns — the panel's amend text never leaks
|
|
1641
|
+
// into the user's next turn (commit AND cancel).
|
|
1277
1642
|
this.#chars = [...panel.stash.chars];
|
|
1278
1643
|
this.#cursor = panel.stash.cursor;
|
|
1279
1644
|
this.#scroll = panel.stash.scroll;
|
|
@@ -1523,7 +1888,7 @@ export class Editor {
|
|
|
1523
1888
|
// W23: the ONE width authority — leadWidth(lead) — the cap follows
|
|
1524
1889
|
// the lead the editor itself renders (the panel lead when the panel
|
|
1525
1890
|
// owns the keys, the brick otherwise): maxW = W − walls − lead.
|
|
1526
|
-
const lead = this.#panel !== null ? panelLead(this.#panel.view, this.#panel.phase, this.#panel.
|
|
1891
|
+
const lead = this.#panel !== null ? panelLead(this.#panel.view, this.#panel.phase, this.#panel.cursor, this.#panel.ask ?? undefined) : PROMPT;
|
|
1527
1892
|
const leadW = leadWidth(lead);
|
|
1528
1893
|
const maxW = Math.max(1, W - leadW - 4); // W6: the box's walls (2+2) — the visible line fits the box's inner width; the "…" rides inside
|
|
1529
1894
|
// KC1: the scroll is the CURSOR LINE's own offset — a single-line
|
package/dist/index.d.ts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* kiso-tui — the PURE terminal layer, extracted from the CLI (the
|
|
3
|
-
* ADR-
|
|
3
|
+
* escape hatch of ADR-0043, which supersedes ADR-0041). Zero runtime
|
|
4
|
+
* dependencies: input is data,
|
|
4
5
|
* output is bytes. TUI v6 (ADR-0046): the ONE compositor (the single
|
|
5
6
|
* writer — body.ts + dock.ts retired), the component tree, the raw-mode
|
|
6
7
|
* editor, the diff renderer, and the palette.
|
|
7
8
|
*/
|
|
8
9
|
export { Body, Dock, CURSOR_MARKER, type BodyOptions } from "./compositor.js";
|
|
9
|
-
export { panelAffordance, panelBlockRows, panelLead, panelLeadPlain, panelLeadWidth, panelStatus, PICK_MAX, modelPickView, pickAffordance, pickBlockRows, pickLeadPlain, type PickOption, type PickResult, type PickRuntime, type PickSpec, type PanelArgs, type PanelFlavor, type PanelPhase, type
|
|
10
|
+
export { panelAffordance, panelBlockRows, panelLead, panelLeadPlain, panelLeadWidth, panelStatus, PICK_MAX, modelPickView, pickAffordance, pickBlockRows, pickLeadPlain, type PickOption, type PickResult, type PickRuntime, type PickSpec, type PanelArgs, type PanelFlavor, type PanelPhase, deletionRiskHint, SAFER_BACK, SAFER_DEGRADED, SAFER_DEGRADED_TRUNCATED, saferDegradedNote, type SaferAnswer, type SaferFailure, type SaferOption, type SaferRuntime, panelOptions, type PanelOption, type PanelOptionKind, type PanelState, type PanelVerdict, type PanelView, } from "./approval-panel.js";
|
|
10
11
|
export { Container, foldLine, foldWords, visibleWidth, SPINNER, type Component, type FrameCtx } from "./components.js";
|
|
11
12
|
export { Editor, MENU_ITEMS, PROMPT, PROMPT_WIDTH, displayWidth, charWidth, widthOf, type MenuItem, } from "./editor.js";
|
|
12
13
|
export { bannerLines, COLOR_OFF, COLOR_ON, escapeTerminal, foldResult, foldThinking, kUnit, palette, renderEvent, renderRecap, renderResumeList, renderSessionLine, renderStatusLine, relativeTime, renderTerminalGap, renderToolSummary, TAGLINE, toolTarget, truncateRow, type Palette, type PathResolver, type RecapStats, type ResumeMeta, type RenderInput, type RenderResult, type RunUsage, } from "./render.js";
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* kiso-tui — the PURE terminal layer, extracted from the CLI (the
|
|
3
|
-
* ADR-
|
|
3
|
+
* escape hatch of ADR-0043, which supersedes ADR-0041). Zero runtime
|
|
4
|
+
* dependencies: input is data,
|
|
4
5
|
* output is bytes. TUI v6 (ADR-0046): the ONE compositor (the single
|
|
5
6
|
* writer — body.ts + dock.ts retired), the component tree, the raw-mode
|
|
6
7
|
* editor, the diff renderer, and the palette.
|
|
@@ -11,7 +12,7 @@ export { Body, Dock, CURSOR_MARKER } from "./compositor.js";
|
|
|
11
12
|
// approval is pending (the shape authority is the committed preview).
|
|
12
13
|
export { panelAffordance, panelBlockRows, panelLead, panelLeadPlain, panelLeadWidth, panelStatus,
|
|
13
14
|
// TUI2-R2 ④: the pick payload — the panel slot's third occupant.
|
|
14
|
-
PICK_MAX, modelPickView, pickAffordance, pickBlockRows, pickLeadPlain, } from "./approval-panel.js";
|
|
15
|
+
PICK_MAX, modelPickView, pickAffordance, pickBlockRows, pickLeadPlain, deletionRiskHint, SAFER_BACK, SAFER_DEGRADED, SAFER_DEGRADED_TRUNCATED, saferDegradedNote, panelOptions, } from "./approval-panel.js";
|
|
15
16
|
export { Container, foldLine, foldWords, visibleWidth, SPINNER } from "./components.js";
|
|
16
17
|
export { Editor, MENU_ITEMS, PROMPT, PROMPT_WIDTH, displayWidth, charWidth, widthOf, } from "./editor.js";
|
|
17
18
|
export { bannerLines, COLOR_OFF, COLOR_ON, escapeTerminal, foldResult, foldThinking, kUnit, palette, renderEvent, renderRecap, renderResumeList, renderSessionLine, renderStatusLine, relativeTime, renderTerminalGap, renderToolSummary, TAGLINE, toolTarget, truncateRow, } from "./render.js";
|
package/dist/status.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* KC2 §5 — the status line's FORMATTERS, extracted from the CLI (the
|
|
3
|
-
*
|
|
3
|
+
* escape hatch of ADR-0043, which supersedes ADR-0041 — this comment
|
|
4
|
+
* cited the superseded ADR as current AND repeated its retired "never a
|
|
5
|
+
* fifth raise" promise; ADR-0043 governs the ceiling now). The split is
|
|
4
6
|
* the one the ADR names: the CLI keeps the STATE (the rotating glyph,
|
|
5
7
|
* the run's start instant, the live usage, whether the dock is up) and
|
|
6
8
|
* the REPAINT; what a status row SAYS is presentation, and presentation
|
package/dist/status.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* KC2 §5 — the status line's FORMATTERS, extracted from the CLI (the
|
|
3
|
-
*
|
|
3
|
+
* escape hatch of ADR-0043, which supersedes ADR-0041 — this comment
|
|
4
|
+
* cited the superseded ADR as current AND repeated its retired "never a
|
|
5
|
+
* fifth raise" promise; ADR-0043 governs the ceiling now). The split is
|
|
4
6
|
* the one the ADR names: the CLI keeps the STATE (the rotating glyph,
|
|
5
7
|
* the run's start instant, the live usage, whether the dock is up) and
|
|
6
8
|
* the REPAINT; what a status row SAYS is presentation, and presentation
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vincemakes/kiso-tui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
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.
|
|
38
|
+
"@vincemakes/kiso-tui-cells": "0.13.0"
|
|
39
39
|
}
|
|
40
40
|
}
|