Reduce Noise in Run Analytics Events#1516
Conversation
Run completed was dispatched whenever error, errorDetails, or friendlyError changed after a run had already ended, so a single Python run could produce multiple Project - Run completed events. Dispatch completion only on the codeRunTriggered true-to-false transition, matching how run started is tracked.
Preview iframe messages were handled by anonymous window listeners that were never removed on unmount, and showModal registered more listeners on each external-link error. Remounts could stack handlers so one RELOAD message triggered multiple code runs and duplicate Ran code events.
WebComponentProject tracked the previous codeRunTriggered value in a ref that reset when the component remounted while a run was still active, causing a second Project - Ran code event. Persist run edge tracking in module state, reset it when the web component disconnects or the project identifier changes.
Only dispatch run analytics events (editor-runStarted / editor-runCompleted) when a project's code differs from the last counted run, so repeated runs of unchanged code no longer generate noise. - Add buildProjectCodeSnapshot to produce a deterministic, order-independent snapshot string from a project's components. - Add runEventCodeSnapshot to gate each run cycle: suppress runStarted when the snapshot is unchanged, reset on project change, and pair runCompleted to the counted start via a per-cycle flag. - Reset snapshot state alongside existing run-event tracking on web-component disconnect so remounts stay safe. - Bypass the snapshot check in read-only mode (teachers viewing student work) so deliberate re-runs of the same code are still tracked.
Add trailing debounce so bursts of run attempts collapse into a single counted analytics dispatch after 250ms of quiet, while deliberate runs still emit once the debounce window has passed. - Replace synchronous run-start gating with scheduleRunEventCycle, which resets a debounce timer on each run start and evaluates the snapshot check when the timer fires. - Pair runCompleted with the debounced runStarted, including when the run finishes before the debounce fires. - Apply the same debounce path to Scratch run-started events in ScratchContainer. - Reset debounce state on project identifier change and web-component disconnect.
…res after fast runs
There was a problem hiding this comment.
Pull request overview
Reduces duplicate/noisy run analytics events in the web component editor by tightening run lifecycle dispatch (start/completed) and adding gating (code snapshot + debounce) to suppress repeated “meaningless” runs.
Changes:
- Adds a debounced run-event cycle with optional per-run code snapshot gating to suppress repeated runs of unchanged code.
- Makes runStarted/runCompleted emission more robust across rerenders/remounts and post-run error-state updates.
- Fixes duplicate handling by ensuring
HtmlRunnercleans upwindow.messagelisteners; adds/updates unit tests to cover new behavior.
Reviewed changes
Copilot reviewed 13 out of 13 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| src/web-component.js | Resets module-level run-event tracking state when the web component disconnects. |
| src/components/WebComponentProject/WebComponentProject.jsx | Routes runStarted/runCompleted dispatch through new debounced + snapshot-gated run-cycle helpers. |
| src/components/WebComponentProject/WebComponentProject.test.js | Updates/extends tests to account for debounced run-event dispatch and snapshot gating. |
| src/components/WebComponentProject/runEventTrackingState.js | Introduces module-level tracking for previous codeRunTriggered and project sync/reset behavior. |
| src/components/WebComponentProject/runEventTrackingState.test.js | Adds unit coverage for the new tracking-state module behavior. |
| src/components/WebComponentProject/runEventCodeSnapshot.js | Implements debounce + “code changed since last counted run” snapshot gating for run events. |
| src/components/WebComponentProject/runEventCodeSnapshot.test.js | Adds unit tests for debounce/snapshot gating behaviors and edge cases. |
| src/components/WebComponentProject/buildProjectCodeSnapshot.js | Adds stable snapshot builder for project components to support gating. |
| src/components/WebComponentProject/buildProjectCodeSnapshot.test.js | Adds unit tests ensuring snapshot stability and change detection. |
| src/components/Editor/Runners/HtmlRunner/HtmlRunner.jsx | Reworks iframe message handling to avoid leaked listeners and duplicate handling across rerenders/unmounts. |
| src/components/Editor/Runners/HtmlRunner/HtmlRunner.test.js | Adds tests asserting messages aren’t handled after unmount and aren’t duplicated after remount. |
| src/components/Editor/Project/ScratchContainer.jsx | Debounces scratch runStarted analytics dispatch via the shared run-event cycle helper. |
| src/components/Editor/Project/ScratchContainer.test.js | Updates tests to account for debounced scratch runStarted dispatch (including burst collapsing). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit be17ed8. Configure here.

Summary
Issue 1543.
Investigation into duplicate
Project - Ran codeandProject - Run completedanalytics events. I was unable to reproduce the kind of event timings reported in the ticket through normal UI interaction alone. The closest match came from running the following in the browser console on a project page:This PR takes a two-pronged approach: fix several edge cases in how run events are dispatched, and add gating so analytics only records runs that are likely to be meaningful. Should provide some fortification against abuse of the run button and over running the code.
Bug fixes
Briefly — see commit messages for detail:
runCompletedonce per run cycle, not again when error state updates after the run ends.window.messagelisteners inHtmlRunnerthat could cause duplicate handling on re-render.runStartedstable across web-component remounts during an active run.Run event dispatch gating
Commits have been used break this down a little
Code-change snapshot (editable projects)
Run analytics events (
editor-runStarted/editor-runCompleted) are only dispatched when the project's code differs from the last counted run. Repeated runs of unchanged code are suppressed.This assumes that a user hammering the run button without changing code does not tell us anything useful for analytics — as these do not reflect a new attempt or progress.
Read-only mode (e.g. a teacher viewing student work) bypasses the snapshot check so deliberate re-runs of the same code are still tracked.
Debounce (250ms quiet period)
A trailing debounce is also applied so bursts of run attempts collapse into a single counted dispatch after 250ms of quiet. This reduces noise on project views where code cannot be edited, or where a code-change snapshot is not practical (e.g. Scratch green-flag runs via
ScratchContainer).Together, snapshot gating handles the common student case; debounce handles read-only and Scratch paths where repeated runs without an easy code-diff are still possible.