@vincemakes/kiso-tui 0.15.0 → 0.15.2

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.
@@ -74,6 +74,11 @@ export interface InputState {
74
74
  readonly cursorCol?: number;
75
75
  }
76
76
  export interface BodyOptions {
77
+ /** REL-0150-D1 test seam: overrides the TERM_PROGRAM detection for
78
+ * the conservative frame mode — process.env is SHARED across
79
+ * concurrently-running test files in one worker, so a test that
80
+ * mutated the env would bleed 40ms frames into its neighbors. */
81
+ readonly termProgram?: string;
77
82
  /** Is the cell renderer live? A color TTY with a real size — checked
78
83
  * per mutation (the TIOCSWINSZ can land after main constructs us). */
79
84
  active: () => boolean;
@@ -153,9 +153,24 @@ export class Body {
153
153
  // box top), the live caps shrink by their rows, and the status
154
154
  // row's right hint shows the count while any turn waits.
155
155
  #queueState = () => [];
156
+ /** REL-0150-D1 — the conservative frame mode. Terminal.app does not
157
+ * support DEC 2026 synchronized output (half-frames paint on its own
158
+ * schedule — the reviewer dogfood watched the tearing live) and its
159
+ * renderer is throughput-weak (typed input lagged seconds behind the
160
+ * stream). Where TERM_PROGRAM says Apple_Terminal: the frame wraps
161
+ * in ?25l/?25h (cursor hidden during the repaint — the classic
162
+ * anti-tearing degrade; every frame re-shows it) instead of the dead
163
+ * 2026 bytes, and the coalesce window widens 16→40ms (fewer, bigger
164
+ * frames: fewer tear opportunities, less renderer pressure). Every
165
+ * other terminal keeps today's bytes exactly. Heuristic on purpose —
166
+ * a DECRQM round-trip would race the editor for stdin at boot. */
167
+ #conservative;
168
+ #frameMs;
156
169
  constructor(opts) {
157
170
  this.#opts = opts;
158
171
  this.#write = opts.write ?? ((s) => process.stdout.write(s));
172
+ this.#conservative = (opts.termProgram ?? process.env.TERM_PROGRAM) === "Apple_Terminal";
173
+ this.#frameMs = this.#conservative ? 40 : FRAME_MS;
159
174
  this.#active = opts.active();
160
175
  // v6: the single writer — the compositor IS the dock; the CLI's
161
176
  // onDock callback (which used to re-pin the dock after a scroll)
@@ -938,7 +953,7 @@ export class Body {
938
953
  this.#dirty = false;
939
954
  this.render();
940
955
  }
941
- }, FRAME_MS);
956
+ }, this.#frameMs);
942
957
  this.#frameTimer.unref();
943
958
  }
944
959
  /** The spinner: a ONE-SHOT re-armed ONLY while a running tool exists —
@@ -1165,7 +1180,7 @@ export class Body {
1165
1180
  panelSpan === null ? null : { top: liveTop + panelSpan.offset, count: panelSpan.count, first: panelSpan.first };
1166
1181
  // 5. the frame bytes.
1167
1182
  const out = [];
1168
- out.push("\x1b[?2026h"); // synchronized output ON (DEC 2026)
1183
+ out.push(this.#conservative ? "\x1b[?25l" : "\x1b[?2026h"); // D1: sync ON, or cursor-hide where 2026 is dead bytes
1169
1184
  // A8: the bottom-anchored window (the model's last H rows) shifts
1170
1185
  // DOWN when the live region SHRINKS — the done-fold, the fold-hold
1171
1186
  // release at the terminal event. The steady path's scroll syncs
@@ -1186,7 +1201,7 @@ export class Body {
1186
1201
  else {
1187
1202
  this.#drawSteady(out, W, H, liveTop, liveLines, queueRows, menuRows, editor);
1188
1203
  }
1189
- out.push("\x1b[?2026l");
1204
+ out.push(this.#conservative ? "\x1b[?25h" : "\x1b[?2026l");
1190
1205
  this.#write(out.join(""));
1191
1206
  this.#lastLiveTop = liveTop;
1192
1207
  this.#lastLiveRows = liveRowsTotal;
package/dist/editor.d.ts CHANGED
@@ -60,6 +60,9 @@ export declare class Editor {
60
60
  onSigint(cb: () => void): void;
61
61
  onEot(cb: () => void): void;
62
62
  onEscape(cb: () => void): void;
63
+ /** R3a — Shift+Tab: the approval-tier cycle. The MEANING lives in the
64
+ * CLI (which tier follows which); the editor only reports the key. */
65
+ onModeCycle(cb: () => void): void;
63
66
  onExpand(cb: () => void): void;
