@vincemakes/kiso-tui-cells 0.9.0 → 0.10.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/approval-panel.d.ts +75 -0
- package/dist/approval-panel.js +93 -1
- package/dist/components.d.ts +25 -0
- package/dist/components.js +65 -10
- package/dist/index.d.ts +2 -1
- package/dist/index.js +4 -1
- package/dist/render.d.ts +8 -0
- package/dist/render.js +2 -2
- package/dist/strings.d.ts +4 -0
- package/dist/strings.js +34 -0
- package/dist/width.d.ts +16 -6
- package/dist/width.js +78 -6
- package/package.json +1 -1
package/dist/approval-panel.d.ts
CHANGED
|
@@ -75,6 +75,38 @@ export interface AskRuntime {
|
|
|
75
75
|
readonly custom: readonly (string | null)[];
|
|
76
76
|
readonly phase: "options" | "custom";
|
|
77
77
|
}
|
|
78
|
+
/** One thing that can be picked: what it is, and what qualifies it. */
|
|
79
|
+
export interface PickOption {
|
|
80
|
+
readonly label: string;
|
|
81
|
+
/** the dim qualifier ("profile: ds \u00b7 current") \u2014 what tells two
|
|
82
|
+
* similar rows apart */
|
|
83
|
+
readonly note?: string;
|
|
84
|
+
}
|
|
85
|
+
/** The whole pick: the header sentence, the options, the free-text
|
|
86
|
+
* escape hatch, and the honest empty state. */
|
|
87
|
+
export interface PickSpec {
|
|
88
|
+
readonly header: string;
|
|
89
|
+
readonly options: readonly PickOption[];
|
|
90
|
+
/** the `t` row \u2014 typing it directly is always available, because a
|
|
91
|
+
* list of profiles is never the list of models that exist */
|
|
92
|
+
readonly typeHint: string;
|
|
93
|
+
/** shown INSTEAD of the options when there are none. The copy is the
|
|
94
|
+
* caller's and is reproduced verbatim. */
|
|
95
|
+
readonly emptyNote?: string;
|
|
96
|
+
}
|
|
97
|
+
/** The pick panel's runtime state \u2014 the editor owns it, the compositor
|
|
98
|
+
* reads it (the AskRuntime precedent, two fields instead of five). */
|
|
99
|
+
export interface PickRuntime {
|
|
100
|
+
readonly cursor: number;
|
|
101
|
+
readonly phase: "options" | "custom";
|
|
102
|
+
}
|
|
103
|
+
/** What was picked: a listed option by INDEX (never a label the caller
|
|
104
|
+
* would have to re-match against its own list), or typed text. */
|
|
105
|
+
export type PickResult = {
|
|
106
|
+
readonly index: number;
|
|
107
|
+
} | {
|
|
108
|
+
readonly custom: string;
|
|
109
|
+
};
|
|
78
110
|
/** The ALWAYS-verbose args (the panel's body): the untruncated diff
|
|
79
111
|
* (edit/write), or the full text (shell = the command line, other =
|
|
80
112
|
* the pretty-printed JSON). The CLI composes them UNTRUNCATED — the
|
|
@@ -116,6 +148,9 @@ export interface PanelView {
|
|
|
116
148
|
* panel renders the ask block and the editor routes the ask keys;
|
|
117
149
|
* absent = the approval/simple panel, unchanged. */
|
|
118
150
|
readonly ask?: AskSpec;
|
|
151
|
+
/** TUI2-R2 \u2463: the options, when this view is a PICK. Same contract
|
|
152
|
+
* as `ask`, one payload over. */
|
|
153
|
+
readonly pick?: PickSpec;
|
|
119
154
|
}
|
|
120
155
|
export type PanelVerdict = {
|
|
121
156
|
readonly action: "allow";
|
|
@@ -135,6 +170,13 @@ export type PanelVerdict = {
|
|
|
135
170
|
| {
|
|
136
171
|
readonly action: "answers";
|
|
137
172
|
readonly result: AskResult;
|
|
173
|
+
}
|
|
174
|
+
/** TUI2-R2 \u2463: the pick's verdict \u2014 the chosen index or the typed
|
|
175
|
+
* text. Only pick views ever produce it, so the approval path's
|
|
176
|
+
* switch is untouched. */
|
|
177
|
+
| {
|
|
178
|
+
readonly action: "picked";
|
|
179
|
+
readonly result: PickResult;
|
|
138
180
|
};
|
|
139
181
|
/** The bound panel state the compositor reads — the editor owns the
|
|
140
182
|
* phase/selection state machine and the key routing; the compositor
|
|
@@ -145,6 +187,8 @@ export interface PanelState {
|
|
|
145
187
|
readonly sel: PanelSel;
|
|
146
188
|
/** KC3.5: the ask's walk — present exactly when `view.ask` is. */
|
|
147
189
|
readonly ask?: AskRuntime;
|
|
190
|
+
/** TUI2-R2 \u2463: the pick's walk — present exactly when `view.pick` is. */
|
|
191
|
+
readonly pick?: PickRuntime;
|
|
148
192
|
}
|
|
149
193
|
/** The block's rows — EXACTLY the preview's frame shape, the gutter at
|
|
150
194
|
* the left edge (the preview's two-space mock indent is its own
|
|
@@ -168,3 +212,34 @@ export declare function panelStatus(view: PanelView, phase: PanelPhase, sel: Pan
|
|
|
168
212
|
* phase's keys. The approval flavor gains the tab-amend path; the
|
|
169
213
|
* simple flavor (the trust/uncertain gates) never does. */
|
|
170
214
|
export declare function panelAffordance(view: PanelView, phase: PanelPhase, sel: PanelSel): string;
|
|
215
|
+
/**
|
|
216
|
+
* The pick block's rows — the prototype's C frame.
|
|
217
|
+
*
|
|
218
|
+
* The header says what is in effect right now, because the first
|
|
219
|
+
* question anyone opening this panel has is "what am I on?". The
|
|
220
|
+
* options are numbered from 1 and the number IS the key. The `t` row is
|
|
221
|
+
* last and always present: a profile list is a convenience, never the
|
|
222
|
+
* set of models that exist, and a picker that can only offer what
|
|
223
|
+
* someone remembered to configure is a smaller product than the one it
|
|
224
|
+
* replaced.
|
|
225
|
+
*
|
|
226
|
+
* Single-row discipline: every row CUTS, never folds — the block's
|
|
227
|
+
* height is its row count (the W20 rule the #checked throw demands).
|
|
228
|
+
*/
|
|
229
|
+
export declare function pickBlockRows(view: PanelView, state: PickRuntime, W: number, maxRows: number): string[];
|
|
230
|
+
/** The digits are the keys, so the list the panel offers is bounded by
|
|
231
|
+
* the digits there are. Beyond it, `/model <name>` still takes any
|
|
232
|
+
* profile — the panel says so rather than paginating. */
|
|
233
|
+
export declare const PICK_MAX = 9;
|
|
234
|
+
/** The input row's lead: the digit range while picking, the named
|
|
235
|
+
* prompt while typing one out. */
|
|
236
|
+
export declare function pickLeadPlain(view: PanelView, state: PickRuntime): string;
|
|
237
|
+
export declare function pickLead(view: PanelView, state: PickRuntime): string;
|
|
238
|
+
/** The status row's left text \u2014 the CALLER's, because only the caller
|
|
239
|
+
* knows whether a run is paused behind this panel. */
|
|
240
|
+
export declare function pickStatus(view: PanelView): string;
|
|
241
|
+
export declare function pickAffordance(state: PickRuntime): string;
|
|
242
|
+
/** Compose a pick view. The flavor/name/title/args fields exist for the
|
|
243
|
+
* approval path and are given inert values here \u2014 the pick block
|
|
244
|
+
* reads none of them. */
|
|
245
|
+
export declare function modelPickView(spec: PickSpec, statusText: string): PanelView;
|
package/dist/approval-panel.js
CHANGED
|
@@ -26,6 +26,9 @@
|
|
|
26
26
|
*/
|
|
27
27
|
import { displayWidth } from "./width.js";
|
|
28
28
|
import { cutLine, diffBody, gutterFold, visibleWidth, widthCut } from "./components.js";
|
|
29
|
+
// TUI2-R2pre ④: strings.js takes only a TYPE from this module, so the
|
|
30
|
+
// import is erased at compile time and no runtime cycle exists.
|
|
31
|
+
import { displayVerb } from "./strings.js";
|
|
29
32
|
import { escapeTerminal, palette } from "./render.js";
|
|
30
33
|
/** The rule line's text — the why-asked line (the R3 chain): the tool
|
|
31
34
|
* name, the first non-abstain speaker, the fix hint (the §3.5 table,
|
|
@@ -37,7 +40,11 @@ function panelRuleText(view) {
|
|
|
37
40
|
if (view.ruleOverride !== undefined)
|
|
38
41
|
return escapeTerminal(view.ruleOverride);
|
|
39
42
|
const hint = view.hint;
|
|
40
|
-
|
|
43
|
+
// TUI2-R2pre ④: the rule line is the panel's header — it says the ACT
|
|
44
|
+
// ("edit needs approval"). view.name keeps the RAW tool name, which is
|
|
45
|
+
// what the option-2 rule prefill and the fallbackQuestion (the
|
|
46
|
+
// dock-less/pipe path — byte-identical by ruling) still read.
|
|
47
|
+
const base = `${p.bold}${escapeTerminal(displayVerb(view.name))}${p.reset} ${p.dim}needs approval — asked by${p.reset} ${p.bold}${escapeTerminal(view.speaker)}${p.reset}`;
|
|
41
48
|
return hint ? `${base}${p.dim} ·${p.reset} ${p.code}${escapeTerminal(hint)}${p.reset}` : base;
|
|
42
49
|
}
|
|
43
50
|
/** The numbered options row — "1 Yes 2 Yes, don't ask again for
|
|
@@ -170,3 +177,88 @@ export function panelAffordance(view, phase, sel) {
|
|
|
170
177
|
return sel === 0 ? "tab amend · esc cancel" : "enter sends · esc backs out";
|
|
171
178
|
return sel === 0 ? "esc cancel" : "enter sends";
|
|
172
179
|
}
|
|
180
|
+
// ── TUI2-R2 ④: the pick block, its lead, its status, its affordance ──
|
|
181
|
+
/**
|
|
182
|
+
* The pick block's rows — the prototype's C frame.
|
|
183
|
+
*
|
|
184
|
+
* The header says what is in effect right now, because the first
|
|
185
|
+
* question anyone opening this panel has is "what am I on?". The
|
|
186
|
+
* options are numbered from 1 and the number IS the key. The `t` row is
|
|
187
|
+
* last and always present: a profile list is a convenience, never the
|
|
188
|
+
* set of models that exist, and a picker that can only offer what
|
|
189
|
+
* someone remembered to configure is a smaller product than the one it
|
|
190
|
+
* replaced.
|
|
191
|
+
*
|
|
192
|
+
* Single-row discipline: every row CUTS, never folds — the block's
|
|
193
|
+
* height is its row count (the W20 rule the #checked throw demands).
|
|
194
|
+
*/
|
|
195
|
+
export function pickBlockRows(view, state, W, maxRows) {
|
|
196
|
+
const p = palette();
|
|
197
|
+
const spec = view.pick;
|
|
198
|
+
const gutter = `${p.dim}\u2502${p.reset} `;
|
|
199
|
+
const rows = [];
|
|
200
|
+
const room = Math.max(1, W - 2);
|
|
201
|
+
rows.push(`${gutter}${cutLine(`${p.bold}${escapeTerminal(spec.header.split(" \u2014 ")[0] ?? spec.header)}${p.reset}${p.dim}${escapeTerminal(spec.header.slice((spec.header.split(" \u2014 ")[0] ?? "").length))}${p.reset}`, room)}`);
|
|
202
|
+
if (spec.options.length === 0) {
|
|
203
|
+
// the honest empty state \u2014 the caller's own copy, verbatim
|
|
204
|
+
rows.push(`${gutter}${cutLine(`${p.dim} ${escapeTerminal(spec.emptyNote ?? "no options")}${p.reset}`, room)}`);
|
|
205
|
+
}
|
|
206
|
+
else {
|
|
207
|
+
// the budget: the header, the t row, the affordance and the rule
|
|
208
|
+
const budget = Math.max(1, maxRows - 4);
|
|
209
|
+
const shown = spec.options.slice(0, Math.min(budget, PICK_MAX));
|
|
210
|
+
for (let i = 0; i < shown.length; i += 1) {
|
|
211
|
+
const o = shown[i];
|
|
212
|
+
const mark = i === state.cursor && state.phase === "options";
|
|
213
|
+
const head = `${mark ? p.bold : ""} ${i + 1} ${escapeTerminal(o.label)}${mark ? p.reset : ""}`;
|
|
214
|
+
const note = o.note === undefined ? "" : `${p.dim} ${escapeTerminal(o.note)}${p.reset}`;
|
|
215
|
+
rows.push(`${gutter}${cutLine(`${head}${note}`, room)}`);
|
|
216
|
+
}
|
|
217
|
+
if (spec.options.length > shown.length) {
|
|
218
|
+
rows.push(`${gutter}${cutLine(`${p.dim} \u2514 +${spec.options.length - shown.length} more \u2014 /model <name> takes any of them${p.reset}`, room)}`);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
rows.push(`${gutter}${cutLine(`${state.phase === "custom" ? p.bold : ""} t ${p.reset}${p.dim}${escapeTerminal(spec.typeHint)}${p.reset}`, room)}`);
|
|
222
|
+
rows.push(`${gutter}${p.dim}${cutLine(pickAffordance(state), room)}${p.reset}`);
|
|
223
|
+
rows.push(`${p.dim}\u2514${"\u2500".repeat(Math.max(0, W - 1))}${p.reset}`);
|
|
224
|
+
return rows;
|
|
225
|
+
}
|
|
226
|
+
/** The digits are the keys, so the list the panel offers is bounded by
|
|
227
|
+
* the digits there are. Beyond it, `/model <name>` still takes any
|
|
228
|
+
* profile — the panel says so rather than paginating. */
|
|
229
|
+
export const PICK_MAX = 9;
|
|
230
|
+
/** The input row's lead: the digit range while picking, the named
|
|
231
|
+
* prompt while typing one out. */
|
|
232
|
+
export function pickLeadPlain(view, state) {
|
|
233
|
+
if (state.phase === "custom")
|
|
234
|
+
return "provider/model: ";
|
|
235
|
+
const n = Math.min(view.pick.options.length, PICK_MAX);
|
|
236
|
+
return n === 0 ? "t> " : `1-${n}> `;
|
|
237
|
+
}
|
|
238
|
+
export function pickLead(view, state) {
|
|
239
|
+
const p = palette();
|
|
240
|
+
return `${p.bold}${pickLeadPlain(view, state)}${p.reset}`;
|
|
241
|
+
}
|
|
242
|
+
/** The status row's left text \u2014 the CALLER's, because only the caller
|
|
243
|
+
* knows whether a run is paused behind this panel. */
|
|
244
|
+
export function pickStatus(view) {
|
|
245
|
+
return view.statusText;
|
|
246
|
+
}
|
|
247
|
+
export function pickAffordance(state) {
|
|
248
|
+
return state.phase === "custom" ? "enter commits \u00b7 esc backs out" : "digits pick \u00b7 \u23ce confirms \u00b7 esc";
|
|
249
|
+
}
|
|
250
|
+
/** Compose a pick view. The flavor/name/title/args fields exist for the
|
|
251
|
+
* approval path and are given inert values here \u2014 the pick block
|
|
252
|
+
* reads none of them. */
|
|
253
|
+
export function modelPickView(spec, statusText) {
|
|
254
|
+
return {
|
|
255
|
+
flavor: "simple",
|
|
256
|
+
name: "model",
|
|
257
|
+
title: "model",
|
|
258
|
+
speaker: "you",
|
|
259
|
+
statusText,
|
|
260
|
+
args: { kind: "text", lines: [] },
|
|
261
|
+
fallbackQuestion: "switch model? (name) ",
|
|
262
|
+
pick: spec,
|
|
263
|
+
};
|
|
264
|
+
}
|
package/dist/components.d.ts
CHANGED
|
@@ -215,12 +215,37 @@ export declare function gutterFold(gutter: string, line: string, W: number): str
|
|
|
215
215
|
* ellipsis ride the row (the invariant ① cap holds). */
|
|
216
216
|
export declare function gutterCut(gutter: string, line: string, W: number): string[];
|
|
217
217
|
export declare function expandSuffix(lines: number | null, room: number): string;
|
|
218
|
+
/**
|
|
219
|
+
* TUI2-R2 ⑤ (D, candidate 1) — the FOCUS tint.
|
|
220
|
+
*
|
|
221
|
+
* The cell the next ctrl+r will act on brightens its own `ctrl+r` token
|
|
222
|
+
* to the code tint; the rest of the suffix — the separator, the count —
|
|
223
|
+
* stays dim, because what is being marked is the KEY's target, not the
|
|
224
|
+
* row. Zero new rows, zero new columns: the affordance the cell already
|
|
225
|
+
* prints is the marker.
|
|
226
|
+
*
|
|
227
|
+
* Applied to a row rather than composed into it on purpose. The token is
|
|
228
|
+
* emitted from several places (the settled suffix, the renderer's own
|
|
229
|
+
* `└ +N … · ctrl+r` cut rows) and threading a flag through all of them
|
|
230
|
+
* would put the invariant "exactly one bright token" in as many hands as
|
|
231
|
+
* there are emitters. Here it has exactly one.
|
|
232
|
+
*
|
|
233
|
+
* NO_COLOR: p.code is empty, so the row's bytes are untouched.
|
|
234
|
+
*/
|
|
235
|
+
export declare function focusToken(row: string, W: number): string;
|
|
218
236
|
/** W13 — the rollup opt-in table: which tools collapse, and the count
|
|
219
237
|
* NOUN (read_file calls → "5 files", list_dir → "5 dirs", search_text
|
|
220
238
|
* → "5 matches"). Only these tools opt in — a shell burst is never
|
|
221
239
|
* rolled up (its rows carry meaning). The folded-turn line (W14) reuses
|
|
222
240
|
* the plurals for its other-tool terms ("2 dirs", "1 match"). */
|
|
223
241
|
export declare const ROLLUP_NOUN: Readonly<Record<string, string>>;
|
|
242
|
+
/** TUI2-R1 (B) — the verb column of the expanded list names the ACT.
|
|
243
|
+
* TUI2-R2pre ④: this used to be a private three-tool table saying the
|
|
244
|
+
* same thing as the card head's `_file` strip, in a different way and
|
|
245
|
+
* for a different set of tools. Both are `displayVerb` now — the whole
|
|
246
|
+
* point of the ruling is that there is ONE answer to "what does the
|
|
247
|
+
* screen call this". The cut note, which used to be the deliberate
|
|
248
|
+
* exception here, moved with it (see toolCutNote). */
|
|
224
249
|
/** Whether a tool joins an exploration run. Exactly the read-only set —
|
|
225
250
|
* writes, edits, shells and extension tools never group (a burst of
|
|
226
251
|
* side effects is a list of things that HAPPENED, and every row of it
|
package/dist/components.js
CHANGED
|
@@ -18,6 +18,10 @@
|
|
|
18
18
|
* tint, fold wording).
|
|
19
19
|
*/
|
|
20
20
|
import { displayWidth } from "./width.js";
|
|
21
|
+
// TUI2-R2pre ④: the ONE display-verb table (strings.ts, beside
|
|
22
|
+
// KEY_BINDINGS). strings.js imports only render/width here, so this edge
|
|
23
|
+
// adds no cycle.
|
|
24
|
+
import { displayVerb } from "./strings.js";
|
|
21
25
|
import { bannerLines, escapeTerminal, foldThinking, foldResult, colorInlineCode, renderTerminalGap, renderToolSummary, toolTarget, kUnit, palette, } from "./render.js";
|
|
22
26
|
/** The spinner glyphs, cycled by the compositor's on-demand tick. */
|
|
23
27
|
export const SPINNER = ["▖", "▘", "▝", "▗"];
|
|
@@ -454,7 +458,7 @@ class ToolExecution {
|
|
|
454
458
|
render(W, ctx) {
|
|
455
459
|
const p = palette();
|
|
456
460
|
const c = this.cell;
|
|
457
|
-
const verb = escapeTerminal(c.name
|
|
461
|
+
const verb = escapeTerminal(displayVerb(c.name));
|
|
458
462
|
const verbCol = verb.length < 5 ? `${verb}${" ".repeat(5 - verb.length)}` : verb;
|
|
459
463
|
const parts = c.rolled?.parts;
|
|
460
464
|
if (c.rolled !== null && parts !== undefined) {
|
|
@@ -711,6 +715,46 @@ function appendSuffix(row, suffix) {
|
|
|
711
715
|
const p = palette();
|
|
712
716
|
return `${row}${p.dim}${suffix}${p.reset}`;
|
|
713
717
|
}
|
|
718
|
+
/**
|
|
719
|
+
* TUI2-R2 ⑤ (D, candidate 1) — the FOCUS tint.
|
|
720
|
+
*
|
|
721
|
+
* The cell the next ctrl+r will act on brightens its own `ctrl+r` token
|
|
722
|
+
* to the code tint; the rest of the suffix — the separator, the count —
|
|
723
|
+
* stays dim, because what is being marked is the KEY's target, not the
|
|
724
|
+
* row. Zero new rows, zero new columns: the affordance the cell already
|
|
725
|
+
* prints is the marker.
|
|
726
|
+
*
|
|
727
|
+
* Applied to a row rather than composed into it on purpose. The token is
|
|
728
|
+
* emitted from several places (the settled suffix, the renderer's own
|
|
729
|
+
* `└ +N … · ctrl+r` cut rows) and threading a flag through all of them
|
|
730
|
+
* would put the invariant "exactly one bright token" in as many hands as
|
|
731
|
+
* there are emitters. Here it has exactly one.
|
|
732
|
+
*
|
|
733
|
+
* NO_COLOR: p.code is empty, so the row's bytes are untouched.
|
|
734
|
+
*/
|
|
735
|
+
export function focusToken(row, W) {
|
|
736
|
+
const p = palette();
|
|
737
|
+
const at = row.lastIndexOf(CTRL_R);
|
|
738
|
+
if (at !== -1) {
|
|
739
|
+
// the row already names the key — brighten the token in place, and
|
|
740
|
+
// leave every other span exactly as it was
|
|
741
|
+
if (p.code === "")
|
|
742
|
+
return row;
|
|
743
|
+
return `${row.slice(0, at)}${p.code}${CTRL_R}${p.reset}${p.dim}${row.slice(at + CTRL_R.length)}`;
|
|
744
|
+
}
|
|
745
|
+
// A LIVE row does not carry the affordance today, and the live cell is
|
|
746
|
+
// the one ctrl+r takes FIRST (expandNext scans the live tail before
|
|
747
|
+
// the committed ring) — so the row the key is aimed at was the one row
|
|
748
|
+
// that never said the key existed. The affordance IS the marker here:
|
|
749
|
+
// it appears on the focused row and nowhere else, which is why no
|
|
750
|
+
// unfocused row's bytes move (every existing live-row assertion
|
|
751
|
+
// renders a cell with no focus and is untouched).
|
|
752
|
+
const room = W - visibleWidth(row);
|
|
753
|
+
if (room < SUFFIX_MIN)
|
|
754
|
+
return row; // never at the cost of invariant ①
|
|
755
|
+
return `${row}${p.dim} · ${p.reset}${p.code}${CTRL_R}${p.reset}`;
|
|
756
|
+
}
|
|
757
|
+
const CTRL_R = "ctrl+r";
|
|
714
758
|
/** TUI2-R1 (A) — the expanded block's last row: the way back. The
|
|
715
759
|
* rollup's expanded list carries a second clause (its members' full
|
|
716
760
|
* outputs live in /last, which the group row cannot show). */
|
|
@@ -736,10 +780,13 @@ const EXPLORE_NOUN = {
|
|
|
736
780
|
list_dir: ["dir", "dirs"],
|
|
737
781
|
search_text: ["search", "searches"],
|
|
738
782
|
};
|
|
739
|
-
/** TUI2-R1 (B) — the verb column of the expanded list.
|
|
740
|
-
*
|
|
741
|
-
*
|
|
742
|
-
|
|
783
|
+
/** TUI2-R1 (B) — the verb column of the expanded list names the ACT.
|
|
784
|
+
* TUI2-R2pre ④: this used to be a private three-tool table saying the
|
|
785
|
+
* same thing as the card head's `_file` strip, in a different way and
|
|
786
|
+
* for a different set of tools. Both are `displayVerb` now — the whole
|
|
787
|
+
* point of the ruling is that there is ONE answer to "what does the
|
|
788
|
+
* screen call this". The cut note, which used to be the deliberate
|
|
789
|
+
* exception here, moved with it (see toolCutNote). */
|
|
743
790
|
/** Whether a tool joins an exploration run. Exactly the read-only set —
|
|
744
791
|
* writes, edits, shells and extension tools never group (a burst of
|
|
745
792
|
* side effects is a list of things that HAPPENED, and every row of it
|
|
@@ -770,7 +817,7 @@ export function exploreRows(parts, W) {
|
|
|
770
817
|
counts.set(s, (counts.get(s) ?? 0) + 1);
|
|
771
818
|
const shown = [...counts.entries()].slice(0, 3).map(([s, n]) => (n > 1 ? `${s} ×${n}` : s));
|
|
772
819
|
const more = counts.size > 3 ? ` (+${counts.size - 3})` : "";
|
|
773
|
-
const verb =
|
|
820
|
+
const verb = displayVerb(part.name);
|
|
774
821
|
rows.push(cutLine(`${p.dim}${BODY_ROW}${escapeTerminal(`${verb.padEnd(6)} ${shown.join(" · ")}${more}`)}${p.reset}`, W));
|
|
775
822
|
}
|
|
776
823
|
// TUI2-R1.5 ① (VD-15): the footer used to promise "/last shows the full
|
|
@@ -812,7 +859,7 @@ export function turnFold(t, W) {
|
|
|
812
859
|
parts.push(countTerm(n, noun.endsWith("es") ? noun.slice(0, -2) : noun.slice(0, -1), noun));
|
|
813
860
|
}
|
|
814
861
|
else {
|
|
815
|
-
const verb = name
|
|
862
|
+
const verb = displayVerb(name);
|
|
816
863
|
parts.push(countTerm(n, verb, `${verb}s`));
|
|
817
864
|
}
|
|
818
865
|
}
|
|
@@ -1110,14 +1157,22 @@ export function diffBody(diff, W, expanded = false) {
|
|
|
1110
1157
|
* offset=N", the output cap, list_dir's entry cap). The note reaches
|
|
1111
1158
|
* the MODEL and never the human — this row surfaces it. Detected in
|
|
1112
1159
|
* the result's TAIL (the note is appended at the end); returns null
|
|
1113
|
-
* when the tool did not truncate.
|
|
1160
|
+
* when the tool did not truncate.
|
|
1161
|
+
*
|
|
1162
|
+
* TUI2-R2pre ④: the verb here is the DISPLAY one now. This row used to
|
|
1163
|
+
* be the sanctioned raw-name exception, on the reasoning that it names
|
|
1164
|
+
* the tool the model should call again — but the row is addressed to
|
|
1165
|
+
* the HUMAN (the model already has the note in its own transcript, which
|
|
1166
|
+
* is where it read it), and the ruling names this advisory family
|
|
1167
|
+
* explicitly. The `offset=N` it carries is the actionable half and is
|
|
1168
|
+
* untouched. */
|
|
1114
1169
|
function toolCutNote(name, resultText) {
|
|
1115
1170
|
const tail = resultText.slice(-300);
|
|
1116
1171
|
const m = /offset=(\d+)/.exec(tail);
|
|
1117
1172
|
if (m !== null)
|
|
1118
|
-
return `capped by ${escapeTerminal(name)} · offset=${m[1]} for the rest`;
|
|
1173
|
+
return `capped by ${escapeTerminal(displayVerb(name))} · offset=${m[1]} for the rest`;
|
|
1119
1174
|
if (/…\[truncated\]/.test(tail) || /… \+?\d+ more (?:lines|entries)/.test(tail))
|
|
1120
|
-
return `capped by ${escapeTerminal(name)} · /last for the rest`;
|
|
1175
|
+
return `capped by ${escapeTerminal(displayVerb(name))} · /last for the rest`;
|
|
1121
1176
|
return null;
|
|
1122
1177
|
}
|
|
1123
1178
|
/** The assistant body text — wrapped at W, the inline-code tint per
|
package/dist/index.d.ts
CHANGED
|
@@ -6,11 +6,12 @@
|
|
|
6
6
|
* package); the cli never imports it directly. Experimental — no
|
|
7
7
|
* API-stability promise yet.
|
|
8
8
|
*/
|
|
9
|
-
export { SPINNER, foldLine, foldWords, visibleWidth, bodySpacing, Container, cellComponent, ROLLUP_NOUN, turnFold, CAP_TASK_LIVE, formatDuration, statusLine, boxTop, boxBottom, terminalPipe, type FrameCtx, type RenderLine, type Component, type BodyCell, } from "./components.js";
|
|
9
|
+
export { SPINNER, foldLine, foldWords, visibleWidth, bodySpacing, Container, cellComponent, focusToken, ROLLUP_NOUN, turnFold, CAP_TASK_LIVE, formatDuration, statusLine, boxTop, boxBottom, terminalPipe, type FrameCtx, type RenderLine, type Component, type BodyCell, } from "./components.js";
|
|
10
10
|
export { editFileDiff, truncateDiff, writeFileDiff, type DiffLine, type DiffResult } from "./diff.js";
|
|
11
11
|
export { pendingQueueRows } from "./components.js";
|
|
12
12
|
export { charWidth, displayWidth, leadWidth, widthOf } from "./width.js";
|
|
13
13
|
export { panelAffordance, panelBlockRows, panelLead, panelLeadPlain, panelLeadWidth, panelStatus, type PanelArgs, type PanelFlavor, type PanelPhase, type PanelSel, type PanelState, type PanelVerdict, type PanelView, type AskAnswer, type AskOption, type AskQuestion, type AskResult, type AskRuntime, type AskSpec, } from "./approval-panel.js";
|
|
14
14
|
export { interactivePrompt, projectTrustRows, projectTrustView, projectUntrustedNote, uncertainView, type TrustArtifact, } from "./strings.js";
|
|
15
15
|
export { extensionsBannerText, helpRows, unansweredAskView, type BannerExtension } from "./strings.js";
|
|
16
|
+
export { displayVerb } from "./strings.js";
|
|
16
17
|
export { bannerLines, COLOR_OFF, COLOR_ON, colorInlineCode, escapeTerminal, foldResult, foldThinking, kUnit, palette, relativeTime, renderResumeList, renderTerminalGap, renderToolSummary, TAGLINE, toolTarget, truncateRow, type Palette, type ResumeMeta, } from "./render.js";
|
package/dist/index.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* package); the cli never imports it directly. Experimental — no
|
|
7
7
|
* API-stability promise yet.
|
|
8
8
|
*/
|
|
9
|
-
export { SPINNER, foldLine, foldWords, visibleWidth, bodySpacing, Container, cellComponent, ROLLUP_NOUN, turnFold, CAP_TASK_LIVE, formatDuration, statusLine, boxTop, boxBottom, terminalPipe, } from "./components.js";
|
|
9
|
+
export { SPINNER, foldLine, foldWords, visibleWidth, bodySpacing, Container, cellComponent, focusToken, ROLLUP_NOUN, turnFold, CAP_TASK_LIVE, formatDuration, statusLine, boxTop, boxBottom, terminalPipe, } from "./components.js";
|
|
10
10
|
export { editFileDiff, truncateDiff, writeFileDiff } from "./diff.js";
|
|
11
11
|
// W22 (the v8 input round): the pending-queue chips — the SAME
|
|
12
12
|
// UserMessage chip with the □ gutter, pre-rendered above the input
|
|
@@ -25,4 +25,7 @@ export { panelAffordance, panelBlockRows, panelLead, panelLeadPlain, panelLeadWi
|
|
|
25
25
|
export { interactivePrompt, projectTrustRows, projectTrustView, projectUntrustedNote, uncertainView, } from "./strings.js";
|
|
26
26
|
// KC3.5: the interrupted-ask copy and the extracted /help table.
|
|
27
27
|
export { extensionsBannerText, helpRows, unansweredAskView } from "./strings.js";
|
|
28
|
+
// TUI2-R2pre ④: the ONE display-verb table — the screen names the act,
|
|
29
|
+
// the tool table names the call.
|
|
30
|
+
export { displayVerb } from "./strings.js";
|
|
28
31
|
export { bannerLines, COLOR_OFF, COLOR_ON, colorInlineCode, escapeTerminal, foldResult, foldThinking, kUnit, palette, relativeTime, renderResumeList, renderTerminalGap, renderToolSummary, TAGLINE, toolTarget, truncateRow, } from "./render.js";
|
package/dist/render.d.ts
CHANGED
|
@@ -34,6 +34,14 @@ export interface Palette {
|
|
|
34
34
|
readonly dim: string;
|
|
35
35
|
readonly red: string;
|
|
36
36
|
readonly green: string;
|
|
37
|
+
/** TUI2-R2 ①: the third functional exception, finally spelled. The
|
|
38
|
+
* mono-discipline ruling above names "green ✓, yellow warn and red
|
|
39
|
+
* error" as the ONLY functional colours; warn had no entry because
|
|
40
|
+
* nothing had needed it yet. The uncertain badge needs exactly it —
|
|
41
|
+
* a state that is neither success nor failure but a question
|
|
42
|
+
* addressed to the human. This is the ruling's own set gaining its
|
|
43
|
+
* missing member, not a fourth colour. */
|
|
44
|
+
readonly warn: string;
|
|
37
45
|
readonly code: string;
|
|
38
46
|
readonly rv: string;
|
|
39
47
|
readonly rvEnd: string;
|
package/dist/render.js
CHANGED
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
* dependencies (the tui-cells package has none).
|
|
7
7
|
*/
|
|
8
8
|
import { charWidth, displayWidth } from "./width.js";
|
|
9
|
-
export const COLOR_ON = { bold: "\x1b[1m", dim: "\x1b[2m", red: "\x1b[31m", green: "\x1b[32m", code: "\x1b[38;5;252m", rv: "\x1b[7m", rvEnd: "\x1b[27m", reset: "\x1b[0m" };
|
|
10
|
-
export const COLOR_OFF = { bold: "", dim: "", red: "", green: "", code: "", rv: "", rvEnd: "", reset: "" };
|
|
9
|
+
export const COLOR_ON = { bold: "\x1b[1m", dim: "\x1b[2m", red: "\x1b[31m", green: "\x1b[32m", warn: "\x1b[33m", code: "\x1b[38;5;252m", rv: "\x1b[7m", rvEnd: "\x1b[27m", reset: "\x1b[0m" };
|
|
10
|
+
export const COLOR_OFF = { bold: "", dim: "", red: "", green: "", warn: "", code: "", rv: "", rvEnd: "", reset: "" };
|
|
11
11
|
export function palette() {
|
|
12
12
|
return process.env.NO_COLOR === undefined && process.stdout.isTTY ? COLOR_ON : COLOR_OFF;
|
|
13
13
|
}
|
package/dist/strings.d.ts
CHANGED
|
@@ -111,6 +111,10 @@ export interface KeyBinding {
|
|
|
111
111
|
* then the completions.
|
|
112
112
|
*/
|
|
113
113
|
export declare const KEY_BINDINGS: readonly KeyBinding[];
|
|
114
|
+
/** A tool's name as the SCREEN says it. Display-only: the raw name stays
|
|
115
|
+
* on the cell, and dispatch, the mode gate, the policy keys, the /last
|
|
116
|
+
* RAW block and every model-facing byte keep reading that. */
|
|
117
|
+
export declare function displayVerb(name: string): string;
|
|
114
118
|
/** The panel keys, which belong to a panel rather than the composer —
|
|
115
119
|
* one dim line rather than four table rows, because they apply only
|
|
116
120
|
* while a panel is up. */
|
package/dist/strings.js
CHANGED
|
@@ -155,6 +155,40 @@ export const KEY_BINDINGS = [
|
|
|
155
155
|
{ keys: "tab", what: "complete (menu / @)" },
|
|
156
156
|
{ keys: "?", what: "this sheet" },
|
|
157
157
|
];
|
|
158
|
+
/**
|
|
159
|
+
* TUI2-R2pre ④ — THE display-verb table (the integrator's ruling).
|
|
160
|
+
*
|
|
161
|
+
* The screen names the ACT; the tool table names the CALL. Two
|
|
162
|
+
* audiences, two vocabularies, and only the human's one lives here: the
|
|
163
|
+
* API names DO NOT change, because the model-request surface is frozen
|
|
164
|
+
* rent and every byte of it is paid for on every turn. The
|
|
165
|
+
* rename-the-tools path is REJECTED by ruling.
|
|
166
|
+
*
|
|
167
|
+
* One table, for the same reason KEY_BINDINGS above is one table. The
|
|
168
|
+
* mapping used to exist three and a half times — a `.replace("_file",
|
|
169
|
+
* "")` in components.ts, another in render.ts, two more in the
|
|
170
|
+
* compositor, and a private three-tool table for the rollup's expanded
|
|
171
|
+
* list — and the drift was visible on a single screen: a card head
|
|
172
|
+
* reading `read` directly above one reading `list_dir`.
|
|
173
|
+
*
|
|
174
|
+
* An unmapped tool (an extension's, an MCP server's) renders its own
|
|
175
|
+
* name. Inventing a verb for a tool this package has never heard of
|
|
176
|
+
* would be a worse lie than printing what the model actually calls.
|
|
177
|
+
*/
|
|
178
|
+
const DISPLAY_VERB = {
|
|
179
|
+
read_file: "read",
|
|
180
|
+
list_dir: "list",
|
|
181
|
+
search_text: "search",
|
|
182
|
+
write_file: "write",
|
|
183
|
+
edit_file: "edit",
|
|
184
|
+
shell: "shell",
|
|
185
|
+
};
|
|
186
|
+
/** A tool's name as the SCREEN says it. Display-only: the raw name stays
|
|
187
|
+
* on the cell, and dispatch, the mode gate, the policy keys, the /last
|
|
188
|
+
* RAW block and every model-facing byte keep reading that. */
|
|
189
|
+
export function displayVerb(name) {
|
|
190
|
+
return DISPLAY_VERB[name] ?? name;
|
|
191
|
+
}
|
|
158
192
|
/** The panel keys, which belong to a panel rather than the composer —
|
|
159
193
|
* one dim line rather than four table rows, because they apply only
|
|
160
194
|
* while a panel is up. */
|
package/dist/width.d.ts
CHANGED
|
@@ -1,11 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The display-width primitives — the SINGLE width authority (TUI v5
|
|
3
|
-
* #16e: "charWidth is the width authority"). The
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
3
|
+
* #16e: "charWidth is the width authority"). The table covers the East
|
|
4
|
+
* Asian Wide/Fullwidth ranges and Emoji_Presentation=Yes; everything
|
|
5
|
+
* else is one column, the box-drawing/brick glyphs █▀▄▞▸ and the text-
|
|
6
|
+
* presentation marks ✓ ✗ ⚠ ⏸ included.
|
|
7
|
+
*
|
|
8
|
+
* This is the compositor's FLOOR, not a cosmetic detail: a glyph scored
|
|
9
|
+
* one column that a terminal draws in two makes a line whose measured
|
|
10
|
+
* width is <= W really need W+1, the terminal soft-wraps the tail onto
|
|
11
|
+
* the row below, and the live region silently eats a row it never
|
|
12
|
+
* budgeted (TUI2-R2pre ① — the composer clobber).
|
|
13
|
+
*
|
|
14
|
+
* Known limitation, documented in the README: emoji ZWJ clusters and
|
|
15
|
+
* variation-selector sequences are not guaranteed perfect — each code
|
|
16
|
+
* point counts as its own width (U+26A0 + FE0F sums to 2, which is what
|
|
17
|
+
* a terminal draws, but that is arithmetic luck, not a model).
|
|
18
|
+
* Zero dependencies (importable from any module).
|
|
9
19
|
*/
|
|
10
20
|
/** A code point's display width: 2 for the wide ranges, 1 otherwise. */
|
|
11
21
|
export declare function charWidth(cp: number): number;
|
package/dist/width.js
CHANGED
|
@@ -1,12 +1,63 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The display-width primitives — the SINGLE width authority (TUI v5
|
|
3
|
-
* #16e: "charWidth is the width authority"). The
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
3
|
+
* #16e: "charWidth is the width authority"). The table covers the East
|
|
4
|
+
* Asian Wide/Fullwidth ranges and Emoji_Presentation=Yes; everything
|
|
5
|
+
* else is one column, the box-drawing/brick glyphs █▀▄▞▸ and the text-
|
|
6
|
+
* presentation marks ✓ ✗ ⚠ ⏸ included.
|
|
7
|
+
*
|
|
8
|
+
* This is the compositor's FLOOR, not a cosmetic detail: a glyph scored
|
|
9
|
+
* one column that a terminal draws in two makes a line whose measured
|
|
10
|
+
* width is <= W really need W+1, the terminal soft-wraps the tail onto
|
|
11
|
+
* the row below, and the live region silently eats a row it never
|
|
12
|
+
* budgeted (TUI2-R2pre ① — the composer clobber).
|
|
13
|
+
*
|
|
14
|
+
* Known limitation, documented in the README: emoji ZWJ clusters and
|
|
15
|
+
* variation-selector sequences are not guaranteed perfect — each code
|
|
16
|
+
* point counts as its own width (U+26A0 + FE0F sums to 2, which is what
|
|
17
|
+
* a terminal draws, but that is arithmetic luck, not a model).
|
|
18
|
+
* Zero dependencies (importable from any module).
|
|
9
19
|
*/
|
|
20
|
+
/** TUI2-R2pre ① — the Emoji_Presentation=Yes code points inside
|
|
21
|
+
* U+2000..U+2BFF. The rest of that span is TEXT presentation and stays
|
|
22
|
+
* one column: ✓ ✗ ⚠ ⏸ ▞ ▸ and the box-drawing rails are all narrow, and
|
|
23
|
+
* widening any of them would move every card head on the screen. Listed
|
|
24
|
+
* as ranges because that is what the property is — the singles are
|
|
25
|
+
* singles in Unicode too. */
|
|
26
|
+
const EMOJI_PRESENTATION = [
|
|
27
|
+
[0x231a, 0x231b],
|
|
28
|
+
[0x23e9, 0x23ec],
|
|
29
|
+
[0x23f0, 0x23f0],
|
|
30
|
+
[0x23f3, 0x23f3],
|
|
31
|
+
[0x25fd, 0x25fe],
|
|
32
|
+
[0x2614, 0x2615],
|
|
33
|
+
[0x2648, 0x2653],
|
|
34
|
+
[0x267f, 0x267f],
|
|
35
|
+
[0x2693, 0x2693],
|
|
36
|
+
[0x26a1, 0x26a1],
|
|
37
|
+
[0x26aa, 0x26ab],
|
|
38
|
+
[0x26bd, 0x26be],
|
|
39
|
+
[0x26c4, 0x26c5],
|
|
40
|
+
[0x26ce, 0x26ce],
|
|
41
|
+
[0x26d4, 0x26d4],
|
|
42
|
+
[0x26ea, 0x26ea],
|
|
43
|
+
[0x26f2, 0x26f3],
|
|
44
|
+
[0x26f5, 0x26f5],
|
|
45
|
+
[0x26fa, 0x26fa],
|
|
46
|
+
[0x26fd, 0x26fd],
|
|
47
|
+
[0x2705, 0x2705],
|
|
48
|
+
[0x270a, 0x270b],
|
|
49
|
+
[0x2728, 0x2728],
|
|
50
|
+
[0x274c, 0x274c],
|
|
51
|
+
[0x274e, 0x274e],
|
|
52
|
+
[0x2753, 0x2755],
|
|
53
|
+
[0x2757, 0x2757],
|
|
54
|
+
[0x2795, 0x2797],
|
|
55
|
+
[0x27b0, 0x27b0],
|
|
56
|
+
[0x27bf, 0x27bf],
|
|
57
|
+
[0x2b1b, 0x2b1c],
|
|
58
|
+
[0x2b50, 0x2b50],
|
|
59
|
+
[0x2b55, 0x2b55],
|
|
60
|
+
];
|
|
10
61
|
/** A code point's display width: 2 for the wide ranges, 1 otherwise. */
|
|
11
62
|
export function charWidth(cp) {
|
|
12
63
|
if (cp >= 0x1100 && cp <= 0x115f)
|
|
@@ -35,12 +86,33 @@ export function charWidth(cp) {
|
|
|
35
86
|
return 2; // fullwidth forms
|
|
36
87
|
if (cp >= 0xffe0 && cp <= 0xffe6)
|
|
37
88
|
return 2; // fullwidth signs
|
|
89
|
+
// TUI2-R1.5 shipped only two of the pictographic ranges; the holes
|
|
90
|
+
// (transport, mahjong/cards, enclosed, colored shapes, the extended
|
|
91
|
+
// block) were scored ONE column while every terminal draws them in
|
|
92
|
+
// two — the composer clobber of the owner's field report (①).
|
|
93
|
+
if (cp === 0x1f004 || cp === 0x1f0cf)
|
|
94
|
+
return 2; // mahjong red dragon, joker
|
|
95
|
+
if (cp >= 0x1f18e && cp <= 0x1f19a)
|
|
96
|
+
return 2; // enclosed alphanumerics
|
|
97
|
+
if (cp >= 0x1f200 && cp <= 0x1f251)
|
|
98
|
+
return 2; // enclosed ideographic
|
|
38
99
|
if (cp >= 0x1f300 && cp <= 0x1f64f)
|
|
39
100
|
return 2; // emoji (misc + emoticons)
|
|
101
|
+
if (cp >= 0x1f680 && cp <= 0x1f6ff)
|
|
102
|
+
return 2; // transport + map
|
|
103
|
+
if (cp >= 0x1f7e0 && cp <= 0x1f7eb)
|
|
104
|
+
return 2; // colored circles + squares
|
|
40
105
|
if (cp >= 0x1f900 && cp <= 0x1f9ff)
|
|
41
106
|
return 2; // supplemental emoji
|
|
107
|
+
if (cp >= 0x1fa70 && cp <= 0x1faff)
|
|
108
|
+
return 2; // symbols + pictographs ext-A
|
|
42
109
|
if (cp >= 0x20000 && cp <= 0x3fffd)
|
|
43
110
|
return 2; // CJK ext B..G
|
|
111
|
+
if (cp >= 0x231a && cp <= 0x2b55) {
|
|
112
|
+
for (const [lo, hi] of EMOJI_PRESENTATION)
|
|
113
|
+
if (cp >= lo && cp <= hi)
|
|
114
|
+
return 2;
|
|
115
|
+
}
|
|
44
116
|
return 1;
|
|
45
117
|
}
|
|
46
118
|
/** Display width of a code-point array (cursor math, scrolling). */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vincemakes/kiso-tui-cells",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "kiso tui-cells — the components cell renderer (components, diff, width, the render slice). Zero runtime dependencies: input is data, output is bytes.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|