Repository navigation
T4: blackout (startBlackout / endBlackout / isBlackout) - #40
Conversation
Core owns a tiny idempotent (not counted) BlackoutState, exposed through the service container (BlackoutToken) with an internal onChange subscription for the upcoming BlackoutStarted/BlackoutEnded lifecycle events. Visual providers take a read-only BlackoutGate: the rrweb recorder is stopped for the window and restarted (fresh full snapshot) at the end, input presses, ui.* breadcrumbs, ui.interaction spans and the report-time viewtree are suppressed. Logs, network, traces and events continue. Real-Chromium e2e proves a DOM change made during a blackout is absent from the report's replay. 🤖 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: T4 blackout (startBlackout / endBlackout / isBlackout)
Reviewed 8a59c9b (e8826c8 is the CI retrigger) against the surrounding compositions, not just the diff: @bugsee/core client lifecycle, browser input source, WebView host bridge, Electron main/renderer/pixel-video, and the interaction/APM path. I did not run the package test suites in this pass.
The kernel piece is in good shape: idempotent, not counted, snapshot dispatch, isolated listener throws, and the browser replay path actually stops rrweb (session token drops late emits; a blackout already active when the lazy chunk resolves never records). The Chromium e2e pins the property that matters for DOM replay.
The privacy contract does not hold once you leave that browser-replay happy path. startBlackout() lives on the shared BugseeClient, so WebView and Electron expose it, isBlackout() returns true, and visual capture continues. Even on the wired browser path, the input source keeps gesture/wheel state across the edge, so coordinates collected during the blackout land in input.json after endBlackout().
Findings
| Sev | Issue |
|---|---|
| P1 | Shared API is a no-op for WebView (JS input/ui still stream; native video is never told) and Electron (renderer replay + pixel video.webm keep running when the session owner calls startBlackout) |
| P1 | Browser input source is not gated — #openGestures / #penPaths / #mouseDragPaths and #wheelAccum survive the blackout, so a press or scroll begun inside the window is emitted after endBlackout() |
| P2 | Interaction source drops by delivery-time isActive(), not event time — a PerformanceObserver callback that fires after endBlackout() records a click that happened during it |
| P2 | stop() does not clear the flag, so launch() after a blacked-out stop() starts a permanently dark session (Android clears on stop) |
| P2 | WebView inertClient is missing the new methods (TypeError on the path that exists to prevent that); restrictedClientFor spreads the real client, so a repeat launch() can endBlackout() |
Accepted as documented in the PR body (not re-reported): persistent DOM in the fresh snapshot; unmatched begin when a pre-blackout press ends during the window; video.aux / navigation crumbs / app-supplied ui.* breadcrumbs not gated; BlackoutStarted/Ended deferred to T1.
Overall risk: High
Merge recommendation: Request changes
Do not treat startBlackout() as shipping privacy until the session-owning compositions honor it and the input source cannot replay a blackout gesture.
Most important to fix
- Electron: forward main blackout on the control channel (including the session handshake) and stop/clear pixel video for the span.
- WebView: pass the gate into
createInputProvider/createUiBreadcrumbProvider, and tell native to start/end its own blackout. Block the methods onrestrictedClientFor; add noops oninertClient. - Input source: subscribe to the gate; refuse to collect during a blackout; clear
#openGestures,#penPaths,#mouseDragPaths,#hoverIds, and#wheelAccumon both edges. - Interactions: drop by event
startTimeoverlapping a blackout span, not byisActive()at callback time. - Lifecycle:
stop()clears the flag (keep a pre-launch()hold if that remains the Android rule).
Positive
Replay is the right design (halt + fresh snapshot, not a mute flag). The lazy-load race is handled. Provider-level input/ui/viewtree gates and the real-browser e2e are the right shape for the browser path.
Sent by Cursor Automation: Bugsee code review
|
Review findings triaged against current main (this PR branch is a frozen snapshot, so its diff does not show the fixes): Pointer gestures cannot span or start in a blackout (5bf4791); the rest was already fixed on main (WebView/Electron forwarding, interaction timing). Native Android/iOS receivers for the WebView blackout message remain a follow-up. The overview PR #46 (head = main) was re-reviewed at ba608c4: no remaining findings, recommendation Approve. 🤖 Generated with Claude Code |
There was a problem hiding this comment.
Re-check vs origin/main @ ba608c4
Author triage is correct. This PR branch is a frozen T4 snapshot; the findings were verified against current main, not this diff.
| Finding | Verdict |
|---|---|
| P1 input source gesture/wheel span | fully_addressed in 5bf4791: #dispatch returns while dark (no map writes); #clearGestures + #wheelAccum = null on both edges. Tests pin start-in-dark and span-across. |
| P1 WebView / Electron ignore the API | fully_addressed on main: WebView binds the gate into the input source/provider and posts blackoutMessage; Electron forwards blackout-start/blackout-end (handshake bo) and pixel video stop/clear/no-snapshot. |
| P2 interaction delivery-time | fully_addressed: BlackoutSpan + Event Timing startTime overlap, not isActive() at callback time. |
P2 stop() latch / WebView inert+restricted |
fully_addressed as a later design: startBlackout is a no-op when not launched; endBlackout can lift a carried flag while stopped; inertClient noops the methods; restrictedClientFor allow-lists and refuses endBlackout. |
Residual (accepted, out of this JS PR): native Android/iOS receivers for the WebView blackout message are still a host-SDK follow-up. Until those land, a legacy native host that never declares config.blackout keeps recording pixels; JS already warns when support is not declared.
Overall risk: Low (for JS main). Merge recommendation: Approve — this review-only PR changes nothing; the work is on main.
Sent by Cursor Automation: Bugsee code review


Mobile parity:
Bugsee.startBlackout/endBlackout/isBlackout.While active: replay recorder stopped (resumes with a fresh full snapshot), input presses,
ui.*breadcrumbs, interaction performance transactions and the report-time viewtree are suppressed. Logs/network/events/traces continue. Idempotent, not counted. Real-Chromium e2e proves nothing from the blackout window reaches the replay.Review focus (privacy)
endBlackoutappears in the fresh snapshot (same as mobile video).begin.video.auxviewport geometry and navigation breadcrumbs are not gated; app-suppliedui.*breadcrumbs are deliberately not gated.BlackoutStarted/Endedevents are wired onmainin a later commit (e3e8253).🤖 Generated with Claude Code