@goliapkg/sentori-react-native 5.1.2 → 5.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/android/src/main/java/com/sentori/SentoriReplayCapture.kt +77 -49
- package/ios/SentoriReplayCapture.swift +26 -8
- package/lib/replay.d.ts +32 -4
- package/lib/replay.d.ts.map +1 -1
- package/lib/replay.js +33 -13
- package/lib/replay.js.map +1 -1
- package/lib/transport.js +1 -1
- package/package.json +1 -1
- package/src/__tests__/replay-encoding.test.ts +30 -3
- package/src/replay.ts +50 -18
- package/src/transport.ts +1 -1
|
@@ -120,7 +120,8 @@ object SentoriReplayCapture {
|
|
|
120
120
|
val nodes = JSONArray()
|
|
121
121
|
val rootLoc = IntArray(2).also { root.getLocationInWindow(it) }
|
|
122
122
|
val ctx = WalkContext(rootLoc = rootLoc, maskedSet = maskedSet)
|
|
123
|
-
|
|
123
|
+
val rootClip = android.graphics.Rect(0, 0, root.width, root.height)
|
|
124
|
+
walk(root, depth = 0, parentMasked = false, ctx = ctx, clip = rootClip, nodes = nodes)
|
|
124
125
|
|
|
125
126
|
val payload = JSONObject().apply {
|
|
126
127
|
put("ts", System.currentTimeMillis())
|
|
@@ -173,6 +174,7 @@ object SentoriReplayCapture {
|
|
|
173
174
|
depth: Int,
|
|
174
175
|
parentMasked: Boolean,
|
|
175
176
|
ctx: WalkContext,
|
|
177
|
+
clip: android.graphics.Rect,
|
|
176
178
|
nodes: JSONArray,
|
|
177
179
|
) {
|
|
178
180
|
if (nodes.length() >= MAX_NODES) return
|
|
@@ -188,68 +190,94 @@ object SentoriReplayCapture {
|
|
|
188
190
|
val w = view.width
|
|
189
191
|
val h = view.height
|
|
190
192
|
|
|
193
|
+
// Insight 2026-08-01: nothing was clipped here at all — a
|
|
194
|
+
// scroll container's full content width shipped as-is and
|
|
195
|
+
// drew past the device in the replay. `clip` accumulates
|
|
196
|
+
// every clipping ancestor (clipChildren defaults to true, so
|
|
197
|
+
// scroll viewports narrow it); a node ships as its visible
|
|
198
|
+
// intersection, an invisible one not at all. This is the
|
|
199
|
+
// root-cause fix the 05-18 decorView→content anchor swap
|
|
200
|
+
// only sidestepped.
|
|
201
|
+
var childClip = clip
|
|
202
|
+
|
|
191
203
|
// Emit a node ONLY when the view has visual extent. A zero-
|
|
192
204
|
// size view contributes nothing to render but its subtree
|
|
193
|
-
// might; recurse below regardless
|
|
205
|
+
// might; recurse below regardless (and without narrowing the
|
|
206
|
+
// clip — transient zero-size Fabric wrappers do not really
|
|
207
|
+
// clip their laid-out descendants).
|
|
194
208
|
if (w > 0 && h > 0) {
|
|
195
209
|
val loc = IntArray(2)
|
|
196
210
|
view.getLocationInWindow(loc)
|
|
197
211
|
val x = loc[0] - ctx.rootLoc[0]
|
|
198
212
|
val y = loc[1] - ctx.rootLoc[1]
|
|
199
213
|
|
|
200
|
-
val
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
214
|
+
val selfRect = android.graphics.Rect(x, y, x + w, y + h)
|
|
215
|
+
val visible = android.graphics.Rect(selfRect)
|
|
216
|
+
if (!visible.intersect(clip)) {
|
|
217
|
+
// Fully outside every clipping ancestor: the view
|
|
218
|
+
// shows nothing, and if it clips its own children the
|
|
219
|
+
// whole subtree is gone with it.
|
|
220
|
+
if (view is ViewGroup && view.clipChildren) return
|
|
221
|
+
visible.setEmpty()
|
|
205
222
|
}
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
val text = view.text.toString().let { if (it.length > 200) it.substring(0, 200) else it }
|
|
216
|
-
node.put("text", text)
|
|
217
|
-
node.put("color", colorToHex(view.currentTextColor))
|
|
218
|
-
kindEmitted = true
|
|
219
|
-
}
|
|
220
|
-
view is EditText -> {
|
|
221
|
-
node.put("kind", "text")
|
|
222
|
-
val text = (view.text ?: "").toString().let { if (it.length > 200) it.substring(0, 200) else it }
|
|
223
|
-
node.put("text", text)
|
|
224
|
-
kindEmitted = true
|
|
225
|
-
}
|
|
226
|
-
view is ImageView -> {
|
|
227
|
-
node.put("kind", "image")
|
|
228
|
-
kindEmitted = true
|
|
223
|
+
if (view is ViewGroup && view.clipChildren) {
|
|
224
|
+
childClip = android.graphics.Rect(visible)
|
|
225
|
+
}
|
|
226
|
+
if (!visible.isEmpty) {
|
|
227
|
+
val node = JSONObject().apply {
|
|
228
|
+
put("x", visible.left)
|
|
229
|
+
put("y", visible.top)
|
|
230
|
+
put("w", visible.width())
|
|
231
|
+
put("h", visible.height())
|
|
229
232
|
}
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
233
|
+
|
|
234
|
+
var kindEmitted = false
|
|
235
|
+
when {
|
|
236
|
+
masked -> {
|
|
237
|
+
node.put("kind", "mask")
|
|
238
|
+
kindEmitted = true
|
|
239
|
+
}
|
|
240
|
+
view is TextView && !view.text.isNullOrEmpty() -> {
|
|
241
|
+
node.put("kind", "text")
|
|
242
|
+
val text = view.text.toString().let { if (it.length > 200) it.substring(0, 200) else it }
|
|
243
|
+
node.put("text", text)
|
|
244
|
+
node.put("color", colorToHex(view.currentTextColor))
|
|
245
|
+
kindEmitted = true
|
|
246
|
+
}
|
|
247
|
+
view is EditText -> {
|
|
248
|
+
node.put("kind", "text")
|
|
249
|
+
val text = (view.text ?: "").toString().let { if (it.length > 200) it.substring(0, 200) else it }
|
|
250
|
+
node.put("text", text)
|
|
251
|
+
kindEmitted = true
|
|
252
|
+
}
|
|
253
|
+
view is ImageView -> {
|
|
239
254
|
node.put("kind", "image")
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
255
|
+
kindEmitted = true
|
|
256
|
+
}
|
|
257
|
+
view.background != null -> {
|
|
258
|
+
// rc.8 — backgrounds backed by a BitmapDrawable
|
|
259
|
+
// (a View whose backgroundImage / drawable
|
|
260
|
+
// resource is a raster) emit as `image` kind so
|
|
261
|
+
// the dashboard renders them as a media region,
|
|
262
|
+
// not as a grey rect. Everything else stays
|
|
263
|
+
// `rect` and tries to extract a fill colour.
|
|
264
|
+
val bg = view.background
|
|
265
|
+
if (bg is BitmapDrawable) {
|
|
266
|
+
node.put("kind", "image")
|
|
267
|
+
} else {
|
|
268
|
+
node.put("kind", "rect")
|
|
269
|
+
val color = extractDrawableColor(bg)
|
|
270
|
+
if (color != null && (color shr 24 and 0xff) != 0) {
|
|
271
|
+
node.put("color", colorToHex(color))
|
|
272
|
+
}
|
|
245
273
|
}
|
|
274
|
+
kindEmitted = true
|
|
246
275
|
}
|
|
247
|
-
kindEmitted = true
|
|
248
276
|
}
|
|
249
|
-
}
|
|
250
277
|
|
|
251
|
-
|
|
252
|
-
|
|
278
|
+
if (kindEmitted) {
|
|
279
|
+
nodes.put(node)
|
|
280
|
+
}
|
|
253
281
|
}
|
|
254
282
|
}
|
|
255
283
|
|
|
@@ -257,7 +285,7 @@ object SentoriReplayCapture {
|
|
|
257
285
|
// descendants (the rc.3 fix).
|
|
258
286
|
if (!masked && view is ViewGroup) {
|
|
259
287
|
for (i in 0 until view.childCount) {
|
|
260
|
-
walk(view.getChildAt(i), depth + 1, masked, ctx, nodes)
|
|
288
|
+
walk(view.getChildAt(i), depth + 1, masked, ctx, childClip, nodes)
|
|
261
289
|
}
|
|
262
290
|
}
|
|
263
291
|
}
|
|
@@ -74,6 +74,7 @@ import UIKit
|
|
|
74
74
|
parentMasked: false,
|
|
75
75
|
maskedIds: maskedIds,
|
|
76
76
|
window: window,
|
|
77
|
+
clip: window.bounds,
|
|
77
78
|
nodes: &nodes
|
|
78
79
|
)
|
|
79
80
|
lastDiagNodes = nodes.count
|
|
@@ -195,6 +196,7 @@ import UIKit
|
|
|
195
196
|
parentMasked: Bool,
|
|
196
197
|
maskedIds: Set<String>,
|
|
197
198
|
window: UIWindow,
|
|
199
|
+
clip: CGRect,
|
|
198
200
|
nodes: inout [[String: Any]]
|
|
199
201
|
) {
|
|
200
202
|
if nodes.count >= MAX_NODES { return }
|
|
@@ -207,17 +209,29 @@ import UIKit
|
|
|
207
209
|
.map { maskedIds.contains($0) } ?? false
|
|
208
210
|
let masked = parentMasked || isThisMasked
|
|
209
211
|
|
|
212
|
+
// Insight 2026-08-01: the old check culled only nodes FULLY
|
|
213
|
+
// outside the window, so a 2000pt scroll ruler shipped at
|
|
214
|
+
// 2000pt and drew past the phone. `clip` accumulates every
|
|
215
|
+
// clipping ancestor (scroll views, clipsToBounds containers)
|
|
216
|
+
// from the window down; a node ships as the part of it that
|
|
217
|
+
// is actually visible, and a subtree behind a clipping edge
|
|
218
|
+
// ships not at all. This replaces the 05-18 anchor-swap class
|
|
219
|
+
// of fix: clipping is the root cause, anchors were symptoms.
|
|
210
220
|
let frame = view.convert(view.bounds, to: window)
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
221
|
+
let visible = frame.intersection(clip)
|
|
222
|
+
let clipsChildren = view.clipsToBounds
|
|
223
|
+
if visible.isNull || visible.isEmpty {
|
|
224
|
+
// Nothing of this view shows. Its children can still show
|
|
225
|
+
// when this view does not clip them (transform-heavy or
|
|
226
|
+
// zero-size wrappers); a clipping view ends the subtree.
|
|
227
|
+
if clipsChildren { return }
|
|
214
228
|
}
|
|
215
229
|
|
|
216
230
|
var node: [String: Any] = [
|
|
217
|
-
"x": Double(
|
|
218
|
-
"y": Double(
|
|
219
|
-
"w": Double(
|
|
220
|
-
"h": Double(
|
|
231
|
+
"x": Double(visible.origin.x),
|
|
232
|
+
"y": Double(visible.origin.y),
|
|
233
|
+
"w": Double(visible.size.width),
|
|
234
|
+
"h": Double(visible.size.height),
|
|
221
235
|
]
|
|
222
236
|
|
|
223
237
|
if masked {
|
|
@@ -239,11 +253,14 @@ import UIKit
|
|
|
239
253
|
}
|
|
240
254
|
// else: invisible container — skip emitting but recurse.
|
|
241
255
|
|
|
242
|
-
if node["kind"] != nil {
|
|
256
|
+
if node["kind"] != nil, !visible.isNull, !visible.isEmpty {
|
|
243
257
|
nodes.append(node)
|
|
244
258
|
}
|
|
245
259
|
|
|
246
260
|
if !masked {
|
|
261
|
+
// A clipping view narrows the clip for everything below
|
|
262
|
+
// it; a non-clipping one passes the inherited clip on.
|
|
263
|
+
let childClip = clipsChildren ? visible : clip
|
|
247
264
|
// Don't expose internals of masked subtrees.
|
|
248
265
|
for sub in view.subviews {
|
|
249
266
|
walk(
|
|
@@ -253,6 +270,7 @@ import UIKit
|
|
|
253
270
|
parentMasked: masked,
|
|
254
271
|
maskedIds: maskedIds,
|
|
255
272
|
window: window,
|
|
273
|
+
clip: childClip,
|
|
256
274
|
nodes: &nodes
|
|
257
275
|
)
|
|
258
276
|
}
|
package/lib/replay.d.ts
CHANGED
|
@@ -18,12 +18,40 @@ export type ReplayOptions = {
|
|
|
18
18
|
};
|
|
19
19
|
export declare function startReplay(opts: ReplayOptions): void;
|
|
20
20
|
export declare function stopReplay(): void;
|
|
21
|
+
/**
|
|
22
|
+
* Identity = geometry + occurrence index in walk order.
|
|
23
|
+
*
|
|
24
|
+
* Geometry alone is NOT identity: a page root and the fullscreen
|
|
25
|
+
* overlay covering it share x/y/w/h, and keying a Map on that
|
|
26
|
+
* collapsed them into one node — the overlay inherited the root's
|
|
27
|
+
* z-position and the replay drew the page THROUGH it (insight
|
|
28
|
+
* 2026-08-01). The occurrence suffix keeps every stacked layer its
|
|
29
|
+
* own entry, and stays stable across ticks because the native walk
|
|
30
|
+
* order is deterministic for a stable tree.
|
|
31
|
+
*/
|
|
32
|
+
export declare function indexNodes(nodes: Node[]): Map<string, {
|
|
33
|
+
node: Node;
|
|
34
|
+
at: number;
|
|
35
|
+
}>;
|
|
21
36
|
type Delta = {
|
|
22
|
-
added: Node
|
|
23
|
-
|
|
24
|
-
|
|
37
|
+
added: (Node & {
|
|
38
|
+
at: number;
|
|
39
|
+
id: string;
|
|
40
|
+
})[];
|
|
41
|
+
changed: (Node & {
|
|
42
|
+
id: string;
|
|
43
|
+
})[];
|
|
44
|
+
removed: (Pick<Node, 'x' | 'y' | 'w' | 'h'> & {
|
|
45
|
+
id: string;
|
|
46
|
+
})[];
|
|
25
47
|
};
|
|
26
|
-
export declare function computeDelta(prev: Map<string,
|
|
48
|
+
export declare function computeDelta(prev: Map<string, {
|
|
49
|
+
node: Node;
|
|
50
|
+
at: number;
|
|
51
|
+
}>, curr: Map<string, {
|
|
52
|
+
node: Node;
|
|
53
|
+
at: number;
|
|
54
|
+
}>): Delta;
|
|
27
55
|
export declare function isReplayRunning(): boolean;
|
|
28
56
|
/** Drain the ring as NDJSON (keyframe or delta per line). Empty
|
|
29
57
|
* string when the ring is empty. Resets state so the next session's
|
package/lib/replay.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"replay.d.ts","sourceRoot":"","sources":["../src/replay.ts"],"names":[],"mappings":"AAyDA,KAAK,IAAI,GAAG;IACV,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAkBF,MAAM,MAAM,aAAa,GAAG;IAC1B,IAAI,CAAC,EAAE,KAAK,GAAG,WAAW,CAAC;IAC3B;;mCAE+B;IAC/B,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,4CAA4C;IAC5C,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAIF,wBAAgB,WAAW,CAAC,IAAI,EAAE,aAAa,GAAG,IAAI,CA2BrD;AAED,wBAAgB,UAAU,IAAI,IAAI,CAajC;
|
|
1
|
+
{"version":3,"file":"replay.d.ts","sourceRoot":"","sources":["../src/replay.ts"],"names":[],"mappings":"AAyDA,KAAK,IAAI,GAAG;IACV,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAkBF,MAAM,MAAM,aAAa,GAAG;IAC1B,IAAI,CAAC,EAAE,KAAK,GAAG,WAAW,CAAC;IAC3B;;mCAE+B;IAC/B,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,4CAA4C;IAC5C,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAIF,wBAAgB,WAAW,CAAC,IAAI,EAAE,aAAa,GAAG,IAAI,CA2BrD;AAED,wBAAgB,UAAU,IAAI,IAAI,CAajC;AAmHD;;;;;;;;;;GAUG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,GAAG,CAAC,MAAM,EAAE;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,CAAC,CAUjF;AAED,KAAK,KAAK,GAAG;IACX,KAAK,EAAE,CAAC,IAAI,GAAG;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC,EAAE,CAAC;IAC7C,OAAO,EAAE,CAAC,IAAI,GAAG;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC,EAAE,CAAC;IACnC,OAAO,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC,GAAG;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC,EAAE,CAAC;CACjE,CAAC;AAEF,wBAAgB,YAAY,CAC1B,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,CAAC,EAC7C,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,CAAC,GAC5C,KAAK,CAsBP;AA8DD,wBAAgB,eAAe,IAAI,OAAO,CAEzC;AAED;;2CAE2C;AAC3C,wBAAgB,WAAW,IAAI,MAAM,CAOpC;AAED,wBAAgB,qBAAqB,IAAI,IAAI,CAK5C;AAED;mEACmE;AACnE,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAO1D"}
|
package/lib/replay.js
CHANGED
|
@@ -129,9 +129,7 @@ function captureTick() {
|
|
|
129
129
|
}
|
|
130
130
|
}
|
|
131
131
|
function encodeAndPush(snapshot) {
|
|
132
|
-
const currentState =
|
|
133
|
-
for (const n of snapshot.nodes)
|
|
134
|
-
currentState.set(fingerprint(n), n);
|
|
132
|
+
const currentState = indexNodes(snapshot.nodes);
|
|
135
133
|
const ts = snapshot.ts;
|
|
136
134
|
const isCold = _lastFrameState === null;
|
|
137
135
|
const keyframeOverdue = ts - _lastKeyframeTs >= _keyframeIntervalMs;
|
|
@@ -190,25 +188,47 @@ function evictRing(nowTs) {
|
|
|
190
188
|
function fingerprint(n) {
|
|
191
189
|
return `${n.x | 0},${n.y | 0},${n.w | 0},${n.h | 0}`;
|
|
192
190
|
}
|
|
191
|
+
/**
|
|
192
|
+
* Identity = geometry + occurrence index in walk order.
|
|
193
|
+
*
|
|
194
|
+
* Geometry alone is NOT identity: a page root and the fullscreen
|
|
195
|
+
* overlay covering it share x/y/w/h, and keying a Map on that
|
|
196
|
+
* collapsed them into one node — the overlay inherited the root's
|
|
197
|
+
* z-position and the replay drew the page THROUGH it (insight
|
|
198
|
+
* 2026-08-01). The occurrence suffix keeps every stacked layer its
|
|
199
|
+
* own entry, and stays stable across ticks because the native walk
|
|
200
|
+
* order is deterministic for a stable tree.
|
|
201
|
+
*/
|
|
202
|
+
export function indexNodes(nodes) {
|
|
203
|
+
const out = new Map();
|
|
204
|
+
const seen = new Map();
|
|
205
|
+
nodes.forEach((n, at) => {
|
|
206
|
+
const base = fingerprint(n);
|
|
207
|
+
const occ = seen.get(base) ?? 0;
|
|
208
|
+
seen.set(base, occ + 1);
|
|
209
|
+
out.set(`${base}#${occ}`, { node: n, at });
|
|
210
|
+
});
|
|
211
|
+
return out;
|
|
212
|
+
}
|
|
193
213
|
export function computeDelta(prev, curr) {
|
|
194
214
|
const added = [];
|
|
195
215
|
const changed = [];
|
|
196
216
|
const removed = [];
|
|
197
|
-
for (const [
|
|
198
|
-
const p = prev.get(
|
|
217
|
+
for (const [id, { node, at }] of curr) {
|
|
218
|
+
const p = prev.get(id);
|
|
199
219
|
if (!p) {
|
|
200
|
-
added.push(node);
|
|
220
|
+
added.push({ ...node, at, id });
|
|
201
221
|
continue;
|
|
202
222
|
}
|
|
203
|
-
if ((p.kind ?? '') !== (node.kind ?? '') ||
|
|
204
|
-
(p.color ?? '') !== (node.color ?? '') ||
|
|
205
|
-
(p.text ?? '') !== (node.text ?? '')) {
|
|
206
|
-
changed.push(node);
|
|
223
|
+
if ((p.node.kind ?? '') !== (node.kind ?? '') ||
|
|
224
|
+
(p.node.color ?? '') !== (node.color ?? '') ||
|
|
225
|
+
(p.node.text ?? '') !== (node.text ?? '')) {
|
|
226
|
+
changed.push({ ...node, id });
|
|
207
227
|
}
|
|
208
228
|
}
|
|
209
|
-
for (const [
|
|
210
|
-
if (!curr.has(
|
|
211
|
-
removed.push({ x: node.x, y: node.y, w: node.w, h: node.h });
|
|
229
|
+
for (const [id, { node }] of prev) {
|
|
230
|
+
if (!curr.has(id))
|
|
231
|
+
removed.push({ x: node.x, y: node.y, w: node.w, h: node.h, id });
|
|
212
232
|
}
|
|
213
233
|
return { added, changed, removed };
|
|
214
234
|
}
|
package/lib/replay.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"replay.js","sourceRoot":"","sources":["../src/replay.ts"],"names":[],"mappings":"AAAA,+CAA+C;AAC/C,EAAE;AACF,sEAAsE;AACtE,mEAAmE;AACnE,wEAAwE;AACxE,uEAAuE;AACvE,qEAAqE;AACrE,+CAA+C;AAC/C,mEAAmE;AACnE,EAAE;AACF,uEAAuE;AACvE,oEAAoE;AACpE,qEAAqE;AACrE,gCAAgC;AAChC,EAAE;AACF,2CAA2C;AAE3C,OAAO,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AAEhD,OAAO,EAAE,uBAAuB,EAAE,MAAM,UAAU,CAAC;AAInD;;;;;uEAKuE;AACvE,MAAM,gBAAgB,GAAG,GAAG,CAAC;AAE7B;+DAC+D;AAC/D,MAAM,oBAAoB,GAAG,KAAK,CAAC;AAEnC;;+DAE+D;AAC/D,MAAM,uBAAuB,GAAG,GAAG,CAAC;AAEpC;;0DAE0D;AAC1D,MAAM,wBAAwB,GAAG,EAAE,CAAC;AAEpC,sEAAsE;AACtE,MAAM,gBAAgB,GAAG,MAAM,CAAC;AAEhC;;8BAE8B;AAC9B,MAAM,cAAc,GAAG,IAAI,CAAC;AAE5B;yCACyC;AACzC,MAAM,kBAAkB,GAAG,GAAG,CAAC;AAgB/B,IAAI,KAAK,GAAe,EAAE,CAAC;AAC3B,IAAI,MAAM,GAA0C,IAAI,CAAC;AACzD,IAAI,QAAQ,GAAG,KAAK,CAAC;AAErB;;oCAEoC;AACpC,IAAI,eAAe,
|
|
1
|
+
{"version":3,"file":"replay.js","sourceRoot":"","sources":["../src/replay.ts"],"names":[],"mappings":"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"}
|
package/lib/transport.js
CHANGED
|
@@ -15,7 +15,7 @@ const MAX_RETRY = 3;
|
|
|
15
15
|
const STORAGE_KEY = '@sentori/pending';
|
|
16
16
|
const MAX_PERSISTED = 1000;
|
|
17
17
|
// Pinned to package.json by a test — bump both together.
|
|
18
|
-
const SDK_VERSION = '5.1.
|
|
18
|
+
const SDK_VERSION = '5.1.4';
|
|
19
19
|
let _queue = [];
|
|
20
20
|
let _assertStats = new Map();
|
|
21
21
|
let _flushTimer = null;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goliapkg/sentori-react-native",
|
|
3
|
-
"version": "5.1.
|
|
3
|
+
"version": "5.1.4",
|
|
4
4
|
"description": "Sentori SDK for React Native \u2014 JS-layer error capture, native crash handlers (iOS / Android), batched transport, fetch + react-navigation tracing.",
|
|
5
5
|
"license": "Apache-2.0 OR MIT",
|
|
6
6
|
"author": "GOLIA K.K. <takagi@golia.jp> (https://golia.jp)",
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
__resetReplayForTests,
|
|
6
6
|
computeDelta,
|
|
7
7
|
drainReplay,
|
|
8
|
+
indexNodes,
|
|
8
9
|
} from '../replay';
|
|
9
10
|
|
|
10
11
|
// Unit coverage for the rc.9 v2 replay encoder.
|
|
@@ -206,9 +207,7 @@ describe('rc.9 encoder — keyframe vs delta', () => {
|
|
|
206
207
|
});
|
|
207
208
|
|
|
208
209
|
describe('computeDelta', () => {
|
|
209
|
-
|
|
210
|
-
return new Map(nodes.map((n) => [`${n.x | 0},${n.y | 0},${n.w | 0},${n.h | 0}`, n]));
|
|
211
|
-
}
|
|
210
|
+
const asMap = (nodes: Node[]) => indexNodes(nodes);
|
|
212
211
|
|
|
213
212
|
test('empty-vs-empty produces all-empty delta', () => {
|
|
214
213
|
const d = computeDelta(new Map(), new Map());
|
|
@@ -234,4 +233,32 @@ describe('computeDelta', () => {
|
|
|
234
233
|
expect(d.changed.length).toBe(0);
|
|
235
234
|
expect(d.removed.length).toBe(0);
|
|
236
235
|
});
|
|
236
|
+
|
|
237
|
+
test('stacked same-geometry layers keep separate identities (insight overlay bug)', () => {
|
|
238
|
+
// A page root and a fullscreen opaque overlay: identical x/y/w/h.
|
|
239
|
+
const pageRoot: Node = { x: 0, y: 0, w: 400, h: 800, kind: 'rect', color: '#101014FF' };
|
|
240
|
+
const overlay: Node = { x: 0, y: 0, w: 400, h: 800, kind: 'rect', color: '#FFFFFFFF' };
|
|
241
|
+
const m = indexNodes([pageRoot, overlay]);
|
|
242
|
+
// Both survive indexing…
|
|
243
|
+
expect(m.size).toBe(2);
|
|
244
|
+
expect([...m.keys()]).toEqual(['0,0,400,800#0', '0,0,400,800#1']);
|
|
245
|
+
// …and z-order info rides along.
|
|
246
|
+
expect(m.get('0,0,400,800#1')?.at).toBe(1);
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
test('delta entries carry ids; overlay removal targets the right layer', () => {
|
|
250
|
+
const pageRoot: Node = { x: 0, y: 0, w: 400, h: 800, kind: 'rect', color: '#101014FF' };
|
|
251
|
+
const overlay: Node = { x: 0, y: 0, w: 400, h: 800, kind: 'rect', color: '#FFFFFFFF' };
|
|
252
|
+
const withOverlay = indexNodes([pageRoot, overlay]);
|
|
253
|
+
const without = indexNodes([pageRoot]);
|
|
254
|
+
|
|
255
|
+
const closed = computeDelta(withOverlay, without);
|
|
256
|
+
expect(closed.removed.length).toBe(1);
|
|
257
|
+
expect(closed.removed[0]?.id).toBe('0,0,400,800#1');
|
|
258
|
+
|
|
259
|
+
const opened = computeDelta(without, withOverlay);
|
|
260
|
+
expect(opened.added.length).toBe(1);
|
|
261
|
+
expect(opened.added[0]?.id).toBe('0,0,400,800#1');
|
|
262
|
+
expect(opened.added[0]?.at).toBe(1);
|
|
263
|
+
});
|
|
237
264
|
});
|
package/src/replay.ts
CHANGED
|
@@ -76,7 +76,7 @@ let _running = false;
|
|
|
76
76
|
/** Last emit's reconstructed state — fingerprint → node. Null until
|
|
77
77
|
* the first keyframe lands; reset on drain so the next session
|
|
78
78
|
* starts with a fresh keyframe. */
|
|
79
|
-
let _lastFrameState: Map<string, Node> | null = null;
|
|
79
|
+
let _lastFrameState: Map<string, { node: Node; at: number }> | null = null;
|
|
80
80
|
let _lastKeyframeTs = 0;
|
|
81
81
|
|
|
82
82
|
let _nativeMod: ReplayNativeModule | null = null;
|
|
@@ -182,8 +182,7 @@ function captureTick(): void {
|
|
|
182
182
|
}
|
|
183
183
|
|
|
184
184
|
function encodeAndPush(snapshot: NativeFrame): void {
|
|
185
|
-
const currentState =
|
|
186
|
-
for (const n of snapshot.nodes) currentState.set(fingerprint(n), n);
|
|
185
|
+
const currentState = indexNodes(snapshot.nodes);
|
|
187
186
|
|
|
188
187
|
const ts = snapshot.ts;
|
|
189
188
|
const isCold = _lastFrameState === null;
|
|
@@ -195,7 +194,10 @@ function encodeAndPush(snapshot: NativeFrame): void {
|
|
|
195
194
|
line = encodeKeyframe(snapshot);
|
|
196
195
|
_lastKeyframeTs = ts;
|
|
197
196
|
} else {
|
|
198
|
-
const delta = computeDelta(
|
|
197
|
+
const delta = computeDelta(
|
|
198
|
+
_lastFrameState as Map<string, { node: Node; at: number }>,
|
|
199
|
+
currentState,
|
|
200
|
+
);
|
|
199
201
|
const totalChanged = delta.added.length + delta.changed.length + delta.removed.length;
|
|
200
202
|
if (totalChanged === 0) {
|
|
201
203
|
// No-op heartbeat — drop. Keep _lastFrameState as-is (identical).
|
|
@@ -248,28 +250,58 @@ function fingerprint(n: Node): string {
|
|
|
248
250
|
return `${n.x | 0},${n.y | 0},${n.w | 0},${n.h | 0}`;
|
|
249
251
|
}
|
|
250
252
|
|
|
251
|
-
|
|
253
|
+
/**
|
|
254
|
+
* Identity = geometry + occurrence index in walk order.
|
|
255
|
+
*
|
|
256
|
+
* Geometry alone is NOT identity: a page root and the fullscreen
|
|
257
|
+
* overlay covering it share x/y/w/h, and keying a Map on that
|
|
258
|
+
* collapsed them into one node — the overlay inherited the root's
|
|
259
|
+
* z-position and the replay drew the page THROUGH it (insight
|
|
260
|
+
* 2026-08-01). The occurrence suffix keeps every stacked layer its
|
|
261
|
+
* own entry, and stays stable across ticks because the native walk
|
|
262
|
+
* order is deterministic for a stable tree.
|
|
263
|
+
*/
|
|
264
|
+
export function indexNodes(nodes: Node[]): Map<string, { node: Node; at: number }> {
|
|
265
|
+
const out = new Map<string, { node: Node; at: number }>();
|
|
266
|
+
const seen = new Map<string, number>();
|
|
267
|
+
nodes.forEach((n, at) => {
|
|
268
|
+
const base = fingerprint(n);
|
|
269
|
+
const occ = seen.get(base) ?? 0;
|
|
270
|
+
seen.set(base, occ + 1);
|
|
271
|
+
out.set(`${base}#${occ}`, { node: n, at });
|
|
272
|
+
});
|
|
273
|
+
return out;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
type Delta = {
|
|
277
|
+
added: (Node & { at: number; id: string })[];
|
|
278
|
+
changed: (Node & { id: string })[];
|
|
279
|
+
removed: (Pick<Node, 'x' | 'y' | 'w' | 'h'> & { id: string })[];
|
|
280
|
+
};
|
|
252
281
|
|
|
253
|
-
export function computeDelta(
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
282
|
+
export function computeDelta(
|
|
283
|
+
prev: Map<string, { node: Node; at: number }>,
|
|
284
|
+
curr: Map<string, { node: Node; at: number }>,
|
|
285
|
+
): Delta {
|
|
286
|
+
const added: Delta['added'] = [];
|
|
287
|
+
const changed: Delta['changed'] = [];
|
|
288
|
+
const removed: Delta['removed'] = [];
|
|
289
|
+
for (const [id, { node, at }] of curr) {
|
|
290
|
+
const p = prev.get(id);
|
|
259
291
|
if (!p) {
|
|
260
|
-
added.push(node);
|
|
292
|
+
added.push({ ...node, at, id });
|
|
261
293
|
continue;
|
|
262
294
|
}
|
|
263
295
|
if (
|
|
264
|
-
(p.kind ?? '') !== (node.kind ?? '') ||
|
|
265
|
-
(p.color ?? '') !== (node.color ?? '') ||
|
|
266
|
-
(p.text ?? '') !== (node.text ?? '')
|
|
296
|
+
(p.node.kind ?? '') !== (node.kind ?? '') ||
|
|
297
|
+
(p.node.color ?? '') !== (node.color ?? '') ||
|
|
298
|
+
(p.node.text ?? '') !== (node.text ?? '')
|
|
267
299
|
) {
|
|
268
|
-
changed.push(node);
|
|
300
|
+
changed.push({ ...node, id });
|
|
269
301
|
}
|
|
270
302
|
}
|
|
271
|
-
for (const [
|
|
272
|
-
if (!curr.has(
|
|
303
|
+
for (const [id, { node }] of prev) {
|
|
304
|
+
if (!curr.has(id)) removed.push({ x: node.x, y: node.y, w: node.w, h: node.h, id });
|
|
273
305
|
}
|
|
274
306
|
return { added, changed, removed };
|
|
275
307
|
}
|
package/src/transport.ts
CHANGED
|
@@ -20,7 +20,7 @@ const STORAGE_KEY = '@sentori/pending';
|
|
|
20
20
|
const MAX_PERSISTED = 1000;
|
|
21
21
|
|
|
22
22
|
// Pinned to package.json by a test — bump both together.
|
|
23
|
-
const SDK_VERSION = '5.1.
|
|
23
|
+
const SDK_VERSION = '5.1.4';
|
|
24
24
|
|
|
25
25
|
let _queue: WireEvent[] = [];
|
|
26
26
|
let _assertStats = new Map<string, AssertStat>();
|