@rezi-ui/node 0.1.0-alpha.4 → 0.1.0-alpha.43

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.
Files changed (78) hide show
  1. package/README.md +26 -6
  2. package/dist/backend/emojiWidthPolicy.d.ts +15 -0
  3. package/dist/backend/emojiWidthPolicy.d.ts.map +1 -0
  4. package/dist/backend/emojiWidthPolicy.js +229 -0
  5. package/dist/backend/emojiWidthPolicy.js.map +1 -0
  6. package/dist/backend/nodeBackend.d.ts +25 -2
  7. package/dist/backend/nodeBackend.d.ts.map +1 -1
  8. package/dist/backend/nodeBackend.js +175 -16
  9. package/dist/backend/nodeBackend.js.map +1 -1
  10. package/dist/backend/nodeBackendInline.d.ts +1 -0
  11. package/dist/backend/nodeBackendInline.d.ts.map +1 -1
  12. package/dist/backend/nodeBackendInline.js +195 -27
  13. package/dist/backend/nodeBackendInline.js.map +1 -1
  14. package/dist/backend/terminalProfile.d.ts +5 -0
  15. package/dist/backend/terminalProfile.d.ts.map +1 -0
  16. package/dist/backend/terminalProfile.js +117 -0
  17. package/dist/backend/terminalProfile.js.map +1 -0
  18. package/dist/dev/hotStateReload.d.ts +65 -0
  19. package/dist/dev/hotStateReload.d.ts.map +1 -0
  20. package/dist/dev/hotStateReload.js +438 -0
  21. package/dist/dev/hotStateReload.js.map +1 -0
  22. package/dist/dev/nodeAppHotReload.d.ts +11 -0
  23. package/dist/dev/nodeAppHotReload.d.ts.map +1 -0
  24. package/dist/dev/nodeAppHotReload.js +78 -0
  25. package/dist/dev/nodeAppHotReload.js.map +1 -0
  26. package/dist/image.d.ts +4 -0
  27. package/dist/image.d.ts.map +1 -0
  28. package/dist/image.js +43 -0
  29. package/dist/image.js.map +1 -0
  30. package/dist/index.d.ts +44 -0
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.js +188 -0
  33. package/dist/index.js.map +1 -1
  34. package/dist/repro/index.d.ts +3 -0
  35. package/dist/repro/index.d.ts.map +1 -0
  36. package/dist/repro/index.js +2 -0
  37. package/dist/repro/index.js.map +1 -0
  38. package/dist/repro/recorder.d.ts +30 -0
  39. package/dist/repro/recorder.d.ts.map +1 -0
  40. package/dist/repro/recorder.js +321 -0
  41. package/dist/repro/recorder.js.map +1 -0
  42. package/dist/streams/tail.d.ts +6 -0
  43. package/dist/streams/tail.d.ts.map +1 -0
  44. package/dist/streams/tail.js +113 -0
  45. package/dist/streams/tail.js.map +1 -0
  46. package/dist/worker/engineWorker.js +35 -13
  47. package/dist/worker/engineWorker.js.map +1 -1
  48. package/dist/worker/protocol.d.ts +3 -0
  49. package/dist/worker/protocol.d.ts.map +1 -1
  50. package/dist/worker/testShims/invalidPollBytesNative.d.ts +22 -0
  51. package/dist/worker/testShims/invalidPollBytesNative.d.ts.map +1 -0
  52. package/dist/worker/testShims/invalidPollBytesNative.js +65 -0
  53. package/dist/worker/testShims/invalidPollBytesNative.js.map +1 -0
  54. package/dist/worker/testShims/limitsExpectNative.d.ts +22 -0
  55. package/dist/worker/testShims/limitsExpectNative.d.ts.map +1 -0
  56. package/dist/worker/testShims/limitsExpectNative.js +85 -0
  57. package/dist/worker/testShims/limitsExpectNative.js.map +1 -0
  58. package/dist/worker/testShims/limitsNative.d.ts +22 -0
  59. package/dist/worker/testShims/limitsNative.d.ts.map +1 -0
  60. package/dist/worker/testShims/limitsNative.js +90 -0
  61. package/dist/worker/testShims/limitsNative.js.map +1 -0
  62. package/dist/worker/tickTiming.d.ts +7 -0
  63. package/dist/worker/tickTiming.d.ts.map +1 -0
  64. package/dist/worker/tickTiming.js +26 -0
  65. package/dist/worker/tickTiming.js.map +1 -0
  66. package/package.json +12 -8
  67. package/dist/__e2e__/fixtures/terminal-app.d.ts +0 -2
  68. package/dist/__e2e__/fixtures/terminal-app.d.ts.map +0 -1
  69. package/dist/__e2e__/fixtures/terminal-app.js +0 -42
  70. package/dist/__e2e__/fixtures/terminal-app.js.map +0 -1
  71. package/dist/__e2e__/terminal-render.e2e.test.d.ts +0 -2
  72. package/dist/__e2e__/terminal-render.e2e.test.d.ts.map +0 -1
  73. package/dist/__e2e__/terminal-render.e2e.test.js +0 -125
  74. package/dist/__e2e__/terminal-render.e2e.test.js.map +0 -1
  75. package/dist/__tests__/worker_integration.test.d.ts +0 -2
  76. package/dist/__tests__/worker_integration.test.d.ts.map +0 -1
  77. package/dist/__tests__/worker_integration.test.js +0 -569
  78. package/dist/__tests__/worker_integration.test.js.map +0 -1
package/README.md CHANGED
@@ -1,17 +1,37 @@
1
1
  # @rezi-ui/node
2
2
 
3
- Node.js backend for Rezi. This package owns:
3
+ Node.js/Bun backend for Rezi. This package owns:
4
4
 
