From 580c245a33c5d45c09a0656043199d927a4bda78 Mon Sep 17 00:00:00 2001 From: Alexey Karimov Date: Tue, 6 Oct 2026 15:39:37 +0500 Subject: [PATCH 1/4] build(ios): beta4 follow-ups: floor comments, header comments, Podfile.lock MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #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) --- docs/design/2026-09-15-sdk-design.md | 6 ++-- examples/bare/e2e/report-handler.test.ts | 2 +- examples/bare/ios/Podfile.lock | 31 +++++++++++++++++-- packages/react-native-feedback/README.md | 3 +- .../ios/BugseeFeedbackModule.mm | 2 +- .../react-native-feedback/src/appearance.ts | 2 +- .../react-native/BugseeReactNative.podspec | 5 +-- packages/react-native/ios/BugseeModule.mm | 4 +-- .../react-native/ios/Support/Package.swift | 11 +++---- .../src/appearance/__tests__/report.test.ts | 2 +- .../react-native/src/appearance/report.ts | 2 +- scripts/__tests__/platform-floors.test.ts | 4 +-- scripts/platform-floors.ts | 4 ++- 13 files changed, 54 insertions(+), 24 deletions(-) diff --git a/docs/design/2026-09-15-sdk-design.md b/docs/design/2026-09-15-sdk-design.md index a1ec7dbd..1fb83a06 100644 --- a/docs/design/2026-09-15-sdk-design.md +++ b/docs/design/2026-09-15-sdk-design.md @@ -127,7 +127,7 @@ Verified during Phase 3, on-device and by reading both SDKs' sources. `bugsee/sp **Android.** A crash the SDK recovers through its own bounded early-recovery dispatch โ€” a call off the live `BugseeReportHandlerThread`, completion `Callback.NOOP` โ€” completes natively at once (`completed by=recovery`) and never reaches JS; the bridge detects this by thread name, not by `isTerminating` (the plan's Phase 3 rulings). A crash recovered at the *next launch*, when that launch is JS-initiated, is not one path. A Java uncaught exception is dispatched on the live `BugseeReportHandlerThread` with the ordinary 25 s live deadline, so it does reach JS and `onAfterReportCreated`'s edits land in the retained crash bundle. Verified on the WOD_LX1 (Task 3.4d): the relaunch logged `deadline=25000` and `completed by=js`, never `by=recovery`. An NDK crash (SIGSEGV or SIGABRT) recovered at that same JS-initiated launch is the bounded path instead: the SDK dispatches it on `bugsee-report-handler-bounded`, the bridge completes `by=recovery`, and the JS handler is never asked. Verified on the WOD_LX1 (Task 7.6b): the relaunch logged `completed by=recovery phase=after` and never `by=js`. -**iOS.** Live dispatch is on the main thread (25 s deadline); a report recovered at relaunch is dispatched off main (2.5 s deadline) โ€” and, unlike Android's bounded early-recovery path, it **does** reach JS, because iOS re-persists the report on a late completion until the bundle is assembled. The recovery case cannot run in the Simulator: the simulator slice of 7.0.0-beta3 has no crash reporter (Task 3.4f), so it is gated `E2E_IOS_RECOVERY=1` and proven only on physical hardware (ยง12). +**iOS.** Live dispatch is on the main thread (25 s deadline); a report recovered at relaunch is dispatched off main (2.5 s deadline) โ€” and, unlike Android's bounded early-recovery path, it **does** reach JS, because iOS re-persists the report on a late completion until the bundle is assembled. The recovery case cannot run in the Simulator: the simulator slice of 7.0.0-beta3 has no crash reporter (Task 3.4f), and neither has 7.0.0-beta4's (no PLCrashReporter symbol in `ios-arm64_x86_64-simulator`), so it is gated `E2E_IOS_RECOVERY=1` and proven only on physical hardware (ยง12). **iOS threading, load-bearing for both cases.** The SDK invokes the live report handler with `dispatch_async` onto main; its completion is a thread-agnostic run-once that hops to a private queue. Main is never blocked waiting for it (`BGSIssueReportingCoordinator.m` on `nextgen`). Consequently the bridge's own report ops and its call to `completeReportHandler` must never hop to main themselves โ€” there is no need to, and queuing behind whatever UI work is already on main would eat into the handle's deadline for nothing. The bridge completes from background queues throughout. @@ -159,7 +159,7 @@ Typed `EventEmitter` members (available in codegen from RN 0.76, so safe at a - `s.platforms` reads React Native's own `min_ios_version_supported` (15.1 from RN 0.76 onward) rather than a literal, so the pod tracks the app's RN - version. As of `7.0.0-beta3` the SDK's own deployment target is 15.0 (raised + version. As of `7.0.0-beta4` the SDK's own deployment target is 15.0 (raised in beta2, from the 13.0 this section originally recorded), so the two floors now agree โ€” see ยง4.5 and `platform-floors.ts`. - **No `s.dependency 'Bugsee'`** โ€” no pod exists, and none ever will. @@ -518,7 +518,7 @@ Work outside this repository that this design depends on or has surfaced. > of 2026-09-15. Phase 3 of the plan (rulings and *Verified facts*, > 2026-09-28) superseded them: Android moved to `7.3.0` (pinned transitionally > as `7.3.0-SNAPSHOT`), the Gradle plugin to `4.0.7`, and iOS to `7.0.0-beta3` -> with a 15.0 deployment target. Read this appendix for the shape of the +> with a 15.0 deployment target; iOS then moved to `7.0.0-beta4` (2026-10-06). Read this appendix for the shape of the > verification (what was checked and how), not for the version numbers > themselves. diff --git a/examples/bare/e2e/report-handler.test.ts b/examples/bare/e2e/report-handler.test.ts index f45cd2fe..572e957c 100644 --- a/examples/bare/e2e/report-handler.test.ts +++ b/examples/bare/e2e/report-handler.test.ts @@ -80,7 +80,7 @@ import { const itAndroid = ON_ANDROID ? it : it.skip; /** * Case 6 on iOS needs a crash reporter, and the simulator slice of the iOS SDK - * has none: 7.0.0-beta3's `ios-arm64_x86_64-simulator` binary carries no + * has none: 7.0.0-beta3's and 7.0.0-beta4's `ios-arm64_x86_64-simulator` binaries carry no * BGSCrashManager or PLCrashReporter symbols (the SDK compiles crash hooks * out under TARGET_OS_SIMULATOR), so a crash there is never recovered and the * case fails at its first recovery assertion (Task 3.4f report). It runs only diff --git a/examples/bare/ios/Podfile.lock b/examples/bare/ios/Podfile.lock index 071b3e47..4516899a 100644 --- a/examples/bare/ios/Podfile.lock +++ b/examples/bare/ios/Podfile.lock @@ -45,6 +45,29 @@ PODS: - ReactCommon/turbomodule/core - ReactNativeDependencies - Yoga + - BugseeReactNativeFeedback (0.0.0): + - BugseeReactNative + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-bridging + - React-Core + - React-Core-prebuilt + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga - DoubleConversion (1.1.6): - ReactNativeDependencies - fast_float (8.0.0): @@ -1657,6 +1680,7 @@ DEPENDENCIES: - boost (from `build/rndeps-facades/boost`) - BugseeE2ENative (from `../node_modules/bugsee-e2e-native`) - "BugseeReactNative (from `../node_modules/@bugsee/react-native`)" + - "BugseeReactNativeFeedback (from `../node_modules/@bugsee/react-native-feedback`)" - DoubleConversion (from `build/rndeps-facades/DoubleConversion`) - fast_float (from `build/rndeps-facades/fast_float`) - FBLazyVector (from `build/rncore-facades/FBLazyVector`) @@ -1750,6 +1774,8 @@ EXTERNAL SOURCES: :path: "../node_modules/bugsee-e2e-native" BugseeReactNative: :path: "../node_modules/@bugsee/react-native" + BugseeReactNativeFeedback: + :path: "../node_modules/@bugsee/react-native-feedback" DoubleConversion: :path: build/rndeps-facades/DoubleConversion fast_float: @@ -1923,7 +1949,8 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: boost: 8502bfdc46e2804408af2e163049ece181009af4 BugseeE2ENative: b4cd280a78bae9185d1a391769ececb30b0eb846 - BugseeReactNative: 684066f42a96c3dcd61e50cc6bf38397738f679f + BugseeReactNative: ac04523adf1a6e3a445d84f3a7656d980e4d28ab + BugseeReactNativeFeedback: f55b2673ed02f3299b6801e154a3256ed5aa19f0 DoubleConversion: 501a575c40c0b50c89685762dd3e415e6bb26fb0 fast_float: 7c925d1cebf8328d4018d061f49377e9f887e3b4 FBLazyVector: e08a27053b503eeb756ea4bd75d570d2f2522a1b @@ -2011,4 +2038,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: 8b6aa5135b91f56aaf82cda5788ba89799309b83 -COCOAPODS: 1.16.2 +COCOAPODS: 1.17.0 diff --git a/packages/react-native-feedback/README.md b/packages/react-native-feedback/README.md index 343f18ec..7701194d 100644 --- a/packages/react-native-feedback/README.md +++ b/packages/react-native-feedback/README.md @@ -51,7 +51,8 @@ published surface: appearance.backgroundColor = '#112233'; ``` -The `feedback-spm` 7.0.0-beta3 SwiftUI chat hard-codes `Color.accentColor` +The `feedback-spm` 7.0.0-beta4 SwiftUI chat (its sources are unchanged +from 7.0.0-beta3) hard-codes `Color.accentColor` and `Color.gray` and does not read `BugseeTheme`, so that assignment does not change the chat in this beta. The setter still stores the color on `BugseeTheme`. diff --git a/packages/react-native-feedback/ios/BugseeFeedbackModule.mm b/packages/react-native-feedback/ios/BugseeFeedbackModule.mm index a2e3b629..db298353 100644 --- a/packages/react-native-feedback/ios/BugseeFeedbackModule.mm +++ b/packages/react-native-feedback/ios/BugseeFeedbackModule.mm @@ -50,7 +50,7 @@ - (void)emitSentMessage:(NSString *)message; static NSSet *keys; static dispatch_once_t once; dispatch_once(&once, ^{ - // BugseeTheme.h in Bugsee 7.0.0-beta3. The SwiftUI chat in this beta + // BugseeTheme.h in Bugsee 7.0.0-beta4 (unchanged from beta3). The SwiftUI chat in this beta // does not read them; they are still the appearance API the header // publishes, and the only one the feedback package can set. keys = [NSSet setWithArray:@[ diff --git a/packages/react-native-feedback/src/appearance.ts b/packages/react-native-feedback/src/appearance.ts index e884249b..8b6f15e1 100644 --- a/packages/react-native-feedback/src/appearance.ts +++ b/packages/react-native-feedback/src/appearance.ts @@ -7,7 +7,7 @@ import NativeBugseeFeedback from './NativeBugseeFeedback'; * * Android keys are the `FeedbackAppearance` constant values in * `bugsee-android-feedback` 7.3.0 (`Feedback::ActionBarColor`, โ€ฆ). iOS keys - * are `BugseeTheme` properties in the 7.0.0-beta3 `Bugsee.xcframework` + * are `BugseeTheme` properties in the 7.0.0-beta4 `Bugsee.xcframework` * header. A name that exists on only one platform has only that side; setting * it on the other throws, so a color that will not be applied is not stored * as if it had been. diff --git a/packages/react-native/BugseeReactNative.podspec b/packages/react-native/BugseeReactNative.podspec index ec4f1896..085dddbd 100644 --- a/packages/react-native/BugseeReactNative.podspec +++ b/packages/react-native/BugseeReactNative.podspec @@ -18,8 +18,9 @@ Pod::Spec.new do |s| # helper in scripts/react_native_pods.rb, which every RN Podfile requires, # so it is in scope by the time CocoaPods evaluates this podspec; the # literal is the fallback for evaluation outside an app (`pod spec lint`). - # Bugsee itself supports 15.0 and below -- down to 13.0 -- but React-Core - # does not, so an app can never actually sit lower than this. + # Bugsee itself floors at 15.0 (since 7.0.0-beta2; the bugsee/spm manifest + # declares .iOS(.v15)), just below React Native's 15.1, so React Native's is + # the floor that binds and an app can never sit lower than this. s.platforms = { :ios => defined?(min_ios_version_supported) ? min_ios_version_supported : '15.1' } diff --git a/packages/react-native/ios/BugseeModule.mm b/packages/react-native/ios/BugseeModule.mm index 981606f2..0602e260 100644 --- a/packages/react-native/ios/BugseeModule.mm +++ b/packages/react-native/ios/BugseeModule.mm @@ -2482,7 +2482,7 @@ - (NSDictionary *)spanStartChild:(NSString *)handle return released; } -/// Writable report colors on BugseeTheme (7.0.0-beta3). Feedback properties +/// Writable report colors on BugseeTheme (7.0.0-beta4; unchanged from beta3). Feedback properties /// and the readonly palette are not report appearance. KVC with any other /// name throws NSUnknownKeyException. static NSSet *BGSRNReportColorKeys(void) { @@ -2562,7 +2562,7 @@ - (NSString *)getAppearanceColor:(NSString *)name { return hex; } -/// iOS 7.0.0-beta3 returns without invoking `completion` when the SDK is +/// iOS 7.0.0-beta3 and beta4 return without invoking `completion` when the SDK is /// not stopped, so a launched call resolves `false` immediately. That is the /// missing completion, not a wait. Stopped, including a nil instance, still /// calls the SDK method and settles through `BGSRNSettleOnce`: the diff --git a/packages/react-native/ios/Support/Package.swift b/packages/react-native/ios/Support/Package.swift index fdf9a13d..56840b87 100644 --- a/packages/react-native/ios/Support/Package.swift +++ b/packages/react-native/ios/Support/Package.swift @@ -17,12 +17,11 @@ import PackageDescription /// did, and CI ran these tests nowhere at all. let package = Package( name: "BugseeRNSupport", - // 15.0, matching the rest of the wrapper rather than the SDK's own 13.0. - // Nothing here links React, so this target *could* sit at 13.0 -- but no - // React Native app can: RN has floored at 15.1 since 0.76, and every - // version with SPM support is above that. A lower number here would only - // advertise reach that no consumer can use, and the native SDK is moving - // to 15+ as well. + // 15.0: the SDK's own floor (7.0.0-beta2 raised it from 13.0, and the + // bugsee/spm manifest declares .iOS(.v15)), and the rest of the wrapper's. + // SwiftPM refuses a package that sits below a product it consumes, so this + // cannot go lower; React Native, at 15.1 since 0.76, could not use it if it + // did. platforms: [.iOS(.v15)], products: [ .library(name: "BugseeRNSupport", targets: ["BugseeRNSupport"]) diff --git a/packages/react-native/src/appearance/__tests__/report.test.ts b/packages/react-native/src/appearance/__tests__/report.test.ts index 4254be2e..06784f37 100644 --- a/packages/react-native/src/appearance/__tests__/report.test.ts +++ b/packages/react-native/src/appearance/__tests__/report.test.ts @@ -27,7 +27,7 @@ const ANDROID_ENUM: Record = { /** * Writable report color properties on `BugseeTheme` in the vendored - * 7.0.0-beta3 header. Feedback properties and the readonly palette are not + * 7.0.0-beta4 header. Feedback properties and the readonly palette are not * report appearance. */ const IOS_PROPERTY: Record = { diff --git a/packages/react-native/src/appearance/report.ts b/packages/react-native/src/appearance/report.ts index b8e36d2b..fb90ad90 100644 --- a/packages/react-native/src/appearance/report.ts +++ b/packages/react-native/src/appearance/report.ts @@ -7,7 +7,7 @@ import NativeBugsee from '../NativeBugsee'; * * Android values are the `ReportAppearance` constants in bugsee-android 7.3.0 * (`setColor(ReportAppearance.X, int)`). iOS values are writable report color - * properties on `BugseeTheme` in the vendored 7.0.0-beta3 header. A name that + * properties on `BugseeTheme` in the vendored 7.0.0-beta4 header. A name that * exists on only one platform has only that side. Setting it on the other * throws, so a color that will not be applied is not stored as if it had been. * Reading it returns `undefined` and does not call native. diff --git a/scripts/__tests__/platform-floors.test.ts b/scripts/__tests__/platform-floors.test.ts index 4b3f9ba5..8383537f 100644 --- a/scripts/__tests__/platform-floors.test.ts +++ b/scripts/__tests__/platform-floors.test.ts @@ -85,8 +85,8 @@ describe('the wrapper does not raise the floor above the SDK it wraps', () => { }); describe('iOS sits at 15 everywhere, deliberately', () => { - // BugseeRNSupport links no React, so it *could* sit at the SDK's 13.0. It - // does not, because no RN app can reach 13.0 anyway -- see IOS_FLOOR. + // BugseeRNSupport links no React; its floor is the SDK's own 15.0 (since + // 7.0.0-beta2), which SwiftPM will not let it sit below -- see IOS_FLOOR. it('ios/Support/Package.swift declares the wrapper floor', () => { expect(parseSwiftPlatform(read('ios', 'Support', 'Package.swift'))) .toBe(IOS_FLOOR.spm); diff --git a/scripts/platform-floors.ts b/scripts/platform-floors.ts index 057a8822..42c9803c 100644 --- a/scripts/platform-floors.ts +++ b/scripts/platform-floors.ts @@ -24,7 +24,9 @@ export const SDK_FLOORS = { * * 7.0.0-beta1 shipped 13.0 and the wrapper sat above it deliberately, * because no React Native app could reach 13.0 anyway. beta2 raised the - * SDK itself to 15.0, so the two now agree and the exception is gone. */ + * SDK itself to 15.0, so the two now agree and the exception is gone. + * Re-checked on 7.0.0-beta4: `minos 15.0` in every iOS slice, and the + * bugsee/spm manifest declares iOS 15 / tvOS 15 / visionOS 1. */ iosDeploymentTarget: '15.0', } as const; From e1869f28c004571c49d08cb0f2c2077e73b5d550 Mon Sep 17 00:00:00 2001 From: Alexey Karimov Date: Tue, 6 Oct 2026 15:43:34 +0500 Subject: [PATCH 2/4] test(e2e): attributes case 3 is a plain it on iOS now that beta4 seeds reports MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- examples/bare/e2e/attributes.test.ts | 66 +++++++--------------------- 1 file changed, 16 insertions(+), 50 deletions(-) diff --git a/examples/bare/e2e/attributes.test.ts b/examples/bare/e2e/attributes.test.ts index f79f7846..2649d50f 100644 --- a/examples/bare/e2e/attributes.test.ts +++ b/examples/bare/e2e/attributes.test.ts @@ -29,24 +29,15 @@ * own check independently say it should not -- the iOS SDK team has been * asked why, and the row is omitted rather than asserted either way. * - * **iOS SDK regression, confirmed and fixed upstream:** - * https://github.com/bugsee/bugsee-cocoa/pull/164 (base `nextgen`; there is - * no separate issue -- the PR is the record). Root cause: nextgen lacked - * Android's `initializeReport`, so global attributes and the identifier were - * never copied into a new report (`BGSManifestCreator.userAttributes` is - * legacy and unused on this path). So a live `Bugsee.upload()` report's - * `manifest.json` `attrs` and `request.json` `email` never carry the global - * attributes or user identifier at all on iOS 7.0.0-beta3, even though - * `getAttribute`/`getAllAttributes`/`getUserIdentifier` all read them back - * correctly right up to the `upload()` call. Case 3's `manifest.attrs`/ - * `email` assertions therefore run as `it.failing` on iOS only, so the suite - * stays green while asserting the CORRECT (currently unmet) behaviour, and - * turns red -- forcing an update -- once the RN pin moves to an iOS beta - * containing bugsee-cocoa#164. Whether a bundle was retained at all, and - * whether it's this run's own report, is asserted separately in a plain - * `it` that is never `.failing` -- so a harness regression (no bundle - * pulled, or the wrong one) fails loudly instead of being swallowed as "the - * known SDK bug failing as expected". + * **iOS SDK regression, fixed in 7.0.0-beta4:** + * https://github.com/bugsee/bugsee-cocoa/pull/164 (`fe857ad97`). nextgen + * lacked Android's `initializeReport`, so on 7.0.0-beta3 a live + * `Bugsee.upload()` report's `manifest.json` `attrs` and `request.json` + * `email` never carried the global attributes or user identifier. Case 3 + * was `it.failing` on iOS until the pin moved to beta4, where it passed on + * the simulator (2026-10-06) and became a plain `it` on both platforms. + * Whether a bundle was retained at all, and whether it's this run's own + * report, is still asserted separately, before case 3. * * Android preconditions, as for data.test.ts: the debug build is installed on * the handset named in device.ts, and Metro is running with @@ -439,12 +430,9 @@ describeDevice(`attributes and identity round-trip on ${TARGET_NAME}`, () => { /** * `bundles[0]` only -- deliberately asserts nothing. Exactly one bundle, - * and that it's this run's own report, is asserted once, in a plain `it` - * that is never `.failing` (below): if this helper itself threw and were - * called from inside `it.failing`'s case 3, a harness regression (no - * bundle pulled, a stale one from an earlier run) would be swallowed as - * "the known SDK bug failing as expected" and the suite would stay green - * for the wrong reason. + * and that it's this run's own report, is asserted once, in its own `it` + * (below), so a harness regression (no bundle pulled, a stale one from an + * earlier run) fails under its own name rather than as case 3. */ function theBundle(): PulledBundle { return bundles[0]!; @@ -497,33 +485,11 @@ describeDevice(`attributes and identity round-trip on ${TARGET_NAME}`, () => { }); /** - * Confirmed iOS SDK regression, fixed upstream in - * https://github.com/bugsee/bugsee-cocoa/pull/164 (base `nextgen`; there - * is no separate issue -- the PR is the record). Root cause: nextgen - * lacked Android's `initializeReport`, so global attributes and the - * identifier were never copied into a new report -- - * `BGSManifestCreator.userAttributes` is legacy and unused on this path. - * So on iOS 7.0.0-beta3, a live `Bugsee.upload()` report's - * `manifest.json` `attrs` comes back `{}` and `request.json` has no - * `email` key, even though `getAttribute`/`getAllAttributes`/ - * `getUserIdentifier` all read them back correctly right up to the - * `upload()` call (case 1 above). - * - * `it.failing` (a Jest built-in): the body below asserts the CORRECT - * behaviour -- unweakened, identical in shape to Android's -- and this - * test passes exactly because those assertions currently fail on iOS. - * Remove `.failing` when fixed, i.e. once the RN pin moves to an iOS beta - * containing bugsee-cocoa#164. Android runs the same body as a normal - * `it`, since it has no such bug. - * - * ONLY the two SDK-bug assertions (`manifest.attrs` contents and - * `request.json` `email`) live in this block -- the bundle's existence - * and identity are already asserted above, in the plain `it` that - * precedes this one, precisely so this `.failing` cannot mask a harness - * regression as "the known SDK bug failing as expected". + * Case 3. On iOS 7.0.0-beta3 this was `it.failing` (bugsee-cocoa#164: a + * new report did not start from the global attributes and identifier); + * 7.0.0-beta4 carries the fix, so both platforms run it as a plain `it`. */ - const case3 = ON_IOS ? it.failing : it; - case3('the retained report carries the attributes and the identifier', () => { + it('the retained report carries the attributes and the identifier', () => { assertPrecondition(); const bundle = theBundle(); expect(bundle.manifest.attrs).toStrictEqual(expectedAttrs(nonce)); From 01661eaa7d362d89955d20f3137ec4d2ed5ec675 Mon Sep 17 00:00:00 2001 From: Alexey Karimov Date: Tue, 6 Oct 2026 15:55:25 +0500 Subject: [PATCH 3/4] test(e2e): iOS recovers the stored JS crash on beta4, so those cases are plain it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 "; - 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) --- examples/bare/e2e/exceptions.test.ts | 46 ++++++++++++----------- packages/react-native/ios/BugseeModule.mm | 8 ++-- 2 files changed, 29 insertions(+), 25 deletions(-) diff --git a/examples/bare/e2e/exceptions.test.ts b/examples/bare/e2e/exceptions.test.ts index a47b3f19..9858c871 100644 --- a/examples/bare/e2e/exceptions.test.ts +++ b/examples/bare/e2e/exceptions.test.ts @@ -37,12 +37,13 @@ * still shows RN's JavascriptException, but exactly one Bugsee bundle is filed. * On iOS cases 9 and 12 the unhandled report is stored, not sent: a plain * `it` asserts the bundle list is empty, then `terminateIosApp()` and - * `exc-observe`. Exactly one recovered crash is `it.failing` on beta3 - * `0d9c9d0a-9`, which does not claim `override_report.plcrash` on the next - * launch. The harness must not copy that file onto `live_report.plcrash`. - * The Release gate is the same split: the console ended and no - * `RCTFatalException` bundle on a plain `it`; exactly one - * `ReactNativeWebException` crash is `it.failing`. + * `exc-observe`, which must recover exactly one crash. beta3 `0d9c9d0a-9` + * did not claim `override_report.plcrash` on the next launch, so those + * assertions were `it.failing` there; 7.0.0-beta4 claims it (bugsee-cocoa + * #177) and they are plain `it`. The harness must not copy that file onto + * `live_report.plcrash`. The Release gate is the same: the console ended, + * no `RCTFatalException` bundle, and exactly one `ReactNativeWebException` + * crash. */ import { writeFileSync } from 'node:fs'; @@ -1070,11 +1071,12 @@ describeDevice(`JS exceptions on ${TARGET_NAME}`, () => { expect(completed.index).toBeLessThan(appHandler.index); }); - // beta3 0d9c9d0a-9 does not claim override_report.plcrash on the next - // launch, so this list is empty. The harness must not copy that file - // onto live_report.plcrash. The body asserts the correct outcome; - // remove .failing when a beta recovers the stored report. - itIosDevice.failing( + // beta3 0d9c9d0a-9 did not claim override_report.plcrash on the next + // launch, so this was `.failing` there. 7.0.0-beta4 claims it ahead of + // the live crash (bugsee-cocoa #177) and the stored report is recovered + // (KRSFT, 2026-10-06). The harness still must not copy that file onto + // live_report.plcrash. + itIosDevice( 'a fatal JS error is reported as a crash, then RN\'s handler runs', () => { const crashes = bundlesWithReason(bundles, `E2E fatal ${nonce}`).filter( @@ -1204,11 +1206,12 @@ describeDevice(`JS exceptions on ${TARGET_NAME}`, () => { expect(stored).toEqual([]); }); - // beta3 0d9c9d0a-9 does not claim override_report.plcrash on the next - // launch, so this list is empty. The harness must not copy that file - // onto live_report.plcrash. The body asserts the correct outcome; - // remove .failing when a beta recovers the stored report. - itIosDevice.failing( + // beta3 0d9c9d0a-9 did not claim override_report.plcrash on the next + // launch, so this was `.failing` there. 7.0.0-beta4 claims it ahead of + // the live crash (bugsee-cocoa #177) and the stored report is recovered + // (KRSFT, 2026-10-06). The harness still must not copy that file onto + // live_report.plcrash. + itIosDevice( 'a render error with no boundary is reported once, as a crash, by the root reporter', () => { const crashes = bundlesWithReason(bundles, `E2E boundary ${nonce}`).filter( @@ -1330,11 +1333,12 @@ describeDevice(`JS exceptions on ${TARGET_NAME}`, () => { } }); - // beta3 0d9c9d0a-9 does not claim override_report.plcrash on the next - // launch, so this list is empty. The harness must not copy that file - // onto live_report.plcrash. The body asserts the correct outcome; - // remove .failing when a beta recovers the stored report. - itIosDevice.failing('exactly one ReactNativeWebException crash for the fatal', () => { + // beta3 0d9c9d0a-9 did not claim override_report.plcrash on the next + // launch, so this was `.failing` there. 7.0.0-beta4 claims it ahead of + // the live crash (bugsee-cocoa #177) and the stored report is recovered + // (KRSFT, 2026-10-06). The harness still must not copy that file onto + // live_report.plcrash. + itIosDevice('exactly one ReactNativeWebException crash for the fatal', () => { const ours = bundles.filter(b => { if (b.request.type !== 'crash') { return false; diff --git a/packages/react-native/ios/BugseeModule.mm b/packages/react-native/ios/BugseeModule.mm index 0602e260..c4b5cee5 100644 --- a/packages/react-native/ios/BugseeModule.mm +++ b/packages/react-native/ios/BugseeModule.mm @@ -1340,10 +1340,10 @@ - (void)logException:(NSString *)payloadJson }); } -/// An unhandled JS exception. The call stores `override_report.plcrash`. -/// iOS SDK 7.0.0-beta3 `0d9c9d0a-9` claims only `live_report.plcrash` on the -/// next launch, so that report is not recovered. Do not copy one file onto -/// the other. The completion is wrapped in `BGSRNSettleOnce`: on the +/// An unhandled JS exception. The call stores `override_report.plcrash`, +/// which the next launch recovers: 7.0.0-beta4 claims it ahead of +/// `live_report.plcrash` (bugsee-cocoa #177; beta3 claimed only the live +/// file and dropped this report). Do not copy one file onto the other. The completion is wrapped in `BGSRNSettleOnce`: on the /// simulator the SDK compiles `logUnhandledException` out and never calls /// this completion (verified facts), and a promise must still settle. - (void)logUnhandledException:(NSString *)payloadJson From ca60ea93bbd47ee1646fccbe42cc641554e088c7 Mon Sep 17 00:00:00 2001 From: Alexey Karimov Date: Tue, 6 Oct 2026 16:38:53 +0500 Subject: [PATCH 4/4] docs: say iOS recovers the stored JS crash intermittently, not always MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- examples/bare/e2e/exceptions.test.ts | 39 +++++++++++++++-------- packages/react-native/ios/BugseeModule.mm | 6 ++-- 2 files changed, 30 insertions(+), 15 deletions(-) diff --git a/examples/bare/e2e/exceptions.test.ts b/examples/bare/e2e/exceptions.test.ts index 9858c871..2ebc8b10 100644 --- a/examples/bare/e2e/exceptions.test.ts +++ b/examples/bare/e2e/exceptions.test.ts @@ -40,7 +40,8 @@ * `exc-observe`, which must recover exactly one crash. beta3 `0d9c9d0a-9` * did not claim `override_report.plcrash` on the next launch, so those * assertions were `it.failing` there; 7.0.0-beta4 claims it (bugsee-cocoa - * #177) and they are plain `it`. The harness must not copy that file onto + * #177), though intermittently on hardware (see the cases), and they are + * plain `it`. The harness must not copy that file onto * `live_report.plcrash`. The Release gate is the same: the console ended, * no `RCTFatalException` bundle, and exactly one `ReactNativeWebException` * crash. @@ -1071,11 +1072,15 @@ describeDevice(`JS exceptions on ${TARGET_NAME}`, () => { expect(completed.index).toBeLessThan(appHandler.index); }); - // beta3 0d9c9d0a-9 did not claim override_report.plcrash on the next + // beta3 0d9c9d0a-9 never claimed override_report.plcrash on the next // launch, so this was `.failing` there. 7.0.0-beta4 claims it ahead of - // the live crash (bugsee-cocoa #177) and the stored report is recovered - // (KRSFT, 2026-10-06). The harness still must not copy that file onto - // live_report.plcrash. + // the live crash (bugsee-cocoa #177), but not every time: on KRSFT + // (2026-10-06) 2 of 5 relaunches left the file on disk and dispatched + // nothing, the SDK's version gate (+hasVersionMatchForPendingReport) + // being the lead, reported to the iOS SDK team. This asserts the correct + // outcome and can be red on a device run for that reason; it does not + // relaunch again, which would hide the miss. The harness still must not + // copy that file onto live_report.plcrash. itIosDevice( 'a fatal JS error is reported as a crash, then RN\'s handler runs', () => { @@ -1206,11 +1211,15 @@ describeDevice(`JS exceptions on ${TARGET_NAME}`, () => { expect(stored).toEqual([]); }); - // beta3 0d9c9d0a-9 did not claim override_report.plcrash on the next + // beta3 0d9c9d0a-9 never claimed override_report.plcrash on the next // launch, so this was `.failing` there. 7.0.0-beta4 claims it ahead of - // the live crash (bugsee-cocoa #177) and the stored report is recovered - // (KRSFT, 2026-10-06). The harness still must not copy that file onto - // live_report.plcrash. + // the live crash (bugsee-cocoa #177), but not every time: on KRSFT + // (2026-10-06) 2 of 5 relaunches left the file on disk and dispatched + // nothing, the SDK's version gate (+hasVersionMatchForPendingReport) + // being the lead, reported to the iOS SDK team. This asserts the correct + // outcome and can be red on a device run for that reason; it does not + // relaunch again, which would hide the miss. The harness still must not + // copy that file onto live_report.plcrash. itIosDevice( 'a render error with no boundary is reported once, as a crash, by the root reporter', () => { @@ -1333,11 +1342,15 @@ describeDevice(`JS exceptions on ${TARGET_NAME}`, () => { } }); - // beta3 0d9c9d0a-9 did not claim override_report.plcrash on the next + // beta3 0d9c9d0a-9 never claimed override_report.plcrash on the next // launch, so this was `.failing` there. 7.0.0-beta4 claims it ahead of - // the live crash (bugsee-cocoa #177) and the stored report is recovered - // (KRSFT, 2026-10-06). The harness still must not copy that file onto - // live_report.plcrash. + // the live crash (bugsee-cocoa #177), but not every time: on KRSFT + // (2026-10-06) 2 of 5 relaunches left the file on disk and dispatched + // nothing, the SDK's version gate (+hasVersionMatchForPendingReport) + // being the lead, reported to the iOS SDK team. This asserts the correct + // outcome and can be red on a device run for that reason; it does not + // relaunch again, which would hide the miss. The harness still must not + // copy that file onto live_report.plcrash. itIosDevice('exactly one ReactNativeWebException crash for the fatal', () => { const ours = bundles.filter(b => { if (b.request.type !== 'crash') { diff --git a/packages/react-native/ios/BugseeModule.mm b/packages/react-native/ios/BugseeModule.mm index c4b5cee5..9432eff6 100644 --- a/packages/react-native/ios/BugseeModule.mm +++ b/packages/react-native/ios/BugseeModule.mm @@ -1341,9 +1341,11 @@ - (void)logException:(NSString *)payloadJson } /// An unhandled JS exception. The call stores `override_report.plcrash`, -/// which the next launch recovers: 7.0.0-beta4 claims it ahead of +/// for the next launch to recover. 7.0.0-beta4 claims it ahead of /// `live_report.plcrash` (bugsee-cocoa #177; beta3 claimed only the live -/// file and dropped this report). Do not copy one file onto the other. The completion is wrapped in `BGSRNSettleOnce`: on the +/// file and dropped this report), but not on every relaunch: on an iPhone +/// XS 2 of 5 left the file unclaimed (an SDK finding, not ours to work +/// around). Do not copy one file onto the other. The completion is wrapped in `BGSRNSettleOnce`: on the /// simulator the SDK compiles `logUnhandledException` out and never calls /// this completion (verified facts), and a promise must still settle. - (void)logUnhandledException:(NSString *)payloadJson