@vincemakes/kiso-tui 0.5.0 → 0.6.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/editor.d.ts +5 -0
- package/dist/editor.js +109 -17
- package/dist/index.d.ts +1 -0
- package/dist/index.js +3 -0
- package/dist/status.d.ts +38 -0
- package/dist/status.js +52 -0
- package/package.json +2 -2
package/dist/editor.d.ts
CHANGED
|
@@ -48,6 +48,11 @@ export declare class Editor {
|
|
|
48
48
|
onEot(cb: () => void): void;
|
|
49
49
|
onEscape(cb: () => void): void;
|
|
50
50
|
onExpand(cb: () => void): void;
|
|
51
|
+
/** KC2 §2: the redirect chain — the gesture hands the buffer's text
|
|
52
|
+
* over while the run is told to stop. Mirrors onEscape (a list, so
|
|
53
|
+
* listeners can coexist); the line arrives already gone from the
|
|
54
|
+
* composer, exactly as a submit's does. */
|
|
55
|
+
onRedirect(cb: (line: string) => void): void;
|
|
51
56
|
/** W22: bind the pending-turn queue — the CLI's live slots. The ↑
|
|
52
57
|
* pop walks them (each pop leaves the queue, cancelling the turn);
|
|
53
58
|
* esc ends the walk after one more pop. */
|
package/dist/editor.js
CHANGED
|
@@ -87,6 +87,12 @@ export class Editor {
|
|
|
87
87
|
// (dispatch) coexist; a listener removes itself via an unarmed guard
|
|
88
88
|
// (the compact's handler no-ops after its abort has fired).
|
|
89
89
|
#escapeCbs = [];
|
|
90
|
+
// KC2 §2: the redirect LIST — mirrors #escapeCbs. The editor FORWARDS
|
|
91
|
+
// the gesture with the buffer's text; it never interprets it. What a
|
|
92
|
+
// redirect MEANS (abort the run, then run THIS ahead of the queue) is
|
|
93
|
+
// the CLI's — here it is only "these two keys, pressed together, hand
|
|
94
|
+
// the line over by a different door than Enter's".
|
|
95
|
+
#redirectCbs = [];
|
|
90
96
|
// W15: the expand-key list (ctrl+r) — the CLI's dispatch decides the
|
|
91
97
|
// target (a live cell toggles in place; a committed cell appends the
|
|
92
98
|
// expanded block). Mirrors the escape list: multiple listeners can
|
|
@@ -144,6 +150,13 @@ export class Editor {
|
|
|
144
150
|
onExpand(cb) {
|
|
145
151
|
this.#expandCbs.push(cb);
|
|
146
152
|
}
|
|
153
|
+
/** KC2 §2: the redirect chain — the gesture hands the buffer's text
|
|
154
|
+
* over while the run is told to stop. Mirrors onEscape (a list, so
|
|
155
|
+
* listeners can coexist); the line arrives already gone from the
|
|
156
|
+
* composer, exactly as a submit's does. */
|
|
157
|
+
onRedirect(cb) {
|
|
158
|
+
this.#redirectCbs.push(cb);
|
|
159
|
+
}
|
|
147
160
|
/** W22: bind the pending-turn queue — the CLI's live slots. The ↑
|
|
148
161
|
* pop walks them (each pop leaves the queue, cancelling the turn);
|
|
149
162
|
* esc ends the walk after one more pop. */
|
|
@@ -408,6 +421,17 @@ export class Editor {
|
|
|
408
421
|
else if (rest.startsWith("O")) {
|
|
409
422
|
i += 3; // SS3 (function keys) — ignored
|
|
410
423
|
}
|
|
424
|
+
else if (rest.startsWith("\x0d") && this.#composerIdle()) {
|
|
425
|
+
// KC2 §2 — Alt+Enter. A terminal sends Alt+X as ESC and X in
|
|
426
|
+
// ONE write, so SAME-CHUNK is the whole test: no timer, no
|
|
427
|
+
// hold, nothing parked. The identical two bytes arriving in
|
|
428
|
+
// SEPARATE chunks are NOT combined — they fall to the branch
|
|
429
|
+
// below, where the bare Esc fires at once (its immediacy is
|
|
430
|
+
// exactly what a hold would spend) and the next chunk's CR
|
|
431
|
+
// submits: today's two gestures, untouched.
|
|
432
|
+
this.#redirect();
|
|
433
|
+
i += 2; // both bytes belong to the one gesture
|
|
434
|
+
}
|
|
411
435
|
else if (this.#menuOpen) {
|
|
412
436
|
// v3 §04: Esc closes the menu and clears the buffer.
|
|
413
437
|
// CA-4: the closing esc consumes its burst (the `i += 1`
|
|
@@ -541,6 +565,18 @@ export class Editor {
|
|
|
541
565
|
this.#insert(NEWLINE);
|
|
542
566
|
return;
|
|
543
567
|
}
|
|
568
|
+
// KC2 §2 — Ctrl+Enter, the SAME two encodings with modifier 5
|
|
569
|
+
// (1 + ctrl): kitty's CSI-u and xterm's modifyOtherKeys. Never
|
|
570
|
+
// claimed universal — a terminal that encodes neither sends a plain
|
|
571
|
+
// CR, which is an ordinary submit/queue (the safe degrade). The
|
|
572
|
+
// chunk-split safety is the existing #pending CSI resume, shared
|
|
573
|
+
// with Shift+Enter above. Outside the normal composer state the
|
|
574
|
+
// sequence is simply unknown, exactly like any other stray CSI.
|
|
575
|
+
if ((final === "u" && params === "13;5") || (final === "~" && params === "27;5;13")) {
|
|
576
|
+
if (this.#composerIdle())
|
|
577
|
+
this.#redirect();
|
|
578
|
+
return;
|
|
579
|
+
}
|
|
544
580
|
if (final === "~") {
|
|
545
581
|
const n = Number(params);
|
|
546
582
|
if (n === 3)
|
|
@@ -783,8 +819,76 @@ export class Editor {
|
|
|
783
819
|
this.#cursor = i;
|
|
784
820
|
this.#reflow();
|
|
785
821
|
}
|
|
822
|
+
/** KC1/KC2 — the buffer LEAVES: the flat chars, the cursor, the
|
|
823
|
+
* horizontal scroll, the ↑/↓ goal, the menu and the pop-walk all
|
|
824
|
+
* reset together (W22: a departing line ends the pop-walk, so the
|
|
825
|
+
* next esc at rest interrupts again). Shared by the submit and the
|
|
826
|
+
* redirect — the two doors a line can leave by. */
|
|
827
|
+
#takeLine() {
|
|
828
|
+
const line = String.fromCodePoint(...this.#chars);
|
|
829
|
+
this.#chars = [];
|
|
830
|
+
this.#cursor = 0;
|
|
831
|
+
this.#scroll = 0;
|
|
832
|
+
this.#verticalGoalCol = null;
|
|
833
|
+
this.#menuOpen = false;
|
|
834
|
+
this.#menuSel = 0;
|
|
835
|
+
this.#queuePopMode = false;
|
|
836
|
+
return line;
|
|
837
|
+
}
|
|
838
|
+
/** A2: the history remembers submitted TURN lines — never question
|
|
839
|
+
* answers, never empties; adjacent duplicates collapse, the tail
|
|
840
|
+
* caps at 100. A redirect is a turn, so it is remembered too. */
|
|
841
|
+
#remember(line) {
|
|
842
|
+
if (this.#history[this.#history.length - 1] !== line)
|
|
843
|
+
this.#history.push(line);
|
|
844
|
+
if (this.#history.length > 100)
|
|
845
|
+
this.#history.shift();
|
|
846
|
+
}
|
|
847
|
+
/** KC2 §2 — the NORMAL composer state: the redirect gesture is live
|
|
848
|
+
* ONLY here. The approval panel, the slash menu, the history browse
|
|
849
|
+
* and the queue-pop walk each OWN their keys first (the W21 "the
|
|
850
|
+
* panel owns the keys" design, restated as a gate); a pending
|
|
851
|
+
* question is the panel's dock-less twin (askPanel routes to
|
|
852
|
+
* question() when the dock cannot render, so the ask owns the keys
|
|
853
|
+
* there too); and a bracketed paste is literal TEXT, where an ESC CR
|
|
854
|
+
* is the pasted content's own bytes and never a keypress. In every
|
|
855
|
+
* one of those states the two bytes fall through to today's
|
|
856
|
+
* handling — two gestures, unchanged. */
|
|
857
|
+
#composerIdle() {
|
|
858
|
+
return (this.#panel === null &&
|
|
859
|
+
!this.#menuOpen &&
|
|
860
|
+
this.#historyIdx === null &&
|
|
861
|
+
!this.#queuePopMode &&
|
|
862
|
+
!this.#pasting &&
|
|
863
|
+
this.#questionCb === null);
|
|
864
|
+
}
|
|
865
|
+
/**
|
|
866
|
+
* KC2 §2 — the gesture's meaning, kept as small as it can honestly be.
|
|
867
|
+
*
|
|
868
|
+
* An EMPTY buffer carries no correction, so the gesture degenerates to
|
|
869
|
+
* the bare Esc: the abort alone, nothing submitted. With text, the
|
|
870
|
+
* line leaves exactly as a submit's does and the listeners decide (the
|
|
871
|
+
* CLI aborts a live run and front-jumps the correction; idle, it is
|
|
872
|
+
* simply an Enter). UNWIRED — the recovery flow never binds it — the
|
|
873
|
+
* gesture IS a submit: a line is never lost to a missing binding.
|
|
874
|
+
*/
|
|
875
|
+
#redirect() {
|
|
876
|
+
if (this.#chars.length === 0) {
|
|
877
|
+
for (const cb of [...this.#escapeCbs])
|
|
878
|
+
cb();
|
|
879
|
+
return;
|
|
880
|
+
}
|
|
881
|
+
if (this.#redirectCbs.length === 0) {
|
|
882
|
+
this.#submit();
|
|
883
|
+
return;
|
|
884
|
+
}
|
|
885
|
+
const line = this.#takeLine();
|
|
886
|
+
this.#remember(line);
|
|
887
|
+
for (const cb of [...this.#redirectCbs])
|
|
888
|
+
cb(line);
|
|
889
|
+
this.#onRender();
|
|
890
|
+
}
|
|
786
891
|
#submit() {
|
|
787
|
-
let line = String.fromCodePoint(...this.#chars);
|
|
788
892
|
if (this.#menuOpen) {
|
|
789
893
|
// A1 (the feel): Enter submits the EXACT selection directly; a
|
|
790
894
|
// PARTIAL selection COMPLETES the buffer (the Tab semantics)
|
|
@@ -792,7 +896,7 @@ export class Editor {
|
|
|
792
896
|
// again. The old behavior executed the completed command on
|
|
793
897
|
// the first Enter, before the user had seen the completion.
|
|
794
898
|
const m = this.#menuFiltered()[this.#menuSel];
|
|
795
|
-
if (m !== undefined && m.name !== line) {
|
|
899
|
+
if (m !== undefined && m.name !== this.line()) {
|
|
796
900
|
this.#chars = [...m.name].map((ch) => ch.codePointAt(0));
|
|
797
901
|
this.#cursor = this.#chars.length;
|
|
798
902
|
this.#reflow();
|
|
@@ -801,13 +905,7 @@ export class Editor {
|
|
|
801
905
|
return; // completed, not executed
|
|
802
906
|
}
|
|
803
907
|
}
|
|
804
|
-
|
|
805
|
-
this.#cursor = 0;
|
|
806
|
-
this.#scroll = 0;
|
|
807
|
-
this.#verticalGoalCol = null;
|
|
808
|
-
this.#menuOpen = false;
|
|
809
|
-
this.#menuSel = 0;
|
|
810
|
-
this.#queuePopMode = false; // W22: a submit ends the pop-walk — the next esc at rest interrupts again
|
|
908
|
+
const line = this.#takeLine();
|
|
811
909
|
const cb = this.#questionCb;
|
|
812
910
|
this.#questionCb = null;
|
|
813
911
|
if (cb !== null) {
|
|
@@ -819,14 +917,8 @@ export class Editor {
|
|
|
819
917
|
else {
|
|
820
918
|
this.#pendingLines.push(line); // nobody wired yet — hold it
|
|
821
919
|
}
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
if (cb === null && line !== "") {
|
|
825
|
-
if (this.#history[this.#history.length - 1] !== line)
|
|
826
|
-
this.#history.push(line);
|
|
827
|
-
if (this.#history.length > 100)
|
|
828
|
-
this.#history.shift();
|
|
829
|
-
}
|
|
920
|
+
if (cb === null && line !== "")
|
|
921
|
+
this.#remember(line);
|
|
830
922
|
this.#onRender();
|
|
831
923
|
}
|
|
832
924
|
/** A2: step the history browse; a delta past the newest exits back to
|
package/dist/index.d.ts
CHANGED
|
@@ -11,3 +11,4 @@ export { Container, foldLine, visibleWidth, SPINNER, type Component, type FrameC
|
|
|
11
11
|
export { Editor, MENU_ITEMS, PROMPT, PROMPT_WIDTH, displayWidth, charWidth, widthOf, type MenuItem, } from "./editor.js";
|
|
12
12
|
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";
|
|
13
13
|
export { editFileDiff, truncateDiff, writeFileDiff, type DiffLine, type DiffResult } from "./diff.js";
|
|
14
|
+
export { STATUS_GLYPHS, idleStatus, runningStatus } from "./status.js";
|
package/dist/index.js
CHANGED
|
@@ -14,3 +14,6 @@ export { Container, foldLine, visibleWidth, SPINNER } from "./components.js";
|
|
|
14
14
|
export { Editor, MENU_ITEMS, PROMPT, PROMPT_WIDTH, displayWidth, charWidth, widthOf, } from "./editor.js";
|
|
15
15
|
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";
|
|
16
16
|
export { editFileDiff, truncateDiff, writeFileDiff } from "./diff.js";
|
|
17
|
+
// KC2 §5: the status rows' formatters — the CLI keeps the state and the
|
|
18
|
+
// repaint, the terminal layer owns what the row says.
|
|
19
|
+
export { STATUS_GLYPHS, idleStatus, runningStatus } from "./status.js";
|
package/dist/status.d.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* KC2 §5 — the status line's FORMATTERS, extracted from the CLI (the
|
|
3
|
+
* ADR-0041 escape hatch: extraction, never a fifth raise). The split is
|
|
4
|
+
* the one the ADR names: the CLI keeps the STATE (the rotating glyph,
|
|
5
|
+
* the run's start instant, the live usage, whether the dock is up) and
|
|
6
|
+
* the REPAINT; what a status row SAYS is presentation, and presentation
|
|
7
|
+
* belongs to the terminal layer.
|
|
8
|
+
*
|
|
9
|
+
* Two callers built these rows independently before the move — chat's
|
|
10
|
+
* REPL and the recovery flow — with the running row duplicated verbatim
|
|
11
|
+
* in both. One definition now serves both, and the tier stays a
|
|
12
|
+
* PARAMETER precisely because the two callers disagree on it (chat
|
|
13
|
+
* spells plan's read-only posture out per W19, the recovery flow prints
|
|
14
|
+
* the bare mode): the extraction must not silently unify a difference it
|
|
15
|
+
* was not asked to settle.
|
|
16
|
+
*
|
|
17
|
+
* The rows are byte-for-byte what the CLI built before the move — the
|
|
18
|
+
* v2b/v3 §03 shapes the e2e transcripts pin by substring — with ONE
|
|
19
|
+
* deliberate exception: the running row's interrupt hint, which KC2 §2
|
|
20
|
+
* widens to name the new gesture.
|
|
21
|
+
*/
|
|
22
|
+
/** v3 §03/§05 — the working glyph family; the CLI's 200ms spinner walks
|
|
23
|
+
* it and hands each glyph back to `runningStatus`. */
|
|
24
|
+
export declare const STATUS_GLYPHS: readonly ["▖", "▘", "▝", "▗"];
|
|
25
|
+
/**
|
|
26
|
+
* The RUNNING row: the rotating glyph, the wall seconds since `since`
|
|
27
|
+
* (never below 1 — a run that just started still reads "1s", so the row
|
|
28
|
+
* never claims a turn took no time), the streamed output tokens once the
|
|
29
|
+
* count is known, the interrupt hints, and the live ctx estimate.
|
|
30
|
+
*
|
|
31
|
+
* KC2 §2: the hint names BOTH gestures. Esc still stops; alt+⏎ redirects
|
|
32
|
+
* — stop, and do THIS instead. The row is where the gesture is taught,
|
|
33
|
+
* because it is on screen exactly when the gesture is useful.
|
|
34
|
+
*/
|
|
35
|
+
export declare function runningStatus(glyph: string, since: number, outTokens: number | null, ctxRatio: number): string;
|
|
36
|
+
/** The IDLE row: the approval tier as the CALLER names it, the /mode
|
|
37
|
+
* hint, the model driving the session, and the ctx estimate. */
|
|
38
|
+
export declare function idleStatus(tier: string, model: string, ctxRatio: number): string;
|
package/dist/status.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* KC2 §5 — the status line's FORMATTERS, extracted from the CLI (the
|
|
3
|
+
* ADR-0041 escape hatch: extraction, never a fifth raise). The split is
|
|
4
|
+
* the one the ADR names: the CLI keeps the STATE (the rotating glyph,
|
|
5
|
+
* the run's start instant, the live usage, whether the dock is up) and
|
|
6
|
+
* the REPAINT; what a status row SAYS is presentation, and presentation
|
|
7
|
+
* belongs to the terminal layer.
|
|
8
|
+
*
|
|
9
|
+
* Two callers built these rows independently before the move — chat's
|
|
10
|
+
* REPL and the recovery flow — with the running row duplicated verbatim
|
|
11
|
+
* in both. One definition now serves both, and the tier stays a
|
|
12
|
+
* PARAMETER precisely because the two callers disagree on it (chat
|
|
13
|
+
* spells plan's read-only posture out per W19, the recovery flow prints
|
|
14
|
+
* the bare mode): the extraction must not silently unify a difference it
|
|
15
|
+
* was not asked to settle.
|
|
16
|
+
*
|
|
17
|
+
* The rows are byte-for-byte what the CLI built before the move — the
|
|
18
|
+
* v2b/v3 §03 shapes the e2e transcripts pin by substring — with ONE
|
|
19
|
+
* deliberate exception: the running row's interrupt hint, which KC2 §2
|
|
20
|
+
* widens to name the new gesture.
|
|
21
|
+
*/
|
|
22
|
+
import { kUnit } from "./render.js";
|
|
23
|
+
/** v3 §03/§05 — the working glyph family; the CLI's 200ms spinner walks
|
|
24
|
+
* it and hands each glyph back to `runningStatus`. */
|
|
25
|
+
export const STATUS_GLYPHS = ["▖", "▘", "▝", "▗"];
|
|
26
|
+
/** The ~ctx estimate as the whole-percent LEFT. A non-finite ratio (no
|
|
27
|
+
* window, no estimate) yields null and the row prints "~null%" — the
|
|
28
|
+
* long-standing shape, kept on purpose: an honest null beats an
|
|
29
|
+
* invented percentage. */
|
|
30
|
+
function ctxLeft(ratio) {
|
|
31
|
+
return Number.isFinite(ratio) ? Math.round((1 - ratio) * 100) : null;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* The RUNNING row: the rotating glyph, the wall seconds since `since`
|
|
35
|
+
* (never below 1 — a run that just started still reads "1s", so the row
|
|
36
|
+
* never claims a turn took no time), the streamed output tokens once the
|
|
37
|
+
* count is known, the interrupt hints, and the live ctx estimate.
|
|
38
|
+
*
|
|
39
|
+
* KC2 §2: the hint names BOTH gestures. Esc still stops; alt+⏎ redirects
|
|
40
|
+
* — stop, and do THIS instead. The row is where the gesture is taught,
|
|
41
|
+
* because it is on screen exactly when the gesture is useful.
|
|
42
|
+
*/
|
|
43
|
+
export function runningStatus(glyph, since, outTokens, ctxRatio) {
|
|
44
|
+
const out = outTokens !== null ? ` ↓ ${kUnit(outTokens)} tokens` : "";
|
|
45
|
+
const seconds = Math.max(1, Math.round((Date.now() - since) / 1000));
|
|
46
|
+
return `${glyph} working ${seconds}s${out} · esc stop · alt+⏎ redirect · ctx left ~${ctxLeft(ctxRatio)}%`;
|
|
47
|
+
}
|
|
48
|
+
/** The IDLE row: the approval tier as the CALLER names it, the /mode
|
|
49
|
+
* hint, the model driving the session, and the ctx estimate. */
|
|
50
|
+
export function idleStatus(tier, model, ctxRatio) {
|
|
51
|
+
return `▸ ${tier} · /mode to switch · ${model} · ctx left ~${ctxLeft(ctxRatio)}%`;
|
|
52
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vincemakes/kiso-tui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.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.6.0"
|
|
39
39
|
}
|
|
40
40
|
}
|