@rezi-ui/node 0.1.0-alpha.2 → 0.1.0-alpha.21

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 (53) hide show
  1. package/README.md +20 -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 +211 -0
  5. package/dist/backend/emojiWidthPolicy.js.map +1 -0
  6. package/dist/backend/nodeBackend.d.ts +29 -1
  7. package/dist/backend/nodeBackend.d.ts.map +1 -1
  8. package/dist/backend/nodeBackend.js +116 -12
  9. package/dist/backend/nodeBackend.js.map +1 -1
  10. package/dist/backend/nodeBackendInline.d.ts.map +1 -1
  11. package/dist/backend/nodeBackendInline.js +111 -11
  12. package/dist/backend/nodeBackendInline.js.map +1 -1
  13. package/dist/backend/terminalProfile.d.ts +5 -0
  14. package/dist/backend/terminalProfile.d.ts.map +1 -0
  15. package/dist/backend/terminalProfile.js +117 -0
  16. package/dist/backend/terminalProfile.js.map +1 -0
  17. package/dist/image.d.ts +2 -0
  18. package/dist/image.d.ts.map +1 -0
  19. package/dist/image.js +16 -0
  20. package/dist/image.js.map +1 -0
  21. package/dist/index.d.ts +16 -0
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +64 -0
  24. package/dist/index.js.map +1 -1
  25. package/dist/repro/index.d.ts +3 -0
  26. package/dist/repro/index.d.ts.map +1 -0
  27. package/dist/repro/index.js +2 -0
  28. package/dist/repro/index.js.map +1 -0
  29. package/dist/repro/recorder.d.ts +31 -0
  30. package/dist/repro/recorder.d.ts.map +1 -0
  31. package/dist/repro/recorder.js +326 -0
  32. package/dist/repro/recorder.js.map +1 -0
  33. package/dist/worker/engineWorker.js +33 -3
  34. package/dist/worker/engineWorker.js.map +1 -1
  35. package/dist/worker/protocol.d.ts +3 -0
  36. package/dist/worker/protocol.d.ts.map +1 -1
  37. package/dist/worker/testShims/invalidPollBytesNative.d.ts +22 -0
  38. package/dist/worker/testShims/invalidPollBytesNative.d.ts.map +1 -0
  39. package/dist/worker/testShims/invalidPollBytesNative.js +65 -0
  40. package/dist/worker/testShims/invalidPollBytesNative.js.map +1 -0
  41. package/package.json +11 -7
  42. package/dist/__e2e__/fixtures/terminal-app.d.ts +0 -2
  43. package/dist/__e2e__/fixtures/terminal-app.d.ts.map +0 -1
  44. package/dist/__e2e__/fixtures/terminal-app.js +0 -42
  45. package/dist/__e2e__/fixtures/terminal-app.js.map +0 -1
  46. package/dist/__e2e__/terminal-render.e2e.test.d.ts +0 -2
  47. package/dist/__e2e__/terminal-render.e2e.test.d.ts.map +0 -1
  48. package/dist/__e2e__/terminal-render.e2e.test.js +0 -125
  49. package/dist/__e2e__/terminal-render.e2e.test.js.map +0 -1
  50. package/dist/__tests__/worker_integration.test.d.ts +0 -2
  51. package/dist/__tests__/worker_integration.test.d.ts.map +0 -1
  52. package/dist/__tests__/worker_integration.test.js +0 -569
  53. package/dist/__tests__/worker_integration.test.js.map +0 -1
package/README.md CHANGED
@@ -1,17 +1,31 @@
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.
19
+
20
+ Legacy `createNodeBackend() + createApp()` wiring is deprecated for standard
21
+ app construction.
22
+
23
+ Install:
17
24
 