64
67
  /** KC2 §2: the redirect chain — the gesture hands the buffer's text
65
68
  * over while the run is told to stop. Mirrors onEscape (a list, so
@@ -153,4 +156,10 @@ export declare class Editor {
153
156
  count: number;
154
157
  first?: number;
155
158
  } | null) | null): void;
159
+ /** R3a — cross-session input history: seed the recall buffer and
160
+ * register the append sink. The cap and the adjacent-duplicate
161
+ * collapse are unchanged; the seed takes the TAIL of what the CLI
162
+ * loaded. Never persists question answers (#remember's callers
163
+ * already exclude them). */
164
+ bindHistory(seed: readonly string[], persist: (line: string) => void): void;
156
165
  }
package/dist/editor.js CHANGED
@@ -55,6 +55,9 @@ export const MENU_ITEMS = [
55
55
  { name: "/mode", desc: "switch the approval tier (manual/default/accept-edits/plan/bypass)" },
56
56
  { name: "/model", desc: "list model profiles; switch with /model <name|provider/model>" },
57
57
  { name: "/compact", desc: "summarize the older conversation to free context" },
58
+ // the /resume+/clear mini-spec: the session-navigation pair
59
+ { name: "/clear", desc: "start a fresh conversation (the old session stays resumable)" },
60
+ { name: "/resume", desc: "switch to another session; /resume <id> goes directly" },
58
61
  { name: "/think", desc: "show the last full thinking block" },
59
62
  { name: "/last", desc: "show the most recent tool call's input and output" },
60
63
  { name: "/status", desc: "show session id, event count, and context estimate" },
@@ -223,6 +226,12 @@ export class Editor {
223
226
  onEscape(cb) {
224
227
  this.#escapeCbs.push(cb);
225
228
  }
229
+ #onModeCycle = null;
230
+ /** R3a — Shift+Tab: the approval-tier cycle. The MEANING lives in the
231
+ * CLI (which tier follows which); the editor only reports the key. */
232
+ onModeCycle(cb) {
233
+ this.#onModeCycle = cb;
234
+ }
226
235
  onExpand(cb) {
227
236
  this.#expandCbs.push(cb);
228
237
  }
@@ -1144,6 +1153,14 @@ export class Editor {
1144
1153
  this.#mouseEvent(params, final === "M");
1145
1154
  return;
1146
1155
  }
1156
+ // R3a — Shift+Tab (CSI Z, the universal back-tab encoding) cycles
1157
+ // the approval tier. Composer-idle ONLY: a panel, picker, menu,
1158
+ // history browse or question owns its keys first (the W21 gate),
1159
+ // and a mid-word back-tab has no meaning the composer would miss.
1160
+ if (final === "Z" && params === "" && this.#composerIdle()) {
1161
+ this.#onModeCycle?.();
1162
+ return;
1163
+ }
1147
1164
  // KC1 §4 — Shift+Enter WHERE THE TERMINAL ENCODES IT: kitty's
1148
1165
  // CSI-u (ESC [ 13;2 u) and xterm's modifyOtherKeys (ESC [ 27;2;13 ~).
1149
1166
  // Never claimed universal — Ctrl+J is the everywhere baseline; a
@@ -1736,11 +1753,25 @@ export class Editor {
1736
1753
  * answers, never empties; adjacent duplicates collapse, the tail
1737
1754
  * caps at 100. A redirect is a turn, so it is remembered too. */
1738
1755
  #remember(line) {
1739
- if (this.#history[this.#history.length - 1] !== line)
1756
+ if (this.#history[this.#history.length - 1] !== line) {
1740
1757
  this.#history.push(line);
1758
+ // R3a: the persistence seam — the CLI owns the file (the tui
1759
+ // stays I/O-free); adjacent-duplicate collapse already applied
1760
+ this.#persistHistory?.(line);
1761
+ }
1741
1762
  if (this.#history.length > 100)
1742
1763
  this.#history.shift();
1743
1764
  }
1765
+ #persistHistory = null;
1766
+ /** R3a — cross-session input history: seed the recall buffer and
1767
+ * register the append sink. The cap and the adjacent-duplicate
1768
+ * collapse are unchanged; the seed takes the TAIL of what the CLI
1769
+ * loaded. Never persists question answers (#remember's callers
1770
+ * already exclude them). */
1771
+ bindHistory(seed, persist) {
1772
+ this.#history = seed.slice(-100).filter((l) => l !== "");
1773
+ this.#persistHistory = persist;
1774
+ }
1744
1775
  /** KC2 §2 — the NORMAL composer state: the redirect gesture is live
1745
1776
  * ONLY here. The approval panel, the slash menu, the history browse
1746
1777
  * and the queue-pop walk each OWN their keys first (the W21 "the
package/dist/status.d.ts CHANGED
@@ -52,6 +52,12 @@ export declare function runningStatus(glyph: string, since: number, outTokens: n
52
52
  */
53
53
  export interface StatusMeter {
54
54
  readonly cacheHitPct: number | null;
55
+ /** RETIRED from the row (the owner's 2026-08-23 directive): live
56
+ * prices fluctuate and the canonical table is "an approximation,
57
+ * not a bill" — a four-decimal figure on the status bar claimed a
58
+ * precision the data never had. The canonical cost STAYS recorded
59
+ * (trace ledger, /context); the field is kept so callers need not
60
+ * change shape, and it renders NOTHING. */
55
61
  readonly costUsd: number | null;
56
62
  }
57
63
  /** The IDLE row: the approval tier as the CALLER names it, the /mode
package/dist/status.js CHANGED
@@ -55,8 +55,7 @@ export function idleStatus(tier, model, ctxRatio, meter) {
55
55
  const parts = [`▸ ${tier}`, "/mode to switch", model];
56
56
  if (meter?.cacheHitPct != null)
57
57
  parts.push(`CH ${Math.round(meter.cacheHitPct)}%`);
58
- if (meter?.costUsd != null)
59
- parts.push(`$${meter.costUsd.toFixed(4)}`);
58
+ // costUsd deliberately NOT rendered — see StatusMeter.costUsd.
60
59
  parts.push(`ctx left ~${ctxLeft(ctxRatio)}%`);
61
60
  return parts.join(" · ");
62
61
  }
package/package.json CHANGED
@@ -1,40 +1,40 @@
1
1
  {
2
- "name": "@vincemakes/kiso-tui",
3
- "version": "0.15.0",
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
- "type": "module",
6
- "license": "MIT",
7
- "exports": {
8
- ".": {
9
- "types": "./dist/index.d.ts",
10
- "default": "./dist/index.js"
11
- }
12
- },
13
- "files": [
14
- "dist",
15
- "README.md",
16
- "LICENSE"
17
- ],
18
- "scripts": {
19
- "build": "tsc -p tsconfig.build.json",
20
- "typecheck": "tsc -p tsconfig.json",
21
- "test": "vitest run"
22
- },
23
- "devDependencies": {
24
- "@types/node": "^26.1.2",
25
- "typescript": "^5.7.2",
26
- "vitest": "^3.0.0"
27
- },
28
- "repository": {
29
- "type": "git",
30
- "url": "https://github.com/vincemakes/kiso.git",
31
- "directory": "packages/tui"
32
- },
33
- "bugs": {
34
- "url": "https://github.com/vincemakes/kiso/issues"
35
- },
36
- "homepage": "https://github.com/vincemakes/kiso/tree/main/packages/tui#readme",
37
- "dependencies": {
38
- "@vincemakes/kiso-tui-cells": "0.15.0"
39
- }
2
+ "name": "@vincemakes/kiso-tui",
3
+ "version": "0.15.2",
4
+ "description": "kiso tui \u2014 the pure terminal layer (cell renderer, dock, raw editor, diff, palette). Zero runtime dependencies: input is data, output is bytes.",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/index.d.ts",
10
+ "default": "./dist/index.js"
11
+ }
12
+ },
13
+ "files": [
14
+ "dist",
15
+ "README.md",
16
+ "LICENSE"
17
+ ],
18
+ "scripts": {
19
+ "build": "tsc -p tsconfig.build.json",
20
+ "typecheck": "tsc -p tsconfig.json",
21
+ "test": "vitest run"
22
+ },
23
+ "devDependencies": {
24
+ "@types/node": "^26.1.2",
25
+ "typescript": "^5.7.2",
26
+ "vitest": "^3.0.0"
27
+ },
28
+ "repository": {
29
+ "type": "git",
30
+ "url": "https://github.com/vincemakes/kiso.git",
31
+ "directory": "packages/tui"
32
+ },
33
+ "bugs": {
34
+ "url": "https://github.com/vincemakes/kiso/issues"
35
+ },
36
+ "homepage": "https://github.com/vincemakes/kiso/tree/main/packages/tui#readme",
37
+ "dependencies": {
38
+ "@vincemakes/kiso-tui-cells": "0.15.2"
39
+ }
40
40
  }