Skip to content

feat(browser): Action markers in timeline of session replay#1092

Open
allansson wants to merge 26 commits intomainfrom
feat/timeline-markers-for-actions
Open

feat(browser): Action markers in timeline of session replay#1092
allansson wants to merge 26 commits intomainfrom
feat/timeline-markers-for-actions

Conversation

@allansson
Copy link
Copy Markdown
Collaborator

@allansson allansson commented Feb 18, 2026

Description

This PR adds action markers in the timeline of the debugger.

Screen.Recording.2026-03-13.at.18.09.47.mov

Each action can be hovered and clicking it will seek to the beginning of that action. If two or more actions are happening in parallel they will be shown in different lanes to indicate this.

How to Test

NOTE: There is a small graphical glitch where markers may overflow to the right during live playback. I'm looking into it but I don't think it should block this PR any further.

⚠️ We're monkey patching rrweb, so you need to install new packages and clear Vite's cached dependencies:

npm install
rm -rf node_modules/.vite
  1. Debug a browser script
  2. Make sure the action markers match the action list

Checklist

  • I have performed a self-review of my code.
  • I have added tests for my changes.
  • I have commented on my code, particularly in hard-to-understand areas.

Related PR(s)/Issue(s)

Resolves #1064


Note

Medium Risk
Adds new replay/timeline behavior and relies on a patch-package monkey patch of rrweb live-mode behavior, which could affect debugger playback/stop semantics. Main risk is regressions in session stopping/seeking and replay rendering across rrweb updates.

Overview
Adds action markers to the session replay timeline: the playback slider is replaced with a custom TimelineSlider that renders action-duration segments (stacked into lanes for overlaps), supports hover tooltips, and lets users click a marker to seek to that action.

Refactors browser debug data collection into a single useBrowserSession hook that tracks actions and replay together and injects custom rrweb events (action-begin/action-end/recording-end) with explicit timestamps, including aborting in-flight actions on stop.

Introduces patch-package (with a new postinstall) and a patch to rrweb adding Replayer.stopLive() / STOP_LIVE, which is used to cleanly exit live mode when a recording-end event is received; also removes older stop-time replay injection from the main process.

Reviewed by Cursor Bugbot for commit ed668e4. Bugbot is set up for automated code reviews on this repo. Configure here.

@allansson allansson marked this pull request as ready for review March 13, 2026 17:25
@allansson allansson requested a review from a team as a code owner March 13, 2026 17:25
Copy link
Copy Markdown
Contributor

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Invalid CSS pseudo-class selector silently ignored
    • Replaced the invalid :first-child-of-type selector with valid :first-of-type so the first timeline lane correctly has no top margin.

Create PR

Or push these changes by commenting:

@cursor push 86dc152142
Preview (86dc152142)
diff --git a/src/views/Validator/Browser/SessionPlayer/Timeline/TimelineActions.tsx b/src/views/Validator/Browser/SessionPlayer/Timeline/TimelineActions.tsx
--- a/src/views/Validator/Browser/SessionPlayer/Timeline/TimelineActions.tsx
+++ b/src/views/Validator/Browser/SessionPlayer/Timeline/TimelineActions.tsx
@@ -212,7 +212,7 @@
 
             margin-top: 1px;
 