5
- - worker-thread engine ownership (native engine is never called on the main thread)
5
+ - configurable native engine execution mode (`auto` | `worker` | `inline`)
6
6
  - frame scheduling and buffer pooling
7
7
  - transfer of drawlists/events between core and the native addon
8
8
 
9
- Typical usage:
9
+ Recommended usage:
10
10
 
11
11
  ```ts
12
- import { createApp, ui } from "@rezi-ui/core";
13
- import { createNodeBackend } from "@rezi-ui/node";
12
+ import { createNodeApp } from "@rezi-ui/node";
14
13
  ```
15
14
 
16
- Docs: `https://rtlzeromemory.github.io/Rezi/`
15
+ Use `createNodeApp({ initialState, config })` as the default path. It wires
16
+ `@rezi-ui/core` and `@rezi-ui/node` with matched cursor protocol, event caps,
17
+ and fps settings. `executionMode` defaults to `auto` (`fpsCap <= 30` -> inline,
18
+ otherwise worker); set `executionMode: "worker"` or `"inline"` to force a mode.
17
19
 
20
+ For development-time hot swapping, pass `hotReload` to `createNodeApp(...)`:
21
+ - `viewModule` for widget-view apps
22
+ - `routesModule` for route-managed apps
23
+
24
+ `app.hotReload` exposes the controller for optional manual `reloadNow()` calls.
25
+
26
+ `createNodeBackend()` is available when you need direct access to a backend
27
+ instance (benchmarks/custom runners). Most apps should use `createNodeApp()`.
28
+
29
+ Install:
30
+
31
+ ```bash
32
+ npm i @rezi-ui/node
33
+ # or
34
+ bun add @rezi-ui/node
35
+ ```
36
+
37
+ Docs: `https://rezitui.dev/docs`
@@ -0,0 +1,15 @@
1
+ export type BackendEmojiWidthPolicy = "auto" | "wide" | "narrow";
2
+ export type ResolvedEmojiWidthPolicy = "wide" | "narrow";
3
+ /**
4
+ * Resolve backend emoji width policy and align core/native width models.
5
+ *
6
+ * Resolution order:
7
+ * 1) explicit `requested` ("wide"/"narrow")
8
+ * 2) explicit native override (`nativeConfig.widthPolicy|width_policy`)
9
+ * 3) env override (`ZRUI_EMOJI_WIDTH_POLICY`)
10
+ * 4) optional probe (CPR-based) when `ZRUI_EMOJI_WIDTH_PROBE=1`
11
+ * 5) deterministic default ("wide")
12
+ */
13
+ export declare function resolveBackendEmojiWidthPolicy(requested: BackendEmojiWidthPolicy | undefined, nativeConfig: Readonly<Record<string, unknown>>): Promise<ResolvedEmojiWidthPolicy>;
14
+ export declare function applyEmojiWidthPolicy(policy: ResolvedEmojiWidthPolicy): 0 | 1;
15
+ //# sourceMappingURL=emojiWidthPolicy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"emojiWidthPolicy.d.ts","sourceRoot":"","sources":["../../src/backend/emojiWidthPolicy.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,uBAAuB,GAAG,MAAM,GAAG,MAAM,GAAG,QAAQ,CAAC;AACjE,MAAM,MAAM,wBAAwB,GAAG,MAAM,GAAG,QAAQ,CAAC;AAoMzD;;;;;;;;;GASG;AACH,wBAAsB,8BAA8B,CAClD,SAAS,EAAE,uBAAuB,GAAG,SAAS,EAC9C,YAAY,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAC9C,OAAO,CAAC,wBAAwB,CAAC,CA8BnC;AAED,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,wBAAwB,GAAG,CAAC,GAAG,CAAC,CAG7E"}
@@ -0,0 +1,229 @@
1
+ import { ZrUiError, setTextMeasureEmojiPolicy } from "@rezi-ui/core";
2
+ const NATIVE_WIDTH_POLICY_NARROW = 0;
3
+ const NATIVE_WIDTH_POLICY_WIDE = 1;
4
+ const PROBE_TIMEOUT_MS_DEFAULT = 80;
5
+ const PROBE_GLYPHS = Object.freeze(["😀", "🚀", "🧪"]);
6
+ const ENV_EMOJI_WIDTH_POLICY = "ZRUI_EMOJI_WIDTH_POLICY";
7
+ const ENV_EMOJI_WIDTH_PROBE = "ZRUI_EMOJI_WIDTH_PROBE";
8
+ let cachedProbePolicy = null;
9
+ let cachedProbePromise = null;
10
+ function normalizePolicy(raw) {
11
+ if (typeof raw !== "string")
12
+ return null;
13
+ const value = raw.trim().toLowerCase();
14
+ if (value === "auto")
15
+ return "auto";
16
+ if (value === "wide")
17
+ return "wide";
18
+ if (value === "narrow")
19
+ return "narrow";
20
+ return null;
21
+ }
22
+ function nativeWidthPolicyToResolved(value) {
23
+ return value === NATIVE_WIDTH_POLICY_NARROW ? "narrow" : "wide";
24
+ }
25
+ function resolvedToNativeWidthPolicy(value) {
26
+ return value === "narrow" ? NATIVE_WIDTH_POLICY_NARROW : NATIVE_WIDTH_POLICY_WIDE;
27
+ }
28
+ function readNativeWidthPolicyValues(cfg) {
29
+ const parse = (value, key) => {
30
+ if (value === undefined)
31
+ return null;
32
+ if (typeof value !== "number" || !Number.isInteger(value) || (value !== 0 && value !== 1)) {
33
+ throw new ZrUiError("ZRUI_INVALID_PROPS", `createNodeBackend config mismatch: nativeConfig.${key} must be 0 (narrow) or 1 (wide).`);
34
+ }
35
+ return value;
36
+ };
37
+ const record = cfg;
38
+ return {
39
+ camel: parse(record.widthPolicy, "widthPolicy"),
40
+ snake: parse(record.width_policy, "width_policy"),
41
+ };
42
+ }
43
+ function readNativeWidthPolicyOverride(cfg) {
44
+ const values = readNativeWidthPolicyValues(cfg);
45
+ if (values.camel !== null && values.snake !== null && values.camel !== values.snake) {
46
+ throw new ZrUiError("ZRUI_INVALID_PROPS", `createNodeBackend config mismatch: nativeConfig.widthPolicy=${String(values.camel)} must match nativeConfig.width_policy=${String(values.snake)}.`);
47
+ }
48
+ const nativeValue = values.camel ?? values.snake;
49
+ if (nativeValue === null)
50
+ return null;
51
+ return nativeWidthPolicyToResolved(nativeValue);
52
+ }
53
+ function pushParsedCpr(buffer, out) {
54
+ let pending = buffer;
55
+ while (pending.length > 0) {
56
+ const esc = pending.indexOf("\x1b[");
57
+ if (esc < 0) {
58
+ if (pending.length > 128)
59
+ pending = pending.slice(-128);
60
+ break;
61
+ }
62
+ if (esc > 0) {
63
+ pending = pending.slice(esc);
64
+ }
65
+ const end = pending.indexOf("R", 2);
66
+ if (end < 0)
67
+ break;
68
+ const seq = pending.slice(0, end + 1);
69
+ pending = pending.slice(end + 1);
70
+ const body = seq.slice(2, seq.length - 1);
71
+ const sep = body.indexOf(";");
72
+ if (sep <= 0 || sep >= body.length - 1)
73
+ continue;
74
+ const rowText = body.slice(0, sep);
75
+ const colText = body.slice(sep + 1);
76
+ if (!/^\d+$/.test(rowText) || !/^\d+$/.test(colText))
77
+ continue;
78
+ const row = Number.parseInt(rowText, 10);
79
+ const col = Number.parseInt(colText, 10);
80
+ if (Number.isFinite(row) && Number.isFinite(col)) {
81
+ out.push({ row, col });
82
+ }
83
+ }
84
+ return pending;
85
+ }
86
+ async function probeGlyphWidthViaCpr(glyph, timeoutMs) {
87
+ const stdin = process.stdin;
88
+ const stdout = process.stdout;
89
+ if (!stdin.isTTY || !stdout.isTTY)
90
+ return null;
91
+ if (typeof stdin.setRawMode !== "function")
92
+ return null;
93
+ const wasRaw = stdin.isRaw === true;
94
+ let pending = "";
95
+ const cprs = [];
96
+ const onData = (chunk) => {
97
+ const text = typeof chunk === "string" ? chunk : chunk.toString("utf8");
98
+ pending = pushParsedCpr(pending + text, cprs);
99
+ };
100
+ let timeout = null;
101
+ try {
102
+ stdin.on("data", onData);
103
+ stdin.resume();
104
+ if (!wasRaw)
105
+ stdin.setRawMode(true);
106
+ // Save cursor, move to a stable column, query CPR, print glyph, query CPR again, restore cursor.
107
+ await new Promise((resolve, reject) => {
108
+ stdout.write(`\x1b[s\x1b[999;1H\x1b[6n${glyph}\x1b[6n\x1b[u`, (err) => {
109
+ if (err)
110
+ reject(err);
111
+ else
112
+ resolve();
113
+ });
114
+ });
115
+ await new Promise((resolve) => {
116
+ const done = () => {
117
+ if (timeout)
118
+ clearTimeout(timeout);
119
+ timeout = null;
120
+ resolve();
121
+ };
122
+ timeout = setTimeout(done, timeoutMs);
123
+ const poll = () => {
124
+ if (cprs.length >= 2) {
125
+ done();
126
+ return;
127
+ }
128
+ setTimeout(poll, 2);
129
+ };
130
+ poll();
131
+ });
132
+ }
133
+ catch {
134
+ return null;
135
+ }
136
+ finally {
137
+ if (timeout)
138
+ clearTimeout(timeout);
139
+ stdin.off("data", onData);
140
+ if (!wasRaw) {
141
+ try {
142
+ stdin.setRawMode(false);
143
+ }
144
+ catch {
145
+ // no-op
146
+ }
147
+ }
148
+ }
149
+ const a = cprs[0];
150
+ const b = cprs[1];
151
+ if (!a || !b)
152
+ return null;
153
+ if (b.row !== a.row)
154
+ return null;
155
+ const delta = b.col - a.col;
156
+ if (delta === 1 || delta === 2)
157
+ return delta;
158
+ return null;
159
+ }
160
+ async function probeTerminalEmojiWidthPolicy(timeoutMs) {
161
+ const widths = [];
162
+ for (const glyph of PROBE_GLYPHS) {
163
+ const width = await probeGlyphWidthViaCpr(glyph, timeoutMs);
164
+ if (width !== null)
165
+ widths.push(width);
166
+ }
167
+ if (widths.length === 0)
168
+ return null;
169
+ if (widths.includes(1))
170
+ return "narrow";
171
+ return "wide";
172
+ }
173
+ async function probeTerminalEmojiWidthPolicyCached(timeoutMs) {
174
+ if (cachedProbePolicy !== null)
175
+ return cachedProbePolicy;
176
+ if (cachedProbePromise)
177
+ return cachedProbePromise;
178
+ cachedProbePromise = probeTerminalEmojiWidthPolicy(timeoutMs)
179
+ .then((probed) => {
180
+ if (probed !== null)
181
+ cachedProbePolicy = probed;
182
+ return probed;
183
+ })
184
+ .finally(() => {
185
+ cachedProbePromise = null;
186
+ });
187
+ return cachedProbePromise;
188
+ }
189
+ /**
190
+ * Resolve backend emoji width policy and align core/native width models.
191
+ *
192
+ * Resolution order:
193
+ * 1) explicit `requested` ("wide"/"narrow")
194
+ * 2) explicit native override (`nativeConfig.widthPolicy|width_policy`)
195
+ * 3) env override (`ZRUI_EMOJI_WIDTH_POLICY`)
196
+ * 4) optional probe (CPR-based) when `ZRUI_EMOJI_WIDTH_PROBE=1`
197
+ * 5) deterministic default ("wide")
198
+ */
199
+ export async function resolveBackendEmojiWidthPolicy(requested, nativeConfig) {
200
+ const requestedPolicy = requested ?? "auto";
201
+ const nativeOverride = readNativeWidthPolicyOverride(nativeConfig);
202
+ if (requestedPolicy === "narrow" || requestedPolicy === "wide") {
203
+ if (nativeOverride !== null && nativeOverride !== requestedPolicy) {
204
+ throw new ZrUiError("ZRUI_INVALID_PROPS", `createNodeBackend config mismatch: emojiWidthPolicy=${requestedPolicy} must match nativeConfig.widthPolicy/width_policy=${nativeOverride === "narrow" ? 0 : 1}.`);
205
+ }
206
+ return requestedPolicy;
207
+ }
208
+ if (nativeOverride !== null)
209
+ return nativeOverride;
210
+ const envOverride = normalizePolicy(process.env[ENV_EMOJI_WIDTH_POLICY]);
211
+ if (envOverride === "wide" || envOverride === "narrow")
212
+ return envOverride;
213
+ /*
214
+ CPR probing is opt-in because it temporarily consumes stdin bytes while
215
+ collecting CPR responses, which can race startup-time key streams.
216
+ */
217
+ const probeEnabled = process.env[ENV_EMOJI_WIDTH_PROBE] === "1";
218
+ if (probeEnabled) {
219
+ const probed = await probeTerminalEmojiWidthPolicyCached(PROBE_TIMEOUT_MS_DEFAULT);
220
+ if (probed !== null)
221
+ return probed;
222
+ }
223
+ return "wide";
224
+ }
225
+ export function applyEmojiWidthPolicy(policy) {
226
+ setTextMeasureEmojiPolicy(policy);
227
+ return resolvedToNativeWidthPolicy(policy);
228
+ }
229
+ //# sourceMappingURL=emojiWidthPolicy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"emojiWidthPolicy.js","sourceRoot":"","sources":["../../src/backend/emojiWidthPolicy.ts"],"names":[],"mappings":"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"}
@@ -15,10 +15,23 @@ export type NodeBackendConfig = Readonly<{
15
15
  * - "inline": single-thread inline backend (no worker-hop transport)
16
16
  */
17
17
  executionMode?: "auto" | "worker" | "inline";
18
+ /**
19
+ * @deprecated Prefer createNodeApp({ config: { fpsCap } }) so app/core and backend
20
+ * remain aligned by construction.
21
+ */
18
22
  fpsCap?: number;
23
+ /**
24
+ * @deprecated Prefer createNodeApp({ config: { maxEventBytes } }) so app/core and backend
25
+ * remain aligned by construction.
26
+ */
19
27
  maxEventBytes?: number;
20
- /** Request drawlist v2 for native cursor support (default: false for compatibility) */
21
- useDrawlistV2?: boolean;
28
+ /**
29
+ * Explicit drawlist version request.
30
+ *
31
+ * Defaults to `5` (enables v3 style extensions + v4 canvas + v5 image commands).
32
+ * Supported versions are `2`-`5`.
33
+ */
34
+ drawlistVersion?: 2 | 3 | 4 | 5;
22
35
  /**
23
36
  * Frame transport mode:
24
37
  * - "auto": prefer SAB mailbox transport when available, fallback to transfer.
@@ -35,6 +48,16 @@ export type NodeBackendConfig = Readonly<{
35
48
  * Keys are forwarded as-is (camelCase or snake_case accepted by the native parser).
36
49
  */
37
50
  nativeConfig?: Readonly<Record<string, unknown>>;
51
+ /**
52
+ * Emoji width policy used to keep core layout measurement and native rendering aligned.
53
+ * - "auto": use native/env overrides; optional probe when `ZRUI_EMOJI_WIDTH_PROBE=1`
54
+ * then fallback to deterministic "wide"
55
+ * - "wide": emoji clusters consume 2 cells
56
+ * - "narrow": emoji clusters consume 1 cell
57
+ *
58
+ * This sets core text measurement policy and native `widthPolicy` together.
59
+ */
60
+ emojiWidthPolicy?: "auto" | "wide" | "narrow";
38
61
  }>;
