@symbiote-native/engine 1.3.0 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/build/accessibility-props.d.ts +0 -11
  2. package/build/accessibility-props.js +30 -68
  3. package/build/animated/graph.js +1 -1
  4. package/build/animated/leaf-lifecycle.js +2 -2
  5. package/build/asset-source-resolver.d.ts +2 -0
  6. package/build/asset-source-resolver.js +13 -0
  7. package/build/back-handler/index.d.ts +1 -5
  8. package/build/back-handler/index.js +0 -6
  9. package/build/debug.js +8 -22
  10. package/build/dispatch.js +3 -9
  11. package/build/events/index.js +2 -2
  12. package/build/fabric-props.js +74 -179
  13. package/build/fabric.d.ts +0 -13
  14. package/build/fabric.js +18 -38
  15. package/build/host-access.d.ts +1 -128
  16. package/build/host-access.js +96 -205
  17. package/build/host-behavior.d.ts +0 -100
  18. package/build/host-behavior.js +125 -311
  19. package/build/image-loader.js +10 -23
  20. package/build/image-source-resolver.js +3 -7
  21. package/build/image-source-write.d.ts +0 -11
  22. package/build/image-source-write.js +14 -34
  23. package/build/imperative.d.ts +0 -28
  24. package/build/imperative.js +42 -92
  25. package/build/index.d.ts +1 -0
  26. package/build/index.js +24 -37
  27. package/build/mutation-buffer.d.ts +0 -177
  28. package/build/mutation-buffer.js +137 -308
  29. package/build/native-engine.d.ts +0 -102
  30. package/build/native-engine.js +38 -98
  31. package/build/native-events.js +9 -18
  32. package/build/native-tree-host.d.ts +0 -21
  33. package/build/native-tree-host.js +14 -31
  34. package/build/node.d.ts +0 -212
  35. package/build/node.js +338 -774
  36. package/build/post-commit.js +3 -8
  37. package/build/process-aspect-ratio.js +3 -7
  38. package/build/process-background-longhands.js +10 -19
  39. package/build/process-filter.js +11 -19
  40. package/build/process-font-variant.js +3 -7
  41. package/build/registry.d.ts +0 -33
  42. package/build/registry.js +22 -57
  43. package/build/report-error.js +4 -18
  44. package/build/structured-style.d.ts +0 -9
  45. package/build/structured-style.js +16 -31
  46. package/build/styles.js +3 -6
  47. package/build/surface.d.ts +0 -26
  48. package/build/surface.js +29 -76
  49. package/build/text-input-state.js +4 -8
  50. package/build/touch-history.js +5 -11
  51. package/build/tree-host.d.ts +0 -270
  52. package/build/tree-host.js +63 -153
  53. package/build/view-config.js +17 -37
  54. package/package.json +2 -2
@@ -1,62 +1,10 @@
1
1
  import type { ICommittedRecord } from './tree-host';
2
- /**
3
- * The ABI this file knows how to talk to. A binary reporting anything else is refused outright rather
4
- * than probed method by method — the two artefacts ship separately (a pod and an npm package), so
5
- * disagreement is routine, and a partial match is the shape that corrupts memory quietly.
6
- *
7
- * Exported for the tests, which must DERIVE their supported and unsupported arms from it rather than
8
- * restate the number: a bump that leaves a fixture behind reads as "the binary is stale", which is
9
- * exactly the message this constant exists to produce, so the failure looks like the feature working.
10
- */
11
2
  export declare const SUPPORTED_NATIVE_VERSION = 4;
