@claude-code-kit/ink-renderer 0.1.0 → 0.2.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Minnzen
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,45 @@
1
+ # @claude-code-kit/ink-renderer
2
+
3
+ Terminal rendering engine for claude-code-kit — React reconciler, Yoga Flexbox layout, keyboard/mouse events, ANSI output.
4
+
5
+ Part of [claude-code-kit](https://github.com/Minnzen/claude-code-kit).
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ pnpm add @claude-code-kit/ink-renderer react
11
+ ```
12
+
13
+ ## Quick Start
14
+
15
+ ```tsx
16
+ import React from 'react'
17
+ import { render, Box, Text } from '@claude-code-kit/ink-renderer'
18
+
19
+ function App() {
20
+ return (
21
+ <Box flexDirection="column" padding={1}>
22
+ <Text bold color="green">Hello from claude-code-kit</Text>
23
+ <Text>Build terminal UIs like React apps.</Text>
24
+ </Box>
25
+ )
26
+ }
27
+
28
+ await render(<App />)
29
+ ```
30
+
31
+ ## Included
32
+
33
+ - Rendering API: `render`, `renderSync`, `createRoot`
34
+ - Primitives: `Box`, `Text`, `Spacer`, `Newline`, `Link`, `Button`, `ScrollBox`
35
+ - Hooks: `useInput`, `useApp`, `useStdin`, `useInterval`, `useAnimationFrame`
36
+ - Terminal helpers: `AlternateScreen`, `RawAnsi`, `Ansi`, `ErrorOverview`
37
+
38
+ ## Docs
39
+
40
+ - Full project docs: [github.com/Minnzen/claude-code-kit](https://github.com/Minnzen/claude-code-kit)
41
+ - Components overview: [docs/components.md](https://github.com/Minnzen/claude-code-kit/blob/main/docs/components.md)
42
+
43
+ ## License
44
+
45
+ MIT
package/dist/index.js CHANGED
@@ -80,9 +80,96 @@ var import_stream = require("stream");
80
80
  // src/ink.tsx
81
81
  var import_auto_bind = __toESM(require("auto-bind"));
82
82
  var import_fs3 = require("fs");
83
- var import_noop2 = __toESM(require("lodash-es/noop"));
84
- var import_throttle = __toESM(require("lodash-es/throttle"));
85
- var import_constants4 = require("react-reconciler/constants");
83
+
84
+ // src/lodash-replacements.ts
85
+ function noop() {
86
+ }
87
+ function throttle(func, wait, options = {}) {
88
+ let timerId;
89
+ let lastCallTime;
90
+ let lastInvokeTime = 0;
91
+ let lastArgs;
92
+ let result;
93
+ const leading = options.leading !== false;
94
+ const trailing = options.trailing !== false;
95
+ function invokeFunc(time) {
96
+ lastInvokeTime = time;
97
+ const args = lastArgs;
98
+ lastArgs = void 0;
99
+ result = func(...args);
100
+ return result;
101
+ }
102
+ function startTimer(pendingFunc, remainingWait2) {
103
+ timerId = setTimeout(pendingFunc, remainingWait2);
104
+ }
105
+ function shouldInvoke(time) {
106
+ const timeSinceLastCall = lastCallTime === void 0 ? wait : time - lastCallTime;
107
+ const timeSinceLastInvoke = time - lastInvokeTime;
108
+ return lastCallTime === void 0 || timeSinceLastCall >= wait || timeSinceLastCall < 0 || timeSinceLastInvoke >= wait;
109
+ }
110
+ function remainingWait(time) {
111
+ const timeSinceLastCall = time - (lastCallTime ?? 0);
112
+ return Math.max(0, wait - timeSinceLastCall);
113
+ }
114
+ function timerExpired() {
115
+ const time = Date.now();
116
+ if (shouldInvoke(time)) {
117
+ trailingEdge(time);
118
+ return;
119
+ }
120
+ startTimer(timerExpired, remainingWait(time));
121
+ }
122
+ function trailingEdge(time) {
123
+ timerId = void 0;
124
+ if (trailing && lastArgs) {
125
+ invokeFunc(time);
126
+ }
127
+ lastArgs = void 0;
128
+ }
129
+ function leadingEdge(time) {
130
+ lastInvokeTime = time;
131
+ startTimer(timerExpired, wait);
132
+ if (leading) {
133
+ invokeFunc(time);
134
+ }
135
+ }
136
+ const throttled = function(...args) {
137
+ const time = Date.now();
138
+ const isInvoking = shouldInvoke(time);
139
+ lastArgs = args;
140
+ lastCallTime = time;
141
+ if (isInvoking) {
142
+ if (timerId === void 0) {
143
+ leadingEdge(time);
144
+ return result;
145
+ }
146
+ }
147
+ if (timerId === void 0) {
148
+ startTimer(timerExpired, wait);
149
+ }
150
+ return result;
151
+ };
152
+ throttled.cancel = function() {
153
+ if (timerId !== void 0) {
154
+ clearTimeout(timerId);
155
+ }
156
+ lastInvokeTime = 0;
157
+ lastArgs = void 0;
158
+ lastCallTime = void 0;
159
+ timerId = void 0;
160
+ };
161
+ throttled.flush = function() {
162
+ if (timerId === void 0) {
163
+ return result;
164
+ }
165
+ trailingEdge(Date.now());
166
+ return result;
167
+ };
168
+ return throttled;
169
+ }
170
+
171
+ // src/ink.tsx
172
+ var import_constants4 = require("react-reconciler/constants.js");
86
173
  var import_signal_exit = require("signal-exit");
87
174
  var import_yoga_layout3 = require("@claude-code-kit/shared/yoga-layout");
88
175
  var import_shared13 = require("@claude-code-kit/shared");
@@ -172,6 +259,22 @@ var colorize = (str, color, type) => {
172
259
  const thirdValue = Number(matches[3]);
173
260
  return type === "foreground" ? import_chalk.default.rgb(firstValue, secondValue, thirdValue)(str) : import_chalk.default.bgRgb(firstValue, secondValue, thirdValue)(str);
174
261
  }
262
+ const PLAIN_ANSI = {
263
+ black: type === "foreground" ? import_chalk.default.black : import_chalk.default.bgBlack,
264
+ red: type === "foreground" ? import_chalk.default.red : import_chalk.default.bgRed,
265
+ green: type === "foreground" ? import_chalk.default.green : import_chalk.default.bgGreen,
266
+ yellow: type === "foreground" ? import_chalk.default.yellow : import_chalk.default.bgYellow,
267
+ blue: type === "foreground" ? import_chalk.default.blue : import_chalk.default.bgBlue,
268
+ magenta: type === "foreground" ? import_chalk.default.magenta : import_chalk.default.bgMagenta,
269
+ cyan: type === "foreground" ? import_chalk.default.cyan : import_chalk.default.bgCyan,
270
+ white: type === "foreground" ? import_chalk.default.white : import_chalk.default.bgWhite,
271
+ gray: type === "foreground" ? import_chalk.default.gray : import_chalk.default.bgBlackBright,
272
+ grey: type === "foreground" ? import_chalk.default.gray : import_chalk.default.bgBlackBright
273
+ };
274
+ const fn = PLAIN_ANSI[color];
275
+ if (fn) {
276
+ return fn(str);
277
+ }
175
278
  return str;
176
279
  };
177
280
  function applyTextStyles(text, styles2) {
@@ -2153,7 +2256,7 @@ function findOwnerChainAtRow(root, y) {
2153
2256
  }
2154
2257
 
2155
2258
  // src/events/dispatcher.ts
2156
- var import_constants = require("react-reconciler/constants");
2259
+ var import_constants = require("react-reconciler/constants.js");
2157
2260
  var import_shared3 = require("@claude-code-kit/shared");
2158
2261
 
2159
2262
  // src/events/event-handlers.ts
@@ -2813,7 +2916,7 @@ var diff = (before, after) => {
2813
2916
  const changed = {};
2814
2917
  let isChanged = false;
2815
2918
  for (const key of Object.keys(before)) {
2816
- const isDeleted = after ? !Object.hasOwn(after, key) : true;
2919
+ const isDeleted = after ? !Object.prototype.hasOwnProperty.call(after, key) : true;
2817
2920
  if (isDeleted) {
2818
2921
  changed[key] = void 0;
2819
2922
  isChanged = true;
@@ -4142,7 +4245,7 @@ function findPlainTextUrlAt(screen, col, row) {
4142
4245
  let url = token.slice(urlStart, urlEnd);
4143
4246
  const OPENER = { ")": "(", "]": "[", "}": "{" };
4144
4247
  while (url.length > 0) {
4145
- const last = url.at(-1);
4248
+ const last = url.charAt(url.length - 1);
4146
4249
  if (".,;:!?".includes(last)) {
4147
4250
  url = url.slice(0, -1);
4148
4251
  continue;
@@ -4506,7 +4609,7 @@ function osc(...parts) {
4506
4609
  }
4507
4610
  function wrapForMultiplexer(sequence) {
4508
4611
  if (process.env["TMUX"]) {
4509
- const escaped = sequence.replaceAll("\x1B", "\x1B\x1B");
4612
+ const escaped = sequence.split("\x1B").join("\x1B\x1B");
4510
4613
  return `\x1BPtmux;${escaped}\x1B\\`;
4511
4614
  }
4512
4615
  if (process.env["STY"]) {
@@ -4515,7 +4618,7 @@ function wrapForMultiplexer(sequence) {
4515
4618
  return sequence;
4516
4619
  }
4517
4620
  function tmuxPassthrough(payload) {
4518
- return `${ESC}Ptmux;${payload.replaceAll(ESC, ESC + ESC)}${ST}`;
4621
+ return `${ESC}Ptmux;${payload.split(ESC).join(ESC + ESC)}${ST}`;
4519
4622
  }
4520
4623
  async function tmuxLoadBuffer(text) {
4521
4624
  if (!process.env["TMUX"]) return false;
@@ -4755,11 +4858,12 @@ function supportsTabStatus() {
4755
4858
  function tabStatus(fields) {
4756
4859
  const parts = [];
4757
4860
  const rgb2 = (c) => c.type === "rgb" ? `#${[c.r, c.g, c.b].map((n) => n.toString(16).padStart(2, "0")).join("")}` : "";
4861
+ const escapeStatusValue = (value) => value.split("\\").join("\\\\").split(";").join("\\;");
4758
4862
  if ("indicator" in fields)
4759
4863
  parts.push(`indicator=${fields.indicator ? rgb2(fields.indicator) : ""}`);
4760
4864
  if ("status" in fields)
4761
4865
  parts.push(
4762
- `status=${fields.status?.replaceAll("\\", "\\\\").replaceAll(";", "\\;") ?? ""}`
4866
+ `status=${fields.status ? escapeStatusValue(fields.status) : ""}`
4763
4867
  );
4764
4868
  if ("statusColor" in fields)
4765
4869
  parts.push(
@@ -6797,12 +6901,13 @@ var Output = class {
6797
6901
  if (operation.fromAbsolute) absoluteClears.push(rect);
6798
6902
  }
6799
6903
  const clips = [];
6904
+ const getCurrentClip = () => clips[clips.length - 1];
6800
6905
  for (const operation of this.operations) {
6801
6906
  switch (operation.type) {
6802
6907
  case "clear":
6803
6908
  continue;
6804
6909
  case "clip":
6805
- clips.push(intersectClip(clips.at(-1), operation.clip));
6910
+ clips.push(intersectClip(getCurrentClip(), operation.clip));
6806
6911
  continue;
6807
6912
  case "unclip":
6808
6913
  clips.pop();
@@ -6815,7 +6920,7 @@ var Output = class {
6815
6920
  width: regionWidth,
6816
6921
  height: regionHeight
6817
6922
  } = operation;
6818
- const clip = clips.at(-1);
6923
+ const clip = getCurrentClip();
6819
6924
  const startX = Math.max(regionX, clip?.x1 ?? 0);
6820
6925
  const startY = Math.max(regionY, clip?.y1 ?? 0);
6821
6926
  const maxY = Math.min(
@@ -6861,7 +6966,7 @@ var Output = class {
6861
6966
  let lines = text.split("\n");
6862
6967
  let swFrom = 0;
6863
6968
  let prevContentEnd = 0;
6864
- const clip = clips.at(-1);
6969
+ const clip = getCurrentClip();
6865
6970
  if (clip) {
6866
6971
  const clipHorizontally = typeof clip?.x1 === "number" && typeof clip?.x2 === "number";
6867
6972
  const clipVertically = typeof clip?.y1 === "number" && typeof clip?.y2 === "number";
@@ -7984,8 +8089,7 @@ function dropSubtreeCache(node) {
7984
8089
  var render_node_to_output_default = renderNodeToOutput;
7985
8090
 
7986
8091
  // src/render-to-screen.ts
7987
- var import_noop = __toESM(require("lodash-es/noop"));
7988
- var import_constants3 = require("react-reconciler/constants");
8092
+ var import_constants3 = require("react-reconciler/constants.js");
7989
8093
  var import_shared11 = require("@claude-code-kit/shared");
7990
8094
  var timing = { reconcile: 0, yoga: 0, paint: 0, scan: 0, calls: 0 };
7991
8095
  function scanPositions(screen, query) {
@@ -8382,7 +8486,7 @@ var Ink = class {
8382
8486
  stylePool: this.stylePool
8383
8487
  });
8384
8488
  const deferredRender = () => queueMicrotask(this.onRender);
8385
- this.scheduleRender = (0, import_throttle.default)(deferredRender, FRAME_INTERVAL_MS, {
8489
+ this.scheduleRender = throttle(deferredRender, FRAME_INTERVAL_MS, {
8386
8490
  leading: true,
8387
8491
  trailing: true
8388
8492
  });
@@ -8428,13 +8532,13 @@ var Ink = class {
8428
8532
  false,
8429
8533
  null,
8430
8534
  "id",
8431
- import_noop2.default,
8535
+ noop,
8432
8536
  // onUncaughtError
8433
- import_noop2.default,
8537
+ noop,
8434
8538
  // onCaughtError
8435
- import_noop2.default,
8539
+ noop,
8436
8540
  // onRecoverableError
8437
- import_noop2.default
8541
+ noop
8438
8542
  // onDefaultTransitionIndicator
8439
8543
  );
8440
8544
  if (false) {
@@ -9225,7 +9329,7 @@ var Ink = class {
9225
9329
  render(node) {
9226
9330
  this.currentNode = node;
9227
9331
  const tree = /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(App, { stdin: this.options.stdin, stdout: this.options.stdout, stderr: this.options.stderr, exitOnCtrlC: this.options.exitOnCtrlC, onExit: this.unmount, terminalColumns: this.terminalColumns, terminalRows: this.terminalRows, selection: this.selection, onSelectionChange: this.notifySelectionChange, onClickAt: this.dispatchClick, onHoverAt: this.dispatchHover, getHyperlinkAt: this.getHyperlinkAt, onOpenHyperlink: this.openHyperlink, onMultiClick: this.handleMultiClick, onSelectionDrag: this.handleSelectionDrag, onStdinResume: this.reassertTerminalModes, onCursorDeclaration: this.setCursorDeclaration, dispatchKeyboardEvent: this.dispatchKeyboardEvent, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(TerminalWriteProvider, { value: this.writeRaw, children: node }) });
9228
- reconciler_default.updateContainerSync(tree, this.container, null, import_noop2.default);
9332
+ reconciler_default.updateContainerSync(tree, this.container, null, noop);
9229
9333
  reconciler_default.flushSyncWork();
9230
9334
  }
9231
9335
  unmount(error) {
@@ -9261,7 +9365,7 @@ var Ink = class {
9261
9365
  clearTimeout(this.drainTimer);
9262
9366
  this.drainTimer = null;
9263
9367
  }
9264
- reconciler_default.updateContainerSync(null, this.container, null, import_noop2.default);
9368
+ reconciler_default.updateContainerSync(null, this.container, null, noop);
9265
9369
  reconciler_default.flushSyncWork();
9266
9370
  instances_default.delete(this.options.stdout);
9267
9371
  this.rootNode.yogaNode?.free();
package/dist/index.mjs CHANGED
@@ -5,9 +5,96 @@ import { Stream } from "stream";
5
5
  // src/ink.tsx
6
6
  import autoBind from "auto-bind";
7
7
  import { closeSync, constants as fsConstants, openSync, readSync, writeSync } from "fs";
8
- import noop2 from "lodash-es/noop";
9
- import throttle from "lodash-es/throttle";
10
- import { ConcurrentRoot } from "react-reconciler/constants";
8
+
9
+ // src/lodash-replacements.ts
10
+ function noop() {
11
+ }
12
+ function throttle(func, wait, options = {}) {
13
+ let timerId;
14
+ let lastCallTime;
15
+ let lastInvokeTime = 0;
16
+ let lastArgs;
17
+ let result;
18
+ const leading = options.leading !== false;
19
+ const trailing = options.trailing !== false;
20
+ function invokeFunc(time) {
21
+ lastInvokeTime = time;
22
+ const args = lastArgs;
23
+ lastArgs = void 0;
24
+ result = func(...args);
25
+ return result;
26
+ }
27
+ function startTimer(pendingFunc, remainingWait2) {
28
+ timerId = setTimeout(pendingFunc, remainingWait2);
29
+ }
30
+ function shouldInvoke(time) {
31
+ const timeSinceLastCall = lastCallTime === void 0 ? wait : time - lastCallTime;
32
+ const timeSinceLastInvoke = time - lastInvokeTime;
33
+ return lastCallTime === void 0 || timeSinceLastCall >= wait || timeSinceLastCall < 0 || timeSinceLastInvoke >= wait;
34
+ }
35
+ function remainingWait(time) {
36
+ const timeSinceLastCall = time - (lastCallTime ?? 0);
37
+ return Math.max(0, wait - timeSinceLastCall);
38
+ }
39
+ function timerExpired() {
40
+ const time = Date.now();
41
+ if (shouldInvoke(time)) {
42
+ trailingEdge(time);
43
+ return;
44
+ }
45
+ startTimer(timerExpired, remainingWait(time));
46
+ }
47
+ function trailingEdge(time) {
48
+ timerId = void 0;
49
+ if (trailing && lastArgs) {
50
+ invokeFunc(time);
51
+ }
52
+ lastArgs = void 0;
53
+ }
54
+ function leadingEdge(time) {
55
+ lastInvokeTime = time;
56
+ startTimer(timerExpired, wait);
57
+ if (leading) {
58
+ invokeFunc(time);
59
+ }
60
+ }
61
+ const throttled = function(...args) {
62
+ const time = Date.now();
63
+ const isInvoking = shouldInvoke(time);
64
+ lastArgs = args;
65
+ lastCallTime = time;
66
+ if (isInvoking) {
67
+ if (timerId === void 0) {
68
+ leadingEdge(time);
69
+ return result;
70
+ }
71
+ }
72
+ if (timerId === void 0) {
73
+ startTimer(timerExpired, wait);
74
+ }
75
+ return result;
76
+ };
77
+ throttled.cancel = function() {
78
+ if (timerId !== void 0) {
79
+ clearTimeout(timerId);
80
+ }
81
+ lastInvokeTime = 0;
82
+ lastArgs = void 0;
83
+ lastCallTime = void 0;
84
+ timerId = void 0;
85
+ };
86
+ throttled.flush = function() {
87
+ if (timerId === void 0) {
88
+ return result;
89
+ }
90
+ trailingEdge(Date.now());
91
+ return result;
92
+ };
93
+ return throttled;
94
+ }
95
+
96
+ // src/ink.tsx
97
+ import { ConcurrentRoot } from "react-reconciler/constants.js";
11
98
  import { onExit } from "signal-exit";
12
99
  import { getYogaCounters as getYogaCounters2 } from "@claude-code-kit/shared/yoga-layout";
13
100
  import { logForDebugging as logForDebugging7 } from "@claude-code-kit/shared";
@@ -97,6 +184,22 @@ var colorize = (str, color, type) => {
97
184
  const thirdValue = Number(matches[3]);
98
185
  return type === "foreground" ? chalk.rgb(firstValue, secondValue, thirdValue)(str) : chalk.bgRgb(firstValue, secondValue, thirdValue)(str);
99
186
  }
187
+ const PLAIN_ANSI = {
188
+ black: type === "foreground" ? chalk.black : chalk.bgBlack,
189
+ red: type === "foreground" ? chalk.red : chalk.bgRed,
190
+ green: type === "foreground" ? chalk.green : chalk.bgGreen,
191
+ yellow: type === "foreground" ? chalk.yellow : chalk.bgYellow,
192
+ blue: type === "foreground" ? chalk.blue : chalk.bgBlue,
193
+ magenta: type === "foreground" ? chalk.magenta : chalk.bgMagenta,
194
+ cyan: type === "foreground" ? chalk.cyan : chalk.bgCyan,
195
+ white: type === "foreground" ? chalk.white : chalk.bgWhite,
196
+ gray: type === "foreground" ? chalk.gray : chalk.bgBlackBright,
197
+ grey: type === "foreground" ? chalk.gray : chalk.bgBlackBright
198
+ };
199
+ const fn = PLAIN_ANSI[color];
200
+ if (fn) {
201
+ return fn(str);
202
+ }
100
203
  return str;
101
204
  };
102
205
  function applyTextStyles(text, styles2) {
@@ -2095,7 +2198,7 @@ import {
2095
2198
  DefaultEventPriority,
2096
2199
  DiscreteEventPriority,
2097
2200
  NoEventPriority
2098
- } from "react-reconciler/constants";
2201
+ } from "react-reconciler/constants.js";
2099
2202
  import { logError } from "@claude-code-kit/shared";
2100
2203
 
2101
2204
  // src/events/event-handlers.ts
@@ -2755,7 +2858,7 @@ var diff = (before, after) => {
2755
2858
  const changed = {};
2756
2859
  let isChanged = false;
2757
2860
  for (const key of Object.keys(before)) {
2758
- const isDeleted = after ? !Object.hasOwn(after, key) : true;
2861
+ const isDeleted = after ? !Object.prototype.hasOwnProperty.call(after, key) : true;
2759
2862
  if (isDeleted) {
2760
2863
  changed[key] = void 0;
2761
2864
  isChanged = true;
@@ -4087,7 +4190,7 @@ function findPlainTextUrlAt(screen, col, row) {
4087
4190
  let url = token.slice(urlStart, urlEnd);
4088
4191
  const OPENER = { ")": "(", "]": "[", "}": "{" };
4089
4192
  while (url.length > 0) {
4090
- const last = url.at(-1);
4193
+ const last = url.charAt(url.length - 1);
4091
4194
  if (".,;:!?".includes(last)) {
4092
4195
  url = url.slice(0, -1);
4093
4196
  continue;
@@ -4451,7 +4554,7 @@ function osc(...parts) {
4451
4554
  }
4452
4555
  function wrapForMultiplexer(sequence) {
4453
4556
  if (process.env["TMUX"]) {
4454
- const escaped = sequence.replaceAll("\x1B", "\x1B\x1B");
4557
+ const escaped = sequence.split("\x1B").join("\x1B\x1B");
4455
4558
  return `\x1BPtmux;${escaped}\x1B\\`;
4456
4559
  }
4457
4560
  if (process.env["STY"]) {
@@ -4460,7 +4563,7 @@ function wrapForMultiplexer(sequence) {
4460
4563
  return sequence;
4461
4564
  }
4462
4565
  function tmuxPassthrough(payload) {
4463
- return `${ESC}Ptmux;${payload.replaceAll(ESC, ESC + ESC)}${ST}`;
4566
+ return `${ESC}Ptmux;${payload.split(ESC).join(ESC + ESC)}${ST}`;
4464
4567
  }
4465
4568
  async function tmuxLoadBuffer(text) {
4466
4569
  if (!process.env["TMUX"]) return false;
@@ -4700,11 +4803,12 @@ function supportsTabStatus() {
4700
4803
  function tabStatus(fields) {
4701
4804
  const parts = [];
4702
4805
  const rgb2 = (c) => c.type === "rgb" ? `#${[c.r, c.g, c.b].map((n) => n.toString(16).padStart(2, "0")).join("")}` : "";
4806
+ const escapeStatusValue = (value) => value.split("\\").join("\\\\").split(";").join("\\;");
4703
4807
  if ("indicator" in fields)
4704
4808
  parts.push(`indicator=${fields.indicator ? rgb2(fields.indicator) : ""}`);
4705
4809
  if ("status" in fields)
4706
4810
  parts.push(
4707
- `status=${fields.status?.replaceAll("\\", "\\\\").replaceAll(";", "\\;") ?? ""}`
4811
+ `status=${fields.status ? escapeStatusValue(fields.status) : ""}`
4708
4812
  );
4709
4813
  if ("statusColor" in fields)
4710
4814
  parts.push(
@@ -6748,12 +6852,13 @@ var Output = class {
6748
6852
  if (operation.fromAbsolute) absoluteClears.push(rect);
6749
6853
  }
6750
6854
  const clips = [];
6855
+ const getCurrentClip = () => clips[clips.length - 1];
6751
6856
  for (const operation of this.operations) {
6752
6857
  switch (operation.type) {
6753
6858
  case "clear":
6754
6859
  continue;
6755
6860
  case "clip":
6756
- clips.push(intersectClip(clips.at(-1), operation.clip));
6861
+ clips.push(intersectClip(getCurrentClip(), operation.clip));
6757
6862
  continue;
6758
6863
  case "unclip":
6759
6864
  clips.pop();
@@ -6766,7 +6871,7 @@ var Output = class {
6766
6871
  width: regionWidth,
6767
6872
  height: regionHeight
6768
6873
  } = operation;
6769
- const clip = clips.at(-1);
6874
+ const clip = getCurrentClip();
6770
6875
  const startX = Math.max(regionX, clip?.x1 ?? 0);
6771
6876
  const startY = Math.max(regionY, clip?.y1 ?? 0);
6772
6877
  const maxY = Math.min(
@@ -6812,7 +6917,7 @@ var Output = class {
6812
6917
  let lines = text.split("\n");
6813
6918
  let swFrom = 0;
6814
6919
  let prevContentEnd = 0;
6815
- const clip = clips.at(-1);
6920
+ const clip = getCurrentClip();
6816
6921
  if (clip) {
6817
6922
  const clipHorizontally = typeof clip?.x1 === "number" && typeof clip?.x2 === "number";
6818
6923
  const clipVertically = typeof clip?.y1 === "number" && typeof clip?.y2 === "number";
@@ -7935,8 +8040,7 @@ function dropSubtreeCache(node) {
7935
8040
  var render_node_to_output_default = renderNodeToOutput;
7936
8041
 
7937
8042
  // src/render-to-screen.ts
7938
- import noop from "lodash-es/noop";
7939
- import { LegacyRoot } from "react-reconciler/constants";
8043
+ import { LegacyRoot } from "react-reconciler/constants.js";
7940
8044
  import { logForDebugging as logForDebugging5 } from "@claude-code-kit/shared";
7941
8045
  var timing = { reconcile: 0, yoga: 0, paint: 0, scan: 0, calls: 0 };
7942
8046
  function scanPositions(screen, query) {
@@ -8379,13 +8483,13 @@ var Ink = class {
8379
8483
  false,
8380
8484
  null,
8381
8485
  "id",
8382
- noop2,
8486
+ noop,
8383
8487
  // onUncaughtError
8384
- noop2,
8488
+ noop,
8385
8489
  // onCaughtError
8386
- noop2,
8490
+ noop,
8387
8491
  // onRecoverableError
8388
- noop2
8492
+ noop
8389
8493
  // onDefaultTransitionIndicator
8390
8494
  );
8391
8495
  if (false) {
@@ -9176,7 +9280,7 @@ var Ink = class {
9176
9280
  render(node) {
9177
9281
  this.currentNode = node;
9178
9282
  const tree = /* @__PURE__ */ jsx7(App, { stdin: this.options.stdin, stdout: this.options.stdout, stderr: this.options.stderr, exitOnCtrlC: this.options.exitOnCtrlC, onExit: this.unmount, terminalColumns: this.terminalColumns, terminalRows: this.terminalRows, selection: this.selection, onSelectionChange: this.notifySelectionChange, onClickAt: this.dispatchClick, onHoverAt: this.dispatchHover, getHyperlinkAt: this.getHyperlinkAt, onOpenHyperlink: this.openHyperlink, onMultiClick: this.handleMultiClick, onSelectionDrag: this.handleSelectionDrag, onStdinResume: this.reassertTerminalModes, onCursorDeclaration: this.setCursorDeclaration, dispatchKeyboardEvent: this.dispatchKeyboardEvent, children: /* @__PURE__ */ jsx7(TerminalWriteProvider, { value: this.writeRaw, children: node }) });
9179
- reconciler_default.updateContainerSync(tree, this.container, null, noop2);
9283
+ reconciler_default.updateContainerSync(tree, this.container, null, noop);
9180
9284
  reconciler_default.flushSyncWork();
9181
9285
  }
9182
9286
  unmount(error) {
@@ -9212,7 +9316,7 @@ var Ink = class {
9212
9316
  clearTimeout(this.drainTimer);
9213
9317
  this.drainTimer = null;
9214
9318
  }
9215
- reconciler_default.updateContainerSync(null, this.container, null, noop2);
9319
+ reconciler_default.updateContainerSync(null, this.container, null, noop);
9216
9320
  reconciler_default.flushSyncWork();
9217
9321
  instances_default.delete(this.options.stdout);
9218
9322
  this.rootNode.yogaNode?.free();
package/package.json CHANGED
@@ -1,13 +1,14 @@
1
1
  {
2
2
  "name": "@claude-code-kit/ink-renderer",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Terminal rendering engine extracted from Claude Code — React reconciler + Yoga layout + TTY output",
5
5
  "license": "MIT",
6
6
  "main": "./dist/index.js",
7
7
  "module": "./dist/index.mjs",
8
8
  "types": "./dist/index.d.ts",
9
9
  "files": [
10
- "dist"
10
+ "dist",
11
+ "README.md"
11
12
  ],
12
13
  "exports": {
13
14
  ".": {
@@ -26,7 +27,6 @@
26
27
  "emoji-regex": "*",
27
28
  "get-east-asian-width": "*",
28
29
  "indent-string": "*",
29
- "lodash-es": "*",
30
30
  "semver": "*",
31
31
  "signal-exit": "*",
32
32
  "stack-utils": "*",
@@ -35,13 +35,14 @@
35
35
  "type-fest": "*",
36
36
  "usehooks-ts": "*",
37
37
  "wrap-ansi": "*",
38
- "@claude-code-kit/shared": "0.1.0"
38
+ "@claude-code-kit/shared": "0.2.0"
39
39
  },
40
40
  "peerDependencies": {
41
41
  "react": ">=18.0.0",
42
42
  "react-reconciler": ">=0.29.0"
43
43
  },
44
44
  "devDependencies": {
45
+ "@types/react-reconciler": "0.33.0",
45
46
  "react": "*",
46
47
  "react-reconciler": "*",
47
48
  "tsup": "*",
@@ -70,6 +71,8 @@
70
71
  "access": "public"
71
72
  },
72
73
  "scripts": {
73
- "build": "tsup"
74
+ "build": "tsup",
75
+ "typecheck": "tsc --noEmit -p tsconfig.json",
76
+ "lint": "biome check src/"
74
77
  }
75
78
  }