25
+ ```bash
26
+ npm i @rezi-ui/node
27
+ # or
28
+ bun add @rezi-ui/node
29
+ ```
30
+
31
+ Docs: `https://rtlzeromemory.github.io/Rezi/`
@@ -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;AAkLzD;;;;;;;;;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,211 @@
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
+ function normalizePolicy(raw) {
9
+ if (typeof raw !== "string")
10
+ return null;
11
+ const value = raw.trim().toLowerCase();
12
+ if (value === "auto")
13
+ return "auto";
14
+ if (value === "wide")
15
+ return "wide";
16
+ if (value === "narrow")
17
+ return "narrow";
18
+ return null;
19
+ }
20
+ function nativeWidthPolicyToResolved(value) {
21
+ return value === NATIVE_WIDTH_POLICY_NARROW ? "narrow" : "wide";
22
+ }
23
+ function resolvedToNativeWidthPolicy(value) {
24
+ return value === "narrow" ? NATIVE_WIDTH_POLICY_NARROW : NATIVE_WIDTH_POLICY_WIDE;
25
+ }
26
+ function readNativeWidthPolicyValues(cfg) {
27
+ const parse = (value, key) => {
28
+ if (value === undefined)
29
+ return null;
30
+ if (typeof value !== "number" || !Number.isInteger(value) || (value !== 0 && value !== 1)) {
31
+ throw new ZrUiError("ZRUI_INVALID_PROPS", `createNodeBackend config mismatch: nativeConfig.${key} must be 0 (narrow) or 1 (wide).`);
32
+ }
33
+ return value;
34
+ };
35
+ const record = cfg;
36
+ return {
37
+ camel: parse(record.widthPolicy, "widthPolicy"),
38
+ snake: parse(record.width_policy, "width_policy"),
39
+ };
40
+ }
41
+ function readNativeWidthPolicyOverride(cfg) {
42
+ const values = readNativeWidthPolicyValues(cfg);
43
+ if (values.camel !== null && values.snake !== null && values.camel !== values.snake) {
44
+ throw new ZrUiError("ZRUI_INVALID_PROPS", `createNodeBackend config mismatch: nativeConfig.widthPolicy=${String(values.camel)} must match nativeConfig.width_policy=${String(values.snake)}.`);
45
+ }
46
+ const nativeValue = values.camel ?? values.snake;
47
+ if (nativeValue === null)
48
+ return null;
49
+ return nativeWidthPolicyToResolved(nativeValue);
50
+ }
51
+ function pushParsedCpr(buffer, out) {
52
+ let pending = buffer;
53
+ while (pending.length > 0) {
54
+ const esc = pending.indexOf("\x1b[");
55
+ if (esc < 0) {
56
+ if (pending.length > 128)
57
+ pending = pending.slice(-128);
58
+ break;
59
+ }
60
+ if (esc > 0) {
61
+ pending = pending.slice(esc);
62
+ }
63
+ const end = pending.indexOf("R", 2);
64
+ if (end < 0)
65
+ break;
66
+ const seq = pending.slice(0, end + 1);
67
+ pending = pending.slice(end + 1);
68
+ const body = seq.slice(2, seq.length - 1);
69
+ const sep = body.indexOf(";");
70
+ if (sep <= 0 || sep >= body.length - 1)
71
+ continue;
72
+ const rowText = body.slice(0, sep);
73
+ const colText = body.slice(sep + 1);
74
+ if (!/^\d+$/.test(rowText) || !/^\d+$/.test(colText))
75
+ continue;
76
+ const row = Number.parseInt(rowText, 10);
77
+ const col = Number.parseInt(colText, 10);
78
+ if (Number.isFinite(row) && Number.isFinite(col)) {
79
+ out.push({ row, col });
80
+ }
81
+ }
82
+ return pending;
83
+ }
84
+ async function probeGlyphWidthViaCpr(glyph, timeoutMs) {
85
+ const stdin = process.stdin;
86
+ const stdout = process.stdout;
87
+ if (!stdin.isTTY || !stdout.isTTY)
88
+ return null;
89
+ if (typeof stdin.setRawMode !== "function")
90
+ return null;
91
+ const wasRaw = stdin.isRaw === true;
92
+ let pending = "";
93
+ const cprs = [];
94
+ const onData = (chunk) => {
95
+ const text = typeof chunk === "string" ? chunk : chunk.toString("utf8");
96
+ pending = pushParsedCpr(pending + text, cprs);
97
+ };
98
+ let timeout = null;
99
+ try {
100
+ stdin.on("data", onData);
101
+ stdin.resume();
102
+ if (!wasRaw)
103
+ stdin.setRawMode(true);
104
+ // Save cursor, move to a stable column, query CPR, print glyph, query CPR again, restore cursor.
105
+ await new Promise((resolve, reject) => {
106
+ stdout.write(`\x1b[s\x1b[999;1H\x1b[6n${glyph}\x1b[6n\x1b[u`, (err) => {
107
+ if (err)
108
+ reject(err);
109
+ else
110
+ resolve();
111
+ });
112
+ });
113
+ await new Promise((resolve) => {
114
+ const done = () => {
115
+ if (timeout)
116
+ clearTimeout(timeout);
117
+ timeout = null;
118
+ resolve();
119
+ };
120
+ timeout = setTimeout(done, timeoutMs);
121
+ const poll = () => {
122
+ if (cprs.length >= 2) {
123
+ done();
124
+ return;
125
+ }
126
+ setTimeout(poll, 2);
127
+ };
128
+ poll();
129
+ });
130
+ }
131
+ catch {
132
+ return null;
133
+ }
134
+ finally {
135
+ if (timeout)
136
+ clearTimeout(timeout);
137
+ stdin.off("data", onData);
138
+ if (!wasRaw) {
139
+ try {
140
+ stdin.setRawMode(false);
141
+ }
142
+ catch {
143
+ // no-op
144
+ }
145
+ }
146
+ }
147
+ const a = cprs[0];
148
+ const b = cprs[1];
149
+ if (!a || !b)
150
+ return null;
151
+ if (b.row !== a.row)
152
+ return null;
153
+ const delta = b.col - a.col;
154
+ if (delta === 1 || delta === 2)
155
+ return delta;
156
+ return null;
157
+ }
158
+ async function probeTerminalEmojiWidthPolicy(timeoutMs) {
159
+ const widths = [];
160
+ for (const glyph of PROBE_GLYPHS) {
161
+ const width = await probeGlyphWidthViaCpr(glyph, timeoutMs);
162
+ if (width !== null)
163
+ widths.push(width);
164
+ }
165
+ if (widths.length === 0)
166
+ return null;
167
+ if (widths.includes(1))
168
+ return "narrow";
169
+ return "wide";
170
+ }
171
+ /**
172
+ * Resolve backend emoji width policy and align core/native width models.
173
+ *
174
+ * Resolution order:
175
+ * 1) explicit `requested` ("wide"/"narrow")
176
+ * 2) explicit native override (`nativeConfig.widthPolicy|width_policy`)
177
+ * 3) env override (`ZRUI_EMOJI_WIDTH_POLICY`)
178
+ * 4) optional probe (CPR-based) when `ZRUI_EMOJI_WIDTH_PROBE=1`
179
+ * 5) deterministic default ("wide")
180
+ */
181
+ export async function resolveBackendEmojiWidthPolicy(requested, nativeConfig) {
182
+ const requestedPolicy = requested ?? "auto";
183
+ const nativeOverride = readNativeWidthPolicyOverride(nativeConfig);
184
+ if (requestedPolicy === "narrow" || requestedPolicy === "wide") {
185
+ if (nativeOverride !== null && nativeOverride !== requestedPolicy) {
186
+ throw new ZrUiError("ZRUI_INVALID_PROPS", `createNodeBackend config mismatch: emojiWidthPolicy=${requestedPolicy} must match nativeConfig.widthPolicy/width_policy=${nativeOverride === "narrow" ? 0 : 1}.`);
187
+ }
188
+ return requestedPolicy;
189
+ }
190
+ if (nativeOverride !== null)
191
+ return nativeOverride;
192
+ const envOverride = normalizePolicy(process.env[ENV_EMOJI_WIDTH_POLICY]);
193
+ if (envOverride === "wide" || envOverride === "narrow")
194
+ return envOverride;
195
+ /*
196
+ CPR probing is opt-in because it temporarily consumes stdin bytes while
197
+ collecting CPR responses, which can race startup-time key streams.
198
+ */
199
+ const probeEnabled = process.env[ENV_EMOJI_WIDTH_PROBE] === "1";
200
+ if (probeEnabled) {
201
+ const probed = await probeTerminalEmojiWidthPolicy(PROBE_TIMEOUT_MS_DEFAULT);
202
+ if (probed !== null)
203
+ return probed;
204
+ }
205
+ return "wide";
206
+ }
207
+ export function applyEmojiWidthPolicy(policy) {
208
+ setTextMeasureEmojiPolicy(policy);
209
+ return resolvedToNativeWidthPolicy(policy);
210
+ }
211
+ //# sourceMappingURL=emojiWidthPolicy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"emojiWidthPolicy.js","sourceRoot":"","sources":["../../src/backend/emojiWidthPolicy.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,yBAAyB,EAAE,MAAM,eAAe,CAAC;AAKrE,MAAM,0BAA0B,GAAG,CAAU,CAAC;AAC9C,MAAM,wBAAwB,GAAG,CAAU,CAAC;AAC5C,MAAM,wBAAwB,GAAG,EAAE,CAAC;AACpC,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;AACvD,MAAM,sBAAsB,GAAG,yBAAkC,CAAC;AAClE,MAAM,qBAAqB,GAAG,wBAAiC,CAAC;AAIhE,SAAS,eAAe,CAAC,GAAY;IACnC,IAAI,OAAO,GAAG,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IACzC,MAAM,KAAK,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IACvC,IAAI,KAAK,KAAK,MAAM;QAAE,OAAO,MAAM,CAAC;IACpC,IAAI,KAAK,KAAK,MAAM;QAAE,OAAO,MAAM,CAAC;IACpC,IAAI,KAAK,KAAK,QAAQ;QAAE,OAAO,QAAQ,CAAC;IACxC,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,2BAA2B,CAAC,KAAY;IAC/C,OAAO,KAAK,KAAK,0BAA0B,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC;AAClE,CAAC;AAED,SAAS,2BAA2B,CAAC,KAA+B;IAClE,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,0BAA0B,CAAC,CAAC,CAAC,wBAAwB,CAAC;AACpF,CAAC;AAED,SAAS,2BAA2B,CAClC,GAAsC;IAEtC,MAAM,KAAK,GAAG,CAAC,KAAc,EAAE,GAAW,EAAgB,EAAE;QAC1D,IAAI,KAAK,KAAK,SAAS;YAAE,OAAO,IAAI,CAAC;QACrC,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,KAAK,CAAC,IAAI,KAAK,KAAK,CAAC,CAAC,EAAE,CAAC;YAC1F,MAAM,IAAI,SAAS,CACjB,oBAAoB,EACpB,mDAAmD,GAAG,kCAAkC,CACzF,CAAC;QACJ,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC,CAAC;IAEF,MAAM,MAAM,GAAG,GAAkE,CAAC;IAClF,OAAO;QACL,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,WAAW,EAAE,aAAa,CAAC;QAC/C,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,YAAY,EAAE,cAAc,CAAC;KAClD,CAAC;AACJ,CAAC;AAED,SAAS,6BAA6B,CACpC,GAAsC;IAEtC,MAAM,MAAM,GAAG,2BAA2B,CAAC,GAAG,CAAC,CAAC;IAChD,IAAI,MAAM,CAAC,KAAK,KAAK,IAAI,IAAI,MAAM,CAAC,KAAK,KAAK,IAAI,IAAI,MAAM,CAAC,KAAK,KAAK,MAAM,CAAC,KAAK,EAAE,CAAC;QACpF,MAAM,IAAI,SAAS,CACjB,oBAAoB,EACpB,+DAA+D,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,yCAAyC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CACpJ,CAAC;IACJ,CAAC;IACD,MAAM,WAAW,GAAG,MAAM,CAAC,KAAK,IAAI,MAAM,CAAC,KAAK,CAAC;IACjD,IAAI,WAAW,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IACtC,OAAO,2BAA2B,CAAC,WAAW,CAAC,CAAC;AAClD,CAAC;AAED,SAAS,aAAa,CAAC,MAAc,EAAE,GAAU;IAC/C,IAAI,OAAO,GAAG,MAAM,CAAC;IACrB,OAAO,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC1B,MAAM,GAAG,GAAG,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;QACrC,IAAI,GAAG,GAAG,CAAC,EAAE,CAAC;YACZ,IAAI,OAAO,CAAC,MAAM,GAAG,GAAG;gBAAE,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC;YACxD,MAAM;QACR,CAAC;QACD,IAAI,GAAG,GAAG,CAAC,EAAE,CAAC;YACZ,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC/B,CAAC;QAED,MAAM,GAAG,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;QACpC,IAAI,GAAG,GAAG,CAAC;YAAE,MAAM;QACnB,MAAM,GAAG,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,GAAG,CAAC,CAAC,CAAC;QACtC,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC;QAEjC,MAAM,IAAI,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;QAC1C,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAC9B,IAAI,GAAG,IAAI,CAAC,IAAI,GAAG,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC;YAAE,SAAS;QACjD,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;QACnC,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC;QACpC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC;YAAE,SAAS;QAC/D,MAAM,GAAG,GAAG,MAAM,CAAC,QAAQ,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;QACzC,MAAM,GAAG,GAAG,MAAM,CAAC,QAAQ,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;QACzC,IAAI,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;YACjD,GAAG,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC;QACzB,CAAC;IACH,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,KAAK,UAAU,qBAAqB,CAAC,KAAa,EAAE,SAAiB;IACnE,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC;IAC5B,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAC9B,IAAI,CAAC,KAAK,CAAC,KAAK,IAAI,CAAC,MAAM,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IAC/C,IAAI,OAAO,KAAK,CAAC,UAAU,KAAK,UAAU;QAAE,OAAO,IAAI,CAAC;IAExD,MAAM,MAAM,GAAG,KAAK,CAAC,KAAK,KAAK,IAAI,CAAC;IACpC,IAAI,OAAO,GAAG,EAAE,CAAC;IACjB,MAAM,IAAI,GAAU,EAAE,CAAC;IAEvB,MAAM,MAAM,GAAG,CAAC,KAAsB,EAAQ,EAAE;QAC9C,MAAM,IAAI,GAAG,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QACxE,OAAO,GAAG,aAAa,CAAC,OAAO,GAAG,IAAI,EAAE,IAAI,CAAC,CAAC;IAChD,CAAC,CAAC;IAEF,IAAI,OAAO,GAA0B,IAAI,CAAC;IAC1C,IAAI,CAAC;QACH,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QACzB,KAAK,CAAC,MAAM,EAAE,CAAC;QACf,IAAI,CAAC,MAAM;YAAE,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;QAEpC,iGAAiG;QACjG,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YAC1C,MAAM,CAAC,KAAK,CAAC,2BAA2B,KAAK,eAAe,EAAE,CAAC,GAAkB,EAAE,EAAE;gBACnF,IAAI,GAAG;oBAAE,MAAM,CAAC,GAAG,CAAC,CAAC;;oBAChB,OAAO,EAAE,CAAC;YACjB,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;QAEH,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE;YAClC,MAAM,IAAI,GAAG,GAAS,EAAE;gBACtB,IAAI,OAAO;oBAAE,YAAY,CAAC,OAAO,CAAC,CAAC;gBACnC,OAAO,GAAG,IAAI,CAAC;gBACf,OAAO,EAAE,CAAC;YACZ,CAAC,CAAC;YAEF,OAAO,GAAG,UAAU,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;YACtC,MAAM,IAAI,GAAG,GAAS,EAAE;gBACtB,IAAI,IAAI,CAAC,MAAM,IAAI,CAAC,EAAE,CAAC;oBACrB,IAAI,EAAE,CAAC;oBACP,OAAO;gBACT,CAAC;gBACD,UAAU,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;YACtB,CAAC,CAAC;YACF,IAAI,EAAE,CAAC;QACT,CAAC,CAAC,CAAC;IACL,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;YAAS,CAAC;QACT,IAAI,OAAO;YAAE,YAAY,CAAC,OAAO,CAAC,CAAC;QACnC,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAC1B,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,IAAI,CAAC;gBACH,KAAK,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;YAC1B,CAAC;YAAC,MAAM,CAAC;gBACP,QAAQ;YACV,CAAC;QACH,CAAC;IACH,CAAC;IAED,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IAC1B,IAAI,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,GAAG;QAAE,OAAO,IAAI,CAAC;IACjC,MAAM,KAAK,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,GAAG,CAAC;IAC5B,IAAI,KAAK,KAAK,CAAC,IAAI,KAAK,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IAC7C,OAAO,IAAI,CAAC;AACd,CAAC;AAED,KAAK,UAAU,6BAA6B,CAC1C,SAAiB;IAEjB,MAAM,MAAM,GAAiB,EAAE,CAAC;IAChC,KAAK,MAAM,KAAK,IAAI,YAAY,EAAE,CAAC;QACjC,MAAM,KAAK,GAAG,MAAM,qBAAqB,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC;QAC5D,IAAI,KAAK,KAAK,IAAI;YAAE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACzC,CAAC;IACD,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACrC,IAAI,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC;QAAE,OAAO,QAAQ,CAAC;IACxC,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,8BAA8B,CAClD,SAA8C,EAC9C,YAA+C;IAE/C,MAAM,eAAe,GAA4B,SAAS,IAAI,MAAM,CAAC;IACrE,MAAM,cAAc,GAAG,6BAA6B,CAAC,YAAY,CAAC,CAAC;IAEnE,IAAI,eAAe,KAAK,QAAQ,IAAI,eAAe,KAAK,MAAM,EAAE,CAAC;QAC/D,IAAI,cAAc,KAAK,IAAI,IAAI,cAAc,KAAK,eAAe,EAAE,CAAC;YAClE,MAAM,IAAI,SAAS,CACjB,oBAAoB,EACpB,uDAAuD,eAAe,qDAAqD,cAAc,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAClK,CAAC;QACJ,CAAC;QACD,OAAO,eAAe,CAAC;IACzB,CAAC;IAED,IAAI,cAAc,KAAK,IAAI;QAAE,OAAO,cAAc,CAAC;IAEnD,MAAM,WAAW,GAAG,eAAe,CAAC,OAAO,CAAC,GAAG,CAAC,sBAAsB,CAAC,CAAC,CAAC;IACzE,IAAI,WAAW,KAAK,MAAM,IAAI,WAAW,KAAK,QAAQ;QAAE,OAAO,WAAW,CAAC;IAE3E;;;MAGE;IACF,MAAM,YAAY,GAAG,OAAO,CAAC,GAAG,CAAC,qBAAqB,CAAC,KAAK,GAAG,CAAC;IAChE,IAAI,YAAY,EAAE,CAAC;QACjB,MAAM,MAAM,GAAG,MAAM,6BAA6B,CAAC,wBAAwB,CAAC,CAAC;QAC7E,IAAI,MAAM,KAAK,IAAI;YAAE,OAAO,MAAM,CAAC;IACrC,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,MAAgC;IACpE,yBAAyB,CAAC,MAAM,CAAC,CAAC;IAClC,OAAO,2BAA2B,CAAC,MAAM,CAAC,CAAC;AAC7C,CAAC"}
@@ -15,10 +15,28 @@ 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) */
28
+ /**
29
+ * Request drawlist v2 for native cursor support (default: false for compatibility).
30
+ * @deprecated Prefer createNodeApp({ config: { useV2Cursor: true } }).
31
+ */
21
32
  useDrawlistV2?: boolean;