-            &:first-child-of-type {
+            &:first-of-type {
               margin-top: 0;
             }
           `}

@allansson allansson changed the title feat(browser): Markers in timeline of session replay feat(browser): Action markers in timeline of session replay Mar 13, 2026
Copy link
Copy Markdown
Collaborator

@Llandy3d Llandy3d left a comment

Choose a reason for hiding this comment

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

Image

I have an issue that when I press on one of the markers the page becomes white and only when the timeline hits the next marker it appears again 🤔

Small nit: it would be great if when hovering over a marker it would also highlight one on the sidebar 🔖

@allansson
Copy link
Copy Markdown
Collaborator Author

allansson commented Mar 24, 2026

I have an issue that when I press on one of the markers the page becomes white and only when the timeline hits the next marker it appears again 🤔

@Llandy3d Could you check the console (i.e. in the devtools, not the console in the debugger) for errors? Something like not being able to find elements.

@allansson
Copy link
Copy Markdown
Collaborator Author

Small nit: it would be great if when hovering over a marker it would also highlight one on the sidebar 🔖

Yeah, I want to add it too, but I don't want to bloat this PR with it.

}: TimelineTooltipProps) {
return (
<Popover.Root open={open}>
<Popover.Trigger>{children}</Popover.Trigger>
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.

Missing asChild on Popover.Trigger nests buttons

High Severity

The Popover.Trigger component renders a <button> by default, and its children is also a <button>. This creates invalid nested buttons in the DOM, which leads to unpredictable click handling. Specifically, the inner button's event handlers may not fire, breaking seeking functionality when clicking action markers.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit febdd4c. Configure here.

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.

Bugbot Autofix determined this is a false positive.

The Radix UI Themes Popover.Trigger already uses asChild internally by default, so it merges with the child button element rather than wrapping it, preventing nested buttons.

You can send follow-ups to the cloud agent here.

Copy link
Copy Markdown
Collaborator

@going-confetti going-confetti left a comment

Choose a reason for hiding this comment

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

Looks good and works well! The patch fixed the issues I discovered last time I reviewed this branch 🎉 . I'm still experiencing an issues with navigating to individual segments in some cases (see video attached to one of the comments) - not sure if it's related to the specific test.

top: 0;
height: 100%;
background: white;
opacity: 0.3;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Nit: I'd expect the inverted version of this: the already played part of the timeline should have higher opacity/bolder colors than the unplayed part:
Image
I think achieving this can be tricky though :/

return (
<div
css={css`
padding-bottom: 6px;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Is the extra space under the tracks reserved for something?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Yes, the timeline itself. Without it you don't have a surface to seek by clicking.

}

