Repository navigation
Task 8.2a: createReport JS API, native methods stubbed - #19
Conversation
…through R8 Co-authored-by: Cursor <cursoragent@cursor.com>
…rom render
Banner: Bugsee Android SDK 7.3.0 [beb390dc0].
One handled payload (nonce 6877ef246252):
{"name":"TypeError","reason":"E2E handled 6877ef246252","frames":[{"data":{"member":"bugseeE2EThrowSite"},"debug_id":"8a1c2f4e-0d3b-5e6f-9a7b-6877ef246252"}],"cause":{"name":"RangeError","reason":"inner 6877ef246252"},"signature":"8362dd3b5785297d7d387925b94ce0ea5e857cfc","platform_os":"android","debug_ids":{"http://localhost:8081/index.bundle//&platform=android&dev=true&lazy=true&minify=false&app=com.bareexample&modulesOnly=false&runModule=true&excludeSource=true&sourcePaths=url-server":"8a1c2f4e-0d3b-5e6f-9a7b-6877ef246252"}}
Gated release R1–R3 bundles (nonce 824ac61adb58): exactly one crash,
com.bugsee.reactnative.ReactNativeWebException, reason JSON with
"E2E fatal 824ac61adb58". No JavascriptException Bugsee bundle observed
(R2 prediction unmet; assertion left intact for controller ruling).
Thread: exception handled sent tid→comm BugseeRN-except (TASK_COMM_LEN
truncation of BugseeRN-exceptions).
Co-authored-by: Cursor <cursoragent@cursor.com>
Controller ruling: R13's second Bugsee crash is not retained on Android SDK 7.3.0. Logcat still shows FATAL EXCEPTION / JavascriptException and the process dies; Bugsee keeps exactly one crash named ReactNativeWebException. Gated R2/R3 updated; debug case 10 stays it.failing. awaitBundles(1) for the release beforeAll. Release verify (WOD_LX1): R1–R3 PASS; one bundle 894ba5df-af93-4d4b-9678-5d06ec218db4.bundle.zip. Co-authored-by: Cursor <cursoragent@cursor.com>
ExceptionStage registers _bugseeDebugIds synchronously before mounting BugseeE2EThrower, so the map exists for the first render throw. Root polls for a crash with the boundary reason instead of requiring two bundles. Release R1 asserts exactly one ours. Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
…cannot be lost Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
beta3 0d9c9d0a-9 stores the unhandled report and does not claim override_report.plcrash on the next launch. The one-crash asserts stay it.failing so an empty recovery list cannot pass as the desired outcome. Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
…e stubbed Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
Stale comment
Deep review: Task 8.2a
createReportJS API (native stubbed)Reviewed cd79431 against the handler report proxy,
toReportError/normalizeSnapshot, TurboModule spec + both module stubs, the mock, Task 8.2a in the implementation plan, and the 8.2b/8.2c contracts this slice is meant to unlock. Did not review the diff in isolation.No P0–P3 findings.
Adversarial checks that did not produce a reportable bug:
- Dead-before-cross.
CreatedReport.upload()setsdeadsynchronously, then crosses. Two overlappingupload()calls issue one native call; the second rejectsE_REPORT_HANDLE_DEADlocally. A native rejection after that still leaves the JS handle dead, matching the spec note (“whatever the result”).- Validation parity.
update/addFileAttachment/addDataAttachmentuse the samevalidateReportPatch,normalizeFilePath,validateBase64, andvalidateNonEmptyStringpath asBugseeReportProxy. A patch with one bad field sends nothing.file://is stripped; there is nomoveargument (call arity pinned at 4).- Error mapping. Native
E_REPORT_CREATE_BUSYbecomesBugseeReportErrorwith that code. Unknown rejections pass through.handleId == nulltreats bothnullandundefinedas “SDK made none”.- One-at-a-time (P5). Not enforced in JS; that is native
reserve()in 8.2b/8.2c. Stubs never mint a handle, so twocreateReport()calls both resolvenull, which is truthful. No discard API, matching the plan (release isupload()or JS reload /invalidate).- Stubs. Android
createReportresolvesnull; iOSresolve(nil). The fivecreatedReport*methods rejectE_REPORT_HANDLE_DEADwithout touching the SDK. Selectors/signatures match existingreport*methods (patchJsonas JSON text, nullablemimeType, Promiseresolve/reject). RN compat 0.81 / 0.83 / 0.86 / 0.87 already passed codegen + java-signatures against this spec.- In-flight
updatevsupload. JS is single-threaded;ensureAliveis only re-checked at the start of each method. Anupdatethat has already yielded to native can still complete afterupload()has marked dead. Same pattern as the handler proxy; 8.2btake()then makes nativeget()return dead. Sequentialawait update(); await upload()is fine.- Handler timing / iOS attachment caps. Documented on
createReport(P6/P7); JS does not pretend to normalise them. Limits are native 8.2c.Did not run
yarn testhere (node_modulesabsent). At review time: rn-compat SUCCESS on 0.81–0.87; lint/unit/mutation, Android, and iOS jobs were still in progress.Overall risk: Low
Merge recommendation
Safe to merge into
feat/phase-8-1. This is the specified sequential slice: the public JS API and the TurboModule surface land first; Android 8.2b and iOS 8.2c replace the stubs. Do not treat this merge as a workingcreateReport()for apps — on a device it always resolvesnulluntil those PRs land.Related: the Android follow-up (PR 20) has a separate
invalidate()/CreatedReports.clear()reload-order issue; that is native-side and not introduced here.Most important issues to fix
None in this PR.
Positive observations
- Reuse of the handler proxy’s validators, JSON-text patch transport, and snapshot normalisation avoids a second wire shape.
upload()dead-before-cross is tested (including the concurrent case the plan’s mutate step was written to catch).- File attachments cannot accidentally pass
move: trueat the spec layer.ios-spec-coverageandjava-signaturesderive methods from the spec, so the six new native methods cannot silently miss an implementation.Sent by Cursor Automation: Bugsee code review
Mutation was surviving the boundary at severity 5 and any rewrite of the TypeError and RangeError text, which pulled the score under the break threshold. Co-authored-by: Cursor <cursoragent@cursor.com>
The deadline is a third caller into that barrier. On a slow CI runner it fires while the two threads are parked and steals a signal, so the test never finishes. Co-authored-by: Cursor <cursoragent@cursor.com>
Those branches were untested, so their mutants survived and the mutation score stayed under the break threshold. Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
Stale comment
Deep review: Task 8.2a
createReportJS API (native stubbed)Reviewed c8513e8 (base
c437703/feat/phase-8-1) against the handler report proxy,toReportError/normalizeSnapshot, TurboModule spec + both module stubs, the mock, Task 8.2a in the implementation plan, and the 8.2b/8.2c contracts this slice is meant to unlock. Did not review the diff in isolation.Prior assessment was cd79431. Three later commits are tests only; production
CreatedReportand the native stubs are unchanged.No P0–P3 findings.
Adversarial checks that did not produce a reportable bug:
- Dead-before-cross.
CreatedReport.upload()setsdeadsynchronously, then crosses. Two overlappingupload()calls issue one native call; the second rejectsE_REPORT_HANDLE_DEADlocally. A native rejection after that still leaves the JS handle dead, matching the spec note (“whatever the result”).- Validation parity.
update/addFileAttachment/addDataAttachmentuse the samevalidateReportPatch,normalizeFilePath,validateBase64, andvalidateNonEmptyStringpath asBugseeReportProxy. A patch with one bad field sends nothing.file://is stripped; there is nomoveargument (call arity pinned at 4).- Error mapping. Native
E_REPORT_CREATE_BUSYbecomesBugseeReportErrorwith that code. Unknown rejections pass through.handleId == nulltreats bothnullandundefinedas “SDK made none”.- One-at-a-time (P5). Not enforced in JS; that is native
reserve()in 8.2b/8.2c. Stubs never mint a handle, so twocreateReport()calls both resolvenull, which is truthful. No discard API, matching the plan (release isupload()or JS reload /invalidate).- Stubs. Android
createReportresolvesnull; iOSresolve(nil). The fivecreatedReport*methods rejectE_REPORT_HANDLE_DEADwithout touching the SDK. Selectors/signatures match existingreport*methods (patchJsonas JSON text, nullablemimeType, Promiseresolve/reject). RN compat 0.81 / 0.83 / 0.86 / 0.87 passed codegen + java-signatures against this spec on this SHA.- In-flight
updatevsupload. JS is single-threaded;ensureAliveis only re-checked at the start of each method. Anupdatethat has already yielded to native can still complete afterupload()has marked dead. Same pattern as the handler proxy; 8.2btake()then makes nativeget()return dead. Sequentialawait update(); await upload()is fine.- Handler timing / iOS attachment caps. Documented on
createReport(P6/P7); JS does not pretend to normalise them. Limits are native 8.2c.Test-only delta after cd79431:
- Created-report coverage for omitted mime type, empty path/name, data-attachment wire, native rejection mapping, and the dead-handle message.
upload/showReportDialogtests pin severity 5 and the exact TypeError/RangeError text.BGSRNSettleOnceTestsrace usesdeadlineMs=0(a positive deadline is a third caller into that two-party barrier) and 5s waits instead ofDISPATCH_TIME_FOREVER.Did not run
yarn testhere (node_modulesabsent). At review time: rn-compat SUCCESS on 0.81–0.87; lint/unit/mutation, Android, and iOS jobs were still pending on this SHA.Overall risk: Low
Merge recommendation
Safe to merge into
feat/phase-8-1. This is the specified sequential slice: the public JS API and the TurboModule surface land first; Android 8.2b and iOS 8.2c replace the stubs. Do not treat this merge as a workingcreateReport()for apps — on a device it always resolvesnulluntil those PRs land.Related: the Android follow-up (PR 20) has a separate
invalidate()/CreatedReports.clear()reload-order issue; that is native-side and not introduced here.Most important issues to fix
None in this PR.
Positive observations
- Reuse of the handler proxy’s validators, JSON-text patch transport, and snapshot normalisation avoids a second wire shape.
upload()dead-before-cross is tested (including the concurrent case the plan’s mutate step was written to catch).- File attachments cannot accidentally pass
move: trueat the spec layer.ios-spec-coverageandjava-signaturesderive methods from the spec, so the six new native methods cannot silently miss an implementation.- The later test commits kill the mutation survivors (severity 5, exact argument-error text, attachment/rejection paths) without changing production behaviour.
Sent by Cursor Automation: Bugsee code review
A shared go semaphore let the faster caller enter the next window and consume the other caller's release, so that caller never arrived and CI timed out. Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
Stale comment
Deep review: Task 8.2a
createReportJS API (native stubbed)Reviewed bdf2522 (base
c437703/feat/phase-8-1) against the handler report proxy,toReportError/normalizeSnapshot, TurboModule spec + both module stubs, the mock, Task 8.2a in the implementation plan, and the 8.2b/8.2c contracts this slice is meant to unlock. Did not review the diff in isolation.Prior assessment was c8513e8. Production
CreatedReportand the native stubs are unchanged since cd79431. The only new commit is a test fix.No P0–P3 findings. No prior inline threads to resolve.
Adversarial checks that did not produce a reportable bug:
- Dead-before-cross.
CreatedReport.upload()setsdeadsynchronously, then crosses. Two overlappingupload()calls issue one native call; the second rejectsE_REPORT_HANDLE_DEADlocally. A native rejection after that still leaves the JS handle dead, matching the spec note (“whatever the result”).- Validation parity.
update/addFileAttachment/addDataAttachmentuse the samevalidateReportPatch,normalizeFilePath,validateBase64, andvalidateNonEmptyStringpath asBugseeReportProxy. A patch with one bad field sends nothing.file://is stripped; there is nomoveargument (call arity pinned at 4).- Error mapping. Native
E_REPORT_CREATE_BUSYbecomesBugseeReportErrorwith that code. Unknown rejections pass through.handleId == nulltreats bothnullandundefinedas “SDK made none”.- One-at-a-time (P5). Not enforced in JS; that is native
reserve()in 8.2b/8.2c. Stubs never mint a handle, so twocreateReport()calls both resolvenull, which is truthful. No discard API, matching the plan (release isupload()or JS reload /invalidate).- Stubs. Android
createReportresolvesnull; iOSresolve(nil). The fivecreatedReport*methods rejectE_REPORT_HANDLE_DEADwithout touching the SDK. Selectors/signatures match existingreport*methods (patchJsonas JSON text, nullablemimeType, Promiseresolve/reject). RN compat 0.81 / 0.83 / 0.86 / 0.87 passed codegen + java-signatures against this spec on this SHA.- In-flight
updatevsupload. JS is single-threaded;ensureAliveis only re-checked at the start of each method. Anupdatethat has already yielded to native can still complete afterupload()has marked dead. Same pattern as the handler proxy; 8.2btake()then makes nativeget()return dead. Sequentialawait update(); await upload()is fine.- Handler timing / iOS attachment caps. Documented on
createReport(P6/P7); JS does not pretend to normalise them. Limits are native 8.2c.Delta since c8513e8 (
test(ios): keep each settle-once window on its own release):
testTwoConcurrentCallsSettleOncenow has a separate enter/release semaphore pair perRunRaceWindow(before the once-flag load, then after it). A sharedgolet the faster caller leave window 0, enter window 1, and consume the other thread’s release, so that caller never reached window 1 (CI timed out).- Per-thread
_Thread_localphase maps eachfire()’s two visits onto those two windows. That is correct for a single run of this test (each concurrent caller hits the hook twice on its own GCD worker). ProductionBGSRNSettleOnceis unchanged.Did not run
yarn testhere (node_modulesabsent). At review time on this SHA: rn-compat SUCCESS on 0.81–0.87; lint/unit/mutation, Android, and iOS jobs were still in progress. The earlier mutation failure on cd79431 (score 94.57 vs break 95,CreatedReport.tssurvivors) is what the later JS test commits were written to kill.Overall risk: Low
Merge recommendation
Safe to merge into
feat/phase-8-1. This is the specified sequential slice: the public JS API and the TurboModule surface land first; Android 8.2b and iOS 8.2c replace the stubs. Do not treat this merge as a workingcreateReport()for apps — on a device it always resolvesnulluntil those PRs land.Related: the Android follow-up (PR 20) has a separate
invalidate()/CreatedReports.clear()reload-order issue; that is native-side and not introduced here.Most important issues to fix
None in this PR.
Positive observations
- Reuse of the handler proxy’s validators, JSON-text patch transport, and snapshot normalisation avoids a second wire shape.
upload()dead-before-cross is tested (including the concurrent case the plan’s mutate step was written to catch).- File attachments cannot accidentally pass
move: trueat the spec layer.ios-spec-coverageandjava-signaturesderive methods from the spec, so the six new native methods cannot silently miss an implementation.- The per-window settle-once barrier matches how
RunRaceWindowis actually invoked and removes a real CI timeout mode without weakening the double-settle assertion.Sent by Cursor Automation: Bugsee code review
Keep createReport from this branch. Keep main's split exception executors, the beta3 recovery comment, and omitted dialog fields as nil. Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
Stale comment
Deep review: Task 8.2a after retarget to
mainReviewed 3d2c8e9 (
Merge main into feat/phase-8-2a) against currentmain(669b492, which is merged #18). Prior assessment was bdf2522 ontofeat/phase-8-1.The unique 8.2a surface is unchanged.
git diff bdf2522 HEADforCreatedReport.ts,NativeBugsee.ts,index.ts, types, errors, andcreated-report.test.tsis empty. Versusmain, GitHub’s file list is the original 11-file 8.2a slice (stubs + JS API + tests). The merge kept this branch’screateReport/createdReport*stubs and took main’s splitExceptionExecutors(HANDLEDvsUNHANDLED) plus the #18 dialog fix (omitted iOS summary/description staynil).No P0–P3 findings. No prior inline threads to resolve.
Adversarial checks on the merge that did not produce a reportable bug:
- Stubs survived. Android still
promise.resolve(null)/rejectHandleDead; iOS stillresolve(nil)/BGSRNRejectHandleDead. No duplicate methods, no dropped@Overrides.- Exception path not mixed into createReport. Handled/unhandled still post onto
ExceptionExecutors; createReport does not call the SDK.- JS contract. Dead-before-cross
upload(), validator parity with the handler proxy,handleId == nullas none,E_REPORT_CREATE_BUSYmapping, nomoveon created-report files — all as previously reviewed.Did not run
yarn testhere. CI on bdf2522 was SUCCESS; a newcirun is queued on this merge SHA.Overall risk: Low
Merge recommendation
Safe to merge into
main. Same sequential slice as before: public JS API + TurboModule stubs. On a devicecreateReport()still resolvesnulluntil 8.2b/8.2c land. #18’s dialog-nil fix is already onmainand is present on this head.Related residual remains native-side on PR 20 (
invalidate()/CreatedReports.clear()), not introduced here.Most important issues to fix
None in this PR.
Positive observations
- Merge resolution is mechanical and correct: 8.2a stubs on top of main’s exception-executor split, no conflict leftovers.
- Retargeting onto merged #18 shrinks the GitHub diff to the actual 8.2a files instead of re-shipping Phase 7 / 8.1.
Sent by Cursor Automation: Bugsee code review
There was a problem hiding this comment.
Deep review: Task 8.2a createReport JS API (native stubbed)
Reviewed 3d2c8e9 (merge of main / 669b492 into feat/phase-8-2a; production 8.2a still cd79431) against the handler report proxy, toReportError / normalizeSnapshot, TurboModule spec + both module stubs, the mock, Task 8.2a in the implementation plan, and the 8.2b/8.2c contracts this slice unlocks. Did not review the diff in isolation.
Prior assessment was bdf2522. No GitHub inline threads to resolve.
No P0–P3 findings.
Adversarial checks that did not produce a reportable bug:
- Dead-before-cross.
CreatedReport.upload()setsdeadsynchronously, then crosses. Two overlappingupload()calls issue one native call; the second rejectsE_REPORT_HANDLE_DEADlocally. A native rejection after that still leaves the JS handle dead, matching the spec (“whatever the result”). - Validation parity.
update/addFileAttachment/addDataAttachmentuse the samevalidateReportPatch,normalizeFilePath,validateBase64, andvalidateNonEmptyStringpath asBugseeReportProxy. A patch with one bad field sends nothing.file://is stripped; there is nomoveargument (call arity pinned at 4). - Error mapping.
CreateBusyis inReportErrorCode, sotoReportErrormaps nativeE_REPORT_CREATE_BUSY. Unknown rejections pass through.handleId == nulltreats bothnullandundefinedas “SDK made none”. - One-at-a-time (P5). Not enforced in JS; that is native
reserve()in 8.2b/8.2c. Stubs never mint a handle, so twocreateReport()calls both resolvenull, which is truthful. No discard API, matching the plan (release isupload()or JS reload /invalidate). - Stubs. Android
createReportresolvesnull; iOSresolve(nil). The fivecreatedReport*methods rejectE_REPORT_HANDLE_DEADwithout touching the SDK. Selectors/signatures match existingreport*methods (patchJsonas JSON text, nullablemimeType). RN compat 0.81 / 0.83 / 0.86 / 0.87 passed codegen + java-signatures against this spec on this SHA. - In-flight
updatevsupload. JS is single-threaded;ensureAliveis only re-checked at the start of each method. Anupdatethat has already yielded to native can still complete afterupload()has marked dead. Same pattern as the handler proxy; 8.2btake()then makes nativeget()return dead. Sequentialawait update(); await upload()is fine. - Handler timing / iOS attachment caps. Documented on
createReport(P6/P7); JS does not pretend to normalise them. Limits are native 8.2c. - Merge of
main.createReportstubs from this branch are intact (one of each method, no duplicates). Main’s splitExceptionExecutors(handled vs unhandled queues), beta3 unhandled-recovery comment, andshowReportDialogomitted fields asnil(not@"") were kept. Those are already onmain; this merge does not regress them.
Did not run yarn test here. At review time on this SHA: rn-compat SUCCESS on 0.81–0.87; lint/unit/mutation, Android, and iOS jobs were still pending.
Overall risk: Low
Merge recommendation
Safe to merge into main. Task 8.1 is already there (669b492). This is the specified sequential slice: the public JS API and the TurboModule surface land first; Android 8.2b and iOS 8.2c replace the stubs. Do not treat this merge as a working createReport() for apps — on a device it always resolves null until those PRs land.
Related: the Android follow-up (PR 20) has a separate invalidate()/CreatedReports.clear() reload-order issue; that is native-side and not introduced here.
Most important issues to fix
None in this PR.
Positive observations
- Merge resolution is the right split: keep this branch’s
createReportsurface, takemain’s already-shipped exception-executor split and dialog-nil behaviour. - Reuse of the handler proxy’s validators, JSON-text patch transport, and snapshot normalisation avoids a second wire shape.
upload()dead-before-cross is tested (including the concurrent case the plan’s mutate step was written to catch).- File attachments cannot accidentally pass
move: trueat the spec layer. ios-spec-coverageandjava-signaturesderive methods from the spec, so the six new native methods cannot silently miss an implementation.
Sent by Cursor Automation: Bugsee code review


Summary
createReport()returns one outstandingBugseeCreatedReport, or rejectsE_REPORT_CREATE_BUSY.upload()marks the handle dead synchronously before crossing. A created-report file attachment sends nomove.createReportpaths are stubs. Android and iOS implementations are the next two PRs, which branch from here.Test plan
yarn test: 1644 passed, 1 skippedmove: trueon created-report filesMade with Cursor