@vincemakes/kiso-tui 0.30.0 → 0.31.1
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/LICENSE +21 -0
- package/README.md +12 -0
- package/dist/ask-panel.js +1 -1
- package/dist/compositor.js +13 -1
- package/dist/editor.js +4 -1
- package/dist/panel-input.d.ts +7 -1
- package/dist/panel-input.js +39 -8
- package/package.json +2 -2
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 kiso contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# @vincemakes/kiso-tui
|
|
2
|
+
|
|
3
|
+
kiso's terminal layer: the compositor (the scrollback transcript and the
|
|
4
|
+
pinned dock beneath it), the raw-mode editor (prompt, menu, paste, undo),
|
|
5
|
+
the pick panels (model, mode, sessions, the @-file window), the status
|
|
6
|
+
meters, the context ledger, the approval and ask panels, and the key
|
|
7
|
+
bindings sheet. Built on `@vincemakes/kiso-tui-cells` and otherwise
|
|
8
|
+
dependency-free: input is data, output is bytes.
|
|
9
|
+
|
|
10
|
+
The CLI (`@vincemakes/kiso-code`) drives it; nothing here reads a
|
|
11
|
+
provider or a session store. See the repository README for the
|
|
12
|
+
framework overview.
|
package/dist/ask-panel.js
CHANGED
|
@@ -460,7 +460,7 @@ export function panelStatus(view, phase, cursor, ask, pick) {
|
|
|
460
460
|
}
|
|
461
461
|
export function panelAffordance(view, phase, cursor, ask, pick, safer) {
|
|
462
462
|
if (view.pick !== undefined && pick !== undefined)
|
|
463
|
-
return pickAffordance(pick);
|
|
463
|
+
return pickAffordance(pick, view.pick.options[pick.cursor]?.levels !== undefined);
|
|
464
464
|
if (view.ask !== undefined && ask !== undefined)
|
|
465
465
|
return askAffordance(ask, view.ask.questions[ask.qIndex]);
|
|
466
466
|
return basePanelAffordance(view, phase, cursor, safer);
|
package/dist/compositor.js
CHANGED
|
@@ -58,6 +58,10 @@ import { sessionPickerRows } from "./session-picker.js";
|
|
|
58
58
|
import { CAP_PREVIEW, thinkingRow, Container, MOTION_FRAMES, MdStream, bodySpacing, boxBottom, boxTop, cellComponent, gutterCut, cutLine, pendingQueueRows, statusLine, visibleWidth, breathFrame, } from "./components.js";
|
|
59
59
|
import { bannerLines, escapeTerminal, foldResult, foldThinking, palette, renderTerminalGap, renderToolSummary } from "./lines.js";
|
|
60
60
|
import { displayVerb, keysSheetRows } from "./strings.js";
|
|
61
|
+
/** DC-56: does a rendered row carry any visible text once its SGR is
|
|
62
|
+
* stripped? A washed pad row is spaces under a background colour — width
|
|
63
|
+
* without words. */
|
|
64
|
+
const saysSomething = (row) => row.replace(/\x1b\[[0-9;?]*[A-Za-z]/g, "").trim() !== "";
|
|
61
65
|
// R5 — the transcript viewer's PURE projection. The compositor supplies
|
|
62
66
|
// the entries (it holds the cells); the arrangement lives there.
|
|
63
67
|
import { VIEWER_GUTTER, viewerFlat, viewerHint, viewerInit, viewerMove, viewerRows, viewerScroll, viewerTitle, viewerToggle, viewerToggleAll, } from "./transcript.js";
|
|
@@ -936,7 +940,15 @@ export class Body {
|
|
|
936
940
|
cell.expanded = true;
|
|
937
941
|
const rows = cellComponent(cell).render(inner, ctx);
|
|
938
942
|
cell.expanded = saved;
|
|
939
|
-
|
|
943
|
+
// DC-56 (owner, 2026-09-09): the head is the first row that SAYS
|
|
944
|
+
// something. R13's card wears a washed PAD row above its head on
|
|
945
|
+
// the palettes with a real background (light, dark) and none under
|
|
946
|
+
// the neutral reverse-video wash — so `rows[0]` was the head on
|
|
947
|
+
// one palette and a blank washed row on the others, and ctrl+r on
|
|
948
|
+
// a light terminal showed "5 folds" over five empty grey rows.
|
|
949
|
+
const first = rows.findIndex((r) => saysSomething(r));
|
|
950
|
+
const headAt = first < 0 ? 0 : first;
|
|
951
|
+
out.push({ head: rows[headAt] ?? "", body: rows.slice(headAt + 1) });
|
|
940
952
|
}
|
|
941
953
|
return out;
|
|
942
954
|
}
|
package/dist/editor.js
CHANGED
|
@@ -1462,7 +1462,10 @@ export class Editor {
|
|
|
1462
1462
|
this.#move(-1);
|
|
1463
1463
|
}
|
|
1464
1464
|
else if (final === "C") {
|
|
1465
|
-
|
|
1465
|
+
// OR-7: → walks the pick panel's level axis when one is up; the
|
|
1466
|
+
// composer keeps the key everywhere else, exactly as ← does.
|
|
1467
|
+
if (!this.#panelInput.right())
|
|
1468
|
+
this.#move(1);
|
|
1466
1469
|
}
|
|
1467
1470
|
else if (final === "H") {
|
|
1468
1471
|
this.#cursor = this.#cursorBounds().start; // A3: Home follows Ctrl+A — line-local
|
package/dist/panel-input.d.ts
CHANGED
|
@@ -97,8 +97,14 @@ export declare class PanelInput {
|
|
|
97
97
|
* panel owned the key (the caller renders); false when no panel is
|
|
98
98
|
* up. A phase without a list (asking) swallows the key. */
|
|
99
99
|
arrow(dir: "up" | "down"): boolean;
|
|
100
|
-
/** ← while an ask's option row is up walks the ask;
|
|
100
|
+
/** ← while an ask's option row is up walks the ask; while a pick's is
|
|
101
|
+
* up it walks the highlighted option's level axis (OR-7); otherwise
|
|
102
|
+
* the key is the composer's. */
|
|
101
103
|
left(): boolean;
|
|
104
|
+
/** → is the level axis's other direction. The ask never owned it (its
|
|
105
|
+
* own walk is ← back and enter forward), so this key was the
|
|
106
|
+
* composer's alone until OR-7 and still is when no axis is up. */
|
|
107
|
+
right(): boolean;
|
|
102
108
|
/** TUI2-R3v2 ②: a left-button press on the option rows the
|
|
103
109
|
* compositor placed. Outside the list — inert. */
|
|
104
110
|
click(span: PanelRowSpan | null | undefined, row: number | undefined): void;
|
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, } from "./approval-panel.js";
|
|
19
|
+
import { PICK_MAX, panelOptions, 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;
|
|
@@ -49,7 +49,7 @@ export class PanelInput {
|
|
|
49
49
|
safer: opts?.safer,
|
|
50
50
|
saferRun: null,
|
|
51
51
|
ask: view.ask === undefined ? null : askStart(view.ask),
|
|
52
|
-
pick: view.pick === undefined ? null : { cursor: 0, phase: "options" },
|
|
52
|
+
pick: view.pick === undefined ? null : { cursor: 0, phase: "options", level: startLevel(view.pick.options[0]) },
|
|
53
53
|
onCommit,
|
|
54
54
|
stash: this.host.stash(),
|
|
55
55
|
};
|
|
@@ -113,8 +113,10 @@ export class PanelInput {
|
|
|
113
113
|
this.#pickPanelDigit(Number(c) - 1);
|
|
114
114
|
return 1;
|
|
115
115
|
}
|
|
116
|
+
if (!typing && (c === "h" || c === "l") && this.#levelStep(c === "h" ? -1 : 1))
|
|
117
|
+
return 1;
|
|
116
118
|
if (!typing && (c === "t" || c === "T") && panel.view.pick?.typeHint !== undefined) {
|
|
117
|
-
panel.pick = { cursor: panel.pick.cursor, phase: "custom" };
|
|
119
|
+
panel.pick = { cursor: panel.pick.cursor, phase: "custom", level: panel.pick.level };
|
|
118
120
|
this.host.clear();
|
|
119
121
|
this.host.render();
|
|
120
122
|
return 1;
|
|
@@ -206,7 +208,12 @@ export class PanelInput {
|
|
|
206
208
|
if (panel.pick !== null && panel.pick.phase === "options") {
|
|
207
209
|
const n = Math.min(panel.view.pick.options.length, PICK_MAX);
|
|
208
210
|
const cur = panel.pick.cursor;
|
|
209
|
-
|
|
211
|
+
const next = dir === "up" ? Math.max(0, cur - 1) : Math.min(Math.max(0, n - 1), cur + 1);
|
|
212
|
+
// OR-7: the second axis belongs to the highlighted option, so it
|
|
213
|
+
// re-lands on THAT option's own level. Carrying the previous
|
|
214
|
+
// row's index across would point at a level this model may not
|
|
215
|
+
// have — the silent clamp the coordination note exists to end.
|
|
216
|
+
panel.pick = { cursor: next, phase: "options", level: startLevel(panel.view.pick.options[next]) };
|
|
210
217
|
}
|
|
211
218
|
else if (panel.ask !== null && panel.ask.phase === "options")
|
|
212
219
|
this.#askStep(dir);
|
|
@@ -216,14 +223,37 @@ export class PanelInput {
|
|
|
216
223
|
this.#panelMove(dir === "up" ? -1 : 1);
|
|
217
224
|
return true;
|
|
218
225
|
}
|
|
219
|
-
/** ← while an ask's option row is up walks the ask;
|
|
226
|
+
/** ← while an ask's option row is up walks the ask; while a pick's is
|
|
227
|
+
* up it walks the highlighted option's level axis (OR-7); otherwise
|
|
228
|
+
* the key is the composer's. */
|
|
220
229
|
left() {
|
|
230
|
+
if (this.#levelStep(-1))
|
|
231
|
+
return true;
|
|
221
232
|
const panel = this.#panel;
|
|
222
233
|
if (panel?.ask == null || panel.ask.phase !== "options")
|
|
223
234
|
return false;
|
|
224
235
|
this.#askStep("left");
|
|
225
236
|
return true;
|
|
226
237
|
}
|
|
238
|
+
/** → is the level axis's other direction. The ask never owned it (its
|
|
239
|
+
* own walk is ← back and enter forward), so this key was the
|
|
240
|
+
* composer's alone until OR-7 and still is when no axis is up. */
|
|
241
|
+
right() {
|
|
242
|
+
return this.#levelStep(1);
|
|
243
|
+
}
|
|
244
|
+
/** One step along the highlighted option's levels. False when there is
|
|
245
|
+
* no axis to walk, which is what leaves the key to the composer. */
|
|
246
|
+
#levelStep(dir) {
|
|
247
|
+
const panel = this.#panel;
|
|
248
|
+
if (panel === null || panel.pick === null || panel.pick.phase !== "options")
|
|
249
|
+
return false;
|
|
250
|
+
const o = panel.view.pick?.options[panel.pick.cursor];
|
|
251
|
+
if (o?.levels === undefined)
|
|
252
|
+
return false;
|
|
253
|
+
panel.pick = { cursor: panel.pick.cursor, phase: "options", level: stepLevel(o, panel.pick.level, dir) };
|
|
254
|
+
this.host.render();
|
|
255
|
+
return true;
|
|
256
|
+
}
|
|
227
257
|
/** TUI2-R3v2 ②: a left-button press on the option rows the
|
|
228
258
|
* compositor placed. Outside the list — inert. */
|
|
229
259
|
click(span, row) {
|
|
@@ -420,7 +450,7 @@ export class PanelInput {
|
|
|
420
450
|
return;
|
|
421
451
|
if (index < 0 || index >= Math.min(panel.view.pick.options.length, PICK_MAX))
|
|
422
452
|
return;
|
|
423
|
-
panel.pick = { cursor: index, phase: "options" };
|
|
453
|
+
panel.pick = { cursor: index, phase: "options", level: startLevel(panel.view.pick.options[index]) };
|
|
424
454
|
this.host.render();
|
|
425
455
|
}
|
|
426
456
|
#pickPanelEnter() {
|
|
@@ -436,14 +466,15 @@ export class PanelInput {
|
|
|
436
466
|
}
|
|
437
467
|
if (panel.view.pick.options.length === 0)
|
|
438
468
|
return; // nothing to take
|
|
439
|
-
|
|
469
|
+
const level = panel.pick.level;
|
|
470
|
+
this.#close({ action: "picked", result: { index: panel.pick.cursor }, ...(level === null ? {} : { level }) });
|
|
440
471
|
}
|
|
441
472
|
#pickPanelEsc() {
|
|
442
473
|
const panel = this.#panel;
|
|
443
474
|
if (panel === null || panel.pick === null)
|
|
444
475
|
return;
|
|
445
476
|
if (panel.pick.phase === "custom") {
|
|
446
|
-
panel.pick = { cursor: panel.pick.cursor, phase: "options" };
|
|
477
|
+
panel.pick = { cursor: panel.pick.cursor, phase: "options", level: panel.pick.level };
|
|
447
478
|
this.host.clear();
|
|
448
479
|
this.host.render();
|
|
449
480
|
return;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vincemakes/kiso-tui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.31.1",
|
|
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.31.1"
|
|
39
39
|
}
|
|
40
40
|
}
|