onSeek({ time: newTime, commit: true })
const handleSeek = (seekTime: number, commit: boolean) => {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

(Not sure if this is the right place in code, but it's easier to reply to this kind of comments rather than to standalone ones, so here we are)

I've had some issues trying to navigate to a segment by clicking on it in the timeline:

Screen.Recording.2026-04-08.at.11.38.06.mov

Copy link
Copy Markdown
Contributor

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 2 potential issues.

There are 3 total unresolved issues (including 1 from previous review).

Autofix Details

Bugbot Autofix prepared fixes for both issues found in the latest run.

  • ✅ Fixed: Aborted action-end replay events are never generated
    • Changed line 91 to filter session.actions instead of the transformed actions array, allowing action-end events to be correctly generated for in-flight actions.
  • ✅ Fixed: Playback state never transitions to ended after streaming
    • Added setState('ended') call in the recording-end handler to explicitly transition playback state after stopLive() pauses the player.

Create PR

Or push these changes by commenting:

@cursor push bdcd30495d
Preview (bdcd30495d)
diff --git a/package-lock.json b/package-lock.json
--- a/package-lock.json
+++ b/package-lock.json
@@ -331,6 +331,7 @@
       "version": "7.24.9",
       "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.9.tgz",
       "integrity": "sha512-5e3FI4Q3M3Pbr21+5xJwCv6ZT6KmGkI0vw3Tozy5ODAQFTIWe37iT8Cr7Ice2Ntb+M3iSKCEWMB1MBgKrW3whg==",
+      "peer": true,
       "dependencies": {
         "@ampproject/remapping": "^2.2.0",
         "@babel/code-frame": "^7.24.7",
@@ -733,6 +734,7 @@
       "resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz",
       "integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==",
       "license": "MIT",
+      "peer": true,
       "dependencies": {
         "@dnd-kit/accessibility": "^3.1.1",
         "@dnd-kit/utilities": "^3.2.2",
@@ -1258,6 +1260,7 @@
       "integrity": "sha512-zx0EIq78WlY/lBb1uXlziZmDZI4ubcCXIMJ4uGjXzZW0nS19TjSPeXPAjzzTmKQlJUZm0SbmZhPKP7tuQ1SsEw==",
       "dev": true,
       "license": "MIT",
+      "peer": true,
       "dependencies": {
         "chalk": "^4.1.1",
         "fs-extra": "^9.0.1",
@@ -1700,6 +1703,7 @@
       "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz",
       "integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==",
       "license": "MIT",
+      "peer": true,
       "dependencies": {
         "@babel/runtime": "^7.18.3",
         "@emotion/babel-plugin": "^11.13.5",
@@ -2662,6 +2666,7 @@
       "integrity": "sha512-yl43JD/86CIj3Mz5mvvLJqAOfIup7ncxfJ0Btnl0/v5TouVUyeEdcpknfgc+yMevS/48oH9WAkkw93m7otLb/A==",
       "dev": true,
       "license": "MIT",
+      "peer": true,
       "dependencies": {
         "@inquirer/checkbox": "^3.0.1",
         "@inquirer/confirm": "^4.0.1",
@@ -2998,6 +3003,7 @@
       "integrity": "sha512-/g2d4sW9nUDJOMz3mabVQvOGhVa4e/BN/Um7yca9Bb2XTzPPnfTWHWQg+IsEYO7M3Vx+EXvaM/I2pJWIMun1bg==",
       "dev": true,
       "license": "MIT",
+      "peer": true,
       "dependencies": {
         "@octokit/auth-token": "^4.0.0",
         "@octokit/graphql": "^7.1.0",
@@ -3308,6 +3314,7 @@
       "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz",
       "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==",
       "license": "Apache-2.0",
+      "peer": true,
       "engines": {
         "node": ">=8.0.0"
       }
@@ -3329,6 +3336,7 @@
       "resolved": "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-1.30.1.tgz",
       "integrity": "sha512-s5vvxXPVdjqS3kTLKMeBMvop9hbWkwzBpu+mUO2M7sZtlkyDJGwFe33wRKnbaYDo8ExRVBIIdwIGrqpxHuKttA==",
       "license": "Apache-2.0",
+      "peer": true,
       "engines": {
         "node": ">=14"
       },
@@ -3365,6 +3373,7 @@
       "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.57.2.tgz",
       "integrity": "sha512-BdBGhQBh8IjZ2oIIX6F2/Q3LKm/FDDKi6ccYKcBTeilh6SNdNKveDOLk73BkSJjQLJk6qe4Yh+hHw1UPhCDdrg==",
       "license": "Apache-2.0",
+      "peer": true,
       "dependencies": {
         "@opentelemetry/api-logs": "0.57.2",
         "@types/shimmer": "^1.2.0",
@@ -3895,6 +3904,7 @@
       "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.31.0.tgz",
       "integrity": "sha512-cYJeP+6qN0UnBv1r09hXl0YorB8kXHv61BC0NUlBA8vxrylZ4/C8lnva3gd1E8n33DNYSaiGW+DuGoSt0QQ7Dw==",
       "license": "Apache-2.0",
+      "peer": true,
       "engines": {
         "node": ">=14"
       }
@@ -6948,6 +6958,7 @@
       "resolved": "https://registry.npmjs.org/@types/node/-/node-22.18.0.tgz",
       "integrity": "sha512-m5ObIqwsUp6BZzyiy4RdZpzWGub9bqLJMvZDD0QMXhxjqMHMENlj+SqF5QxoUwaQNFe+8kz8XM8ZQhqkQPTgMQ==",
       "license": "MIT",
+      "peer": true,
       "dependencies": {
         "undici-types": "~6.21.0"
       }
@@ -7024,6 +7035,7 @@
       "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.10.tgz",
       "integrity": "sha512-WPigyYuGhgZ/cTPRXB2EwUw+XvsRA3GqHlsP4qteqrnnjDrApbS7MxcGr/hke5iUoeB7E/gQtrs9I37zAJ0Vjw==",
       "license": "MIT",
+      "peer": true,
       "dependencies": {
         "csstype": "^3.2.2"
       }
@@ -7034,6 +7046,7 @@
       "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
       "devOptional": true,
       "license": "MIT",
+      "peer": true,
       "peerDependencies": {
         "@types/react": "^19.2.0"
       }
@@ -7138,6 +7151,7 @@
       "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.16.1.tgz",
       "integrity": "sha512-SxdPak/5bO0EnGktV05+Hq8oatjAYVY3Zh2bye9pGZy6+jwyR3LG3YKkV4YatlsgqXP28BTeVm9pqwJM96vf2A==",
       "dev": true,
+      "peer": true,
       "dependencies": {
         "@eslint-community/regexpp": "^4.10.0",
         "@typescript-eslint/scope-manager": "7.16.1",
@@ -8024,6 +8038,7 @@
       "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
       "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
       "license": "MIT",
+      "peer": true,
       "bin": {
         "acorn": "bin/acorn"
       },
@@ -9226,6 +9241,7 @@
         }
       ],
       "license": "MIT",
+      "peer": true,
       "dependencies": {
         "baseline-browser-mapping": "^2.9.0",
         "caniuse-lite": "^1.0.30001759",
@@ -11445,16 +11461,6 @@
         "node": ">= 0.8"
       }
     },
-    "node_modules/encoding": {
-      "version": "0.1.13",
-      "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz",
-      "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==",
-      "license": "MIT",
-      "optional": true,
-      "dependencies": {
-        "iconv-lite": "^0.6.2"
-      }
-    },
     "node_modules/end-of-stream": {
       "version": "1.4.4",
       "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
@@ -11805,6 +11811,7 @@
       "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz",
       "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==",
       "dev": true,
+      "peer": true,
       "dependencies": {
         "@eslint-community/eslint-utils": "^4.2.0",
         "@eslint-community/regexpp": "^4.6.1",
@@ -11943,6 +11950,7 @@
       "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz",
       "integrity": "sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==",
       "dev": true,
+      "peer": true,
       "dependencies": {
         "array-includes": "^3.1.7",
         "array.prototype.findlastindex": "^1.2.3",
@@ -13840,6 +13848,7 @@
       "version": "10.1.1",
       "resolved": "https://registry.npmjs.org/immer/-/immer-10.1.1.tgz",
       "integrity": "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==",
+      "peer": true,
       "funding": {
         "type": "opencollective",
         "url": "https://opencollective.com/immer"
@@ -16065,6 +16074,7 @@
       "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.55.1.tgz",
       "integrity": "sha512-jz4x+TJNFHwHtwuV9vA9rMujcZRb0CEilTEwG2rRSpe/A7Jdkuj8xPKttCgOh+v/lkHy7HsZ64oj+q3xoAFl9A==",
       "license": "MIT",
+      "peer": true,
       "dependencies": {
         "dompurify": "3.2.7",
         "marked": "14.0.0"
@@ -18050,6 +18060,7 @@
       "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz",
       "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==",
       "license": "MIT",
+      "peer": true,
       "engines": {
         "node": ">=0.10.0"
       }
@@ -18074,6 +18085,7 @@
       "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz",
       "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==",
       "license": "MIT",
+      "peer": true,
       "dependencies": {
         "scheduler": "^0.27.0"
       },
@@ -18085,6 +18097,7 @@
       "version": "7.53.0",
       "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.53.0.tgz",
       "integrity": "sha512-M1n3HhqCww6S2hxLxciEXy2oISPnAzxY7gvwVPrtlczTM/1dDadXgUxDpHMrMTblDOcm/AXtXxHwZ3jpg1mqKQ==",
+      "peer": true,
       "engines": {
         "node": ">=18.0.0"
       },
@@ -19037,6 +19050,7 @@
       "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
       "dev": true,
       "license": "MIT",
+      "peer": true,
       "dependencies": {
         "fast-deep-equal": "^3.1.3",
         "fast-uri": "^3.0.1",
@@ -20546,7 +20560,8 @@
     "node_modules/tslib": {
       "version": "2.6.3",
       "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz",
-      "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ=="
+      "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==",
+      "peer": true
     },
     "node_modules/type-check": {
       "version": "0.4.0",
@@ -20701,6 +20716,7 @@
       "version": "5.5.3",
       "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.3.tgz",
       "integrity": "sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ==",
+      "peer": true,
       "bin": {
         "tsc": "bin/tsc",
         "tsserver": "bin/tsserver"
@@ -21308,6 +21324,7 @@
       "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz",
       "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==",
       "license": "MIT",
+      "peer": true,
       "dependencies": {
         "esbuild": "^0.21.3",
         "postcss": "^8.4.43",
@@ -22430,6 +22447,7 @@
       "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
       "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
       "license": "MIT",
+      "peer": true,
       "funding": {
         "url": "https://github.com/sponsors/colinhacks"
       }

diff --git a/src/hooks/useBrowserSession.ts b/src/hooks/useBrowserSession.ts
--- a/src/hooks/useBrowserSession.ts
+++ b/src/hooks/useBrowserSession.ts
@@ -88,7 +88,7 @@
         })
 
         // Insert action end events for all actions that were running when the script stopped.
-        const actionEnds = actions
+        const actionEnds = session.actions
           .filter((action) => action.type === 'begin')
           .map((action) =>
             createReplayEvent({

diff --git a/src/views/Validator/Browser/SessionPlayer/SessionPlayer.hooks.ts b/src/views/Validator/Browser/SessionPlayer/SessionPlayer.hooks.ts
--- a/src/views/Validator/Browser/SessionPlayer/SessionPlayer.hooks.ts
+++ b/src/views/Validator/Browser/SessionPlayer/SessionPlayer.hooks.ts
@@ -111,6 +111,7 @@
         case 'recording-end':
           newPlayer.stopLive()
           newPlayer.setConfig({ liveMode: false })
+          setState('ended')
 
           break

You can send follow-ups to the cloud agent here.

Copy link
Copy Markdown
Contributor

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 2 potential issues.

There are 3 total unresolved issues (including 1 from previous review).

Autofix Details

Bugbot Autofix prepared fixes for both issues found in the latest run.

  • ✅ Fixed: Double recording-end events on user-initiated stop
    • Removed the immediate ScriptHandler.Stopped send from the Stop IPC handler, keeping only the event that fires after the process actually terminates.
  • ✅ Fixed: Unused exported ActionBeginEvent type in rrweb.ts
    • Removed the unused ActionBeginEvent type export from rrweb.ts since all code uses the ActionBeginEvent type from schema.ts instead.

Create PR

Or push these changes by commenting:

@cursor push 9312cf5f20
Preview (9312cf5f20)
diff --git a/package-lock.json b/package-lock.json
--- a/package-lock.json
+++ b/package-lock.json
@@ -331,6 +331,7 @@
       "version": "7.24.9",
       "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.9.tgz",
       "integrity": "sha512-5e3FI4Q3M3Pbr21+5xJwCv6ZT6KmGkI0vw3Tozy5ODAQFTIWe37iT8Cr7Ice2Ntb+M3iSKCEWMB1MBgKrW3whg==",
+      "peer": true,
       "dependencies": {
         "@ampproject/remapping": "^2.2.0",
         "@babel/code-frame": "^7.24.7",
@@ -733,6 +734,7 @@
       "resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz",
       "integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==",
       "license": "MIT",
+      "peer": true,
       "dependencies": {
         "@dnd-kit/accessibility": "^3.1.1",
         "@dnd-kit/utilities": "^3.2.2",
@@ -1258,6 +1260,7 @@
       "integrity": "sha512-zx0EIq78WlY/lBb1uXlziZmDZI4ubcCXIMJ4uGjXzZW0nS19TjSPeXPAjzzTmKQlJUZm0SbmZhPKP7tuQ1SsEw==",
       "dev": true,
       "license": "MIT",
+      "peer": true,
       "dependencies": {
         "chalk": "^4.1.1",
         "fs-extra": "^9.0.1",
@@ -1700,6 +1703,7 @@
       "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz",
       "integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==",
       "license": "MIT",
+      "peer": true,
       "dependencies": {
         "@babel/runtime": "^7.18.3",
         "@emotion/babel-plugin": "^11.13.5",
@@ -2662,6 +2666,7 @@
       "integrity": "sha512-yl43JD/86CIj3Mz5mvvLJqAOfIup7ncxfJ0Btnl0/v5TouVUyeEdcpknfgc+yMevS/48oH9WAkkw93m7otLb/A==",
       "dev": true,
       "license": "MIT",
+      "peer": true,
       "dependencies": {
         "@inquirer/checkbox": "^3.0.1",
         "@inquirer/confirm": "^4.0.1",
@@ -2998,6 +3003,7 @@
       "integrity": "sha512-/g2d4sW9nUDJOMz3mabVQvOGhVa4e/BN/Um7yca9Bb2XTzPPnfTWHWQg+IsEYO7M3Vx+EXvaM/I2pJWIMun1bg==",
       "dev": true,
       "license": "MIT",
+      "peer": true,
       "dependencies": {
         "@octokit/auth-token": "^4.0.0",
         "@octokit/graphql": "^7.1.0",
@@ -3308,6 +3314,7 @@
       "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz",
       "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==",
       "license": "Apache-2.0",
+      "peer": true,
       "engines": {
         "node": ">=8.0.0"
       }
@@ -3329,6 +3336,7 @@
       "resolved": "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-1.30.1.tgz",
       "integrity": "sha512-s5vvxXPVdjqS3kTLKMeBMvop9hbWkwzBpu+mUO2M7sZtlkyDJGwFe33wRKnbaYDo8ExRVBIIdwIGrqpxHuKttA==",
       "license": "Apache-2.0",
+      "peer": true,
       "engines": {
         "node": ">=14"
       },
@@ -3365,6 +3373,7 @@
       "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.57.2.tgz",
       "integrity": "sha512-BdBGhQBh8IjZ2oIIX6F2/Q3LKm/FDDKi6ccYKcBTeilh6SNdNKveDOLk73BkSJjQLJk6qe4Yh+hHw1UPhCDdrg==",
       "license": "Apache-2.0",
+      "peer": true,
       "dependencies": {
         "@opentelemetry/api-logs": "0.57.2",
         "@types/shimmer": "^1.2.0",
@@ -3895,6 +3904,7 @@
       "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.31.0.tgz",
       "integrity": "sha512-cYJeP+6qN0UnBv1r09hXl0YorB8kXHv61BC0NUlBA8vxrylZ4/C8lnva3gd1E8n33DNYSaiGW+DuGoSt0QQ7Dw==",
       "license": "Apache-2.0",
+      "peer": true,
       "engines": {
         "node": ">=14"
       }
@@ -6948,6 +6958,7 @@
       "resolved": "https://registry.npmjs.org/@types/node/-/node-22.18.0.tgz",
       "integrity": "sha512-m5ObIqwsUp6BZzyiy4RdZpzWGub9bqLJMvZDD0QMXhxjqMHMENlj+SqF5QxoUwaQNFe+8kz8XM8ZQhqkQPTgMQ==",
       "license": "MIT",
+      "peer": true,
       "dependencies": {
         "undici-types": "~6.21.0"
       }
@@ -7024,6 +7035,7 @@
       "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.10.tgz",
       "integrity": "sha512-WPigyYuGhgZ/cTPRXB2EwUw+XvsRA3GqHlsP4qteqrnnjDrApbS7MxcGr/hke5iUoeB7E/gQtrs9I37zAJ0Vjw==",
       "license": "MIT",
+      "peer": true,
       "dependencies": {
         "csstype": "^3.2.2"
       }
@@ -7034,6 +7046,7 @@
       "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
       "devOptional": true,
       "license": "MIT",
+      "peer": true,
       "peerDependencies": {
         "@types/react": "^19.2.0"
       }
@@ -7138,6 +7151,7 @@
       "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.16.1.tgz",
       "integrity": "sha512-SxdPak/5bO0EnGktV05+Hq8oatjAYVY3Zh2bye9pGZy6+jwyR3LG3YKkV4YatlsgqXP28BTeVm9pqwJM96vf2A==",
       "dev": true,
+      "peer": true,
       "dependencies": {
         "@eslint-community/regexpp": "^4.10.0",
         "@typescript-eslint/scope-manager": "7.16.1",
@@ -8024,6 +8038,7 @@
       "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
       "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
       "license": "MIT",
+      "peer": true,
       "bin": {
         "acorn": "bin/acorn"
       },
@@ -9226,6 +9241,7 @@
         }
       ],
       "license": "MIT",
+      "peer": true,
       "dependencies": {
         "baseline-browser-mapping": "^2.9.0",
         "caniuse-lite": "^1.0.30001759",
@@ -11445,16 +11461,6 @@
         "node": ">= 0.8"
       }
     },
-    "node_modules/encoding": {
-      "version": "0.1.13",
-      "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz",
-      "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==",
-      "license": "MIT",
-      "optional": true,
-      "dependencies": {
-        "iconv-lite": "^0.6.2"
-      }
-    },
     "node_modules/end-of-stream": {
       "version": "1.4.4",
       "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
@@ -11805,6 +11811,7 @@
       "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz",
       "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==",
       "dev": true,
+      "peer": true,
       "dependencies": {
         "@eslint-community/eslint-utils": "^4.2.0",
         "@eslint-community/regexpp": "^4.6.1",
@@ -11943,6 +11950,7 @@
       "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz",
       "integrity": "sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==",
       "dev": true,
+      "peer": true,
       "dependencies": {
         "array-includes": "^3.1.7",
         "array.prototype.findlastindex": "^1.2.3",
@@ -13840,6 +13848,7 @@
       "version": "10.1.1",
       "resolved": "https://registry.npmjs.org/immer/-/immer-10.1.1.tgz",
       "integrity": "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==",
+      "peer": true,
       "funding": {
         "type": "opencollective",
         "url": "https://opencollective.com/immer"
@@ -16065,6 +16074,7 @@
       "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.55.1.tgz",
       "integrity": "sha512-jz4x+TJNFHwHtwuV9vA9rMujcZRb0CEilTEwG2rRSpe/A7Jdkuj8xPKttCgOh+v/lkHy7HsZ64oj+q3xoAFl9A==",
       "license": "MIT",
+      "peer": true,
       "dependencies": {
         "dompurify": "3.2.7",
         "marked": "14.0.0"
@@ -18050,6 +18060,7 @@
       "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz",
       "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==",
       "license": "MIT",
+      "peer": true,
       "engines": {
         "node": ">=0.10.0"
       }
@@ -18074,6 +18085,7 @@
       "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz",
       "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==",
       "license": "MIT",
+      "peer": true,
       "dependencies": {
         "scheduler": "^0.27.0"
       },
@@ -18085,6 +18097,7 @@
       "version": "7.53.0",
       "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.53.0.tgz",
       "integrity": "sha512-M1n3HhqCww6S2hxLxciEXy2oISPnAzxY7gvwVPrtlczTM/1dDadXgUxDpHMrMTblDOcm/AXtXxHwZ3jpg1mqKQ==",
+      "peer": true,
       "engines": {
         "node": ">=18.0.0"
       },
@@ -19037,6 +19050,7 @@
       "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
       "dev": true,
       "license": "MIT",
+      "peer": true,
       "dependencies": {
         "fast-deep-equal": "^3.1.3",
         "fast-uri": "^3.0.1",
@@ -20546,7 +20560,8 @@
     "node_modules/tslib": {
       "version": "2.6.3",
       "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz",
-      "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ=="
+      "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==",
+      "peer": true
     },
     "node_modules/type-check": {
       "version": "0.4.0",
@@ -20701,6 +20716,7 @@
       "version": "5.5.3",
       "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.3.tgz",
       "integrity": "sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ==",
+      "peer": true,
       "bin": {
         "tsc": "bin/tsc",
         "tsserver": "bin/tsserver"
@@ -21308,6 +21324,7 @@
       "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz",
       "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==",
       "license": "MIT",
+      "peer": true,
       "dependencies": {
         "esbuild": "^0.21.3",
         "postcss": "^8.4.43",
@@ -22430,6 +22447,7 @@
       "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
       "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
       "license": "MIT",
+      "peer": true,
       "funding": {
         "url": "https://github.com/sponsors/colinhacks"
       }

diff --git a/src/handlers/script/index.ts b/src/handlers/script/index.ts
--- a/src/handlers/script/index.ts
+++ b/src/handlers/script/index.ts
@@ -83,7 +83,7 @@
     })
   })
 
-  ipcMain.on(ScriptHandler.Stop, (event) => {
+  ipcMain.on(ScriptHandler.Stop, () => {
     console.info(`${ScriptHandler.Stop} event received`)
     if (currentTestRun) {
       currentTestRun.stop().catch((error) => {
@@ -92,9 +92,6 @@
 
       currentTestRun = null
     }
-
-    const browserWindow = browserWindowFromEvent(event)
-    browserWindow.webContents.send(ScriptHandler.Stopped)
   })
 
   ipcMain.handle(

diff --git a/src/main/runner/rrweb.ts b/src/main/runner/rrweb.ts
--- a/src/main/runner/rrweb.ts
+++ b/src/main/runner/rrweb.ts
@@ -47,7 +47,6 @@
 
 export type RecordingEndEvent = z.infer<typeof RecordingEndEventSchema>
 export type PageStartEvent = z.infer<typeof PageStartEventSchema>
-export type ActionBeginEvent = z.infer<typeof ActionBeginEventSchema>
 
 export type CustomReplayEvent = z.infer<typeof CustomReplayEventSchema>

You can send follow-ups to the cloud agent here.

Copy link
Copy Markdown
Contributor

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

There are 2 total unresolved issues (including 1 from previous review).

Fix All in Cursor

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Segment clicks inside slider cause competing seek operations
    • Added stopPropagation() to handlePointerDown and handleClick handlers in the segment button to prevent events from bubbling up to the slider.

Create PR

Or push these changes by commenting:

@cursor push 256e39edcf
Preview (256e39edcf)
diff --git a/src/views/Validator/Browser/SessionPlayer/Timeline/TimelineActions.tsx b/src/views/Validator/Browser/SessionPlayer/Timeline/TimelineActions.tsx
--- a/src/views/Validator/Browser/SessionPlayer/Timeline/TimelineActions.tsx
+++ b/src/views/Validator/Browser/SessionPlayer/Timeline/TimelineActions.tsx
@@ -98,10 +98,12 @@
   const status = segment.action.result?.type ?? 'unknown'
 
   const handlePointerDown = (ev: MouseEvent<HTMLElement>) => {
+    ev.stopPropagation()
     mouseDownX.current = ev.clientX
   }
 
   const handleClick = (ev: MouseEvent<HTMLElement>) => {
+    ev.stopPropagation()
     // We want to allow users to drag the timeline so we only register the click
     // if the mouse hasn't moved by more than a few pixels.
     const deltaX = ev.clientX - (mouseDownX.current ?? ev.clientX)

You can send follow-ups to the cloud agent here.

Reviewed by Cursor Bugbot for commit ed668e4. Configure here.

if (Math.abs(deltaX) < 10) {
onSeek(segment.action.timestamp.started)
}
}
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.

Segment clicks inside slider cause competing seek operations

Medium Severity

Segment buttons are rendered inside SliderPrimitive.Track, so pointer events on segments bubble up to the slider root. Clicking a segment triggers both the slider's seek (to the click position on the track via onValueChange/onValueCommit) and the segment's own seek (to segment.action.timestamp.started). These competing seeks cause the player to first jump to the wrong position, then to the intended position, producing visual glitches. The handlePointerDown and handleClick handlers on the segment button don't call stopPropagation() to prevent the slider from also processing the events.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit ed668e4. Configure here.

@going-confetti going-confetti self-requested a review April 10, 2026 17:43
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.

Show action indicators on the timeline in the debugger view

3 participants