@vincemakes/kiso-tui 0.46.2 → 0.47.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/dist/ask-panel.d.ts +10 -5
- package/dist/ask-panel.js +109 -54
- package/dist/at-picker.d.ts +38 -68
- package/dist/at-picker.js +50 -141
- package/dist/compositor.d.ts +79 -10
- package/dist/compositor.js +339 -57
- package/dist/context-ledger.d.ts +28 -0
- package/dist/context-ledger.js +62 -0
- package/dist/editor.d.ts +17 -0
- package/dist/editor.js +76 -15
- package/dist/index.d.ts +10 -10
- package/dist/index.js +10 -10
- package/dist/lines.d.ts +11 -0
- package/dist/lines.js +38 -0
- package/dist/notice-meta.d.ts +25 -0
- package/dist/notice-meta.js +85 -0
- package/dist/panel-input.d.ts +2 -2
- package/dist/panel-input.js +70 -60
- package/dist/pick-input.d.ts +23 -3
- package/dist/pick-input.js +9 -29
- package/dist/session-picker.d.ts +37 -22
- package/dist/session-picker.js +270 -57
- package/dist/status.d.ts +73 -69
- package/dist/status.js +177 -96
- package/package.json +2 -2
package/dist/ask-panel.d.ts
CHANGED
|
@@ -67,7 +67,7 @@ export declare function askCommitCustom(spec: AskSpec, state: AskRuntime, text:
|
|
|
67
67
|
* the widest label, never past the half-width, and 0 (meaning "no
|
|
68
68
|
* column, use the em dash") when what is left would not hold a
|
|
69
69
|
* readable description. */
|
|
70
|
-
export declare function askDescriptionStop(q: AskQuestion, W: number): number;
|
|
70
|
+
export declare function askDescriptionStop(q: AskQuestion, W: number, markCol?: boolean): number;
|
|
71
71
|
/** The ask block's rows — the question as the rule line, the header (or
|
|
72
72
|
* the counter) as the title, the options as the body, and the
|
|
73
73
|
* type-your-own line last. R2: the shape is the RULE's — rule,
|
|
@@ -110,8 +110,8 @@ export declare function askAffordanceFit(state: AskRuntime, q: AskQuestion | und
|
|
|
110
110
|
export declare function askStatus(view: PanelView, state: AskRuntime): string;
|
|
111
111
|
/** The input row's lead: the digit lead while picking, the typing lead
|
|
112
112
|
* in the custom phase (the rule-input phase's shape, reused). */
|
|
113
|
-
export declare function askLeadPlain(
|
|
114
|
-
export declare function panelBlockRows(view: PanelView, phase: PanelPhase, cursor: number, W: number, maxRows: number, ask?: AskRuntime, pick?: PickRuntime, note?: string, safer?: SaferRuntime): string[];
|
|
113
|
+
export declare function askLeadPlain(_state: AskRuntime): string;
|
|
114
|
+
export declare function panelBlockRows(view: PanelView, phase: PanelPhase, cursor: number, W: number, maxRows: number, ask?: AskRuntime, pick?: PickRuntime, note?: string, safer?: SaferRuntime, height?: number): string[];
|
|
115
115
|
export declare function panelLead(view: PanelView, phase: PanelPhase, cursor: number, ask?: AskRuntime, pick?: PickRuntime): string;
|
|
116
116
|
export declare function panelLeadPlain(view: PanelView, phase: PanelPhase, cursor: number, ask?: AskRuntime, pick?: PickRuntime): string;
|
|
117
117
|
export declare function panelStatus(view: PanelView, phase: PanelPhase, cursor: number, ask?: AskRuntime, pick?: PickRuntime): string;
|
|
@@ -119,7 +119,7 @@ export declare function panelAffordance(view: PanelView, phase: PanelPhase, curs
|
|
|
119
119
|
/** The whole panel state in one call — the compositor's four reads share
|
|
120
120
|
* one source, so an ask can never render half as an approval (TUI2-R2
|
|
121
121
|
* ④: nor a pick as either). */
|
|
122
|
-
export declare const panelRowsOf: (s: PanelState, W: number, maxRows: number) => string[];
|
|
122
|
+
export declare const panelRowsOf: (s: PanelState, W: number, maxRows: number, height?: number) => string[];
|
|
123
123
|
/**
|
|
124
124
|
* TUI2-R3v2 ② — the rows AND where the clickable ones are, from ONE
|
|
125
125
|
* call.
|
|
@@ -136,7 +136,7 @@ export declare const panelRowsOf: (s: PanelState, W: number, maxRows: number) =>
|
|
|
136
136
|
* not). A null here means every click is inert, which is the safe way
|
|
137
137
|
* to not-implement something.
|
|
138
138
|
*/
|
|
139
|
-
export declare const panelFrameOf: (s: PanelState, W: number, maxRows: number) => {
|
|
139
|
+
export declare const panelFrameOf: (s: PanelState, W: number, maxRows: number, height?: number) => {
|
|
140
140
|
rows: string[];
|
|
141
141
|
options: {
|
|
142
142
|
offset: number;
|
|
@@ -145,6 +145,11 @@ export declare const panelFrameOf: (s: PanelState, W: number, maxRows: number) =
|
|
|
145
145
|
} | null;
|
|
146
146
|
};
|
|
147
147
|
export declare const panelLeadOf: (s: PanelState) => string;
|
|
148
|
+
/** Graphite P3/P4: what an EMPTY input row says while a panel is up and the
|
|
149
|
+
* composer is where the person types — a pick's filter, the note an
|
|
150
|
+
* approval's amend sends, a typed answer to a question. Null: the row stays
|
|
151
|
+
* empty, as §7.8 has it everywhere else. */
|
|
152
|
+
export declare const panelHintOf: (s: PanelState) => string | null;
|
|
148
153
|
export declare const panelStatusOf: (s: PanelState) => string;
|
|
149
154
|
export declare const panelAffordanceOf: (s: PanelState) => string;
|
|
150
155
|
/** The ask's PanelView. The dock-less fallback question is HONEST: a
|
package/dist/ask-panel.js
CHANGED
|
@@ -27,8 +27,9 @@
|
|
|
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 { 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
|
-
import { cutLine, selectionBar, visibleWidth, widthCut } from "@vincemakes/kiso-tui-cells/components";
|
|
30
|
+
import { panelBlockLayout, panelAffordance as basePanelAffordance, panelBlockRows as basePanelBlockRows, panelLead as basePanelLead, panelLeadPlain as basePanelLeadPlain, panelStatus as basePanelStatus, pickAffordance, pickBlockRows, pickLead, pickList, pickLeadPlain, pickStatus, foldAtSpaces, } from "./approval-panel.js";
|
|
31
|
+
import { boxBottom, cutLine, selectionBar, visibleWidth, widthCut } from "@vincemakes/kiso-tui-cells/components";
|
|
32
|
+
import { bandHeader } from "@vincemakes/kiso-tui-cells/strings";
|
|
32
33
|
import { escapeTerminal, palette } from "./lines.js";
|
|
33
34
|
/** The schema's own bounds — the registry refuses anything outside them
|
|
34
35
|
* (extensions/ask validates; these are the numbers it validates to). */
|
|
@@ -223,17 +224,26 @@ export function askCommitCustom(spec, state, text) {
|
|
|
223
224
|
* A narrow block has no room for two columns; `stop` arrives as 0 and
|
|
224
225
|
* the em-dash form is what it degrades to.
|
|
225
226
|
*/
|
|
226
|
-
function optionRow(o, n, picked, cursor, multi, W, stop = 0) {
|
|
227
|
+
function optionRow(o, n, picked, cursor, multi, W, stop = 0, markCol = multi, opened = false) {
|
|
227
228
|
const p = palette();
|
|
228
|
-
|
|
229
|
+
// Graphite P4 follow-up: a single-select spends no mark column until one
|
|
230
|
+
// of its options is picked (walking back to an answered question shows
|
|
231
|
+
// its ◉) — the approved `1 vite`, where `1 vite` was drawn
|
|
232
|
+
const mark = multi ? (picked ? "◉" : "◯") : !markCol ? "" : picked ? "◉" : " ";
|
|
229
233
|
const lead = askOptionLead(o, n, mark, cursor);
|
|
230
234
|
const head = `${cursor ? p.bold : ""}${lead}${p.reset}`;
|
|
231
235
|
const room = Math.max(1, W - 2);
|
|
232
236
|
let body;
|
|
233
|
-
|
|
237
|
+
// an OPENED option says its description on the rows under it, whole —
|
|
238
|
+
// its own row carries the label alone, so nothing is said twice
|
|
239
|
+
if (o.description === undefined || opened)
|
|
234
240
|
body = "";
|
|
235
241
|
else if (stop > 0) {
|
|
236
|
-
|
|
242
|
+
// cut by cells WITH an ellipsis — a bare cut reads as the whole
|
|
243
|
+
// sentence (the P2/P3 class, here in the model's descriptions)
|
|
244
|
+
const left = Math.max(0, room - stop);
|
|
245
|
+
const said = escapeTerminal(o.description);
|
|
246
|
+
const desc = visibleWidth(said) <= left ? said : left <= 1 ? "" : `${widthCut(said, left - 1)}…`;
|
|
237
247
|
body = `${" ".repeat(Math.max(1, stop - visibleWidth(lead)))}${p.dim}${desc}${p.reset}`;
|
|
238
248
|
}
|
|
239
249
|
else
|
|
@@ -246,12 +256,35 @@ function optionRow(o, n, picked, cursor, multi, W, stop = 0) {
|
|
|
246
256
|
// ruling, applied in the second place it was always about.
|
|
247
257
|
return cursor ? selectionBar(text, visibleWidth(text), W) : ` ${text}`; // R2: the frame is a rule, so a row is just indented
|
|
248
258
|
}
|
|
259
|
+
/** Whether an option's description fits its row as drawn — in the column
|
|
260
|
+
* when there is one, after an em dash when there is not. */
|
|
261
|
+
function descriptionFits(o, n, mark, stop, W) {
|
|
262
|
+
const room = Math.max(1, W - 2);
|
|
263
|
+
const said = escapeTerminal(o.description ?? "");
|
|
264
|
+
if (stop > 0)
|
|
265
|
+
return visibleWidth(said) <= Math.max(0, room - stop);
|
|
266
|
+
return visibleWidth(askOptionLead(o, n, mark, false)) + 3 + visibleWidth(said) <= room;
|
|
267
|
+
}
|
|
268
|
+
/** The OPENED rows of the selected option: its description, whole, folded
|
|
269
|
+
* at its spaces under the label's first cell, on the selection's wash. At
|
|
270
|
+
* most three rows, the third cut with an ellipsis when it must be. */
|
|
271
|
+
const OPENED_MAX = 3;
|
|
272
|
+
function openedDescription(description, labelCol, W) {
|
|
273
|
+
const p = palette();
|
|
274
|
+
const width = Math.max(8, W - labelCol - 1);
|
|
275
|
+
let rows = foldAtSpaces(escapeTerminal(description), width);
|
|
276
|
+
if (rows.length > OPENED_MAX) {
|
|
277
|
+
const last = rows.slice(OPENED_MAX - 1).join(" ");
|
|
278
|
+
rows = [...rows.slice(0, OPENED_MAX - 1), visibleWidth(last) <= width ? last : `${widthCut(last, width - 1)}\u2026`];
|
|
279
|
+
}
|
|
280
|
+
return rows.map((r) => selectionBar(`${" ".repeat(Math.max(0, labelCol - 1))}${p.dim}${r}${p.reset}`, labelCol - 1 + visibleWidth(r), W));
|
|
281
|
+
}
|
|
249
282
|
/** The row's left span, PLAIN — the one place its shape is written, so
|
|
250
283
|
* the column arithmetic below and the row above cannot disagree about
|
|
251
284
|
* how wide it is. The arrow's cell is spent on every row so the digit
|
|
252
285
|
* column does not move as the cursor walks. */
|
|
253
286
|
function askOptionLead(o, n, mark, cursor) {
|
|
254
|
-
return `${cursor ? "→" : " "} ${n} ${mark} ${escapeTerminal(o.label)}`;
|
|
287
|
+
return `${cursor ? "→" : " "} ${n} ${mark === "" ? "" : `${mark} `}${escapeTerminal(o.label)}`;
|
|
255
288
|
}
|
|
256
289
|
/** Below this many cells a right column is not a column, it is a
|
|
257
290
|
* three-word stub — the em-dash form carries more of the sentence. */
|
|
@@ -260,11 +293,11 @@ const ASK_DESC_MIN = 18;
|
|
|
260
293
|
* the widest label, never past the half-width, and 0 (meaning "no
|
|
261
294
|
* column, use the em dash") when what is left would not hold a
|
|
262
295
|
* readable description. */
|
|
263
|
-
export function askDescriptionStop(q, W) {
|
|
296
|
+
export function askDescriptionStop(q, W, markCol = q.multiSelect === true) {
|
|
264
297
|
const multi = q.multiSelect === true;
|
|
265
298
|
if (!q.options.some((o) => o.description !== undefined))
|
|
266
299
|
return 0;
|
|
267
|
-
const widest = Math.max(...q.options.map((o, i) => visibleWidth(askOptionLead(o, i + 1, multi ? "◯" : " ", false))));
|
|
300
|
+
const widest = Math.max(...q.options.map((o, i) => visibleWidth(askOptionLead(o, i + 1, multi ? "◯" : markCol ? " " : "", false))));
|
|
268
301
|
const room = Math.max(1, W - 2);
|
|
269
302
|
const stop = widest + 2;
|
|
270
303
|
if (stop > Math.floor(room / 2) || room - stop < ASK_DESC_MIN)
|
|
@@ -280,25 +313,34 @@ export function askBlockRows(view, state, W, maxRows) {
|
|
|
280
313
|
const spec = view.ask;
|
|
281
314
|
const q = spec.questions[state.qIndex];
|
|
282
315
|
const multi = q.multiSelect === true;
|
|
283
|
-
const counter = spec.questions.length > 1 ? `${p.dim} ‹ ${state.qIndex + 1}/${spec.questions.length} ›${p.reset}` : "";
|
|
284
316
|
const rows = [];
|
|
285
|
-
//
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
//
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
rows.push(` ${cutLine(`${p.dim}${header}${p.reset}${gesture}`, Math.max(1, W - 2))}`);
|
|
298
|
-
rows.push("");
|
|
317
|
+
// Graphite P4 (owner, 2026-10-04): the band's name carries the facts — the
|
|
318
|
+
// question's header, its place in the set, `pick any` — and the question
|
|
319
|
+
// is the first row, bold. DECLARED REVERSAL of the `‹ 1/2 ›` after the
|
|
320
|
+
// question, of the header's row of its own and of the blank row (R2,
|
|
321
|
+
// R6/D2): three rows before the options became one.
|
|
322
|
+
const facts = [
|
|
323
|
+
...(q.header === undefined ? [] : [escapeTerminal(q.header.slice(0, ASK_HEADER_CAP))]),
|
|
324
|
+
...(spec.questions.length > 1 ? [`${state.qIndex + 1} of ${spec.questions.length}`] : []),
|
|
325
|
+
...(multi ? ["pick any"] : []),
|
|
326
|
+
];
|
|
327
|
+
rows.push(bandHeader(["question", ...facts].join(" \u00b7 "), W));
|
|
328
|
+
rows.push(` ${cutLine(`${p.bold}${escapeTerminal(q.question)}${p.reset}`, Math.max(1, W - 2))}`);
|
|
299
329
|
const picks = state.picks[state.qIndex] ?? [];
|
|
300
|
-
const
|
|
301
|
-
const
|
|
330
|
+
const markCol = multi || picks.length > 0;
|
|
331
|
+
const stop = askDescriptionStop(q, W, markCol);
|
|
332
|
+
// Graphite P4 (owner, 2026-10-05): the selected option OPENS when its
|
|
333
|
+
// description does not fit its row — its row keeps the label and the
|
|
334
|
+
// rows under it, on the same wash, say the description whole (the
|
|
335
|
+
// /resume and /model shape). The others keep their one row, cut.
|
|
336
|
+
const body = [];
|
|
337
|
+
q.options.forEach((o, i) => {
|
|
338
|
+
const on = state.cursor === i;
|
|
339
|
+
const opens = on && state.phase !== "custom" && o.description !== undefined && !descriptionFits(o, i + 1, multi ? "◯" : markCol ? " " : "", stop, W);
|
|
340
|
+
body.push(optionRow(o, i + 1, picks.includes(i), on, multi, W, stop, markCol, opens));
|
|
341
|
+
if (opens)
|
|
342
|
+
body.push(...openedDescription(o.description, visibleWidth(askOptionLead(o, i + 1, multi ? "◯" : markCol ? " " : "", false)) - visibleWidth(escapeTerminal(o.label)) + 1, W));
|
|
343
|
+
});
|
|
302
344
|
// REL-0152-D3: the row is part of the list, so it carries the same
|
|
303
345
|
// cursor affordance the options do. Dim-always made a reachable row
|
|
304
346
|
// look like a footnote.
|
|
@@ -319,13 +361,13 @@ export function askBlockRows(view, state, W, maxRows) {
|
|
|
319
361
|
? `${onCustom || typingHere ? p.bold : ""}${customLead}◉ ${escapeTerminal(typed)}${p.reset}`
|
|
320
362
|
: typingHere
|
|
321
363
|
? `${p.bold}${customLead}▸${p.reset} ${p.dim}type your answer — enter sends, esc backs out${p.reset}`
|
|
322
|
-
: `${onCustom ? p.bold : p.dim}${customLead} type your own answer${p.reset}`, Math.max(1, W - 2));
|
|
364
|
+
: `${onCustom ? p.bold : p.dim}${customLead}${markCol ? " " : ""}type your own answer${p.reset}`, Math.max(1, W - 2));
|
|
323
365
|
body.push(onCustom ? selectionBar(customText, visibleWidth(customText), W) : ` ${customText}`);
|
|
324
366
|
// the bounded block: the options fold nothing and cut individually,
|
|
325
367
|
// so the cap drops whole rows with the W21 notice row.
|
|
326
|
-
//
|
|
327
|
-
//
|
|
328
|
-
const budget = Math.max(1, maxRows -
|
|
368
|
+
// P4: THREE rows of frame — the band's name, the question and the key
|
|
369
|
+
// row (no header row, no blank, no closing rule)
|
|
370
|
+
const budget = Math.max(1, maxRows - 3);
|
|
329
371
|
if (body.length > budget) {
|
|
330
372
|
const kept = Math.max(0, budget - 1);
|
|
331
373
|
rows.push(...body.slice(0, kept));
|
|
@@ -335,14 +377,12 @@ export function askBlockRows(view, state, W, maxRows) {
|
|
|
335
377
|
rows.push(...body);
|
|
336
378
|
}
|
|
337
379
|
// R6/D2: FITTED, and the two-space indent is inside the budget —
|
|
338
|
-
// the uncut push here is what made a narrow ask throw.
|
|
339
|
-
|
|
340
|
-
//
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
// collides with; this keeps that finding and only changes which rule.
|
|
345
|
-
rows.push(`${p.dim}${"\u2500".repeat(Math.max(0, W))}${p.reset}`);
|
|
380
|
+
// the uncut push here is what made a narrow ask throw. Graphite P4: the
|
|
381
|
+
// counter rides the right margin (§8.2), and the composer's rail closes
|
|
382
|
+
// the band — DECLARED REVERSAL of the block's own bottom rule (R2).
|
|
383
|
+
const count = `${state.cursor + 1}/${customRow(q) + 1}`;
|
|
384
|
+
const keys = askAffordanceFit(state, q, Math.max(1, W - 1 - 2 - count.length - 2));
|
|
385
|
+
rows.push(` ${p.dim}${keys}${" ".repeat(Math.max(2, W - 1 - 2 - visibleWidth(keys) - count.length))}${count}${p.reset}`);
|
|
346
386
|
return rows;
|
|
347
387
|
}
|
|
348
388
|
/**
|
|
@@ -368,8 +408,8 @@ export function askAffordance(state, q) {
|
|
|
368
408
|
const multi = q?.multiSelect === true;
|
|
369
409
|
const back = state.qIndex > 0 ? ["← back"] : [];
|
|
370
410
|
return (multi
|
|
371
|
-
? ["↑↓ move", `space or 1
|
|
372
|
-
: ["↑↓ move", "⏎ confirms", `1
|
|
411
|
+
? ["↑↓ move", `space or 1–${n} marks`, "⏎ sends the set", "t types", ...back, "esc declines"]
|
|
412
|
+
: ["↑↓ move", "⏎ confirms", `1–${n} instant`, "t types", ...back, "esc declines"]).join(" · ");
|
|
373
413
|
}
|
|
374
414
|
/**
|
|
375
415
|
* The affordance at a width, by the DC-2 clause ladder.
|
|
@@ -392,13 +432,13 @@ export function askAffordanceFit(state, q, W) {
|
|
|
392
432
|
const n = q?.options.length ?? 4;
|
|
393
433
|
const multi = q?.multiSelect === true;
|
|
394
434
|
const back = state.qIndex > 0 ? ["← back"] : [];
|
|
395
|
-
const act = multi ? `space or 1
|
|
435
|
+
const act = multi ? `space or 1–${n} marks` : `1–${n} instant`;
|
|
396
436
|
const go = multi ? "⏎ sends the set" : "⏎ confirms";
|
|
397
437
|
const tiers = [
|
|
398
438
|
["↑↓ move", act, go, "t types", ...back, "esc declines"],
|
|
399
439
|
["↑↓ move", act, go, ...back, "esc declines"],
|
|
400
440
|
[act, go, ...back, "esc declines"],
|
|
401
|
-
[multi ? "space marks" : `1
|
|
441
|
+
[multi ? "space marks" : `1–${n} picks`, multi ? "⏎ sends" : "⏎ confirms", "esc declines"],
|
|
402
442
|
[multi ? "⏎ sends" : "⏎ confirms", "esc declines"],
|
|
403
443
|
];
|
|
404
444
|
for (const tier of tiers) {
|
|
@@ -422,16 +462,18 @@ export function askStatus(view, state) {
|
|
|
422
462
|
}
|
|
423
463
|
/** The input row's lead: the digit lead while picking, the typing lead
|
|
424
464
|
* in the custom phase (the rule-input phase's shape, reused). */
|
|
425
|
-
export function askLeadPlain(
|
|
426
|
-
// REL-0152-D3: "1-4> " was hard-coded and wrong twice over
|
|
427
|
-
//
|
|
428
|
-
//
|
|
429
|
-
|
|
465
|
+
export function askLeadPlain(_state) {
|
|
466
|
+
// REL-0152-D3: "1-4> " was hard-coded and wrong twice over. Graphite P4
|
|
467
|
+
// (owner, 2026-10-04): a panel leaves the input row to the composer —
|
|
468
|
+
// `pick>` and `your answer:` retired; an empty answer row carries a dim
|
|
469
|
+
// hint instead (`panelHintOf`).
|
|
470
|
+
return "";
|
|
430
471
|
}
|
|
431
472
|
// ── the dispatchers: the panel slot, with the ask branch folded in ────
|
|
432
|
-
export function panelBlockRows(view, phase, cursor, W, maxRows, ask, pick, note, safer) {
|
|
473
|
+
export function panelBlockRows(view, phase, cursor, W, maxRows, ask, pick, note, safer, height = 24) {
|
|
474
|
+
// Graphite P3: a pick's window is §8.2's, so it reads the terminal's height
|
|
433
475
|
if (view.pick !== undefined && pick !== undefined)
|
|
434
|
-
return pickBlockRows(view, pick, W, maxRows);
|
|
476
|
+
return pickBlockRows(view, pick, W, maxRows, height);
|
|
435
477
|
if (view.ask !== undefined && ask !== undefined)
|
|
436
478
|
return askBlockRows(view, ask, W, maxRows);
|
|
437
479
|
return basePanelBlockRows(view, phase, cursor, W, maxRows, note, safer);
|
|
@@ -459,8 +501,10 @@ export function panelStatus(view, phase, cursor, ask, pick) {
|
|
|
459
501
|
return basePanelStatus(view, phase, cursor);
|
|
460
502
|
}
|
|
461
503
|
export function panelAffordance(view, phase, cursor, ask, pick, safer) {
|
|
462
|
-
if (view.pick !== undefined && pick !== undefined)
|
|
463
|
-
|
|
504
|
+
if (view.pick !== undefined && pick !== undefined) {
|
|
505
|
+
const here = view.pick.options[pickList(view.pick, pick).cursor];
|
|
506
|
+
return pickAffordance(pick, here?.levels !== undefined ? (here.axisLabel ?? true) : false);
|
|
507
|
+
}
|
|
464
508
|
if (view.ask !== undefined && ask !== undefined)
|
|
465
509
|
return askAffordance(ask, view.ask.questions[ask.qIndex]);
|
|
466
510
|
return basePanelAffordance(view, phase, cursor, safer);
|
|
@@ -468,7 +512,7 @@ export function panelAffordance(view, phase, cursor, ask, pick, safer) {
|
|
|
468
512
|
/** The whole panel state in one call — the compositor's four reads share
|
|
469
513
|
* one source, so an ask can never render half as an approval (TUI2-R2
|
|
470
514
|
* ④: nor a pick as either). */
|
|
471
|
-
export const panelRowsOf = (s, W, maxRows) => panelBlockRows(s.view, s.phase, s.cursor, W, maxRows, s.ask, s.pick, s.note, s.safer);
|
|
515
|
+
export const panelRowsOf = (s, W, maxRows, height = 24) => panelBlockRows(s.view, s.phase, s.cursor, W, maxRows, s.ask, s.pick, s.note, s.safer, height);
|
|
472
516
|
/**
|
|
473
517
|
* TUI2-R3v2 ② — the rows AND where the clickable ones are, from ONE
|
|
474
518
|
* call.
|
|
@@ -485,9 +529,9 @@ export const panelRowsOf = (s, W, maxRows) => panelBlockRows(s.view, s.phase, s.
|
|
|
485
529
|
* not). A null here means every click is inert, which is the safe way
|
|
486
530
|
* to not-implement something.
|
|
487
531
|
*/
|
|
488
|
-
export const panelFrameOf = (s, W, maxRows) => {
|
|
532
|
+
export const panelFrameOf = (s, W, maxRows, height = 24) => {
|
|
489
533
|
if (s.view.pick !== undefined || s.view.ask !== undefined)
|
|
490
|
-
return { rows: panelRowsOf(s, W, maxRows), options: null };
|
|
534
|
+
return { rows: panelRowsOf(s, W, maxRows, height), options: null };
|
|
491
535
|
const layout = panelBlockLayout(s.view, s.phase, s.cursor, W, maxRows, s.note, s.safer);
|
|
492
536
|
return {
|
|
493
537
|
rows: layout.rows,
|
|
@@ -495,6 +539,17 @@ export const panelFrameOf = (s, W, maxRows) => {
|
|
|
495
539
|
};
|
|
496
540
|
};
|
|
497
541
|
export const panelLeadOf = (s) => panelLead(s.view, s.phase, s.cursor, s.ask, s.pick);
|
|
542
|
+
/** Graphite P3/P4: what an EMPTY input row says while a panel is up and the
|
|
543
|
+
* composer is where the person types — a pick's filter, the note an
|
|
544
|
+
* approval's amend sends, a typed answer to a question. Null: the row stays
|
|
545
|
+
* empty, as §7.8 has it everywhere else. */
|
|
546
|
+
export const panelHintOf = (s) => {
|
|
547
|
+
if (s.view.pick !== undefined)
|
|
548
|
+
return s.view.pick.input === "filter" ? (s.view.pick.filterHint ?? null) : null;
|
|
549
|
+
if (s.view.ask !== undefined)
|
|
550
|
+
return s.ask?.phase === "custom" ? "your answer" : null;
|
|
551
|
+
return s.phase === "amend" ? "tell kiso what to do instead" : null;
|
|
552
|
+
};
|
|
498
553
|
export const panelStatusOf = (s) => panelStatus(s.view, s.phase, s.cursor, s.ask, s.pick);
|
|
499
554
|
export const panelAffordanceOf = (s) => panelAffordance(s.view, s.phase, s.cursor, s.ask, s.pick, s.safer);
|
|
500
555
|
// ── the view: what the human reads when the model asks ────────────────
|
package/dist/at-picker.d.ts
CHANGED
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
* lexical order of the path (never localeCompare, whose result depends
|
|
13
13
|
* on the machine's locale).
|
|
14
14
|
*/
|
|
15
|
+
import { atEmbed, bandKeyRow, bandVisible, bandWindow, moreMark } from "./components.js";
|
|
16
|
+
export { atEmbed, bandKeyRow, bandVisible, bandWindow, moreMark };
|
|
15
17
|
import { bandHeader } from "@vincemakes/kiso-tui-cells/strings";
|
|
16
18
|
/** The bound source's item — a repo-relative path and nothing else.
|
|
17
19
|
* Structural: the CLI passes whatever it likes as long as it has a
|
|
@@ -54,26 +56,6 @@ export declare const AT_SKIP: ReadonlySet<string>;
|
|
|
54
56
|
/** KC3 §4 — the panel's visible height. A ceiling, not a promise: the
|
|
55
57
|
* compositor clamps further when the terminal is short. */
|
|
56
58
|
export declare const AT_VISIBLE = 5;
|
|
57
|
-
/**
|
|
58
|
-
* The subsequence embedding, TIGHTENED — the two-pass walk every good
|
|
59
|
-
* fuzzy finder uses, and the reason `@ra` emboldens the "ra" of
|
|
60
|
-
* "src/range.js" rather than the r of "src" and the a of "range".
|
|
61
|
-
*
|
|
62
|
-
* Pass 1 walks forward and stops at the EARLIEST index that completes
|
|
63
|
-
* the query — this both answers "does it match at all" and fixes the
|
|
64
|
-
* right-hand edge. Pass 2 walks backward from that edge, taking the
|
|
65
|
-
* LATEST position for each query character in turn, which slides every
|
|
66
|
-
* matched character as far right as it can go without crossing the
|
|
67
|
-
* next one. The result is the most clustered embedding that ends where
|
|
68
|
-
* the earliest match ends.
|
|
69
|
-
*
|
|
70
|
-
* Case-insensitive: both sides are lowercased by the caller once per
|
|
71
|
-
* query rather than once per character.
|
|
72
|
-
*
|
|
73
|
-
* Returns the ascending match indices, or null when the query is not a
|
|
74
|
-
* subsequence of the path at all.
|
|
75
|
-
*/
|
|
76
|
-
export declare function atEmbed(lowerPath: string, lowerQuery: string): number[] | null;
|
|
77
59
|
/** The longest run of CONSECUTIVE indices in an ascending list. An
|
|
78
60
|
* empty query has no run — every path ties on it, and the rank falls
|
|
79
61
|
* through to path length. */
|
|
@@ -103,61 +85,49 @@ export declare function atFilter(items: readonly AtItem[], query: string): {
|
|
|
103
85
|
matches: AtMatch[];
|
|
104
86
|
capped: boolean;
|
|
105
87
|
};
|
|
88
|
+
/** The path split into the two columns the panel draws: the file's own
|
|
89
|
+
* name, and the directory that qualifies it. A path with no slash is
|
|
90
|
+
* all name and no directory. */
|
|
91
|
+
/** The cells a file's NAME takes in its row — the measure the band's name
|
|
92
|
+
* column is laid over (escaped as the row draws it, so the column a
|
|
93
|
+
* wide or control-character name asks for is the one it gets). */
|
|
94
|
+
export declare function atNameWidth(path: string): number;
|
|
106
95
|
/**
|
|
107
|
-
* KC3 §4
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
* stored, so it can never disagree with the selection it is meant to
|
|
111
|
-
* follow.
|
|
112
|
-
*/
|
|
113
|
-
export declare function atWindow(total: number, selected: number, visible?: number): {
|
|
114
|
-
first: number;
|
|
115
|
-
count: number;
|
|
116
|
-
};
|
|
117
|
-
/**
|
|
118
|
-
* KC3 §4 — ONE row of the panel.
|
|
96
|
+
* KC3 §4, Graphite P2 (owner, 2026-10-03) — ONE row of the panel: the
|
|
97
|
+
* file's NAME, padded to the name column, then its FOLDER as a dim column,
|
|
98
|
+
* so the folders line up. The letters the query matched are gold in both.
|
|
119
99
|
*
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
-
*
|
|
100
|
+
* DECLARED REVERSAL of TUI2-R1.5 ⑧ (VD-9) in two parts: the folder no
|
|
101
|
+
* longer trails each name after an em dash — it is a column one gap after
|
|
102
|
+
* the longest name in the list (never the far edge, which was VD-9's
|
|
103
|
+
* complaint), so twenty rows read as a table; and a match inside the
|
|
104
|
+
* folder is drawn, gold, because the person typed it.
|
|
124
105
|
*
|
|
125
|
-
* The
|
|
126
|
-
*
|
|
127
|
-
*
|
|
128
|
-
* The `hit` indices are over the FULL path, so they are shifted by the
|
|
129
|
-
* directory's length to land on the name. A hit that falls INSIDE the
|
|
130
|
-
* directory is simply not drawn bold — the directory column is
|
|
131
|
-
* uniformly dim by design (a bold fragment in a right-aligned dim
|
|
132
|
-
* column reads as damage, not as information).
|
|
133
|
-
*
|
|
134
|
-
* The row never exceeds W: the name cuts first (it is the flexible
|
|
135
|
-
* column), and the directory is dropped entirely before the name is
|
|
136
|
-
* cut to nothing.
|
|
106
|
+
* The name is the flexible column when the row is short: the folder gives
|
|
107
|
+
* way first, then the name is cut with an ellipsis.
|
|
137
108
|
*/
|
|
138
|
-
export declare function atRow(match: AtMatch, selected: boolean, W: number): string;
|
|
139
|
-
/**
|
|
140
|
-
*
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
109
|
+
export declare function atRow(match: AtMatch, selected: boolean, W: number, nameCol?: number, mark?: string | null): string;
|
|
110
|
+
/** The band's state as the editor hands it over. `query`, `total` and
|
|
111
|
+
* `nameCol` (the widest file name in the whole list, so the folder
|
|
112
|
+
* column never moves while the person types) are Graphite P2's; absent,
|
|
113
|
+
* the band still draws. */
|
|
114
|
+
export interface AtPanelState {
|
|
115
|
+
readonly matches: readonly AtMatch[];
|
|
116
|
+
readonly selected: number;
|
|
117
|
+
readonly capped: boolean;
|
|
118
|
+
readonly query?: string;
|
|
119
|
+
readonly total?: number;
|
|
120
|
+
readonly nameCol?: number;
|
|
121
|
+
}
|
|
150
122
|
/**
|
|
151
|
-
* KC3 §4 — the whole band:
|
|
123
|
+
* KC3 §4, Graphite P2 — the whole band: the named hairline with the count
|
|
124
|
+
* (`files · 8`, `2 of 8 match`, and `first 2000 only` when the walk was
|
|
125
|
+
* cut), the windowed rows with their more-marks, and the key row with the
|
|
152
126
|
* counter. Returned as plain strings for the menu-rows channel, which
|
|
153
|
-
* already accounts them in chromeRows — the picker needs no geometry
|
|
154
|
-
*
|
|
127
|
+
* already accounts them in chromeRows — the picker needs no geometry of
|
|
128
|
+
* its own, which is the entire reason it rides that channel.
|
|
155
129
|
*/
|
|
156
|
-
export declare function atPanelRows(state:
|
|
157
|
-
matches: readonly AtMatch[];
|
|
158
|
-
selected: number;
|
|
159
|
-
capped: boolean;
|
|
160
|
-
}, W: number): string[];
|
|
130
|
+
export declare function atPanelRows(state: AtPanelState, W: number, height?: number): string[];
|
|
161
131
|
/**
|
|
162
132
|
* TUI2-R1.5 ⑦(b) — the one row that turns a band into a surface. Shared
|
|
163
133
|
* by the @ picker, the / menu and the session picker so the three read
|