Skip to content

iOS: keep secure rectangles and the view tree on windows away from the screen's origin - #30

Merged
krassx merged 2 commits into
mainfrom
feat/ios-secure-rect-screen-origin
Oct 6, 2026
Merged

krassx merged 2 commits into
mainfrom
feat/ios-secure-rect-screen-origin

Conversation

@dsheikherev

@dsheikherev dsheikherev commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Rebuilt on main after #48: 02499fa (the fix; the earlier six commits were on the pre-#48 base) and 885bcf1 (the pin to 7.0.0-beta4). #48's per-<Modal> lanes stay as they are; this PR changes where every lane's window is placed.

  • Secure rectangles follow the window onto the screen. JS measures with measureInWindow, in window points. The iOS SDK that composes every app window on the screen draws secure rectangles in screen points. Every lane now adds the window's place on its screen, read through the screen's fixed space the way the SDK places windows (BGSRNWindowRecordedOrigin). That covers the main surface and each <Modal> lane (the presented view's origin in its window plus that place, where Privacy: secure rectangles and view-tree nodes inside a Modal use the Modal's own origin #48 left the rebase note). On a Mac the SDK records only the key window's scene, so the place stays frame.origin. On an iPhone, or an iPad app filling its screen, the place is {0, 0} and nothing changes.
  • Managed view tree. The vh origin is the same window place.
  • Window lookup. BGSRNSdkKeyWindow and BGSRNSdkWalkedWindows follow the SDK's current key-window pick and its composed window list.
  • No window search on every frame. The SDK pulls once per captured frame (BGSContracts.h), and Privacy: secure rectangles and view-tree nodes inside a Modal use the Modal's own origin #48 re-reads every origin on each pull. BGSRNReactRootOriginTracker keeps the React root it found last (weakly) and reads its window's place. A pull searches only when it has no such root, at most once per 0.5 s; on a brownfield app's native screens there is no root to find. A JS publish on the main surface and a vh request search at once, so the first publish does not wait for a pull.
  • Dropped from the earlier version. The 100 ms pull throttle and the publish-on-main ordering. Privacy: secure rectangles and view-tree nodes inside a Modal use the Modal's own origin #48 re-reads origins on every pull and fails closed while an origin is unknown, so the problem they solved, an implicit {0, 0} origin, no longer exists. The store changes are dropped too: Privacy: secure rectangles and view-tree nodes inside a Modal use the Modal's own origin #48 already has the CGPoint origin, outward rounding and saturation.
  • The docs now state the SDK's pull cadence as "once per captured frame", not "2-3 times a second".

Pins the iOS SDK that composes the app's windows on the screen (bugsee-cocoa #170–#172 and #186): 7.0.0-beta4, published 2026-10-06. With beta3 a window away from the screen's origin would be off the other way, so the fix and the pin ship together. 885bcf1 moves the core pins (native-versions.json, ios/Support/Package.swift, Package.resolved → e467f57c) and the feedback package's SPM pins with them; an app using both through SPM cannot resolve two exact pins on bugsee/spm. From beta4 the iOS SDK writes video.aux version 2, so the iOS e2e now checks that its frame is the screen in points with no letterbox padding, instead of checking that there is no video.aux.

Test plan

  • Support package (xcodebuild test -scheme BugseeRNSupport, iPhone 17 Pro, iOS 27.0): 308 tests, 0 failures
    • 12 new tracker tests: no search while the root is in a window, at most one pull search per interval, an immediate search on publish and vh, the root not kept alive, no guess when the place cannot be read
    • 2 new tests: a Modal origin adds the window's placement, and has none without it
    • Privacy: secure rectangles and view-tree nodes inside a Modal use the Modal's own origin #48's Modal tests now pass the placement explicitly, because the test runner's windows are on no screen
  • yarn test: 2939 passed, 12 skipped, 0 failed; yarn lint and yarn typecheck clean. The raw-message scanner caught an exception logged with its text; it now logs the class only, like the rest of the package.
  • Example builds with CocoaPods (iOS, Debug)
  • CI on 02499fa (pin still beta3), including e2e and the SPM build
  • On beta4 locally: Support 308/0, jest 2937 passed / 0 failed, lint and typecheck clean, CocoaPods example build (the xcframework and feedback sources stamped 7.0.0-beta4)
  • CI on 885bcf1 (beta4): ios e2e (simulator) passes with the video.aux v2 check, plus the SPM and CocoaPods builds
  • Simulator e2e in CI (ios e2e (simulator) on 885bcf1)
  • Managed view tree (vh) on the same iPad window, view-tree scenario: both requests answered origin=493,176 (the simulator answers 0,0). In the report's viewtree.json:
    • the managed ReactNative root is [493, 176, 650, 603], the same as the native UIWindow and RCTSurfaceHostingProxyRootView;
    • the managed vh-open host is [523, 476, 150, 60], exactly where its native RCTViewComponentView (frame [30, 300, 150, 60] in the window) is placed.
  • iPad Air 5 (iPadOS 27.2), Stage Manager, BareExample Debug from 885bcf1 with the released 7.0.0-beta4 (Bugsee IOS SDK ver:7.0.0-beta4 build:bb2f0e7e-12), dead endpoint, bundles pulled from the device. The window sits at (492.3, 176.9) on the 1180×820 screen, measured from the witness:
    • secure-component (report screenshot): the mask is at (552.1, 436.5) 179.4×89.7 for an expected (552.3, 436.9) 180×90 when mounted, and at (552.1, 336.9) for an expected (552.3, 336.9) after the scroll; it is gone after unmount. The old frame.origin placement would put it at (60, 260), outside the window.
    • secure-component (video, last frame of each report): the mask sits 40 pt across and 184 pt up from the witness, against an expected 40 and 183 (284 against 283 after the scroll).
    • secure-modal-sheet (pageSheet): no #FF00FF or #00FFFF pixel in the screenshot or in any of the 77 video frames. The red witness is visible, so the sheet was up. Both masks sit on their views within about 3 pt.
  • Pin 7.0.0-beta4

🤖 Generated with Claude Code

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale comment

Deep code review

This PR moves iOS secure rectangles and the managed view-tree origin from the React root window’s points onto the screen, matching Android’s tracker/pulls split and the unreleased cocoa SDK that composites every window. The store, rounding, versioning, and on-main pull refresh are carefully built. Two issues should be fixed before merge.

Findings

P1 High — BGSRNSdkComposesScreen() treats every real iOS device as the new compositor, but this repo still pins 7.0.0-beta3. Beta3 records the key window’s scene and draws rectangles / native nodes in scene points (frame.origin). Adding the screen origin inverts the Stage Manager / Split View bug this PR is fixing: masks and the React tree sit one window-offset away from the content, so secret UI is recorded in the clear. iPhone (origin {0,0}) is unchanged; iPad multitasking is not. Task 6.6 review I1 previously required frame.origin for this reason.

P2 Medium — setSecureRectangles publishes coordinates on the TurboModule queue (origin still {0,0} until a root has been found) and only then dispatch_asyncs the origin read. The SDK pulls on main and will re-read on the version bump from that publish. Until the hop runs, Stage Manager windows are served unmoved.

Overall risk: High

High because the P1 is a privacy defect on the currently declared SDK, in the exact iPad configuration the PR exists to fix. Against the unreleased compositor SDK the design looks right (and the iPad Stage Manager secure-component run is good evidence). I did not run the Support XCTests or e2e here (Linux agent, no iOS toolchain).

Merge recommendation

Do not merge onto main while native-versions.json / ios/Support/Package.swift still pin 7.0.0-beta3. Land it with the compositor SDK (bugsee-cocoa #170–#172), or gate BGSRNWindowRecordedOrigin / window walking on that SDK so beta3 keeps frame.origin.

Most important to fix

  1. Tie screen-space origin to the SDK that actually composites, and bump the pin in the same change.
  2. Publish rectangles only after the origin has been applied, on main, so the SDK never observes an unmoved version.
  3. Still missing: managed view-tree on a device window that is not at the screen origin (called out in the test plan). Simulator e2e cannot distinguish old vs new origin.

What looks solid

  • Served-buffer versioning only when the moved rectangles change; outward floor/ceil for a fractional origin; int32 saturation.
  • Weak cached root and no search on the SDK pull (brownfield-safe); on-main refresh-before-snapshot is stricter than Android’s post-to-UI-thread model.
  • Support tests cover store origin/version, tracker cache vs search, and pull throttling.
Open in Web View Automation 

Sent by Cursor Automation: Bugsee code review

Comment thread packages/react-native/ios/BugseeModule.mm

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale comment

Deep code review

Re-review of 282233b (prior review was 4af4f55). The store, rounding, versioning, weak root cache, and on-main pull refresh are still carefully built. One of the two earlier issues is fixed; the other is not.

Prior findings

P2 — fully addressed in 99012d7. setSecureRectangles now hops to main and -publishCoordinates:forDisplay: reads the window’s place before it writes the rectangles. testAPublishReadsThePlaceBeforeItWritesTheRectangles fails if the store already has the new rects during the origin read. That publish-order leak is gone.

P1 High — not addressed. BGSRNSdkComposesScreen() is still “not Catalyst / not iOS-on-Mac”. The pin is still 7.0.0-beta3. Beta3 records the key window’s scene and draws rectangles / native nodes in scene points (frame.origin). Adding the screen origin inverts the Stage Manager / Split View bug this PR exists to fix: masks and the React tree sit one window-offset away from the content, so secret UI is recorded in the clear. iPhone (origin {0,0}) is unchanged; simulator e2e cannot tell the origins apart. The PR body states the compositor SDK requirement; the later commits only document pull cadence and the one-root tracker limit.

No new P0–P3 on the tracker, pulls, or store. I did not run the Support XCTests or e2e here (Linux agent, no iOS toolchain). CI on this head was still pending at review time.

Overall risk: High

High because the remaining P1 is a privacy defect on the currently declared SDK, in the exact iPad configuration the PR is meant to fix. Against the unreleased compositor SDK the design looks right (the iPad Stage Manager secure-component run is good evidence).

Merge recommendation

Do not merge onto main while native-versions.json / ios/Support/Package.swift still pin 7.0.0-beta3. Land it with the compositor SDK (bugsee-cocoa #170–#172), or gate BGSRNWindowRecordedOrigin / window walking on that SDK so beta3 keeps frame.origin.

Most important to fix

  1. Tie screen-space origin to the SDK that actually composites, and bump the pin in the same change.
  2. Still missing: managed view-tree on a device window that is not at the screen origin (called out in the test plan).

What looks solid

  • Publish is origin-then-write on main; a pull cannot observe new rectangles at a stale origin.
  • Served-buffer versioning only when the moved rectangles change; outward floor/ceil for a fractional origin; int32 saturation.
  • Weak cached root and no search on the SDK pull (brownfield-safe); on-main refresh-before-snapshot is stricter than Android’s post-to-UI-thread model.
  • Support tests cover store origin/version, tracker cache vs search, publish order, and pull throttling.
Open in Web View Automation 

Sent by Cursor Automation: Bugsee code review

@dsheikherev
dsheikherev requested a review from krassx October 2, 2026 09:26
@dsheikherev
dsheikherev marked this pull request as draft October 2, 2026 09:48
krassx added a commit that referenced this pull request Oct 5, 2026
Review fix round 1 (C1, C2, I1, I2, M1, M3, M4, M5).

C1, Android: the origin of a Modal's DialogRootViewGroup is its
locationOnScreen alone. Only a ReactRoot (the activity root) has its
viewport offset applied by Fabric; the dialog root is a RootView that is
not one, and the modal host node's transform is the identity.
ReactRootOriginTracker.surfaceOrigin makes the choice, and the JVM dialog
test now feeds a -51 viewport (a translucent status bar) and expects
locationOnScreen.

C2: no native call per node, and none on a timer. JS reads a view's surface
off the fiber tree: the React tag of the nearest RCTModalHostView host above
it, or the main surface (src/secure/surface.ts). <BugseeSecure> reads it once
per mount and publishes {surface, rects}. The vh walk carries an origin down
the tree and asks secureSurfaceOrigin once per Modal per walk. The spec
loses secureSurfaceKey. Android watches a Modal's dialog root when JS first
publishes on it (found from the host's first child), retries on every
refresh until the root exists, and answers secureSurfaceOrigin from that
cache. The activity root is the main surface and is watched once (M5).

I2, fail closed: a surface other than the main one whose origin has not
been read serves one full-display rectangle on both platforms. A Modal
whose host tag cannot be read publishes on surface -1, never the main one.
Nothing falls back to the main surface on a timeout.

I1, iOS: each Modal has its own lane. Its origin is the presented view
controller's view in window coordinates plus the window's frame.origin,
read at pull time, so a pageSheet is placed inside the window. Per lane the
store takes #30's semantics: a CGPoint origin, edges rounded outward and
saturating, and re-recording the same origin costs nothing (M3). The
comments M4 found deleted are back.

M1: an empty Modal lane is dropped when its dialog root detaches (Android)
or its host is gone (iOS).

Tests: Jest 2093 passed (surface.test.ts, the BugseeSecure, registry, walk
and requests surface cases); JVM 287 root and 317 example-Gradle;
BugseeRNSupport XCTests 246.
Mutation: applying the activity formula to every root fails
aDialogRootsOriginIsItsLocationOnScreenAlone and
aWatchedSurfacesOriginMovesOnlyItsRectangles; on the WOD_LX1 (edge-to-edge
off) the same build fails secure-modal-translucent: 8128 cyan pixels of the
Modal's secure view in the report, served sheet 611-811 against 560-760 on
screen.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
krassx added a commit that referenced this pull request Oct 5, 2026
…closed on every lane

Re-review fix round 2 (N1-N4).

N1: a lane with an unknown origin serves the display's real bounds, not a
2^20 square. Android records Display.getRealSize from the root's display on
every origin read and from DisplayManager on the first publish; iOS records
UIScreen.mainScreen.bounds on every pull. Android also clamps every served
rectangle to the display and drops one left with no area: the SDK's native
video mask (jni/video.c hideRects) clamps rows to the frame but not a
rectangle's right edge to a row, so an oversized one blacked out the start
of the next row on every row. Before the size is known the fallback is a
16384 square.

N2 (Android): a dialog root the tracker forgets (seen detached on a
refresh, or dropped by detach() on host destroy) while its surface holds
rectangles has its origin reset to unknown, so it fails closed, and goes
back to pending, so a later refresh re-watches it. No origin is recorded
from a root that is not attached to its window.

N3 (Android): only a non-empty publish watches a surface, and a pending
surface whose rectangles JS has cleared is dropped with its empty lane
instead of being retried.

N4 (ruling): the main lane fails closed too. Until its origin has been read
once, its rectangles are served as one display-bounds rectangle.

iOS: a note at BGSRNModalSurfaceOrigin for #30's rebase.

Tests: JVM root and example-Gradle 332 tests, 0 failures; BugseeRNSupport
261, 0 failures. Mutations, each reverted: main lane not failing closed (13
JVM failures, 4 XCTest), no clamp (everyServedRectangleIsClampedToTheDisplay),
no forgetOrigin (2), recording an unattached root's origin (2), no pending
prune (2), iOS screen size ignored (4).

🤖 Generated with [Claude Code](https://claude.com/claude-code)
krassx added a commit that referenced this pull request Oct 5, 2026
… Modal's own origin (#48)

* fix(android): serve each React surface's secure rects by its own origin

Fabric measureInWindow is relative to the measured node's nearest
RootNodeKind ancestor, so a rectangle inside a <Modal> is relative to the
dialog's DialogRootViewGroup, not the activity root. SecureRectangleStore
now keeps one lane per surface (raw rects + that root's
locationOnScreen - viewportOffset) and serves them merged in surface-key
order; the legacy single-surface set/setOrigin keep meaning the main
surface. ReactRootOriginTracker watches extra dialog roots, refreshes their
origins with the activity root, mirrors the activity origin onto
MAIN_SURFACE for manual writers, and keeps and releases each watched root's
layout-listener token.

Ported from feat/phase-6-modal (9ec12a2, e6e886f) onto main.

TDD: SecureRectangleStoreTest's two-surface cases failed against a
surface-ignoring stub (expected 2 rectangles, was 1); tracker tests did not
compile without RootHandle.surfaceKey / watchNow.
Mutation: publishing a watched dialog root with the activity root's origin
fails aWatchedSurfacesOriginMovesOnlyItsRectangles (expected 140, was 100);
reverted.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

* fix(ios): keep per-surface secure lanes with their own origin

BGSRNSecureRectangles stores each surface's raw rectangles with the origin
that places them and serves the lanes merged in surface-key order; the
version moves only when the served bytes change, and an origin-only write
that leaves the set empty still reports version 1. The legacy
single-surface API means BGSRNSecureMainSurface. On iOS a Fabric <Modal> is
presentViewController: on the same UIWindow, so it shares the main surface
and that window's frame.origin; only Android dialogs use a second key.

Ported from feat/phase-6-modal (9ec12a2, e6e886f) onto main.

TDD: the two-surface and version-1 XCTests did not compile against main's
store (no surface/origin API).
Mutation: translating every lane by the main surface's origin fails
testTheMainOriginDoesNotMoveAnotherSurfacesRectangle and
testAnotherSurfacesOwnOriginMovesOnlyItsRectangle; reverted.
BugseeRNSupport: 237 tests, 0 failures.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

* fix(privacy): publish <BugseeSecure> and managed nodes on their own surface

Fabric measureInWindow is relative to the measured node's nearest
RootNodeKind ancestor, and a <Modal> is one (an Android Dialog). The spec
gains setSecureRectanglesOnSurface, secureSurfaceKey and
secureSurfaceOrigin. The registry keeps one union per display and surface
and publishes a non-main surface through setSecureRectanglesOnSurface;
<BugseeSecure> asks native for the surface key of the root that holds its
view (main surface when it has no tag or the answer is not an integer);
the view-tree walk places each node by the origin of its own React root,
falling back to the request's origin.

Android resolves the root from the view tag on the UI thread, watches it,
and returns that root's locationOnScreen - viewportOffset. iOS Fabric
presents a Modal on the same UIWindow, so it answers the main surface and
the hosting window's frame.origin, and refreshes that origin on every pull.
New log lines carry a class name only.

Ported from feat/phase-6-modal (9ec12a2, e6e886f) onto main.

TDD: the per-surface registry tests failed against a surface-ignoring stub
(8 of 9); the BugseeSecure surface tests failed 3 of 15 before the lookup;
the walk and requests env tests failed to compile / 15 of 15 before the
env fields existed.
Mutation: placing every node by the request origin fails 3 walk/requests
tests; reverted. Stryker on the four changed files: 95.69%, survivors in new
code are equivalent (a no-op republish, a null read inside a try).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

* test(e2e): cover <BugseeSecure> inside a <Modal> on both surfaces

Scenario secure-modal mounts a white <BugseeSecure> on the activity root
and another inside a transparent <Modal>, logs both measureInWindow
rectangles and uploads. secure-modal.test.ts asserts both regions are black
in the report's screenshot while a control below the main one stays
bright, and on Android that the served set covers the main rectangle (from
JS, scaled and moved by the activity origin) and the sheet (from
uiautomator) within 2 px.

Ported from feat/phase-6-modal (9ec12a2); main already had E2E_METRO_HOST
and its own metro watchFolders, so neither is re-added. The unused
MAIN_LABEL constant is dropped for lint.

Device: WOD_LX1 edge-to-edge on and off, iPhone 17 Pro simulator: 1/1
each, mainInner and sheetInner luma 0.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

* fix(privacy): place each Modal by its own root, keyed off the fiber tree

Review fix round 1 (C1, C2, I1, I2, M1, M3, M4, M5).

C1, Android: the origin of a Modal's DialogRootViewGroup is its
locationOnScreen alone. Only a ReactRoot (the activity root) has its
viewport offset applied by Fabric; the dialog root is a RootView that is
not one, and the modal host node's transform is the identity.
ReactRootOriginTracker.surfaceOrigin makes the choice, and the JVM dialog
test now feeds a -51 viewport (a translucent status bar) and expects
locationOnScreen.

C2: no native call per node, and none on a timer. JS reads a view's surface
off the fiber tree: the React tag of the nearest RCTModalHostView host above
it, or the main surface (src/secure/surface.ts). <BugseeSecure> reads it once
per mount and publishes {surface, rects}. The vh walk carries an origin down
the tree and asks secureSurfaceOrigin once per Modal per walk. The spec
loses secureSurfaceKey. Android watches a Modal's dialog root when JS first
publishes on it (found from the host's first child), retries on every
refresh until the root exists, and answers secureSurfaceOrigin from that
cache. The activity root is the main surface and is watched once (M5).

I2, fail closed: a surface other than the main one whose origin has not
been read serves one full-display rectangle on both platforms. A Modal
whose host tag cannot be read publishes on surface -1, never the main one.
Nothing falls back to the main surface on a timeout.

I1, iOS: each Modal has its own lane. Its origin is the presented view
controller's view in window coordinates plus the window's frame.origin,
read at pull time, so a pageSheet is placed inside the window. Per lane the
store takes #30's semantics: a CGPoint origin, edges rounded outward and
saturating, and re-recording the same origin costs nothing (M3). The
comments M4 found deleted are back.

M1: an empty Modal lane is dropped when its dialog root detaches (Android)
or its host is gone (iOS).

Tests: Jest 2093 passed (surface.test.ts, the BugseeSecure, registry, walk
and requests surface cases); JVM 287 root and 317 example-Gradle;
BugseeRNSupport XCTests 246.
Mutation: applying the activity formula to every root fails
aDialogRootsOriginIsItsLocationOnScreenAlone and
aWatchedSurfacesOriginMovesOnlyItsRectangles; on the WOD_LX1 (edge-to-edge
off) the same build fails secure-modal-translucent: 8128 cyan pixels of the
Modal's secure view in the report, served sheet 611-811 against 560-760 on
screen.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

* test(e2e): find each secure view by its colour, and add the discriminating Modals

Review fix round 1 (C1, I1, I3).

The secure-modal* scenarios paint the main secure view #FF00FF and the
Modal's #00FFFF. Ground truth no longer comes from what the app measured:
the device's own screenshot (adb screencap, simctl io screenshot) finds
where each colour really is, the report's screenshot must be dark there,
and it must not contain either colour anywhere (on the iPhone, which has no
command-line screenshot, only the second check runs).

New scenarios: secure-modal-translucent (Android; statusBarTranslucent, so
with edge-to-edge off the dialog's origin is 0 while the activity root's is
the status bar) and secure-modal-sheet (iOS; an opaque pageSheet, inset
inside the window). The Android main-rectangle lookup matches the activity
root's `kind=activity` origin line instead of `surface=0`, so it cannot be
built from the Modal's origin (I3).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

* test(e2e): an opaque pageSheet hides the main secure view

On an iPhone the pageSheet covers the presenting view, so the device
screenshot shows only the Modal's colour. The sheet case now requires the
main colour to be absent from the device screenshot and checks the report
over the sheet alone; the bright control sits inside the Modal's white
backdrop below its secure view in every case.

Simulator: secure-modal 2/2 and secure-modal-sheet 2/2, sheet found at
104,342-304,442 pt (62 pt below its measureInWindow), sheetInner 0, no
secure colour in the report. With every Modal placed at the window origin
(the pre-I1 rule) the sheet case fails: 17760 cyan pixels, sheetInner 114.9.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

* test(secure): expect the error's name in BugseeSecure's warning

PR 45 (0eb3cf0) made <BugseeSecure> warn with the error's name rather than
the error itself. The surface case added on this branch now expects the
same shape.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

* fix(privacy): bound and clamp what the secure store serves, and fail closed on every lane

Re-review fix round 2 (N1-N4).

N1: a lane with an unknown origin serves the display's real bounds, not a
2^20 square. Android records Display.getRealSize from the root's display on
every origin read and from DisplayManager on the first publish; iOS records
UIScreen.mainScreen.bounds on every pull. Android also clamps every served
rectangle to the display and drops one left with no area: the SDK's native
video mask (jni/video.c hideRects) clamps rows to the frame but not a
rectangle's right edge to a row, so an oversized one blacked out the start
of the next row on every row. Before the size is known the fallback is a
16384 square.

N2 (Android): a dialog root the tracker forgets (seen detached on a
refresh, or dropped by detach() on host destroy) while its surface holds
rectangles has its origin reset to unknown, so it fails closed, and goes
back to pending, so a later refresh re-watches it. No origin is recorded
from a root that is not attached to its window.

N3 (Android): only a non-empty publish watches a surface, and a pending
surface whose rectangles JS has cleared is dropped with its empty lane
instead of being retried.

N4 (ruling): the main lane fails closed too. Until its origin has been read
once, its rectangles are served as one display-bounds rectangle.

iOS: a note at BGSRNModalSurfaceOrigin for #30's rebase.

Tests: JVM root and example-Gradle 332 tests, 0 failures; BugseeRNSupport
261, 0 failures. Mutations, each reverted: main lane not failing closed (13
JVM failures, 4 XCTest), no clamp (everyServedRectangleIsClampedToTheDisplay),
no forgetOrigin (2), recording an unattached root's origin (2), no pending
prune (2), iOS screen size ignored (4).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

* test(e2e): check the Modal's secure view in every video frame too

Re-review fix round 2 (N6). The Modal holds a red witness view, not
secure. On targets with a device screenshot, the test finds the witness
there, crops it and the Modal's secure view from every interior video frame
(letterbox mapped and checked against video.aux on Android, width-fit on
iOS, as secure-component.test.ts does), and requires the secure view to be
dark in every frame that shows the witness, with the Modal still up in the
last frame. Both crops come from the same frames, so the video's timestamp
lag does not matter.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

* fix(privacy): drop a JS runtime's Modal surfaces when it goes away

Re-review 2 fix (N7). After a JS reload with a secure Modal open, the old
runtime's Modal lane failed closed for the rest of the process: dispose
forgot its root and nothing retried it, so every recording was black. A
Modal surface's key is a React tag only its runtime knows, so the store now
drops every surface but the main one when a runtime goes: the new module
claims the store on construction (Android constructor, iOS init) and the
old one releases it on invalidate. Claim and release are serialised, and a
release by a module whose claim is no longer current drops nothing, so an
old module invalidated after the new one started cannot drop the new
runtime's surfaces. A disposed Android tracker keeps nothing pending. The
main lane keeps the N4 behaviour.

N8 (ruled): the store headers now say the main surface stays one
whole-display rectangle while no React root can be found.

Tests: JVM claimingANewRuntimeDropsEveryModalSurfaceButKeepsTheMainOne,
releasingTheCurrentRuntimeDropsItsModalSurfaces,
releasingAnOldRuntimeLeavesTheNewRuntimesSurfaces,
disposingWithAnOpenModalLeavesNothingPending, and the three XCTest twins.
Mutations, each reverted: claim without dropping (2 failures), release
ignoring the claim (1), a disposed tracker re-pending (1).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

* chore(android): log at debug what a JS runtime's claim and release drop

Two debug-level lines (BugseeRN, numbers only): how many Modal surfaces
with rectangles a new runtime's claim found left behind, and how many the
released runtime held and left. They are the device evidence for N7: a
dev reload on the WOD_LX1 logs `released: 0 ... held, 0 left` then
`claimed: dropped 0`, since RN's teardown has already cleared the Modal.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

* fix(privacy,android): ignore secure-store writes from a runtime whose claim is stale

A reload can construct the new BugseeModule before the old one is
invalidated, and Fabric numbers React tags from 1 again. The old module's
late publish could put back a Modal lane the new claim dropped, or its
unmount's empty publish could clear the new runtime's lane on the same
tag and uncover the new Modal.

Every lane write a runtime makes now carries its claim: the module's
publish (publishForRuntime) and its tracker's setOrigin, forgetOrigin and
dropSurfaceIfEmpty. The store ignores a write whose claim is not current,
under the same lock as claimRuntime/releaseRuntime, and logs it at debug
with the two claim numbers only.

Addresses PR 48 review thread 4182289966.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

* fix(privacy,ios): ignore secure-store writes from a runtime whose claim is stale

The iOS twin of the Android fix. The module's rectangle writes now carry
its claim (setCoordinates:count:forDisplay:surface:runtime:). The store
ignores one whose claim is no longer current, under the same lock as
claimRuntime/releaseRuntime, and logs it at debug with the two claim
numbers only. So the old runtime's late publish can neither put back a
dropped Modal lane nor clear the new runtime's lane on the same React tag.

Origins, display sizes and empty-lane drops come from the wrapper's pull,
which reads the live view tree and belongs to no runtime, so they are not
claim-gated.

Addresses PR 48 review thread 4182289966.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

* fix(ios): find a Modal host by class and React tag, not viewWithTag:

viewWithTag: returns the first view in a window with that tag. A native
view with the same small integer ahead of the RCTModalHostViewComponentView
made the lookup skip the rest of that window, so the Modal's origin was
never read and its lane failed closed over the whole screen.

BGSRNTaggedView walks each window breadth-first, bounded per window, and
matches a view that is both the host class and has the tag; a wrong-class
view with the tag does not end the search. The Fabric registry is not
used: a module reaches it only through viewRegistry_DEPRECATED, and the
lookup runs in the wrapper's pull, which has no module. The read stays on
main, behind the existing weak host cache.

Addresses PR 48 review thread 4182289985.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

* fix(android): a runtime claim empties the main surface's rectangles

Once stale writes are ignored, the old runtime's unmount [] cannot clear
the main set, so a new tree that never publishes on the main surface kept
masking where the old <BugseeSecure> was until the process died.
claimRuntime now empties the main lane's rectangles and keeps its origin,
so the new runtime's first publish is the only main set. Stale empty
writes stay ignored.

Addresses PR 48 review thread 4184201603.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

* fix(ios): a runtime claim empties the main surface's rectangles

The iOS twin of the Android fix: claimRuntime empties the main lane's
rectangles and keeps its origin, so the old runtime's main set cannot
outlive it when its clearing write is ignored as stale and the new tree
never publishes on the main surface. Stale empty writes stay ignored.

Addresses PR 48 review thread 4184201603.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

* fix(privacy,ios): place a Modal lane by the current runtime's own host

The pull found a Modal's origin by searching the windows for a host with
the lane's React tag, and cached the first hit. During a reload Fabric
numbers tags from 1 again, so the old runtime's still-presented host can
share the tag, and its origin (a sheet inset, say) would place the new
runtime's rectangles somewhere else: a leak, not a blackout.

The module now names its own runtime's lookup with each Modal publish:
its viewRegistry_DEPRECATED (RCTViewRegistry, per runtime), claim-gated
in the store like every other write. Fabric mounts the host after JS has
published inside it, so the pull asks that lookup on main until it finds
an RCTModalHostViewComponentView with the tag, then holds it weakly and
reads the origin from it, the shape of Android's watchSurface. A claim
forgets every host and lookup; a surface's go with its last lane.

Without a registry, or while it finds nothing, the pull falls back to the
bounded class+tag walk, which now gives no origin for several matches or
for a match that is not presented: the lane stays unknown and is served
as the whole display. The weak host cache is gone.

Addresses PR 48 review thread 4184201590.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

* fix(privacy,ios): a named Modal host that is not found yet fails closed

Fabric mounts a Modal's host after the publish that names this runtime's
lookup, so the lookup finds nothing for the first pulls of every new
Modal. The pull then fell back to the class+tag walk, and during a reload
the old runtime's still-presented host can be its only match: for about a
second its origin placed the new runtime's rectangles.

Once the current runtime has named a host or lookup for a surface
(isHostNamedForSurface:), a lookup that finds nothing means the origin is
unknown: the lane stays fail-closed, served as the whole display, until
the lookup finds this runtime's host. The walk runs only when nothing was
ever named, such as without a view registry.

Addresses PR 48 review thread 4184201590 (round 2 review, issue 1).

🤖 Generated with [Claude Code](https://claude.com/claude-code)
…om the screen's origin

JS measures with `measureInWindow`, in the window's points. The iOS SDK
that composes every window of the app on the screen (bugsee-cocoa
#170-#172, #186) draws secure rectangles and places native view-tree
nodes in screen points, so in a window away from the screen's origin
(iPad Stage Manager, the right-hand side of Split View, iPhone Duo side
by side) a rectangle moved only by the window's `frame.origin` lands off
the view it covers, and the view is recorded in the clear.

Every lane now takes the window's place on its screen, read the way the
SDK places windows (through the screen's fixed space): the main surface,
each <Modal> lane from #48 (the presented view's origin in its window
plus that place), and the `vh` origin. On a Mac the SDK records the key
window's scene alone, and the place stays `frame.origin`.

The key window and the windows searched follow the SDK's current pick:
the application's key window while its scene is in the foreground, and
every foreground scene's windows on that screen when there are several.

The SDK pulls the rectangles once per captured frame, and the main
surface's origin is re-read on every pull. BGSRNReactRootOriginTracker
keeps the React root found last, weakly, and reads its window's place;
only without one does a pull search, at most once per 0.5 s (a brownfield
app's native screens have no root). A JS publish on the main surface and
a `vh` request search at once.

Needs the iOS SDK release with that composition; ships with the pin bump.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@dsheikherev
dsheikherev force-pushed the feat/ios-secure-rect-screen-origin branch from 282233b to 02499fa Compare October 6, 2026 09:50
@dsheikherev
dsheikherev marked this pull request as ready for review October 6, 2026 10:01

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale comment

Deep code review

Re-review of 02499fa (one-commit rebuild on main after #48). Prior heads were 282233b and 4af4f55. The tracker, window placement, and Modal term are carefully built against #48’s lanes. The merge gate from the last two reviews is still open.

Prior findings

P2 — fully addressed (still). #48 records originKnown and serves the whole display until an origin is set, and the SDK pull re-reads every origin before it snapshots. The earlier 99012d7 publish-on-main ordering was dropped on purpose; that does not reintroduce unmoved rectangles. A first setSecureRectangles on the TurboModule queue is fail-closed until the main-thread hop (or the next pull) records the place.

P1 High — not addressed. BGSRNSdkComposesScreen() is still “not Catalyst / not iOS-on-Mac”. The pin is still 7.0.0-beta3. Beta3 records the key window’s scene and draws rectangles / native nodes in scene points (frame.origin). Adding the screen origin inverts the Stage Manager / Split View bug this PR exists to fix: masks and the React tree sit one window-offset away from the content, so secret UI is recorded in the clear. iPhone (origin {0,0}) is unchanged; simulator e2e cannot tell the origins apart. The PR body still names 7.0.0-beta4 (cocoa #170–#172 and #186) as the gate and keeps the PR in draft until that pin lands in this change.

No new P0–P3 on the tracker, Modal window term, key-window pick, or pull path.

Overall risk: High

High because the remaining P1 is a privacy defect on the currently declared SDK, in the exact iPad configuration the PR is meant to fix. Against the unreleased compositor SDK the design looks right.

Merge recommendation

Do not merge onto main while native-versions.json / ios/Support/Package.swift still pin 7.0.0-beta3. Keep the draft until the compositor SDK is pinned in this PR, as the description already states. I did not run the Support XCTests or e2e on this Linux agent. CI on this SHA already reported ios unit (BugseeRNSupport), ios (cocoapods), and ios (spm) SUCCESS — those jobs resolve beta3, so green CI does not close P1.

Most important to fix

  1. Tie screen-space origin (and the multi-scene window walk) to the SDK that actually composites, and bump the pin in the same change.
  2. Still missing on this SHA: managed view-tree and secure-component / secure-modal on a device window that is not at the screen origin (called out in the test plan).

What looks solid

  • Weak cached React root; a pull only searches when there is none, at most once per 0.5 s (brownfield-safe). A JS publish on the main surface and a vh request search at once.
  • Every pull re-reads the cached root’s place, so a Stage Manager drag is followed with no extra search.
  • Modal origin is the presented view in its window plus BGSRNWindowRecordedOrigin, and a window with no screen is no origin (fail closed) rather than frame.origin.
  • #48’s host naming / unique-tag walk is unchanged; tests now pass the window term explicitly because the test runner’s windows are on no screen.
Open in Web View Automation 

Sent by Cursor Automation: Bugsee code review

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale comment

Deep code review

Re-review of 02499fa (ready-for-review; prior reviews were of 282233b / 4af4f55, then this same rebuild). The tracker, fail-closed store, pull-time origin refresh, and Modal window-placement term are carefully built against the compositor SDK. The merge gate this PR stated is not met.

Prior findings

P2 — fully addressed. setSecureRectangles still writes coordinates on the TurboModule queue and hops to main for the origin. That is safe here because #48’s originKnown serves the whole display until an origin is recorded, and secureRectanglesForDisplay: re-reads every origin on the SDK pull before it snapshots. The dropped 99012d7 publish-on-main order does not re-open a leak of unmoved rectangles.

P1 High — not addressed. BGSRNSdkComposesScreen() is still “not Catalyst / not iOS-on-Mac”. The pin is still 7.0.0-beta3. Beta3 records the key window’s scene and draws rectangles / native nodes in scene points (frame.origin). Adding the screen origin inverts the Stage Manager / Split View bug this PR exists to fix: masks and the React tree sit one window-offset away from the content, so secret UI is recorded in the clear. Marking the PR ready does not change what a consumer of this branch resolves. bugsee/spm still has no 7.0.0-beta4 tag.

No new P0–P3 on the tracker (weak root, 0.5 s pull-search cap, immediate search on publish/vh), the Modal windowOrigin term, or window lookup. I did not run the Support XCTests or e2e on this Linux agent. CI on this head: ios unit (BugseeRNSupport), ios (cocoapods), ios (spm), and ios e2e (simulator) are green — those delivery jobs resolve beta3, and the simulator cannot distinguish scene vs screen origin.

Overall risk: High

High because the remaining P1 is a privacy defect on the currently declared SDK, in the exact iPad configuration the PR is meant to fix. Against the unreleased compositor SDK the design looks right.

Merge recommendation

Do not merge onto main while native-versions.json / ios/Support/Package.swift still pin 7.0.0-beta3. Return the PR to draft until the compositor SDK is pinned in the same change, or gate BGSRNWindowRecordedOrigin / the multi-scene walk so beta3 keeps frame.origin.

Most important to fix

  1. Tie screen-space origin (and the composed window walk) to the SDK that actually composites, and bump the pin in the same change.
  2. Still missing from the test plan on this SHA: iPad Stage Manager secure-component / secure-modal / managed view-tree against that SDK.

What looks solid

  • Pull refreshes origins on main before snapshot; unknown origin fails closed over the display rather than serving window-space rectangles.
  • Weak cached React root, no per-frame search while it stays in a window, brownfield-safe 0.5 s cap, immediate search on JS publish and vh.
  • Modal origin is presented-content-in-window plus the same window placement as the main surface; a window with no place yields no origin.
  • Mac / Catalyst still uses frame.origin, matching the SDK recording the key window’s scene alone.
Open in Web View Automation 

Sent by Cursor Automation: Bugsee code review

7.0.0-beta4 is the release that composes every window of the app on the
screen (bugsee-cocoa #170-#172, #186), which the previous commit places
secure rectangles and the view tree for.

- Core: native-versions.json, ios/Support/Package.swift and
  Package.resolved (revision e467f57c, the bugsee/spm tag).
- Feedback: both exact SPM pins and the README. An app using both packages
  through SPM cannot resolve two different exact pins on bugsee/spm.
- The design's Goals and the plan's Global Constraints name the live pin
  (docs-versions check).
- e2e secure-component and secure-modal: from beta4 the iOS SDK writes
  video.aux version 2. A full-screen app's frame is the screen, in points,
  with no letterbox padding, so the width-fit mapping still holds; the check
  now asserts that instead of no video.aux at all.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deep code review

Re-review of 885bcf1 (pin to 7.0.0-beta4 on top of the 02499fa rebuild). Prior heads were 282233b, 4af4f55, then 02499fa still on beta3. The tracker, window placement, and Modal term were already aligned with the compositor SDK; the merge gate is now met in the declared pin.

Prior findings

P2 Medium — fully addressed (still). setSecureRectangles still writes coordinates on the TurboModule queue and hops to main for the origin. That remains safe because #48’s originKnown serves the whole display until an origin is recorded, and secureRectanglesForDisplay: re-reads every origin on the SDK pull before it snapshots.

P1 High — fully addressed. native-versions.json, ios/Support/Package.swift, Package.resolved (revision e467f57c), and the feedback package’s SPM pins all name 7.0.0-beta4. The bugsee/spm and bugsee/feedback-spm tags exist; the xcframework zip at download.bugsee.com answers HTTP 200. BGSRNSdkComposesScreen() is still only “not Catalyst / not iOS-on-Mac”, which is the correct gate for this pin: beta4 is the release that composes every app window onto the screen (cocoa #170–#172 and #186). Adding the window’s place through fixedCoordinateSpace → screen.coordinateSpace now matches what the SDK draws, instead of inverting it.

No new P0–P3 on the tracker, Modal windowOrigin term, key-window pick, or pull path. I did not run the Support XCTests on this Linux host. CI on this SHA already reports ios unit (BugseeRNSupport), ios (cocoapods), and lint, typecheck, unit SUCCESS. ios e2e (simulator) and ios (spm) were still running at review time.

Overall risk: Low

The privacy defect against beta3 is closed. Against the pinned compositor SDK the design matches the cocoa contract (window → fixed space → screen space; nil when the window is on no screen).

Merge recommendation

Safe to merge once ios e2e (simulator) and ios (spm) are green on this SHA. Those jobs are the first delivery path that actually resolve beta4 and assert video.aux v2. Do not treat the earlier green e2e on 02499fa (beta3, no video.aux) as coverage of this pin.

Most important remaining work (not merge blockers for the stated one-window case)

  1. The test plan’s iPad Stage Manager / Duo secure-component and secure-modal pass is still unchecked on this SHA (the earlier device check used a locally built cocoa SDK).
  2. Simulator e2e cannot tell scene origin from screen origin, because the scene fills the screen.

What looks solid

  • Weak cached React root; a pull only searches when it has none, at most once per 0.5 s (brownfield-safe). A JS publish on the main surface and a vh request search at once. Every pull re-reads the cached root’s place, so a Stage Manager drag is followed with no extra search.
  • Modal origin is the presented view in its window plus BGSRNWindowRecordedOrigin; a window with no screen is no origin (fail closed) rather than frame.origin.
  • Mac / Catalyst still uses frame.origin, matching the SDK recording the key window’s scene alone.
  • Fail-closed store: unknown origin still covers the display instead of serving window-space rectangles.
Open in Web View Automation 

Sent by Cursor Automation: Bugsee code review

@dsheikherev

Copy link
Copy Markdown
Contributor Author

iPad check on 885bcf1 with the released 7.0.0-beta4

Setup

  • Device: iPad Air 5, iPadOS 27.2, Stage Manager, with the BareExample window moved away from the screen's top-left corner.
  • Build: BareExample Debug built from 885bcf1. The SDK line reads Bugsee IOS SDK ver:7.0.0-beta4 build:bb2f0e7e-12.
  • Launch: each scenario through its launch arguments, with the dead endpoint, so reports stay on the device. Bundles were pulled from the app container (Library/Caches/com.bugsee.data).
  • Window position: (492.3, 176.9) points on the 1180×820 screen, derived from the secure-component witness. The vh origin below rounds it to (493, 176).

secure-component

Each mask was found as the black rectangle in the white content. Report screenshot, in screen points:

phase expected (window + measureInWindow) measured
mounted (552.3, 436.9) 180×90 (552.1, 436.5) 179.4×89.7
scrolled (552.3, 336.9) 180×90 (552.1, 336.9) 179.4×89.7
unmounted no mask no mask

Video, last frame of each report. The MOV is a portrait canvas with the content rotated, so the mask is measured relative to the witness:

phase expected offset from the witness measured
mounted (40, −183) (40, −184)
scrolled (40, −283) (40, −284)

With the previous frame.origin placement, which is {0, 0} in Stage Manager, the mask would have been at (60, 260) on the screen, outside the window.

secure-modal-sheet (pageSheet)

  • Leaks: not a single #FF00FF (root) or #00FFFF (sheet) pixel in the report screenshot or in any of the 77 video frames.
  • Witness: the red witness is visible, so the sheet was presented.
  • Placement: both masks sit on their views within about 3 pt. The root mask is at (532.7, 296.7) 158×79 for an expected (532, 297) 160×80. The sheet mask is at (596.7, 509.1) 199×101 for an expected (596, 512) 200×100, with the sheet's content starting at about (492, 232).

view-tree

  • Both vh requests answered origin=493,176. On the simulator this is (0, 0).
  • In the report's viewtree.json, the managed ReactNative root is [493, 176, 650, 603], the same as the native UIWindow and RCTSurfaceHostingProxyRootView.
  • The managed vh-open host is [523, 476, 150, 60], exactly where the SDK places its native RCTViewComponentView (frame [30, 300, 150, 60] in the window).

video.aux on the iPad is {"version":2, "frameW":1180, "frameH":820, "density":2} with no padding.

Not covered: a real iPhone Duo, Split View, and a window dragged during recording. Each run kept the window in one place.

🤖 Generated with Claude Code

@krassx
krassx merged commit d55c45e into main Oct 6, 2026
36 checks passed
@krassx
krassx deleted the feat/ios-secure-rect-screen-origin branch October 6, 2026 11:09
krassx added a commit that referenced this pull request Oct 6, 2026
…e.lock

#30 pinned 7.0.0-beta4. This is what that pin left behind.

Verified against the published artifact: bugsee/spm 7.0.0-beta4 ->
e467f57c, Bugsee-7.0.0-beta4.zip SHA-256 90a923ec...f6ec87b (= the
Package.swift checksum), build stamp bb2f0e7e-12. Headers and module maps
are byte-identical to beta3's in the ios-arm64 and simulator slices; every
iOS slice is minos 15.0; the simulator slice still has no crash reporter.
bugsee/feedback-spm 7.0.0-beta4 (f23afe34) has the same sources as beta3.

- Comments that said Bugsee supports iOS below 15 (podspec, the Support
  manifest, platform-floors.ts and its test) now say what is true: its
  floor is 15.0 since beta2, re-checked on beta4.
- Comments that cite the vendored header (report and feedback appearance)
  name beta4; the ones describing behaviour beta4 did not change say so
  (deleteCollectedDataOnDevice, the simulator slice without a crash
  reporter).
- Podfile.lock: a plain `pod install`. The committed lock predated the
  feedback pod.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
krassx added a commit that referenced this pull request Oct 6, 2026
* build(ios): beta4 follow-ups: floor comments, header comments, Podfile.lock

#30 pinned 7.0.0-beta4. This is what that pin left behind.

Verified against the published artifact: bugsee/spm 7.0.0-beta4 ->
e467f57c, Bugsee-7.0.0-beta4.zip SHA-256 90a923ec...f6ec87b (= the
Package.swift checksum), build stamp bb2f0e7e-12. Headers and module maps
are byte-identical to beta3's in the ios-arm64 and simulator slices; every
iOS slice is minos 15.0; the simulator slice still has no crash reporter.
bugsee/feedback-spm 7.0.0-beta4 (f23afe34) has the same sources as beta3.

- Comments that said Bugsee supports iOS below 15 (podspec, the Support
  manifest, platform-floors.ts and its test) now say what is true: its
  floor is 15.0 since beta2, re-checked on beta4.
- Comments that cite the vendored header (report and feedback appearance)
  name beta4; the ones describing behaviour beta4 did not change say so
  (deleteCollectedDataOnDevice, the simulator slice without a crash
  reporter).
- Podfile.lock: a plain `pod install`. The committed lock predated the
  feedback pod.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

* test(e2e): attributes case 3 is a plain it on iOS now that beta4 seeds reports

7.0.0-beta4 carries bugsee-cocoa#164 (fe857ad97): a new report starts from
the global attributes and the user identifier. On the iOS simulator
(6FA9B3E8, beta4 bb2f0e7e-12) the `it.failing` case 3 failed with "Failing
test passed even though it was supposed to fail"; as a plain `it` all six
cases pass. Android already ran it as a plain `it`.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

* test(e2e): iOS recovers the stored JS crash on beta4, so those cases are plain it

7.0.0-beta4 claims override_report.plcrash ahead of the live crash
(bugsee-cocoa #177, 7bca39c59). On the iPhone XS (KRSFT, iOS 18.7.9, SDK
bb2f0e7e-12) the three `itIosDevice.failing` cases failed with "Failing
test passed even though it was supposed to fail":

- exc-fatal: the relaunch recovered one `crash` bundle, a
  ReactNativeWebException carrying "E2E fatal <nonce>";
- exc-root: one `crash` bundle, a ReactNativeWebException;
- Release gate: the console ended, no RCTFatalException bundle, and exactly
  one ReactNativeWebException crash.

They are plain `it` now. BugseeModule's logUnhandledException comment no
longer says the stored report is dropped.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

* docs: say iOS recovers the stored JS crash intermittently, not always

Review (cursor, #56): the exceptions comments and logUnhandledException's
read as if beta4 always recovers override_report.plcrash. On the iPhone XS
2 of 5 relaunches left it unclaimed. The cases stay a plain `it` asserting
the correct outcome; they do not relaunch again, because a second launch
would hide the SDK miss the device run exists to show.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants