Conversation
Updated the class name from SessionReplayEventGenerator to RRWebEventGenerator for improved clarity and consistency. Adjusted the instantiation in SessionReplayExporter accordingly.
Updated the RRWebEventGenerator to improve the generation of incremental events. Introduced new constants for DOM elements and refined the state management for image nodes. The event generation now utilizes a more structured approach for adding and removing nodes, enhancing clarity and maintainability.
Modified the RRWebEventGenerator to use a constant for the image MIME type, ensuring consistency in data URL generation for incremental events. This change enhances maintainability and clarity in the codebase.
…natureManager Updated the observability module to replace instances of CaptureEvent with the new ExportFrame class, enhancing the structure of exported frames. Introduced TileSignatureManager for improved tile-based signature computation. Adjusted related tests and export logic to accommodate these changes, ensuring consistency across the codebase.
Added CaptureManager class to facilitate capturing frames from the lowest visible window. This implementation includes functionality for emitting captured frames as ExportFrame objects, integrating session management, and applying masks for privacy. The new class enhances the observability module by providing a structured approach to frame capture, with future optimizations planned for capture quality and memory management.
Modified ExportDiffManager to accept ImageCaptureService.RawFrame instead of the previously defined RawFrame data class. This change streamlines the frame capture process and enhances integration with the ImageCaptureService, improving overall code clarity and maintainability. Additionally, removed the RawFrame data class from ExportDiffManager to reduce redundancy.
…ions Modified the TileSignatureManager to accept separate width and height parameters for tile size during signature computation. This change enhances flexibility in tile processing and improves the accuracy of tile-based signatures. Additionally, updated the ExportDiffManager to utilize the new signature computation method, ensuring consistency across the observability module.
…roved state management Updated the SessionReplayExporter to include a condition for forcing full captures based on canvas size and keyframe status. Refined the RRWebEventGenerator to manage node IDs and image handling more effectively, including the introduction of a new tile-based signature system. Adjusted the ExportFrame structure to utilize IntSize for original dimensions, enhancing clarity and consistency across the observability module.
…hashing and signature computation Modified the TileSignature data class to include separate hashLo and hashHi values, enhancing the hashing mechanism. Updated the TileSignatureManager to support multiple compute methods with preferred tile dimensions and added convenience overloads for square tiles. Adjusted internal hashing logic to reduce collision probability and improved test cases to reflect changes in tile signature handling.
| val (node, canvasSize) = tileNode(exportFrame, image) | ||
| totalCanvasSize += canvasSize | ||
| node | ||
| } |
There was a problem hiding this comment.
Missing nodeIds.clear() in generateCaptureFullEvents causes stale mappings
Medium Severity
generateCaptureFullEvents resets lastNodeId to 0 (so new node IDs restart from 1) but never clears the nodeIds map. Stale entries from previous snapshots remain, mapping old TileSignature keys to node IDs that are now reused for different tiles. When a subsequent incremental frame references one of those stale signatures in its removeImages, addCommandNodes resolves it to a wrong node, causing incorrect DOM mutations in the replay. The nodeIds.clear() call only exists in addCommandNodes for keyframe incrementals — the full-snapshot path is missing it.
Additional Locations (1)
| * Queue cost derived from the base64 string length; it is a cost unit, not exact bytes. | ||
| */ | ||
| override fun cost(): Int = capture.imageBase64.length | ||
| override fun cost(): Int = capture.addImages.firstOrNull()?.imageBase64?.length ?: 0 |
There was a problem hiding this comment.
cost() underestimates queue cost for multi-tile frames
Low Severity
cost() only accounts for the first element of addImages. With tile compression, a frame may contain multiple AddImage entries, and remove-only rollback frames report zero cost since addImages is empty. Summing all image sizes would give a more accurate queue cost estimate.
(cherry picked from commit 49e4fb9)


Summary
How did you test this change?
Are there any deployment considerations?
Note
Medium Risk
Touches core session replay capture and RRWeb export logic (frame diffing, bitmap lifecycle, event/state sequencing), so regressions could impact replay correctness or memory usage, but scope is contained to the replay module with added tests.
Overview
Session replay capture/export is refactored from emitting single full-screen
CaptureEvents to emittingExportFrames that can represent incremental tile updates (adds/removes) with keyframes.This introduces a new capture pipeline (
CaptureManager+ImageCaptureService+TileDiffManager+ExportDiffManager) with configurableReplayOptions.compression(ScreenImagevsOverlayTileswith layer/keyframe cadence and optional backtracking/rollback).RRWeb event generation is rewritten (
RRWebEventGeneratorreplacesSessionReplayEventGenerator) to build full snapshots as positioned<img>nodes and subsequent updates as mutation add/remove operations, tracking node IDs and keyframe continuity; exporter logic is updated accordingly (including revised canvas-buffer-limit behavior).Written by Cursor Bugbot for commit 425d82d. This will update automatically on new commits. Configure here.