Skip to content

Capture events post replay triggering and send them as spans#1339

Merged
matux merged 8 commits into
matux/leading-replayfrom
matux/post-replay
Oct 2, 2025
Merged

Capture events post replay triggering and send them as spans#1339
matux merged 8 commits into
matux/leading-replayfrom
matux/post-replay

Conversation

@matux

@matux matux commented Oct 2, 2025

Copy link
Copy Markdown
Contributor

Note

PR Description written down with the help of Claude Code by human guided diff analyzing.

Summary

Introduces a ring buffer (two-slot) with stable buffer cursor ({slot, offset}) to improve leading replay capture robustness across single buffer rotations, replacing prior event count boundary logic. Core changes refactor Recorder internals (buffers, cursor, event collection) and update ReplayManager to use the cursor while expanding test coverage for various rotation and timing scenarios.

  • Implemented two-slot ring buffer and cursor-based leading boundary.
  • Refactored export logic into _collectAll / _collectEventsFromCursor with replay.end marker helper.
  • Updated ReplayManager to schedule and export leading spans using buffer cursor and added extensive new tests.

Key Changes

Recorder

  • Implemented 2-slot ring buffer with _currentSlot and _previousSlot (XOR-based).
  • Added bufferCursor() returning {slot: 0|1, offset: number} for stable position capture.
  • Updated exportRecordingSpan() to accept cursor or export all events.
  • Split event collection into _collectAll() and _collectEventsFromCursor(cursor).
  • Extracted static helpers: _replayEndEvent(), _logEvent().

ReplayManager

  • Captures buffer cursor instead of event count when scheduling leading replay.
  • Passes cursor to exportRecordingSpan() for leading boundary.

Benefits

  • Cursor survives single checkout (buffer rotation).
  • Best-effort handling with multiple checkouts + warning.
  • Type-safe with JSDoc typedefs.
  • Cleaner code: removed _events facade, added _previousSlot getter.

Test Coverage

  • 29 new tests (unit/integration/e2e).
  • All 124 replay tests passing ✅.

@matux matux requested a review from Copilot October 2, 2025 03:25
@matux matux self-assigned this Oct 2, 2025

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

Introduces a ring buffer (two-slot) with stable buffer cursor ({slot, offset}) to improve leading replay capture robustness across single buffer rotations, replacing prior event count boundary logic. Core changes refactor Recorder internals (buffers, cursor, event collection) and update ReplayManager to use the cursor while expanding test coverage for various rotation and timing scenarios.

  • Implemented two-slot ring buffer and cursor-based leading boundary.
  • Refactored export logic into _collectAll / _collectEventsFromCursor with replay.end marker helper.
  • Updated ReplayManager to schedule and export leading spans using buffer cursor (removed isRecording gate) and added extensive new tests.

Reviewed Changes

Copilot reviewed 8 out of 9 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
src/browser/replay/recorder.js Replaced event arrays with two-slot ring buffer, added bufferCursor, refactored collection/export logic, added helpers & static logging.
src/browser/replay/replayManager.js Switched from event count to buffer cursor for leading capture; updated scheduling and export; added typedef annotations.
test/replay/util/recorder.js Added helper accessors/mutators for internal buffers for test control.
test/replay/unit/recorder.collectEvents.test.js Added unit tests for new collection methods and cursor behavior.
test/replay/integration/replayManager.bufferIndex.test.js Integration tests validating cursor-based leading capture and state transitions.
test/replay/integration/replayManager.bufferIndex.checkoutResilience.test.js Resilience tests across rotations, multiple checkouts, and error handling.
test/replay/integration/e2e.test.js Added end-to-end test covering combined trailing + leading flow with cursor assertions.
test/browser.replay.recorder.test.js Adjusted expectations for new options (postDuration) and removed obsolete skipped test.

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Comment thread src/browser/replay/recorder.js
Comment thread src/browser/replay/replayManager.js
if (isCheckout && event.type === EventType.Meta) {
this._events.previous = this._events.current;
this._events.current = [];
this._buffers[(this._currentSlot = this._previousSlot)] = [];

Copilot AI Oct 2, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The in-line assignment with an expression side-effect obscures intent (rotating the active slot and clearing the new current buffer). Refactor for clarity, e.g.
const nextSlot = this._previousSlot;
this._currentSlot = nextSlot;
this._buffers[nextSlot] = [];
to make rotation semantics easier to read and maintain.

Suggested change
this._buffers[(this._currentSlot = this._previousSlot)] = [];
const nextSlot = this._previousSlot;
this._currentSlot = nextSlot;
this._buffers[nextSlot] = [];

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lemme be clever! I can change on request. 🫥

Comment thread src/browser/replay/replayManager.js Outdated
Comment thread src/browser/replay/recorder.js
@matux matux force-pushed the matux/post-replay branch from 32df702 to b770d71 Compare October 2, 2025 03:37
_options;
_rrwebOptions;

_isReady = false;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved _isReady initialization here from the constructor to follow the pattern we use with the rest of the initialized-at-instancing properties.

_currentSlot = 0;
/** Index of the finalized inactive slot (0|1). Frozen until next checkout. */
get _previousSlot() {
return this._currentSlot ^ 1;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the ring has two slots we can just XOR which effectively flips the bit, so we avoid the modulo from (currentIndex + 1) % 2.

}

this._events.current.push(event);
this._buffers[this._currentSlot].push(event);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 This push after resetting the current slot ensures the -1 cursor can never happen while isReady is set. Might be worth a code comment.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added @remarks to bufferCursor() jsdoc: d9b25ae

@waltjones waltjones left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work!

@matux matux merged commit 0d7ee71 into matux/leading-replay Oct 2, 2025
4 checks passed
@matux matux deleted the matux/post-replay branch October 2, 2025 12:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants