fix(replay-ui): skip synthetic zero-bounds root in deviceSpaceOf

The iOS hierarchy prepends a zero-bounds node before the real root
window, so elements[0] returned undefined and the overlay fell back to
the screenshot's pixel size. Take the first element with positive
extent instead; pre-order puts the root window before any content.
Verified against a real iOS trace in the replay UI.
This commit is contained in:
pj committed 2026-06-06 00:53:08 +05:30
1 parent 0550e522cd
commit 2b773bbf5d
2 files changed
+21 -6

No files matched your search

@@ -33,4 +33,15 @@ describe("deviceSpaceOf", () => {
expect(deviceSpaceOf(hierarchyWithRoot(393, 0))).toBeUndefined(); expect(deviceSpaceOf(hierarchyWithRoot(393, 0))).toBeUndefined();
expect(deviceSpaceOf(hierarchyWithRoot(-1, -1))).toBeUndefined(); expect(deviceSpaceOf(hierarchyWithRoot(-1, -1))).toBeUndefined();
}); });
it("skips the iOS synthetic zero-bounds root", () => {
const hierarchy: Hierarchy = {
elements: [
{ bounds: { left: 0, top: 0, right: 0, bottom: 0 } },
{ bounds: { left: 0, top: 0, right: 402, bottom: 874 } },
{ bounds: { left: 20, top: 100, right: 380, bottom: 150 } },
],
};
expect(deviceSpaceOf(hierarchy)).toEqual({ width: 402, height: 874 });
});
}); });
+10 -6
View File
@@ -3,14 +3,18 @@ import type { Hierarchy } from "../types";
// Tap points and resolved bounds in the trace share the hierarchy root's // Tap points and resolved bounds in the trace share the hierarchy root's
// coordinate space (iOS points, Android pixels, web CSS px). Screenshots may // coordinate space (iOS points, Android pixels, web CSS px). Screenshots may
// be scaled (iOS 3x, web DPR>1), so the overlay viewBox must come from the // be scaled (iOS 3x, web DPR>1), so the overlay viewBox must come from the
// root bounds, not the image's natural pixel size. // root bounds, not the image's natural pixel size. Elements are in pre-order;
// the first one with positive extent is the root window (iOS prepends a
// synthetic zero-bounds node, so plain elements[0] is not enough).
export function deviceSpaceOf( export function deviceSpaceOf(
hierarchy?: Hierarchy, hierarchy?: Hierarchy,
): { width: number; height: number } | undefined { ): { width: number; height: number } | undefined {
const root = hierarchy?.elements?.[0]; for (const element of hierarchy?.elements ?? []) {
if (!root) return undefined; const width = element.bounds.right;
const width = root.bounds.right; const height = element.bounds.bottom;
const height = root.bounds.bottom; if (width > 0 && height > 0) {
if (!(width > 0) || !(height > 0)) return undefined;
return { width, height }; return { width, height };
}
}
return undefined;
} }