33
+ /**
34
+ * Explicit drawlist version request.
35
+ *
36
+ * Defaults to `5` (enables v3 style extensions + v4 canvas + v5 image commands).
37
+ * Set to `1`/`2` only for legacy protocol compatibility testing.
38
+ */
39
+ drawlistVersion?: 1 | 2 | 3 | 4 | 5;
22
40
  /**
23
41
  * Frame transport mode:
24
42
  * - "auto": prefer SAB mailbox transport when available, fallback to transfer.
@@ -35,6 +53,16 @@ export type NodeBackendConfig = Readonly<{
35
53
  * Keys are forwarded as-is (camelCase or snake_case accepted by the native parser).
36
54
  */
37
55
  nativeConfig?: Readonly<Record<string, unknown>>;
56
+ /**
57
+ * Emoji width policy used to keep core layout measurement and native rendering aligned.
58
+ * - "auto": use native/env overrides; optional probe when `ZRUI_EMOJI_WIDTH_PROBE=1`
59
+ * then fallback to deterministic "wide"
60
+ * - "wide": emoji clusters consume 2 cells
61
+ * - "narrow": emoji clusters consume 1 cell
62
+ *
63
+ * This sets core text measurement policy and native `widthPolicy` together.
64
+ */
65
+ emojiWidthPolicy?: "auto" | "wide" | "narrow";
38
66
  }>;
