sitepong 0.2.9 → 0.2.11

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.
@@ -802,6 +802,7 @@ declare class StructuralCapture {
802
802
  private flushing;
803
803
  private readonly scrollOffsets;
804
804
  private overloadSkip;
805
+ private explicitRoot;
805
806
  private appStateSub;
806
807
  private readonly teardowns;
807
808
  private now;
@@ -812,6 +813,13 @@ declare class StructuralCapture {
812
813
  private installCrashFlush;
813
814
  /** Flush on background/inactive so timer suspension doesn't strand the tail. */
814
815
  private installBackgroundFlush;
816
+ /**
817
+ * Acquire the FiberRoot from the provider's root-View public instance (a ref).
818
+ * Called once on provider mount. This makes capture independent of the React
819
+ * DevTools commit hook, which Release builds strip — without it the fiber walk
820
+ * never runs in production and only the initial viewport message is emitted.
821
+ */
822
+ setRootInstance(instance: unknown): void;
815
823
  private sample;
816
824
  setScreen(name: string): void;
817
825
  /** Resolve a native touch to a node + emit a tap. `target` is the RN
@@ -3430,33 +3430,35 @@ function attrDiff(id, prev, next) {
3430
3430
  }
3431
3431
  function diff(prev, next, mirror) {
3432
3432
  const { next: nextIndex, order } = indexTree(next, mirror);
3433
- const messages = [];
3434
- const removed = /* @__PURE__ */ new Set();
3435
- for (const id of prev.keys()) if (!nextIndex.has(id)) removed.add(id);
3436
- for (const id of removed) {
3437
- const rec = prev.get(id);
3438
- if (!removed.has(rec.parentId)) messages.push(RemoveNode(id));
3439
- }
3433
+ const creates = [];
3434
+ const updates = [];
3440
3435
  for (const id of order) {
3441
3436
  const rec = nextIndex.get(id);
3442
3437
  const before = prev.get(id);
3443
3438
  if (!before) {
3444
3439
  if (rec.tag === "#text") {
3445
- messages.push(CreateTextNode(id, rec.parentId, rec.index));
3440
+ creates.push(CreateTextNode(id, rec.parentId, rec.index));
3446
3441
  } else {
3447
- messages.push(CreateElementNode(id, rec.parentId, rec.index, rec.tag));
3442
+ creates.push(CreateElementNode(id, rec.parentId, rec.index, rec.tag));
3448
3443
  }
3449
- for (const k of Object.keys(rec.attrs)) messages.push(SetNodeAttribute(id, k, rec.attrs[k]));
3450
- if (rec.text !== void 0 && rec.text !== "") messages.push(SetNodeData(id, rec.text));
3444
+ for (const k of Object.keys(rec.attrs)) creates.push(SetNodeAttribute(id, k, rec.attrs[k]));
3445
+ if (rec.text !== void 0 && rec.text !== "") creates.push(SetNodeData(id, rec.text));
3451
3446
  } else {
3452
3447
  if (before.parentId !== rec.parentId || before.index !== rec.index) {
3453
- messages.push(MoveNode(id, rec.parentId, rec.index));
3448
+ updates.push(MoveNode(id, rec.parentId, rec.index));
3454
3449
  }
3455
- messages.push(...attrDiff(id, before.attrs, rec.attrs));
3456
- if ((before.text ?? "") !== (rec.text ?? "")) messages.push(SetNodeData(id, rec.text ?? ""));
3450
+ updates.push(...attrDiff(id, before.attrs, rec.attrs));
3451
+ if ((before.text ?? "") !== (rec.text ?? "")) updates.push(SetNodeData(id, rec.text ?? ""));
3457
3452
  }
3458
3453
  }
3459
- return { messages, index: nextIndex };
3454
+ const removed = /* @__PURE__ */ new Set();
3455
+ for (const id of prev.keys()) if (!nextIndex.has(id)) removed.add(id);
3456
+ const removes = [];
3457
+ for (const id of removed) {
3458
+ const rec = prev.get(id);
3459
+ if (!removed.has(rec.parentId)) removes.push(RemoveNode(id));
3460
+ }
3461
+ return { messages: [...creates, ...updates, ...removes], index: nextIndex };
3460
3462
  }
3461
3463
  function stableAnchor(step) {
3462
3464
  if (step.attrs.testID) return `@${step.attrs.testID}`;
@@ -3920,6 +3922,16 @@ function walkFiberRoot(root) {
3920
3922
  const tree = { key: rootKey, tag: "View", attrs: { style: JSON.stringify({ flex: 1 }) }, children };
3921
3923
  return { tree, index };
3922
3924
  }
3925
+ function fiberRootFromInstance(instance) {
3926
+ if (!instance || typeof instance !== "object") return null;
3927
+ const inst = instance;
3928
+ const fiber = inst.__internalInstanceHandle ?? inst._internalFiberInstanceHandleDEV ?? null;
3929
+ if (!fiber || typeof fiber !== "object") return null;
3930
+ let top = fiber;
3931
+ for (let guard = 0; top.return && guard < 1e5; guard++) top = top.return;
3932
+ const fiberRoot = top.stateNode;
3933
+ return fiberRoot && typeof fiberRoot === "object" && fiberRoot.current ? fiberRoot : null;
3934
+ }
3923
3935
 
3924
3936
  // src/react-native/structural/capture.ts
3925
3937
  var WALK_BUDGET_MS = 24;
@@ -3950,6 +3962,14 @@ var StructuralCapture = class {
3950
3962
  this.scrollOffsets = /* @__PURE__ */ new Map();
3951
3963
  // Adaptive sampling: skip this many upcoming ticks after a heavy walk.
3952
3964
  this.overloadSkip = 0;
3965
+ // FiberRoot acquired from the provider's root-View ref (fiber.ts). This is the
3966
+ // production path: Release builds strip the React DevTools commit hook, so
3967
+ // `hookState.latestRoot` never populates and hook-based capture emits only the
3968
+ // initial viewport message. The FiberRoot persists for the app lifetime and
3969
+ // walkFiberRoot reads its live `.current` each sample, so we hold it here and
3970
+ // walk every tick (no `dirty` signal exists without the hook).
3971
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
3972
+ this.explicitRoot = null;
3953
3973
  this.appStateSub = null;
3954
3974
  this.teardowns = [];
3955
3975
  }
@@ -3972,6 +3992,7 @@ var StructuralCapture = class {
3972
3992
  this.sentCount = 0;
3973
3993
  this.scrollOffsets.clear();
3974
3994
  this.overloadSkip = 0;
3995
+ this.explicitRoot = null;
3975
3996
  const { width, height } = reactNative.Dimensions.get("window");
3976
3997
  this.recorder.setViewport(Math.round(width), Math.round(height), 0);
3977
3998
  this.sampleTimer = setInterval(() => this.sample(), cfg.sampleIntervalMs ?? 120);
@@ -4004,15 +4025,31 @@ var StructuralCapture = class {
4004
4025
  this.appStateSub = null;
4005
4026
  });
4006
4027
  }
4028
+ /**
4029
+ * Acquire the FiberRoot from the provider's root-View public instance (a ref).
4030
+ * Called once on provider mount. This makes capture independent of the React
4031
+ * DevTools commit hook, which Release builds strip — without it the fiber walk
4032
+ * never runs in production and only the initial viewport message is emitted.
4033
+ */
4034
+ setRootInstance(instance) {
4035
+ const root = fiberRootFromInstance(instance);
4036
+ if (root) {
4037
+ this.explicitRoot = root;
4038
+ if (this.cfg?.debug) console.log("[structural] fiber root acquired via ref");
4039
+ this.sample(true);
4040
+ } else if (this.cfg?.debug) {
4041
+ console.log("[structural] ref did not yield a fiber root \u2014 relying on the commit hook (dev only)");
4042
+ }
4043
+ }
4007
4044
  sample(force = false) {
4008
4045
  if (!this.running) return;
4009
4046
  if (!force && this.overloadSkip > 0) {
4010
4047
  this.overloadSkip--;
4011
4048
  return;
4012
4049
  }
4013
- if (!force && !hookState.dirty) return;
4050
+ if (!force && !this.explicitRoot && !hookState.dirty) return;
4014
4051
  hookState.dirty = false;
4015
- const root = hookState.latestRoot;
4052
+ const root = hookState.latestRoot ?? this.explicitRoot;
4016
4053
  if (!root) return;
4017
4054
  const t0 = Date.now();
4018
4055
  try {
@@ -4122,6 +4159,7 @@ var StructuralCapture = class {
4122
4159
  this.sampleTimer = null;
4123
4160
  this.flushTimer = null;
4124
4161
  this.running = false;
4162
+ this.explicitRoot = null;
4125
4163
  for (const t of this.teardowns.splice(0)) {
4126
4164
  try {
4127
4165
  t();
@@ -4738,9 +4776,32 @@ var TAP_MOVE_THRESHOLD_PX = 12;
4738
4776
  function StructuralCaptureProvider({ children, ...cfg }) {
4739
4777
  const cfgRef = React2.useRef(cfg);
4740
4778
  cfgRef.current = cfg;
4779
+ const rootRef = React2.useRef(null);
4741
4780
  React2.useEffect(() => {
4742
- structuralCapture.start(cfgRef.current);
4781
+ let cancelled = false;
4782
+ let poll = null;
4783
+ const begin = (sessionId) => {
4784
+ if (cancelled) return;
4785
+ structuralCapture.start({ ...cfgRef.current, sessionId });
4786
+ structuralCapture.setRootInstance(rootRef.current);
4787
+ };
4788
+ const explicit = cfgRef.current.sessionId;
4789
+ if (explicit) {
4790
+ begin(explicit);
4791
+ } else {
4792
+ let tries = 0;
4793
+ const resolve = () => {
4794
+ if (cancelled) return;
4795
+ const wt = getWatchtowerSessionId();
4796
+ if (wt) return begin(wt);
4797
+ if (++tries >= 25) return begin(void 0);
4798
+ poll = setTimeout(resolve, 200);
4799
+ };
4800
+ resolve();
4801
+ }
4743
4802
  return () => {
4803
+ cancelled = true;
4804
+ if (poll) clearTimeout(poll);
4744
4805
  void structuralCapture.stop();
4745
4806
  };
4746
4807
  }, []);
@@ -4781,7 +4842,7 @@ function StructuralCaptureProvider({ children, ...cfg }) {
4781
4842
  }
4782
4843
  structuralCapture.handleTap(g2.target, ne.pageX, ne.pageY);
4783
4844
  };
4784
- return /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: { flex: 1 }, onTouchStart, onTouchMove, onTouchEnd, children });
4845
+ return /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { ref: rootRef, style: { flex: 1 }, onTouchStart, onTouchMove, onTouchEnd, children });
4785
4846
  }
4786
4847
  function useStructuralScreen(name) {
4787
4848
  React2.useEffect(() => {