12
- /**
13
- * What `installJSIBindingsWithRuntime:` puts on the global.
14
- *
15
- * `allocInt32Array` hands back a view over memory NATIVE owns — not a copy — which is the only reason
16
- * any of this exists. Every other route from JS to native structure costs a JSI crossing per element,
17
- * and the census that sized this design counted ~4 000 reads for a two-row swap.
18
- */
19
3
  export type INativeEngineBindings = {
20
4
  version: number;
21
5
  allocInt32Array: (lengthInElements: number) => Int32Array;
22
- /**
23
- * Item 8c-1's bring-up probe: shadow trees the real `UIManager` holds, or -1 when we could not
24
- * reach one at all. Not a capability the engine uses — it exists so one device run answers whether
25
- * our pod compiles against ReactCommon's renderer, links against the prebuilt framework, and can
26
- * resolve the UIManager from a plain JSI runtime. See the C++ side for why -1 and 0 differ.
27
- */
28
6
  probeUIManager: () => number;
29
- /**
30
- * Replay one recorded mutation batch — the five fields of `IMutationBatch`, spread, because JSI
31
- * reads five arguments cheaper than it reads five properties off one object.
32
- *
33
- * `ops` is read as MEMORY on the native side and never becomes JS values; the four side tables
34
- * carry what JSI has to marshal either way.
35
- *
36
- * `handles` is the load-bearing one and it travels OUT, not back. It holds the placeholder object
37
- * for every slot the ops address, and the host attaches each created node's
38
- * `shared_ptr<const ShadowNode>` to the object at that slot as JSI `NativeState` — so the objects
39
- * the adapter is already holding become the real handles in place. Nothing is returned.
40
- *
41
- * That is the whole lifetime design, and it is RN's own: `nativeFabricUIManager.createNode()`
42
- * hands back an object whose NativeState owns the node, so Hermes collecting the object is what
43
- * frees it. The version this replaced kept a `Map<int, shared_ptr>` in C++ instead — a second
44
- * owner nothing could tell to let go, which cost 792 -> 1492 MB across one benchmark suite.
45
- */
46
7
  applyOps: (ops: Int32Array, strings: readonly string[], values: readonly unknown[], instanceHandles: readonly unknown[], handles: readonly object[]) => void;
47
- /**
48
- * The four reads of `ITreeHost`, taking the same placeholder object `applyOps` put the node on.
49
- *
50
- * None is on a commit path — they run at GESTURE or lifecycle rate (a host behavior seeing the
51
- * props it reacts to, an app measuring a ref, a framework seam navigating what it just built), so
52
- * the crossing cost is irrelevant. `undefined` / empty is an ordinary answer from all of them: a
53
- * handle native has not seen and a genuinely absent value are indistinguishable here, and both
54
- * degrade.
55
- *
56
- * `getViewName` answers the RESOLVED name, which native may have changed at insert — a `<Text>`
57
- * inside another `<Text>` commits as `RCTVirtualText`, and only the side holding the parent link
58
- * knows.
59
- */
60
8
  getProp: (handle: object, key: string) => unknown;
61
9
  getProps: (handle: object) => Readonly<Record<string, unknown>>;
62
10
  /** The one WRITE among them: dirty a node no op named. See `markPropsDirty` (node.ts). */
@@ -77,63 +25,21 @@ export type INativeEngineBindings = {
77
25
  committedRecordOf: (handle: object) => ICommittedRecord | undefined;
78
26
  /** A TEST read — the payload the last commit sent. See `ITreeHost.committedPayloadOf`. */
79
27
  committedPayloadOf: (handle: object) => Readonly<Record<string, unknown>> | undefined;
80
- /**
81
- * The imperative six, taking the same placeholder object `applyOps` put the node on.
82
- *
83
- * They are here because `nativeFabricUIManager`'s own copies unwrap a handle IT minted, and under
84
- * the batched applier every handle in play was minted by the batching slot. Ours carry the node on
85
- * their `NativeState` exactly as RN's do, so these six are the same code reading a different
86
- * object. An app calling `measure()` on a ref reaches native through here or not at all.
87
- *
88
- * The callback protocol is Fabric's own, not ours: `measure` answers six numbers, `measureInWindow`
89
- * four, and `measureLayout` calls `onFail` when the surface has no committed revision — copied
90
- * from `UIManagerBinding` so a component that already handles those cases keeps working.
91
- */
92
28
  dispatchCommand: (handle: object, commandName: string, args: readonly unknown[]) => void;
93
29
  sendAccessibilityEvent: (handle: object, eventType: string) => void;
94
30
  measure: (handle: object, callback: (x: number, y: number, width: number, height: number, pageX: number, pageY: number) => void) => void;
95
31
  measureInWindow: (handle: object, callback: (x: number, y: number, width: number, height: number) => void) => void;
96
32
  measureLayout: (handle: object, relativeTo: object, onFail: () => void, onSuccess: (x: number, y: number, width: number, height: number) => void) => void;
97
33
  setIsJSResponder: (handle: object, isResponder: boolean, blockNativeResponder: boolean) => void;
98
- /**
99
- * RN's own commit telemetry for ANY surface, including one this host never drove.
100
- *
101
- * OPTIONAL: it is read with `?.`, so a pod predating it degrades instead of throwing, unlike the
102
- * members above that `isBindings` requires. Treat an absent member as "no answer", never as
103
- * zeroes — a zero reads as "React's commit measures no text", the claim this exists to test.
104
- */
105
- /**
106
- * The C++ half's diagnostics (`SymbioteDebug.h`). OPTIONAL, like `readSurfaceTelemetry` and for
107
- * the same reason: an older native binary that predates them must still pass `isBindings` rather
108
- * than failing bring-up over a member nothing on the critical path needs.
109
- */
110
34
  setDebugEnabled?: (enabled: boolean) => void;
111
35
  takeDebugLog?: () => readonly string[];
112
36
  readSurfaceTelemetry?: (surfaceId: number) => {
113
- /**
114
- * The inside of a commit, read out of RN's OWN `TransactionTelemetry` rather than timed by us
115
- * — a `ShadowTreeRevision` carries the telemetry of the commit that produced it.
116
- *
117
- * `layoutNodes` is the one that answers the question the timings only pose: Yoga reports how
118
- * many layoutable nodes it actually touched, so a one-row change that reports the whole tree is
119
- * a re-layout, not a slow layout.
120
- */
121
37
  layoutMs: number;
122
38
  textMs: number;
123
- /**
124
- * `ShadowTree::commit`'s own window, and NOT `materialize`'s — this used to say otherwise, and
125
- * three rounds of investigation read the number that way. `materialize` runs in `kOpCommit`
126
- * before `completeSurface` is called, so it falls outside this window and layout's alike.
127
- */
128
39
  commitMs: number;
129
40
  layoutNodes: number;
130
41
  textMeasures: number;
131
- /**
132
- * Parents that took the targeted-replace path since the last read, zeroed on read. OURS, not
133
- * RN's — a LIVENESS signal for a fast path whose absence no correctness test can see.
134
- */
135
42
  targetedReplaces: number;
136
- /** `materialize`'s own walk and its breakdown — ours, zeroed on read. See `ISurfaceTelemetry`. */
137
43
  walkMs: number;
138
44
  propsMs: number;
139
45
  foldLookupMs: number;
@@ -173,14 +79,6 @@ export type INativeEngineBindings = {
173
79
  declare global {
174
80
  var __symbioteEngineNative: unknown;
175
81
  }
176
- /**
177
- * The native bindings, or `undefined` when this platform has none.
178
- *
179
- * Resolving the TurboModule is done for its SIDE EFFECT: `RCTTurboModuleManager` runs
180
- * `installJSIBindingsWithRuntime:` at the moment it creates a module, so touching the module by name
181
- * is what puts the global there. The module's own methods are not the capability and are not called
182
- * here — which is why a reader looking for the payload in the spec file will not find it.
183
- */
184
82
  export declare function nativeEngine(): INativeEngineBindings | undefined;
185
83
  /** Test seam: forget what was resolved, so a fixture can install or remove the global between cases. */
186
84
  export declare function resetNativeEngine(): void;
@@ -1,61 +1,29 @@
1
- // The JS half of our own native module (item 8c of `symbiote-fabric-cxx-surface`). It answers one
2
- // question — is there a native store on this platform — and every caller must be able to take `no`.
3
- //
4
- // `no` is not an error case, it is the majority case:
5
- //
6
- // headless (vitest, 5 600 tests) no native anything; the JS backing store is the only one
7
- // Android the module is iOS-only for now, by choice, not by oversight
8
- // an app that skipped `pod install` the standing hazard of this repo's local-dev loop
9
- // an older pod against newer JS two artefacts, two install steps, no shared version gate
10
- //
11
- // So the contract is: `nativeEngine()` returns the bindings or `undefined`, and nothing in the engine
12
- // may require them. The native store is an ACCELERATOR behind the same seam the JS one sits behind.
13
- //
14
- // ── THIS IS NOT A LICENCE FOR TWO IMPLEMENTATIONS, AND THE DISTINCTION IS THE POINT ──────────────
15
- //
16
- // Two different things get called "the native path" and only one of them is a fork:
17
- //
18
- // the STORE (8b') `new Int32Array(n)` vs an `Int32Array` over native memory. `node-table.ts`
19
- // is byte-identical either way — one implementation, two allocators. There is
20
- // nothing here that can drift.
21
- // the APPLIER (8c-1) `replayChildOps` in commit.ts vs `cloneMultiple` inside a commit hook. That
22
- // IS two implementations of one fold, and a permanent fallback to the JS half
23
- // would be exactly the silent divergence this repo keeps finding.
24
- //
25
- // So the graceful `undefined` below is correct for the store and TRANSITIONAL for the applier. When
26
- // the applier moves, the JS one is DELETED — the way `node.children` and `node.parent` were — and a
27
- // missing or mismatched module has to fail LOUD rather than quietly run a second implementation. A
28
- // silent fallback at that point is a measurement that lies, not resilience.
29
- //
30
- // The expensive part of that transition, named here because it is easy to discover too late: ~5 600
31
- // headless tests drive the JS applier against a fake Fabric slot. Faking `new Int32Array` is free;
32
- // faking C++ tree cloning is not, so those tests stop covering what actually runs.
33
- //
34
- // Fantom was the intended answer and it is NOT available — measured 2026-09-08, four independent
35
- // blockers. `@react-native/fantom` is not on npm at all (404); it exists only as vendored source
36
- // under `.vendors/react-native/private/`, marked `private: true`. That vendored tree is RN **main**
37
- // while this repo consumes 0.86.0, so a tester built from it would exercise the wrong C++. Building
38
- // it needs a yarn install plus a gradle/CMake build of Hermes, folly and ReactCommon, all written
39
- // INTO `.vendors/`. And `config/metro.config.js` computes `projectRoot` from `__dirname` with an
40
- // empty `watchFolders`, so a test file outside that monorepo cannot be resolved — not overridable
41
- // by env, config merge or CLI. RN's own README says it plainly: "not currently supported for
42
- // testing application-specific code in React Native apps."
43
- //
44
- // So the oracle for a C++ commit path has to be built here: a TypeScript reference implementation
1
+ // The JS half of our own native module. It answers one question — is there a native store on this
2
+ // platform — and every caller must be able to take no. no is the majority case: headless tests,
3
+ // Android (iOS-only for now), a skipped pod install, or a pod/JS version mismatch.
4
+ // nativeEngine() returns the bindings or undefined, and nothing in the engine may require them —
5
+ // the native store is an accelerator behind the same seam the JS one sits behind.
6
+ // Not a licence for two implementations, though. The store (Int32Array vs. native memory) is
7
+ // byte-identical either way, one implementation two allocators — nothing here can drift. The
8
+ // applier (replayChildOps vs. cloneMultiple) IS two implementations of one fold.
9
+ // So undefined below is correct for the store and transitional for the applier: when the applier
10
+ // moves, the JS one is deleted, and a missing or mismatched module must fail loud rather than
11
+ // quietly run a second implementation — a silent fallback then would be a measurement that lies.
12
+ // Fantom (RN's own headless C++ test runner) is not a usable oracle for this: not published to
13
+ // npm, vendored from RN main against a repo pinned to 0.86, needing a from-scratch Hermes/folly/
14
+ // ReactCommon build, and its own README says it isn't supported for app-specific test code yet.
15
+ // So the oracle for a C++ commit path is built here instead: a TypeScript reference implementation
45
16
  // the existing suite already exercises, plus a gtest target linking ReactCommon from the installed
46
- // react-native. Do not cite Fantom as the plan again without re-running that check.
17
+ // react-native.
47
18
  import { dlog } from './debug.js';
48
19
  import { getNativeModule } from './native-modules/index.js';
49
20
  import { isRecord } from './type-guards.js';
50
- /**
51
- * The ABI this file knows how to talk to. A binary reporting anything else is refused outright rather
52
- * than probed method by method — the two artefacts ship separately (a pod and an npm package), so
53
- * disagreement is routine, and a partial match is the shape that corrupts memory quietly.
54
- *
55
- * Exported for the tests, which must DERIVE their supported and unsupported arms from it rather than
56
- * restate the number: a bump that leaves a fixture behind reads as "the binary is stale", which is
57
- * exactly the message this constant exists to produce, so the failure looks like the feature working.
58
- */
21
+ // The ABI this file knows how to talk to. A binary reporting anything else is refused outright
22
+ // rather than probed method by method — the two artefacts ship separately (a pod and an npm
23
+ // package), so disagreement is routine and a partial match corrupts memory quietly.
24
+ // Exported for the tests, which must derive their supported/unsupported arms from it rather than
25
+ // restate the number: a bump that leaves a fixture behind reads as "the binary is stale", which is
26
+ // exactly the message this constant exists to produce.
59
27
  export const SUPPORTED_NATIVE_VERSION = 4;
60
28
  function isBindings(value) {
61
29
  if (!isRecord(value))
@@ -68,10 +36,9 @@ function isBindings(value) {
68
36
  return false;
69
37
  if (typeof value.applyOps !== 'function')
70
38
  return false;
71
- // The tree reads, checked one by one rather than as a blob for the reason the whole guard is
72
- // written this way: a member added to the type without a line here resolves fine at bring-up and
73
- // throws at the first call site, which is a gesture or a `measure()` — one language and several
74
- // seconds away from the install that caused it.
39
+ // The tree reads, checked one by one: a member added to the type without a line here resolves
40
+ // fine at bring-up and throws at the first call site — a gesture or a measure() — one language
41
+ // and several seconds away from the install that caused it.
75
42
  if (typeof value.getProp !== 'function')
76
43
  return false;
77
44
  if (typeof value.getProps !== 'function')
@@ -98,9 +65,8 @@ function isBindings(value) {
98
65
  return false;
99
66
  if (typeof value.committedRecordOf !== 'function')
100
67
  return false;
101
- // The imperative six, checked by name for the same reason as the rest: a pod that has `applyOps`
102
- // but not these is an OLDER binary, and accepting it would leave `measure()` reaching a method
103
- // that is not there — at the moment an app measures a ref, not at bring-up.
68
+ // The imperative six, checked by name: a pod with applyOps but not these is an older binary,
69
+ // and accepting it means measure() reaches a missing method at gesture time, not bring-up.
104
70
  if (typeof value.dispatchCommand !== 'function')
105
71
  return false;
106
72
  if (typeof value.sendAccessibilityEvent !== 'function')
@@ -113,45 +79,20 @@ function isBindings(value) {
113
79
  return false;
114
80
  return typeof value.setIsJSResponder === 'function';
115
81
  }
116
- // Why `SUPPORTED_NATIVE_VERSION` did NOT move when `probeUIManager` was added, since bumping it is
117
- // the reflex. The version field guards a MEMORY LAYOUT — it is what stops JS reading a store whose
118
- // fields moved. Adding a host function changes no layout, and the shape guard above already refuses
119
- // an older pod that lacks the name, giving the same clean degrade for free. Bumping instead would
120
- // cost every app on an older pod its native STORE to announce a probe it does not use. Bump the
121
- // version when the meaning of the bytes changes, not when the object gains a key.
122
- //
123
- // ── AND WHY IT DID MOVE, 1 -> 2, FOR THE HANDLE REWRITE ──────────────────────────────────────────
124
- //
125
- // Because the shape guard cannot see this one. Every NAME is unchanged: a v1 pod has `applyOps` and
126
- // all five, so `isBindings` passes it. What changed is what the arguments MEAN — `applyOps` gained
127
- // `handles`, which a v1 binary silently ignores, and the five went from taking an integer id to
128
- // taking the handle object, which a v1 binary reads with `.asNumber()`. The first fails by
129
- // committing nothing and leaking; the second throws one language away from the cause. That is the
130
- // same class as a moved field, arriving through a calling convention rather than a struct, and it is
131
- // exactly what this number exists to refuse.
132
- //
133
- // ── AND 3 -> 4, FOR THE SAME REASON THE SHAPE GUARD CANNOT HELP WITH ─────────────────────────────
134
- //
135
- // The four tree reads are new NAMES, so the guard above would turn a v3 pod away on its own. But
136
- // `applyOps` also went from six arguments to five, and the name did not move: a v3 binary reads
137
- // argument 1 as `childIds` (an `Int32Array`) where JS now passes `strings`, and argument 2 as a
138
- // props table where JS now passes `values`. Every one of those reads succeeds against the wrong
139
- // object and commits a wrong tree rather than throwing. That is a calling convention change, which
140
- // is what this number is for — the guard would have refused this pod by luck, on the reads, and a
141
- // refusal by luck is not a refusal.
82
+ // SUPPORTED_NATIVE_VERSION guards a memory layout, not a member list: the shape guard above
83
+ // already refuses an older pod lacking a new function's name, so bumping for that alone would
84
+ // cost every app on an older pod its native store for nothing.
85
+ // Bump only when the meaning of existing bytes or arguments changes: a calling-convention change
86
+ // the shape guard can't see, since names stay the same while what they mean does not — every read
87
+ // still resolves to a value, just the wrong one, committing a wrong tree rather than throwing.
142
88
  // `undefined` means "resolved, and there is none" — distinct from `resolved === false`, which means
143
89
  // nobody has looked. Collapsing the two would re-run the lookup on every miss, and the miss is the
144
90
  // common case.
145
91
  let resolved = false;
146
92
  let bindings;
147
- /**
148
- * The native bindings, or `undefined` when this platform has none.
149
- *
150
- * Resolving the TurboModule is done for its SIDE EFFECT: `RCTTurboModuleManager` runs
151
- * `installJSIBindingsWithRuntime:` at the moment it creates a module, so touching the module by name
152
- * is what puts the global there. The module's own methods are not the capability and are not called
153
- * here — which is why a reader looking for the payload in the spec file will not find it.
154
- */
93
+ // The native bindings, or undefined when this platform has none. Resolving the TurboModule is
94
+ // done for its side effect: RCTTurboModuleManager runs installJSIBindingsWithRuntime: at the
95
+ // moment it creates a module, so touching the module by name is what puts the global there.
155
96
  export function nativeEngine() {
156
97
  if (resolved)
157
98
  return bindings;
@@ -164,9 +105,8 @@ export function nativeEngine() {
164
105
  return undefined;
165
106
  }
166
107
  if (installed.version !== SUPPORTED_NATIVE_VERSION) {
167
- // Loud, because the repair is a `pod install` and the symptom otherwise is a wrong memory layout
168
- // rather than a missing feature. `<examples_vs_dot_examples>` records how routinely an install
169
- // replaces the JS half without the native one.
108
+ // Loud, because the repair is a pod install and the symptom otherwise is a wrong memory
109
+ // layout rather than a missing feature — an install routinely replaces the JS half alone.
170
110
  dlog(`native-engine: REFUSING native store, ABI ${installed.version} ` +
171
111
  `against supported ${SUPPORTED_NATIVE_VERSION} — run \`pod install\`. Using the JS arrays`);
172
112
  return undefined;
@@ -1,12 +1,6 @@
1
- // native -> JS: receiving native module events. The native side emits ALL device
2
- // events by invoking ONE callable JS module under the fixed name
3
- // `RCTDeviceEventEmitter`. On a real RN host that name is already owned by RN's own
4
- // RCTDeviceEventEmitter, and `RN$registerCallableModule` cannot steal it: native's
5
- // `callableModules_.emplace` ignores a duplicate key (ReactInstance.cpp), so a
6
- // second registration is a silent no-op. So we do NOT register our own hub on a
7
- // real host: the app injects RN's DeviceEventEmitter (the bus native actually
8
- // calls) via `setDeviceEventSource`, exactly like setColorProcessor. The built-in
9
- // hub below stays as the fallback bus for headless/non-RN runs.
1
+ // native -> JS: receiving native module events, all under one callable JS module name. A real
2
+ // host already owns that name, so a second registration is a no-op — the app injects RN's
3
+ // DeviceEventEmitter instead via setDeviceEventSource; below is the fallback for headless runs.
10
4
  import { dlog } from './debug.js';
11
5
  import { runWrapped } from './dispatch.js';
12
6
  import { invariant } from './invariant.js';
@@ -19,10 +13,9 @@ function emit(eventType, ...args) {
19
13
  dlog(`device hub emit "${eventType}" -> ${set?.size ?? 0} listener(s)`);
20
14
  if (set === undefined)
21
15
  return;
22
- // A device event arrives outside the framework's update loop; route the fan-out
23
- // through the shared dispatch wrapper so a listener's setState lands on the sync
24
- // lane and flushes, the same seam Fabric touch events use. Snapshot before
25
- // iterating: a listener may remove itself mid-dispatch.
16
+ // Route through the shared dispatch wrapper so a listener's setState flushes on the sync
17
+ // lane, the same seam Fabric touch events use. Snapshot first: a listener may remove
18
+ // itself mid-dispatch.
26
19
  const snapshot = [...set];
27
20
  runWrapped(() => {
28
21
  for (const listener of snapshot)
@@ -65,11 +58,9 @@ let injectedSource;
65
58
  export function setDeviceEventSource(source) {
66
59
  injectedSource = source;
67
60
  }
68
- // True only when the module actually carries both observe-counter methods. A
69
- // resolved TurboModule whose spec omits addListener/removeListeners (or a host where
70
- // the module isn't a real event emitter) leaves them undefined, so calling through
71
- // would throw "undefined is not a function" — the `?.` guards the module, not a
72
- // missing method. Mirrors RN's NativeEventEmitter constructor probe.
61
+ // True only when the module carries both observe-counter methods. A spec that omits
62
+ // addListener/removeListeners leaves them undefined, so calling through would throw —
63
+ // the guard here protects the module, not a missing method. Mirrors RN's constructor probe.
73
64
  function hasObserveCounters(module) {
74
65
  return (typeof module.addListener === 'function' &&
75
66
  typeof module.removeListeners === 'function');
@@ -1,25 +1,4 @@
1
1
  import { type INativeEngineBindings } from './native-engine';
2
2
  import { type ITreeHost } from './tree-host';
3
- /**
4
- * Present one resolved set of bindings as the engine's tree host.
5
- *
6
- * `census` is the only member that does not reach native, and it is deliberately not on the ABI: it
7
- * has exactly ONE engine caller (`censusRetainedTree`), it is diagnostics, and answering it honestly
8
- * would cost a full native walk of the very tree the design exists to stop walking. The empty census
9
- * is what `censusRetainedTree` already answers with no host at all, so a probe reading it sees the
10
- * same zeroes it has always seen off a device.
11
- */
12
3
  export declare function nativeTreeHost(bindings: INativeEngineBindings): ITreeHost;
13
- /**
14
- * Install it, if this runtime has a native module and nothing has claimed the seam already.
15
- *
16
- * PRECEDENCE: an installed host WINS. `installFabric()` is the only other caller of `setTreeHost`,
17
- * and it puts the TypeScript applier in before any fixture can bind a slot — so a headless run that
18
- * also happens to carry fake bindings must keep the applier, or the ~5 500 tests written against it
19
- * would silently start driving a stub. The reverse ordering cannot occur on a device: nothing there
20
- * installs a host but this.
21
- *
22
- * No native module is the ORDINARY answer (`native-engine.ts`'s header lists where), and it stays a
23
- * quiet one here: the ops simply keep accumulating, exactly as they did before this file existed.
24
- */
25
4
  export declare function installNativeTreeHost(): void;
@@ -1,24 +1,14 @@
1
- // The NATIVE tree host — `INativeEngineBindings` presented as an `ITreeHost`.
2
- //
3
- // It is the half that makes the buffer mean anything on a device. `tree-host.ts` records ops and
4
- // then asks a host to turn them into a tree; headlessly `installFabric()` installs the TypeScript
5
- // applier, and until this file existed a device installed NOTHING — `commitSurfaceOps` returned
6
- // early, the ops stayed pending forever, and the screen stayed blank with nothing red anywhere.
7
- //
8
- // There is no logic here on purpose. Everything the host is asked is something native already
9
- // answers, so this is a rename and an argument spread; a mapping thin enough to read in one pass is
10
- // what keeps the two sides auditable against each other.
1
+ // The NATIVE tree host — `INativeEngineBindings` presented as an `ITreeHost`. It is the half that
2
+ // makes the buffer mean anything on a device: `tree-host.ts` records ops and asks a host to turn
3
+ // them into a tree; headlessly, `installFabric()` installs the TS applier instead.
4
+ // No logic here on purpose: everything the host is asked is something native already answers, so
5
+ // this stays a rename plus an argument spread, thin enough to audit against native's own side.
11
6
  import { nativeEngine } from './native-engine.js';
12
7
  import { EMPTY_CENSUS, setTreeHost, treeHost, } from './tree-host.js';
13
- /**
14
- * Present one resolved set of bindings as the engine's tree host.
15
- *
16
- * `census` is the only member that does not reach native, and it is deliberately not on the ABI: it
17
- * has exactly ONE engine caller (`censusRetainedTree`), it is diagnostics, and answering it honestly
18
- * would cost a full native walk of the very tree the design exists to stop walking. The empty census
19
- * is what `censusRetainedTree` already answers with no host at all, so a probe reading it sees the
20
- * same zeroes it has always seen off a device.
21
- */
8
+ // Present one resolved set of bindings as the engine's tree host.
9
+ // `census` is the only member that never reaches native — deliberately not on the ABI, since its
10
+ // one caller (censusRetainedTree) is diagnostics and answering honestly costs a full native walk
11
+ // of the tree the design exists to avoid walking. The empty census matches the no-host answer.
22
12
  export function nativeTreeHost(bindings) {
23
13
  return {
24
14
  // Spread rather than passed as the batch object: JSI reads five arguments cheaper than five
@@ -48,18 +38,11 @@ export function nativeTreeHost(bindings) {
48
38
  setIsJSResponder: bindings.setIsJSResponder,
49
39
  };
50
40
  }
51
- /**
52
- * Install it, if this runtime has a native module and nothing has claimed the seam already.
53
- *
54
- * PRECEDENCE: an installed host WINS. `installFabric()` is the only other caller of `setTreeHost`,
55
- * and it puts the TypeScript applier in before any fixture can bind a slot — so a headless run that
56
- * also happens to carry fake bindings must keep the applier, or the ~5 500 tests written against it
57
- * would silently start driving a stub. The reverse ordering cannot occur on a device: nothing there
58
- * installs a host but this.
59
- *
60
- * No native module is the ORDINARY answer (`native-engine.ts`'s header lists where), and it stays a
61
- * quiet one here: the ops simply keep accumulating, exactly as they did before this file existed.
62
- */
41
+ // Install it, if this runtime has a native module and nothing has claimed the seam already.
42
+ // An installed host wins: installFabric() (the only other setTreeHost caller) puts the TS applier
43
+ // in first, so a headless run carrying fake bindings must keep the applier, not the stub.
44
+ // No native module is the ordinary answer (see native-engine.ts); ops then simply keep
45
+ // accumulating, undelivered.
63
46
  export function installNativeTreeHost() {
64
47
  const bindings = nativeEngine();
65
48
  if (bindings === undefined || treeHost() !== undefined)