Repository navigation
Conversation
…, hide wire internals
- UmbrellaExtensionOptions gains performanceUploadMode ('batched' | 'realtime'); type test proves no cast needed.
- startTransaction / startSpan / getActiveSpan on the launched client (browser, node, bun, deno; no-op
versions on worker, edge-light and workerd and when performance is off), backed by a new
PerformanceFacade in @bugsee/performance.
- UploadOptions.trigger and LogExceptionOptions.mechanism move to Internal* types reached via
asInternalClient(); the public BugseeClient no longer accepts them. Internal callers (report dialog,
adapters, server instrumentation) updated.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
There was a problem hiding this comment.
Deep review — T2 performance facade / typed upload mode / hide wire internals
Reviewed e588c92 (task commit 74a1dc9) against the surrounding client lifecycle, performance extension, uploader, and asInternalClient call sites — not the diff alone. No prior review threads on this PR.
T2 does three things: type performanceUploadMode, put startTransaction / startSpan / getActiveSpan on the launched umbrella client, and move trigger / mechanism behind Internal* + asInternalClient. The hide-internals pass is consistent (adapters and the report dialog were updated; umbrella type tests pin the public surface). The new facade and the newly public upload-mode option have two real correctness holes.
Findings
- P2 — Facade ignores
stopped/killed/isLaunched(). Afterstop(),ext('performance')is still registered, so the new top-level methods still create sampled transactions; composedflush()/ a secondstop()still uploads them. - P2 —
performanceUploadMode: 'realtime'is now a typed, documented launch option and is never read. The uploader is interval-batch only. Before this change, TypeScript rejected the friendly name onlaunch().
Residuals (not blocking)
createClient()now returnsInternalBugseeClient, so@bugsee/corecallers still typechecktrigger/mechanism. The umbrella surface is clean.startSpanwith a parent does not becomegetActiveSpan()(no span stack — already documented as a later slice). SequentialstartSpancalls are siblings under the current transaction, not nested.- Pageload collection is not torn down in
wirePerformance.stop()(pre-existing). Combined with (1),getActiveSpan()afterstop()can still return the leftover pageload.
Did not run the suite (node_modules is not installed in this environment). Conclusions are from the implementation, the extension/uploader stop path, and the new tests (which cover "performance off" and repeat-launch, not stop/kill or realtime).
Verdict
- Overall risk: Medium
- Merge recommendation: Request changes
- Fix first: gate the facade on
client.isLaunched()(same aslogException/upload); do not ship a working-lookingperformanceUploadModeuntil the uploader honors it — or drop it from the public options until then - What landed well:
asInternalClientis a type-only view and the call-site sweep is complete; no-op facade on worker/edge matches "performance off"; lazygetApi()correctly picks up a lateregisterExt; idempotent in-place install is the right shape for the process singleton
Sent by Cursor Automation: Bugsee code review
| function performanceApiOf(client: BugseeClient): PerformanceApi | undefined { | ||
| try { | ||
| return client.ext('performance'); | ||
| } catch { | ||
| return undefined; | ||
| } |
There was a problem hiding this comment.
P2 — Facade ignores stopped / killed / isLaunched()
performanceApiOf only treats "extension not registered" as absent. After stop() (and after KILL_SDK), ext('performance') still succeeds:
createPerformanceExtension().stop()is a no-op (packages/performance/src/extension.ts)registerExtis never undonecreatePerformanceUploader().stop()onlyclearIntervals;flush()stillstore.drain()+send- umbrella
client.flush/client.stopalways call thatflush()before the core early-return (packages/bugsee/src/wire.tscomposedstop/flush)
logException / upload / prepareUpload already no-op when stopped || killed (packages/core/src/client.ts). These new mobile-parity methods do not.
Impact: APM keeps recording after the app asked the SDK to stop, and a later flush() or a defensive second stop() uploads those traces.
Scenario:
const client = await launch(token, opts)(performance on).- Logout / privacy path:
await client.stop()— carrier cleared, capture halted, uploader interval cleared, leftover pageload may still sit in the active slot. - Leftover instrumentation calls
client.startTransaction('Checkout', 'ui.load').finish()(orstartSpanunder the leftover pageload).ext('performance')works → real sampled txn →store.add. - Cleanup calls
stop()again, orflush(). Composed wrapper runsuploader.flush()and POSTs/v2/performance/transactions.
Same hole existed behind client.ext('performance') (PR 46 residual). This PR makes that path the public, recommended API and does not close it. Tests cover "performanceMonitoring: false" and repeat-launch, not stop/kill.
Fix: Resolve no-op when the client is not live, e.g.
function performanceApiOf(client: BugseeClient): PerformanceApi | undefined {
if (!client.isLaunched()) return undefined;
try {
return client.ext('performance');
} catch {
return undefined;
}
}isLaunched() is already false after both stop() and enterKillState. Add a test: startTransaction after stop() is unsampled; a following flush() / stop() must not POST a performance batch.
| /** Upload cadence: `batched` (every flush interval, the default) or `realtime` (per transaction). */ | ||
| performanceUploadMode?: PerformanceUploadMode; | ||
| /** Batched-upload flush interval in milliseconds. Default 30000. */ |
There was a problem hiding this comment.
P2 — performanceUploadMode is a public, documented option that nothing reads
This field is new on UmbrellaExtensionOptions. The docstring describes two working cadences. PERFORMANCE_OPTION_DEFINITIONS already maps the friendly name, and wireUmbrella does resolveLaunchOptions(..., PERFORMANCE_OPTION_DEFINITIONS), but the resolved PerformanceOption.UploadMode is never passed on.
wirePerformance / createPerformanceUploader have no upload-mode parameter. The only delivery path is scheduler.setInterval(..., flushIntervalMs) (default 30s). There is no per-transaction send.
Before this PR, launch(token, { performanceUploadMode: 'realtime' }) was an excess-property TypeScript error. Now it typechecks, is documented, and is pinned by public-surface.test-d.ts — as a silent no-op.
Impact: Callers who set 'realtime' (crash-before-next-flush, long-lived SPA that should not wait 30s, anything that is not calling flush() on a timer) still get batched uploads and can lose traces. The type system now encourages the broken setting.
Scenario: launch(token, { performanceUploadMode: 'realtime' }), finish a transaction, process dies or the tab crashes before the 30s tick and before an explicit flush(). The option promised an immediate upload; the store still waits for the interval.
Fix: Either implement it (on each sampled onFinish, if mode is realtime, void uploader.flush() — the uploader already sends whatever drain() returns) or keep the canonical key internal and drop this field from the public launch options until the uploader honors it. A type-only "known gap" should not look like a working API.
…s survive the renderer/WebView JSON hops, report-level attachment names cannot lose a report, performanceUploadMode realtime is honoured - PR #38/#39 reviews 🤖 Generated with [Claude Code](https://claude.com/claude-code)
|
Review findings triaged against current main (this PR branch is a frozen snapshot, so its diff does not show the fixes): Facade now records nothing outside a launched client (5bf4791); performanceUploadMode 'realtime' is honoured (dc5fc29); uploader stops when the client is no longer launched (31208e3). The overview PR #46 (head = main) was re-reviewed at ba608c4: no remaining findings, recommendation Approve. 🤖 Generated with Claude Code |
|
Closing instead of merging: this review-only branch is a frozen snapshot that now conflicts with the review base branch after #37/#40/#43/#46 were merged. Its content is already on 🤖 Generated with Claude Code |


performanceUploadModeadded toUmbrellaExtensionOptions(typed, tsc-checked).startTransaction/startSpan/getActiveSpanon the launched client (browser/node/bun/deno); no-ops on worker/edge entries and whenperformanceMonitoringis off.UploadOptions.triggerandMechanismare no longer public; internalInternal*types andasInternalClient.Review focus / known gaps
performanceUploadModeis typed but nothing reads it (already true before this change).startSpanwith no active span opens a new root transaction (my reading of mobile's behaviour).Internal*types are still exported from@bugsee/core(marked@internal), not from the umbrella.🤖 Generated with Claude Code