39
62
  export type NodeBackendInternalOpts = Readonly<{
40
63
  config?: NodeBackendConfig;
@@ -1 +1 @@
1
- {"version":3,"file":"nodeBackend.d.ts","sourceRoot":"","sources":["../../src/backend/nodeBackend.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EAEV,YAAY,EAKZ,cAAc,EAEf,MAAM,eAAe,CAAC;AAkCvB,MAAM,MAAM,iBAAiB,GAAG,QAAQ,CAAC;IACvC;;;;;OAKG;IACH,aAAa,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,QAAQ,CAAC;IAC7C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,uFAAuF;IACvF,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB;;;;;OAKG;IACH,cAAc,CAAC,EAAE,MAAM,GAAG,UAAU,GAAG,KAAK,CAAC;IAC7C,2CAA2C;IAC3C,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,mDAAmD;IACnD,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B;;;OAGG;IACH,YAAY,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;CAClD,CAAC,CAAC;AAEH,MAAM,MAAM,uBAAuB,GAAG,QAAQ,CAAC;IAC7C,MAAM,CAAC,EAAE,iBAAiB,CAAC;IAC3B,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B,CAAC,CAAC;AAEH,MAAM,MAAM,uBAAuB,GAAG,QAAQ,CAAC;IAC7C,MAAM,EAAE,QAAQ,CACd,MAAM,CACJ,MAAM,EACN;QACE,KAAK,EAAE,MAAM,CAAC;QACd,GAAG,EAAE,MAAM,CAAC;QACZ,GAAG,EAAE,MAAM,CAAC;QACZ,GAAG,EAAE,MAAM,CAAC;QACZ,GAAG,EAAE,MAAM,CAAC;QACZ,GAAG,EAAE,MAAM,CAAC;QACZ,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;KAC5B,CACF,CACF,CAAC;CACH,CAAC,CAAC;AAEH,MAAM,MAAM,eAAe,GAAG,QAAQ,CAAC;IACrC,YAAY,EAAE,MAAM,OAAO,CAAC,uBAAuB,CAAC,CAAC;CACtD,CAAC,CAAC;AAEH,MAAM,MAAM,WAAW,GAAG,cAAc,GAAG,QAAQ,CAAC;IAAE,KAAK,EAAE,YAAY,CAAC;IAAC,IAAI,EAAE,eAAe,CAAA;CAAE,CAAC,CAAC;AAwKpG,wBAAgB,yBAAyB,CAAC,IAAI,GAAE,uBAA4B,GAAG,WAAW,CAq3BzF"}
1
+ {"version":3,"file":"nodeBackend.d.ts","sourceRoot":"","sources":["../../src/backend/nodeBackend.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EAGV,YAAY,EAKZ,cAAc,EAGf,MAAM,eAAe,CAAC;AA2CvB,MAAM,MAAM,iBAAiB,GAAG,QAAQ,CAAC;IACvC;;;;;OAKG;IACH,aAAa,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,QAAQ,CAAC;IAC7C;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;;OAKG;IACH,eAAe,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAChC;;;;;OAKG;IACH,cAAc,CAAC,EAAE,MAAM,GAAG,UAAU,GAAG,KAAK,CAAC;IAC7C,2CAA2C;IAC3C,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,mDAAmD;IACnD,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B;;;OAGG;IACH,YAAY,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACjD;;;;;;;;OAQG;IACH,gBAAgB,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,QAAQ,CAAC;CAC/C,CAAC,CAAC;AAEH,MAAM,MAAM,uBAAuB,GAAG,QAAQ,CAAC;IAC7C,MAAM,CAAC,EAAE,iBAAiB,CAAC;IAC3B,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B,CAAC,CAAC;AAEH,MAAM,MAAM,uBAAuB,GAAG,QAAQ,CAAC;IAC7C,MAAM,EAAE,QAAQ,CACd,MAAM,CACJ,MAAM,EACN;QACE,KAAK,EAAE,MAAM,CAAC;QACd,GAAG,EAAE,MAAM,CAAC;QACZ,GAAG,EAAE,MAAM,CAAC;QACZ,GAAG,EAAE,MAAM,CAAC;QACZ,GAAG,EAAE,MAAM,CAAC;QACZ,GAAG,EAAE,MAAM,CAAC;QACZ,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;KAC5B,CACF,CACF,CAAC;CACH,CAAC,CAAC;AAEH,MAAM,MAAM,eAAe,GAAG,QAAQ,CAAC;IACrC,YAAY,EAAE,MAAM,OAAO,CAAC,uBAAuB,CAAC,CAAC;CACtD,CAAC,CAAC;AAEH,MAAM,MAAM,WAAW,GAAG,cAAc,GAAG,QAAQ,CAAC;IAAE,KAAK,EAAE,YAAY,CAAC;IAAC,IAAI,EAAE,eAAe,CAAA;CAAE,CAAC,CAAC;AAiRpG,wBAAgB,yBAAyB,CAAC,IAAI,GAAE,uBAA4B,GAAG,WAAW,CAk+BzF"}
@@ -7,10 +7,26 @@
7
7
  * @see docs/backend/native.md
8
8
  */
9
9
  import { Worker } from "node:worker_threads";
10
- import { DEFAULT_TERMINAL_CAPS, FRAME_ACCEPTED_ACK_MARKER } from "@rezi-ui/core";
11
- import { ZR_DRAWLIST_VERSION_V1, ZR_DRAWLIST_VERSION_V2, ZR_ENGINE_ABI_MAJOR, ZR_ENGINE_ABI_MINOR, ZR_ENGINE_ABI_PATCH, ZR_EVENT_BATCH_VERSION_V1, ZrUiError, severityToNum, } from "@rezi-ui/core";
10
+ import { BACKEND_DRAWLIST_VERSION_MARKER, BACKEND_FPS_CAP_MARKER, BACKEND_MAX_EVENT_BYTES_MARKER, BACKEND_RAW_WRITE_MARKER, DEFAULT_TERMINAL_CAPS, FRAME_ACCEPTED_ACK_MARKER, } from "@rezi-ui/core";
11
+ import { ZR_DRAWLIST_VERSION_V5, ZR_ENGINE_ABI_MAJOR, ZR_ENGINE_ABI_MINOR, ZR_ENGINE_ABI_PATCH, ZR_EVENT_BATCH_VERSION_V1, ZrUiError, setTextMeasureEmojiPolicy, severityToNum, } from "@rezi-ui/core";
12
12
  import { FRAME_SAB_CONTROL_CONSUMED_SEQ_WORD, FRAME_SAB_CONTROL_HEADER_WORDS, FRAME_SAB_CONTROL_PUBLISHED_BYTES_WORD, FRAME_SAB_CONTROL_PUBLISHED_SEQ_WORD, FRAME_SAB_CONTROL_PUBLISHED_SLOT_WORD, FRAME_SAB_CONTROL_PUBLISHED_TOKEN_WORD, FRAME_SAB_CONTROL_WORDS_PER_SLOT, FRAME_SAB_SLOT_STATE_FREE, FRAME_SAB_SLOT_STATE_READY, FRAME_SAB_SLOT_STATE_WRITING, FRAME_TRANSPORT_SAB_V1, FRAME_TRANSPORT_TRANSFER_V1, FRAME_TRANSPORT_VERSION, } from "../worker/protocol.js";
13
+ import { applyEmojiWidthPolicy, resolveBackendEmojiWidthPolicy } from "./emojiWidthPolicy.js";
13
14
  import { createNodeBackendInlineInternal } from "./nodeBackendInline.js";
15
+ import { terminalProfileFromNodeEnv } from "./terminalProfile.js";
16
+ const WIDTH_POLICY_KEY = "widthPolicy";
17
+ const DEFAULT_NATIVE_LIMITS = Object.freeze({
18
+ // Align native validation caps with JS drawlist builder defaults.
19
+ //
20
+ // Native defaults are intentionally conservative; however, @rezi-ui/core's
21
+ // drawlist builders default to 2 MiB max drawlist bytes and large command
22
+ // budgets. Without overriding, moderately large frames (e.g. images/canvas)
23
+ // can fail with ZR_ERR_LIMIT (-3) at submit time.
24
+ outMaxBytesPerFrame: 2 * 1024 * 1024,
25
+ dlMaxTotalBytes: 2 * 1024 * 1024,
26
+ dlMaxCmds: 100_000,
27
+ dlMaxStrings: 10_000,
28
+ dlMaxBlobs: 10_000,
29
+ });
14
30
  function deferred() {
15
31
  let resolve;
16
32
  let reject;
@@ -20,6 +36,28 @@ function deferred() {
20
36
  });
21
37
  return { promise, resolve, reject };
22
38
  }
39
+ function isPlainObject(v) {
40
+ return typeof v === "object" && v !== null && !Array.isArray(v);
41
+ }
42
+ function mergeNativeLimits(nativeConfig) {
43
+ // biome-ignore lint/complexity/useLiteralKeys: bracket access is required by noPropertyAccessFromIndexSignature.
44
+ const limitsValue = nativeConfig["limits"];
45
+ const existingLimits = isPlainObject(limitsValue)
46
+ ? limitsValue
47
+ : null;
48
+ const limits = { ...(existingLimits ?? {}) };
49
+ const has = (camel) => {
50
+ const snake = camel.replace(/[A-Z]/g, (m) => `_${m.toLowerCase()}`);
51
+ return (Object.prototype.hasOwnProperty.call(limits, camel) ||
52
+ Object.prototype.hasOwnProperty.call(limits, snake));
53
+ };
54
+ for (const [camel, value] of Object.entries(DEFAULT_NATIVE_LIMITS)) {
55
+ if (has(camel))
56
+ continue;
57
+ limits[camel] = value;
58
+ }
59
+ return Object.freeze({ ...nativeConfig, limits: Object.freeze(limits) });
60
+ }
23
61
  function parsePositiveIntOr(n, fallback) {
24
62
  if (typeof n !== "number")
25
63
  return fallback;
@@ -42,9 +80,48 @@ function parsePositiveInt(n) {
42
80
  return null;
43
81
  return n;
44
82
  }
45
- function readNativeTargetFps(cfg) {
83
+ function parseDrawlistVersion(v) {
84
+ if (v === undefined)
85
+ return null;
86
+ if (v === 2 || v === 3 || v === 4 || v === 5)
87
+ return v;
88
+ throw new ZrUiError("ZRUI_INVALID_PROPS", `createNodeBackend config mismatch: drawlistVersion must be one of 2, 3, 4, 5 (got ${String(v)}).`);
89
+ }
90
+ function resolveRequestedDrawlistVersion(config) {
91
+ const explicitDrawlistVersion = parseDrawlistVersion(config.drawlistVersion);
92
+ if (explicitDrawlistVersion !== null)
93
+ return explicitDrawlistVersion;
94
+ return ZR_DRAWLIST_VERSION_V5;
95
+ }
96
+ function parseBoundedPositiveIntOrThrow(name, value, fallback, max) {
97
+ if (value === undefined)
98
+ return fallback;
99
+ const parsed = parsePositiveInt(value);
100
+ if (parsed === null) {
101
+ throw new ZrUiError("ZRUI_INVALID_PROPS", `${name} must be a positive integer`);
102
+ }
103
+ if (parsed > max) {
104
+ throw new ZrUiError("ZRUI_INVALID_PROPS", `${name} must be <= ${String(max)}`);
105
+ }
106
+ return parsed;
107
+ }
108
+ function readNativeTargetFpsValues(cfg) {
46
109
  const targetFpsCfg = cfg;
47
- return parsePositiveInt(targetFpsCfg.targetFps) ?? parsePositiveInt(targetFpsCfg.target_fps);
110
+ return {
111
+ camel: parsePositiveInt(targetFpsCfg.targetFps),
112
+ snake: parsePositiveInt(targetFpsCfg.target_fps),
113
+ };
114
+ }
115
+ function resolveTargetFps(fpsCap, nativeConfig) {
116
+ const values = readNativeTargetFpsValues(nativeConfig);
117
+ if (values.camel !== null && values.snake !== null && values.camel !== values.snake) {
118
+ throw new ZrUiError("ZRUI_INVALID_PROPS", `createNodeBackend config mismatch: nativeConfig.targetFps=${String(values.camel)} must match nativeConfig.target_fps=${String(values.snake)}.`);
119
+ }
120
+ const nativeTargetFps = values.camel ?? values.snake;
121
+ if (nativeTargetFps !== null && nativeTargetFps !== fpsCap) {
122
+ throw new ZrUiError("ZRUI_INVALID_PROPS", `createNodeBackend config mismatch: fpsCap=${String(fpsCap)} must match nativeConfig.targetFps/target_fps=${String(nativeTargetFps)}. Fix: set nativeConfig.targetFps (or target_fps) to ${String(fpsCap)}, or remove the override and use fpsCap only.`);
123
+ }
124
+ return fpsCap;
48
125
  }
49
126
  function safeErr(err) {
50
127
  return err instanceof Error ? err : new Error(String(err));
@@ -53,6 +130,10 @@ const DEBUG_QUERY_DEFAULT_RECORDS = 4096;
53
130
  const DEBUG_QUERY_MAX_RECORDS = 16384;
54
131
  const FRAME_SAB_SLOT_COUNT_DEFAULT = 8;
55
132
  const FRAME_SAB_SLOT_BYTES_DEFAULT = 1 << 20;
133
+ const DEFAULT_FPS_CAP = 60;
134
+ const MAX_SAFE_FPS_CAP = 1000;
135
+ const DEFAULT_MAX_EVENT_BYTES = 1 << 20;
136
+ const MAX_SAFE_EVENT_BYTES = 4 << 20;
56
137
  function copyInto(buf, bytes) {
57
138
  new Uint8Array(buf, 0, bytes.byteLength).set(bytes);
58
139
  }
@@ -132,7 +213,7 @@ function publishSabFrame(t, frameSeq, slotIndex, slotToken, byteLen) {
132
213
  }
133
214
  export function createNodeBackendInternal(opts = {}) {
134
215
  const cfg = opts.config ?? {};
135
- const fpsCap = parsePositiveIntOr(cfg.fpsCap, 60);
216
+ const fpsCap = parseBoundedPositiveIntOrThrow("fpsCap", cfg.fpsCap, DEFAULT_FPS_CAP, MAX_SAFE_FPS_CAP);
136
217
  const requestedExecutionMode = cfg.executionMode ?? "auto";
137
218
  const executionMode = requestedExecutionMode === "inline"
138
219
  ? "inline"
@@ -144,8 +225,8 @@ export function createNodeBackendInternal(opts = {}) {
144
225
  if (executionMode === "inline") {
145
226
  return createNodeBackendInlineInternal(opts);
146
227
  }
147
- const maxEventBytes = parsePositiveIntOr(cfg.maxEventBytes, 1 << 20);
148
- const useDrawlistV2 = cfg.useDrawlistV2 === true;
228
+ const requestedDrawlistVersion = resolveRequestedDrawlistVersion(cfg);
229
+ const maxEventBytes = parseBoundedPositiveIntOrThrow("maxEventBytes", cfg.maxEventBytes, DEFAULT_MAX_EVENT_BYTES, MAX_SAFE_EVENT_BYTES);
149
230
  const frameTransportMode = cfg.frameTransport === "transfer" || cfg.frameTransport === "sab" ? cfg.frameTransport : "auto";
150
231
  const frameSabSlotCount = parsePositiveIntOr(cfg.frameSabSlotCount, FRAME_SAB_SLOT_COUNT_DEFAULT);
151
232
  const frameSabSlotBytes = parsePositiveIntOr(cfg.frameSabSlotBytes, FRAME_SAB_SLOT_BYTES_DEFAULT);
@@ -168,25 +249,25 @@ export function createNodeBackendInternal(opts = {}) {
168
249
  const nativeConfig = typeof cfg.nativeConfig === "object" &&
169
250
  cfg.nativeConfig !== null &&
170
251
  !Array.isArray(cfg.nativeConfig)
171
- ? cfg.nativeConfig
172
- : Object.freeze({});
173
- const nativeTargetFps = readNativeTargetFps(nativeConfig) ?? fpsCap;
174
- const initConfig = {
252
+ ? mergeNativeLimits(cfg.nativeConfig)
253
+ : mergeNativeLimits(Object.freeze({}));
254
+ const nativeTargetFps = resolveTargetFps(fpsCap, nativeConfig);
255
+ const initConfigBase = {
175
256
  ...nativeConfig,
176
- // Keep native tick generation aligned with app/backend fpsCap unless
177
- // explicitly overridden in nativeConfig.
257
+ // fpsCap is the single frame-scheduling knob; native target fps must align.
178
258
  targetFps: nativeTargetFps,
179
259
  // Negotiation pins (docs/16 + docs/01)
180
260
  requestedEngineAbiMajor: ZR_ENGINE_ABI_MAJOR,
181
261
  requestedEngineAbiMinor: ZR_ENGINE_ABI_MINOR,
182
262
  requestedEngineAbiPatch: ZR_ENGINE_ABI_PATCH,
183
- requestedDrawlistVersion: useDrawlistV2 ? ZR_DRAWLIST_VERSION_V2 : ZR_DRAWLIST_VERSION_V1,
263
+ requestedDrawlistVersion: requestedDrawlistVersion,
184
264
  requestedEventBatchVersion: ZR_EVENT_BATCH_VERSION_V1,
185
265
  // Node worker runtime caps
186
266
  fpsCap,
187
267
  maxEventBytes,
188
268
  frameTransport: frameTransportWire,
189
269
  };
270
+ let initConfigResolved = null;
190
271
  let worker = null;
191
272
  let disposed = false;
192
273
  let started = false;
@@ -369,6 +450,26 @@ export function createNodeBackendInternal(opts = {}) {
369
450
  return;
370
451
  }
371
452
  case "events": {
453
+ if (!Number.isInteger(msg.byteLen) || msg.byteLen < 0) {
454
+ fatal = new ZrUiError("ZRUI_BACKEND_ERROR", `events: invalid byteLen=${String(msg.byteLen)}`);
455
+ failAll(fatal);
456
+ return;
457
+ }
458
+ if (msg.byteLen > msg.batch.byteLength) {
459
+ fatal = new ZrUiError("ZRUI_BACKEND_ERROR", `events: byteLen=${String(msg.byteLen)} exceeds batch.byteLength=${String(msg.batch.byteLength)}`);
460
+ failAll(fatal);
461
+ return;
462
+ }
463
+ if (msg.byteLen > maxEventBytes) {
464
+ fatal = new ZrUiError("ZRUI_BACKEND_ERROR", `events: byteLen=${String(msg.byteLen)} exceeds maxEventBytes=${String(maxEventBytes)}`);
465
+ failAll(fatal);
466
+ return;
467
+ }
468
+ if (!Number.isInteger(msg.droppedSinceLast) || msg.droppedSinceLast < 0) {
469
+ fatal = new ZrUiError("ZRUI_BACKEND_ERROR", `events: invalid droppedSinceLast=${String(msg.droppedSinceLast)}`);
470
+ failAll(fatal);
471
+ return;
472
+ }
372
473
  const waiter = eventWaiters.shift();
373
474
  if (waiter !== undefined) {
374
475
  const buf = msg.batch;
@@ -526,6 +627,9 @@ export function createNodeBackendInternal(opts = {}) {
526
627
  supportsScrollRegion: msg.supportsScrollRegion,
527
628
  supportsCursorShape: msg.supportsCursorShape,
528
629
  supportsOutputWaitWritable: msg.supportsOutputWaitWritable,
630
+ supportsUnderlineStyles: msg.supportsUnderlineStyles,
631
+ supportsColoredUnderlines: msg.supportsColoredUnderlines,
632
+ supportsHyperlinks: msg.supportsHyperlinks,
529
633
  sgrAttrsSupported: msg.sgrAttrsSupported,
530
634
  };
531
635
  cachedCaps = caps;
@@ -569,6 +673,21 @@ export function createNodeBackendInternal(opts = {}) {
569
673
  if (started)
570
674
  return;
571
675
  if (worker === null) {
676
+ if (initConfigResolved === null) {
677
+ const resolvedEmojiWidthPolicy = await resolveBackendEmojiWidthPolicy(cfg.emojiWidthPolicy, nativeConfig);
678
+ const nativeWidthPolicy = applyEmojiWidthPolicy(resolvedEmojiWidthPolicy);
679
+ initConfigResolved = {
680
+ ...initConfigBase,
681
+ widthPolicy: nativeWidthPolicy,
682
+ };
683
+ }
684
+ else {
685
+ // Keep core measurement policy deterministic across stop/start cycles.
686
+ const widthPolicy = initConfigResolved[WIDTH_POLICY_KEY];
687
+ if (typeof widthPolicy === "number") {
688
+ setTextMeasureEmojiPolicy(widthPolicy === 0 ? "narrow" : "wide");
689
+ }
690
+ }
572
691
  startDef = deferred();
573
692
  startSettled = false;
574
693
  stopDef = null;
@@ -592,7 +711,7 @@ export function createNodeBackendInternal(opts = {}) {
592
711
  worker.on("exit", (code) => {
593
712
  handleWorkerExit(code);
594
713
  });
595
- send({ type: "init", config: initConfig });
714
+ send({ type: "init", config: initConfigResolved });
596
715
  }
597
716
  if (startDef === null)
598
717
  throw new Error("NodeBackend: invariant violated (startDef is null)");
@@ -763,6 +882,10 @@ export function createNodeBackendInternal(opts = {}) {
763
882
  send({ type: "getCaps" });
764
883
  return d.promise;
765
884
  },
885
+ async getTerminalProfile() {
886
+ const caps = await backend.getCaps();
887
+ return terminalProfileFromNodeEnv(caps);
888
+ },
766
889
  };
767
890
  const debug = {
768
891
  debugEnable: (config) => enqueueDebug(async () => {
@@ -937,6 +1060,42 @@ export function createNodeBackendInternal(opts = {}) {
937
1060
  return snapshot;
938
1061
  }),
939
1062
  };
940
- return Object.assign(backend, { debug, perf });
1063
+ const out = Object.assign(backend, { debug, perf });
1064
+ Object.defineProperties(out, {
1065
+ [BACKEND_DRAWLIST_VERSION_MARKER]: {
1066
+ value: requestedDrawlistVersion,
1067
+ writable: false,
1068
+ enumerable: false,
1069
+ configurable: false,
1070
+ },
1071
+ [BACKEND_MAX_EVENT_BYTES_MARKER]: {
1072
+ value: maxEventBytes,
1073
+ writable: false,
1074
+ enumerable: false,
1075
+ configurable: false,
1076
+ },
1077
+ [BACKEND_FPS_CAP_MARKER]: {
1078
+ value: fpsCap,
1079
+ writable: false,
1080
+ enumerable: false,
1081
+ configurable: false,
1082
+ },
1083
+ [BACKEND_RAW_WRITE_MARKER]: {
1084
+ value: ((text) => {
1085
+ if (typeof text !== "string" || text.length === 0)
1086
+ return;
1087
+ try {
1088
+ process.stdout.write(text);
1089
+ }
1090
+ catch {
1091
+ // Preserve backend determinism: clipboard write failures are non-fatal.
1092
+ }
1093
+ }),
1094
+ writable: false,
1095
+ enumerable: false,
1096
+ configurable: false,
1097
+ },
1098
+ });
1099
+ return out;
941
1100
  }
942
1101
  //# sourceMappingURL=nodeBackend.js.map