39
67
  export type NodeBackendInternalOpts = Readonly<{
40
68
  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,EAEV,YAAY,EAKZ,cAAc,EAGf,MAAM,eAAe,CAAC;AA4CvB,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;;;OAGG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB;;;;;OAKG;IACH,eAAe,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACpC;;;;;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;AA2NpG,wBAAgB,yBAAyB,CAAC,IAAI,GAAE,uBAA4B,GAAG,WAAW,CAi9BzF"}
@@ -7,10 +7,13 @@
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_V2_MARKER, BACKEND_DRAWLIST_VERSION_MARKER, BACKEND_FPS_CAP_MARKER, BACKEND_MAX_EVENT_BYTES_MARKER, DEFAULT_TERMINAL_CAPS, FRAME_ACCEPTED_ACK_MARKER, } from "@rezi-ui/core";
11
+ import { ZR_DRAWLIST_VERSION_V2, 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";
14
17
  function deferred() {
15
18
  let resolve;
16
19
  let reject;
@@ -42,9 +45,41 @@ function parsePositiveInt(n) {
42
45
  return null;
43
46
  return n;
44
47
  }
45
- function readNativeTargetFps(cfg) {
48
+ function parseDrawlistVersion(v) {
49
+ if (v === undefined)
50
+ return null;
51
+ if (v === 1 || v === 2 || v === 3 || v === 4 || v === 5)
52
+ return v;
53
+ throw new ZrUiError("ZRUI_INVALID_PROPS", `createNodeBackend config mismatch: drawlistVersion must be one of 1, 2, 3, 4, 5 (got ${String(v)}).`);
54
+ }
55
+ function resolveRequestedDrawlistVersion(config) {
56
+ const explicitDrawlistVersion = parseDrawlistVersion(config.drawlistVersion);
57
+ const useDrawlistV2 = config.useDrawlistV2 === true;
58
+ if (explicitDrawlistVersion !== null) {
59
+ if (useDrawlistV2 && explicitDrawlistVersion < ZR_DRAWLIST_VERSION_V2) {
60
+ throw new ZrUiError("ZRUI_INVALID_PROPS", "createNodeBackend config mismatch: useDrawlistV2=true requires drawlistVersion >= 2.");
61
+ }
62
+ return explicitDrawlistVersion;
63
+ }
64
+ return useDrawlistV2 ? ZR_DRAWLIST_VERSION_V2 : ZR_DRAWLIST_VERSION_V5;
65
+ }
66
+ function readNativeTargetFpsValues(cfg) {
46
67
  const targetFpsCfg = cfg;
47
- return parsePositiveInt(targetFpsCfg.targetFps) ?? parsePositiveInt(targetFpsCfg.target_fps);
68
+ return {
69
+ camel: parsePositiveInt(targetFpsCfg.targetFps),
70
+ snake: parsePositiveInt(targetFpsCfg.target_fps),
71
+ };
72
+ }
73
+ function resolveTargetFps(fpsCap, nativeConfig) {
74
+ const values = readNativeTargetFpsValues(nativeConfig);
75
+ if (values.camel !== null && values.snake !== null && values.camel !== values.snake) {
76
+ throw new ZrUiError("ZRUI_INVALID_PROPS", `createNodeBackend config mismatch: nativeConfig.targetFps=${String(values.camel)} must match nativeConfig.target_fps=${String(values.snake)}.`);
77
+ }
78
+ const nativeTargetFps = values.camel ?? values.snake;
79
+ if (nativeTargetFps !== null && nativeTargetFps !== fpsCap) {
80
+ 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.`);
81
+ }
82
+ return fpsCap;
48
83
  }
49
84
  function safeErr(err) {
50
85
  return err instanceof Error ? err : new Error(String(err));
@@ -145,7 +180,7 @@ export function createNodeBackendInternal(opts = {}) {
145
180
  return createNodeBackendInlineInternal(opts);
146
181
  }
147
182
  const maxEventBytes = parsePositiveIntOr(cfg.maxEventBytes, 1 << 20);
148
- const useDrawlistV2 = cfg.useDrawlistV2 === true;
183
+ const requestedDrawlistVersion = resolveRequestedDrawlistVersion(cfg);
149
184
  const frameTransportMode = cfg.frameTransport === "transfer" || cfg.frameTransport === "sab" ? cfg.frameTransport : "auto";
150
185
  const frameSabSlotCount = parsePositiveIntOr(cfg.frameSabSlotCount, FRAME_SAB_SLOT_COUNT_DEFAULT);
151
186
  const frameSabSlotBytes = parsePositiveIntOr(cfg.frameSabSlotBytes, FRAME_SAB_SLOT_BYTES_DEFAULT);
@@ -170,23 +205,23 @@ export function createNodeBackendInternal(opts = {}) {
170
205
  !Array.isArray(cfg.nativeConfig)
171
206
  ? cfg.nativeConfig
172
207
  : Object.freeze({});
173
- const nativeTargetFps = readNativeTargetFps(nativeConfig) ?? fpsCap;
174
- const initConfig = {
208
+ const nativeTargetFps = resolveTargetFps(fpsCap, nativeConfig);
209
+ const initConfigBase = {
175
210
  ...nativeConfig,
176
- // Keep native tick generation aligned with app/backend fpsCap unless
177
- // explicitly overridden in nativeConfig.
211
+ // fpsCap is the single frame-scheduling knob; native target fps must align.
178
212
  targetFps: nativeTargetFps,
179
213
  // Negotiation pins (docs/16 + docs/01)
180
214
  requestedEngineAbiMajor: ZR_ENGINE_ABI_MAJOR,
181
215
  requestedEngineAbiMinor: ZR_ENGINE_ABI_MINOR,
182
216
  requestedEngineAbiPatch: ZR_ENGINE_ABI_PATCH,
183
- requestedDrawlistVersion: useDrawlistV2 ? ZR_DRAWLIST_VERSION_V2 : ZR_DRAWLIST_VERSION_V1,
217
+ requestedDrawlistVersion: requestedDrawlistVersion,
184
218
  requestedEventBatchVersion: ZR_EVENT_BATCH_VERSION_V1,
185
219
  // Node worker runtime caps
186
220
  fpsCap,
187
221
  maxEventBytes,
188
222
  frameTransport: frameTransportWire,
189
223
  };
224
+ let initConfigResolved = null;
190
225
  let worker = null;
191
226
  let disposed = false;
192
227
  let started = false;
@@ -369,6 +404,26 @@ export function createNodeBackendInternal(opts = {}) {
369
404
  return;
370
405
  }
371
406
  case "events": {
407
+ if (!Number.isInteger(msg.byteLen) || msg.byteLen < 0) {
408
+ fatal = new ZrUiError("ZRUI_BACKEND_ERROR", `events: invalid byteLen=${String(msg.byteLen)}`);
409
+ failAll(fatal);
410
+ return;
411
+ }
412
+ if (msg.byteLen > msg.batch.byteLength) {
413
+ fatal = new ZrUiError("ZRUI_BACKEND_ERROR", `events: byteLen=${String(msg.byteLen)} exceeds batch.byteLength=${String(msg.batch.byteLength)}`);
414
+ failAll(fatal);
415
+ return;
416
+ }
417
+ if (msg.byteLen > maxEventBytes) {
418
+ fatal = new ZrUiError("ZRUI_BACKEND_ERROR", `events: byteLen=${String(msg.byteLen)} exceeds maxEventBytes=${String(maxEventBytes)}`);
419
+ failAll(fatal);
420
+ return;
421
+ }
422
+ if (!Number.isInteger(msg.droppedSinceLast) || msg.droppedSinceLast < 0) {
423
+ fatal = new ZrUiError("ZRUI_BACKEND_ERROR", `events: invalid droppedSinceLast=${String(msg.droppedSinceLast)}`);
424
+ failAll(fatal);
425
+ return;
426
+ }
372
427
  const waiter = eventWaiters.shift();
373
428
  if (waiter !== undefined) {
374
429
  const buf = msg.batch;
@@ -526,6 +581,9 @@ export function createNodeBackendInternal(opts = {}) {
526
581
  supportsScrollRegion: msg.supportsScrollRegion,
527
582
  supportsCursorShape: msg.supportsCursorShape,
528
583
  supportsOutputWaitWritable: msg.supportsOutputWaitWritable,
584
+ supportsUnderlineStyles: msg.supportsUnderlineStyles,
585
+ supportsColoredUnderlines: msg.supportsColoredUnderlines,
586
+ supportsHyperlinks: msg.supportsHyperlinks,
529
587
  sgrAttrsSupported: msg.sgrAttrsSupported,
530
588
  };
531
589
  cachedCaps = caps;
@@ -569,6 +627,21 @@ export function createNodeBackendInternal(opts = {}) {
569
627
  if (started)
570
628
  return;
571
629
  if (worker === null) {
630
+ if (initConfigResolved === null) {
631
+ const resolvedEmojiWidthPolicy = await resolveBackendEmojiWidthPolicy(cfg.emojiWidthPolicy, nativeConfig);
632
+ const nativeWidthPolicy = applyEmojiWidthPolicy(resolvedEmojiWidthPolicy);
633
+ initConfigResolved = {
634
+ ...initConfigBase,
635
+ widthPolicy: nativeWidthPolicy,
636
+ };
637
+ }
638
+ else {
639
+ // Keep core measurement policy deterministic across stop/start cycles.
640
+ const widthPolicy = initConfigResolved[WIDTH_POLICY_KEY];
641
+ if (typeof widthPolicy === "number") {
642
+ setTextMeasureEmojiPolicy(widthPolicy === 0 ? "narrow" : "wide");
643
+ }
644
+ }
572
645
  startDef = deferred();
573
646
  startSettled = false;
574
647
  stopDef = null;
@@ -592,7 +665,7 @@ export function createNodeBackendInternal(opts = {}) {
592
665
  worker.on("exit", (code) => {
593
666
  handleWorkerExit(code);
594
667
  });
595
- send({ type: "init", config: initConfig });
668
+ send({ type: "init", config: initConfigResolved });
596
669
  }
597
670
  if (startDef === null)
598
671
  throw new Error("NodeBackend: invariant violated (startDef is null)");
@@ -763,6 +836,10 @@ export function createNodeBackendInternal(opts = {}) {
763
836
  send({ type: "getCaps" });
764
837
  return d.promise;
765
838
  },
839
+ async getTerminalProfile() {
840
+ const caps = await backend.getCaps();
841
+ return terminalProfileFromNodeEnv(caps);
842
+ },
766
843
  };
767
844
  const debug = {
768
845
  debugEnable: (config) => enqueueDebug(async () => {
@@ -937,6 +1014,33 @@ export function createNodeBackendInternal(opts = {}) {
937
1014
  return snapshot;
938
1015
  }),
939
1016
  };
940
- return Object.assign(backend, { debug, perf });
1017
+ const out = Object.assign(backend, { debug, perf });
1018
+ Object.defineProperties(out, {
1019
+ [BACKEND_DRAWLIST_V2_MARKER]: {
1020
+ value: requestedDrawlistVersion >= ZR_DRAWLIST_VERSION_V2,
1021
+ writable: false,
1022
+ enumerable: false,
1023
+ configurable: false,
1024
+ },
1025
+ [BACKEND_DRAWLIST_VERSION_MARKER]: {
1026
+ value: requestedDrawlistVersion,
1027
+ writable: false,
1028
+ enumerable: false,
1029
+ configurable: false,
1030
+ },
1031
+ [BACKEND_MAX_EVENT_BYTES_MARKER]: {
1032
+ value: maxEventBytes,
1033
+ writable: false,
1034
+ enumerable: false,
1035
+ configurable: false,
1036
+ },
1037
+ [BACKEND_FPS_CAP_MARKER]: {
1038
+ value: fpsCap,
1039
+ writable: false,
1040
+ enumerable: false,
1041
+ configurable: false,
1042
+ },
1043
+ });
1044
+ return out;
941
1045
  }
942
1046
  //# sourceMappingURL=nodeBackend.js.map