Skip to content

feat: keystroke overlay (record keys + built-in "Keystrokes" extension)#694

Open
flol3622 wants to merge 3 commits into
webadderallorg:mainfrom
flol3622:feat/keystroke-telemetry
Open

feat: keystroke overlay (record keys + built-in "Keystrokes" extension)#694
flol3622 wants to merge 3 commits into
webadderallorg:mainfrom
flol3622:feat/keystroke-telemetry

Conversation

@flol3622

@flol3622 flol3622 commented Jun 22, 2026

Copy link
Copy Markdown

What

Records keyboard input during capture and shows pressed keys as on-screen
keycaps during playback and export — like keyviz.

Ships as a built-in extension (Keystrokes) that auto-activates; its settings
live under the cursor section.

Why

getKeystrokesInRange() already existed in the extension API surface, but
nothing ever populated it — setKeystrokeEvents() was never called, so it
always returned []. This wires up the capture side and exposes the data.

How

Capture (main process) — extends the existing uiohook-napi interaction
capture (already used for mouse) with keydown events:

  • Keystrokes are timestamped against the recording clock and saved to a
    <video>.keys.json sidecar on stop, mirroring the .cursor.json pattern.
  • Modifiers are read from the event's shiftKey/ctrlKey/altKey/metaKey
    booleans; bare modifier presses are not emitted on their own.
  • New IPC get-keystroke-telemetry + preload binding; the editor loads the
    sidecar and feeds extensionHost.setKeystrokeEvents().

Overlay (built-in extension)public/builtin-extensions/keystrokes:

  • White 3D keycaps (extruded base, soft shadow), sized to the canvas.
  • keyviz-style pop/slide/fade animation, configurable position + margins.
  • Keyboard layout setting (QWERTY/AZERTY/QWERTZ): uiohook reports physical
    key positions, so this remaps to the user's layout.

Notes / limitations

  • Physical-key remap covers QWERTY/AZERTY/QWERTZ letters + common punctuation;
    it is not a full per-layout character translation (e.g. AZERTY number-row
    symbols). Knob is exposed so users can pick their layout.
  • Built-in extensions are bundled via a new extraResources entry.
  • Native helper binaries and the bun lockfile were intentionally left out of
    the diff (local build artifacts).

Related

Files

Area Change
electron/ipc/cursor/interaction.ts keydown capture + keycode map
electron/ipc/cursor/telemetry.ts save/load/reset keystroke sidecar
electron/ipc/recording/mac.ts, register/recording.ts persist on stop + IPC handler
electron/ipc/{types,state,constants,utils}.ts supporting types/state
electron/preload.ts, electron-env.d.ts getKeystrokeTelemetry
src/components/video-editor/VideoEditor.tsx load sidecar → extension host
public/builtin-extensions/keystrokes/* built-in overlay extension
electron-builder.json5 bundle built-in extensions

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • New Features
    • Added keystroke telemetry recording that captures keyboard input during screen recordings
    • Introduced a built-in "Keystrokes" overlay extension that displays pressed keys as visual badges during video playback with customizable keyboard layout mapping, on-canvas positioning, margins, and animation timing

flol3622 and others added 3 commits June 22, 2026 10:07
…ension API

Wire up uiohook-napi keyboard events alongside existing mouse capture to record
keystrokes with playback timestamps. Save to a .keys.json sidecar on recording
stop (mirrors .cursor.json pattern). Expose via IPC + extensionHost.setKeystrokeEvents
so getKeystrokesInRange() works in extensions.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…nimation

- Read modifier state from uiohook event booleans (shiftKey/ctrlKey/altKey/
  metaKey) instead of hand-tracking keycodes — fixes Shift not showing in
  chords like Shift+Cmd+V, and deletes the manual modifier set + keyup handler
- Correct the extended nav keycodes (Home/End/PageUp/PageDown/Insert/Delete)
  to match uiohook-napi's UiohookKey constants
- Bundle the overlay as a built-in extension (public/builtin-extensions/
  keystrokes) so it auto-activates and ships with the app; settings nest under
  the cursor section via parentSection
- keyviz-style entrance/exit animation (pop + slide + fade) and configurable
  horizontal/vertical margins; drop shadow on badges

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Redesign overlay as white 3D keycaps (extruded dark base, soft shadow,
  black glyphs) sized to the canvas; modifiers render glyph + label stacked
- Add Keyboard layout setting (QWERTY/AZERTY/QWERTZ): uiohook reports physical
  key positions, so AZERTY 'A' (physical Q slot) was showing as Q. Remap fixes
  the common non-US layouts.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions github-actions Bot added the Slop label Jun 22, 2026
@github-actions

Copy link
Copy Markdown
Contributor

⚠️ This pull request has been flagged by Anti-Slop.
Our automated checks detected patterns commonly associated with
low-quality or automated/AI submissions (failure count reached).
No automatic closure — a maintainer will review it.
If this is legitimate work, please add more context, link issues, or ping us.

@coderabbitai

coderabbitai Bot commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds end-to-end keystroke telemetry to Recordly. During recording, keyboard events are captured via uiohook-napi, converted to KeystrokeEvent objects, and persisted as .keys.json sidecar files on recording stop (Mac and Windows). A new get-keystroke-telemetry IPC endpoint loads these events into the renderer. A new built-in extension (recordly.keystrokes) reads those events and renders animated keycap badge overlays onto the video canvas during export.

Changes

Keystroke Telemetry & Overlay Extension

Layer / File(s) Summary
Keyboard types, state, and sidecar path utility
electron/ipc/types.ts, electron/ipc/constants.ts, electron/ipc/state.ts, electron/ipc/utils.ts
Adds HookKeyboardEvent, KeystrokeEvent, and related listener types; widens UiohookLike event subscriptions to accept keyboard names/listeners; adds KEYSTROKE_TELEMETRY_VERSION = 1, activeKeystrokeEvents array + setter, and getKeystrokePathForVideo sidecar path helper.
Keystroke capture in interaction hook
electron/ipc/cursor/interaction.ts
Adds KEYCODE_MAP and keycodeToKey for uiohook-to-Web-key conversion; registers an onKeyDown listener gated on capture-active/not-paused that timestamps events via getCursorCaptureElapsedMs() and calls pushKeystroke; extends cleanup to unregister the keydown handler.
Keystroke telemetry in-memory state and sidecar persistence
electron/ipc/cursor/telemetry.ts
Adds pushKeystroke, getActiveKeystrokes, saveKeystrokeTelemetry (writes/deletes .keys.json sidecar), loadKeystrokeTelemetry (reads sidecar with version + field validation), and resetKeystrokeTelemetry.
Save and reset on recording stop (Mac + Windows)
electron/ipc/recording/mac.ts, electron/ipc/register/recording.ts
finalizeStoredVideo on Mac and the Windows native stop path both now call saveKeystrokeTelemetry(videoPath) in a try/catch (warning on failure) followed by resetKeystrokeTelemetry().
IPC endpoint, preload bridge, and VideoEditor loading
electron/ipc/register/recording.ts, electron/preload.ts, electron/electron-env.d.ts, src/components/video-editor/VideoEditor.tsx
Registers get-keystroke-telemetry IPC handler; exposes getKeystrokeTelemetry(videoPath?) in preload and its type declaration; VideoEditor adds a useEffect to load keystroke events on videoSourcePath changes and populate extensionHost.
Keystrokes built-in extension and packaging
public/builtin-extensions/keystrokes/recordly-extension.json, public/builtin-extensions/keystrokes/index.js, electron-builder.json5
Adds extension manifest (permissions: render+ui). activate(api) registers settings (layout, position, margins, duration) and a final render hook that retrieves recent events, computes fade/slide/scale animations, remaps keys to QWERTZ/AZERTY, formats special keys, measures text, and draws keycap badges. electron-builder.json5 packages public/builtin-extensionsbuiltin-extensions.

Sequence Diagram

sequenceDiagram
  participant uiohook as uiohook-napi
  participant interaction as interaction.ts
  participant telemetry as cursor/telemetry.ts
  participant recording as recording stop (mac/win)
  participant ipcMain as get-keystroke-telemetry
  participant VideoEditor as VideoEditor
  participant extension as keystrokes/index.js

  uiohook->>interaction: keydown (HookKeyboardEvent)
  interaction->>telemetry: pushKeystroke({ timeMs, key, modifiers })
  recording->>telemetry: saveKeystrokeTelemetry(videoPath)
  telemetry-->>recording: writes .keys.json sidecar
  recording->>telemetry: resetKeystrokeTelemetry()
  VideoEditor->>ipcMain: getKeystrokeTelemetry(videoSourcePath)
  ipcMain->>telemetry: loadKeystrokeTelemetry(videoPath)
  telemetry-->>ipcMain: KeystrokeEvent[]
  ipcMain-->>VideoEditor: { success, events }
  VideoEditor->>extension: extensionHost.setKeystrokeEvents(events)
  extension->>extension: final render hook draws keycap badges on canvas
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • webadderallorg/Recordly#254: The keystroke telemetry feature directly extends cursor telemetry modules created in that refactor — electron/ipc/cursor/interaction.ts, electron/ipc/cursor/telemetry.ts, shared state/types/constants — by adding keyboard hook handling, keystroke persistence, and IPC plumbing.
  • webadderallorg/Recordly#399: The keystroke capture in interaction.ts relies on pause-aware gating and timestamping via getCursorCaptureElapsedMs() that were introduced for cursor capture pause/resume in this earlier PR.

Poem

🐰 Tap tap tap, the keys go click,
Each stroke recorded, smooth and quick.
A .keys.json sidecar born,
Keycap badges, neatly worn.
The rabbit watches what you type —
Your keystrokes rendered, pixel-ripe! 🎹

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change: adding keystroke recording and overlay visualization through a new built-in extension, matching the core functionality introduced in the PR.
Description check ✅ Passed The PR description covers all required template sections: What (keystroke recording/visualization), Why (wiring up existing API), How (capture infrastructure, overlay extension), with detailed implementation notes, limitations, and file organization. However, the formal sections (Type of Change, Testing Guide, Checklist) from the template are not explicitly completed.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Warning

⚠️ This pull request shows signs of AI-generated slop (defensive_cruft). It has been flagged by CodeRabbit slop detection and should be reviewed carefully.

@github-actions

Copy link
Copy Markdown
Contributor

⚠️ This pull request has been flagged by Anti-Slop.
Our automated checks detected patterns commonly associated with
low-quality or automated/AI submissions (failure count reached).
No automatic closure — a maintainer will review it.
If this is legitimate work, please add more context, link issues, or ping us.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@electron/ipc/cursor/telemetry.ts`:
- Around line 370-377: The filter function that validates KeystrokeEvent objects
is only checking that the modifiers property is an array, but not validating
that each element within the array is a string. This allows malformed telemetry
with non-string modifier values to pass through the IPC boundary. Add an
additional validation check to the filter condition that ensures every element
in the modifiers array is of type string, in addition to the existing
Array.isArray check on line 376.

In `@electron/ipc/register/recording.ts`:
- Around line 1003-1008: The resetKeystrokeTelemetry() call at the native stop
flow is executed before finalizeStoredVideo() is invoked later in the mux flow
(around line 1473), which causes the keystroke buffer to be cleared prematurely.
When finalizeStoredVideo() subsequently attempts to save keystrokes and
encounters an empty buffer, it triggers the delete-on-empty branch and removes
the keystroke sidecar file that was just saved. Move the
resetKeystrokeTelemetry() call to execute after finalizeStoredVideo() completes,
and also add resetKeystrokeTelemetry() calls to all terminal failure exit paths
that do not reach finalizeStoredVideo() to prevent stale keystroke data from
persisting across multiple recordings.

In `@src/components/video-editor/VideoEditor.tsx`:
- Around line 3341-3353: The useEffect hook fetching keystroke telemetry via
window.electronAPI.getKeystrokeTelemetry() has a race condition where stale
responses from previous videoSourcePath values can overwrite newer results. To
fix this, add a cleanup function that tracks whether the current effect is still
active, immediately call extensionHost.setKeystrokeEvents([]) at the start of
the effect, and modify the promise chain to only apply results if the effect has
not been cancelled (by checking a flag set in the cleanup function before
calling extensionHost.setKeystrokeEvents() in both the .then() and .catch()
handlers).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 5d35015b-d98e-4e0c-a091-0dfcf6688e5e

📥 Commits

Reviewing files that changed from the base of the PR and between d8e34a1 and 501eb7a.

📒 Files selected for processing (14)
  • electron-builder.json5
  • electron/electron-env.d.ts
  • electron/ipc/constants.ts
  • electron/ipc/cursor/interaction.ts
  • electron/ipc/cursor/telemetry.ts
  • electron/ipc/recording/mac.ts
  • electron/ipc/register/recording.ts
  • electron/ipc/state.ts
  • electron/ipc/types.ts
  • electron/ipc/utils.ts
  • electron/preload.ts
  • public/builtin-extensions/keystrokes/index.js
  • public/builtin-extensions/keystrokes/recordly-extension.json
  • src/components/video-editor/VideoEditor.tsx

Comment on lines +370 to +377
return parsed.events.filter(
(e: unknown): e is KeystrokeEvent =>
e !== null &&
typeof e === "object" &&
typeof (e as KeystrokeEvent).timeMs === "number" &&
typeof (e as KeystrokeEvent).key === "string" &&
Array.isArray((e as KeystrokeEvent).modifiers),
);

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.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Tighten modifier element validation in loader.

Line 376 only checks that modifiers is an array; non-string elements still pass and can propagate malformed telemetry across the IPC boundary.

Suggested fix
 		return parsed.events.filter(
 			(e: unknown): e is KeystrokeEvent =>
 				e !== null &&
 				typeof e === "object" &&
 				typeof (e as KeystrokeEvent).timeMs === "number" &&
 				typeof (e as KeystrokeEvent).key === "string" &&
-				Array.isArray((e as KeystrokeEvent).modifiers),
+				Array.isArray((e as KeystrokeEvent).modifiers) &&
+				(e as KeystrokeEvent).modifiers.every((m: unknown) => typeof m === "string"),
 		);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
return parsed.events.filter(
(e: unknown): e is KeystrokeEvent =>
e !== null &&
typeof e === "object" &&
typeof (e as KeystrokeEvent).timeMs === "number" &&
typeof (e as KeystrokeEvent).key === "string" &&
Array.isArray((e as KeystrokeEvent).modifiers),
);
return parsed.events.filter(
(e: unknown): e is KeystrokeEvent =>
e !== null &&
typeof e === "object" &&
typeof (e as KeystrokeEvent).timeMs === "number" &&
typeof (e as KeystrokeEvent).key === "string" &&
Array.isArray((e as KeystrokeEvent).modifiers) &&
(e as KeystrokeEvent).modifiers.every((m: unknown) => typeof m === "string"),
);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@electron/ipc/cursor/telemetry.ts` around lines 370 - 377, The filter function
that validates KeystrokeEvent objects is only checking that the modifiers
property is an array, but not validating that each element within the array is a
string. This allows malformed telemetry with non-string modifier values to pass
through the IPC boundary. Add an additional validation check to the filter
condition that ensures every element in the modifiers array is of type string,
in addition to the existing Array.isArray check on line 376.

Comment on lines +1003 to +1008
try {
await saveKeystrokeTelemetry(finalVideoPath);
} catch (error) {
console.warn("Failed to persist keystroke telemetry during native stop:", error);
}
resetKeystrokeTelemetry();

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.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Reset timing in Windows stop flow can delete the just-saved keystroke sidecar.

Line 1008 clears keystrokes before the later finalizeStoredVideo call in mux (Line 1473). That finalizer saves keystrokes again, and with an empty buffer it takes the delete-on-empty branch, removing ${videoPath}.keys.json. It also leaves terminal stop-failure paths without a guaranteed reset.

Suggested direction
 				try {
 					await saveKeystrokeTelemetry(finalVideoPath);
 				} catch (error) {
 					console.warn("Failed to persist keystroke telemetry during native stop:", error);
 				}
-				resetKeystrokeTelemetry();
+				// Defer keystroke reset until finalizeStoredVideo() after mux/finalization.

Also add a reset in terminal failure exits that do not reach finalizeStoredVideo() to prevent stale carryover.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@electron/ipc/register/recording.ts` around lines 1003 - 1008, The
resetKeystrokeTelemetry() call at the native stop flow is executed before
finalizeStoredVideo() is invoked later in the mux flow (around line 1473), which
causes the keystroke buffer to be cleared prematurely. When
finalizeStoredVideo() subsequently attempts to save keystrokes and encounters an
empty buffer, it triggers the delete-on-empty branch and removes the keystroke
sidecar file that was just saved. Move the resetKeystrokeTelemetry() call to
execute after finalizeStoredVideo() completes, and also add
resetKeystrokeTelemetry() calls to all terminal failure exit paths that do not
reach finalizeStoredVideo() to prevent stale keystroke data from persisting
across multiple recordings.

Comment on lines +3341 to +3353
useEffect(() => {
if (!videoSourcePath) {
extensionHost.setKeystrokeEvents([]);
return;
}
window.electronAPI
.getKeystrokeTelemetry(videoSourcePath)
.then((result: { success: boolean; events: Array<{ timeMs: number; key: string; modifiers: string[] }> }) => {
extensionHost.setKeystrokeEvents(result.success ? result.events : []);
})
.catch(() => extensionHost.setKeystrokeEvents([]));
}, [videoSourcePath]);

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.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -name "VideoEditor.tsx" -type f

Repository: webadderallorg/Recordly

Length of output: 113


🏁 Script executed:

wc -l src/components/video-editor/VideoEditor.tsx

Repository: webadderallorg/Recordly

Length of output: 116


🏁 Script executed:

sed -n '3341,3353p' src/components/video-editor/VideoEditor.tsx

Repository: webadderallorg/Recordly

Length of output: 507


🏁 Script executed:

# Check if extensionHost.setKeystrokeEvents is called elsewhere and in what context
rg "setKeystrokeEvents" src/components/video-editor/VideoEditor.tsx -B 2 -A 2

Repository: webadderallorg/Recordly

Length of output: 489


🏁 Script executed:

# Search for extensionHost definition/initialization
rg "extensionHost" src/components/video-editor/VideoEditor.tsx | head -20

Repository: webadderallorg/Recordly

Length of output: 689


🏁 Script executed:

# Look at the imports and top of the file to understand extensionHost
head -100 src/components/video-editor/VideoEditor.tsx | grep -E "extensionHost|import"

Repository: webadderallorg/Recordly

Length of output: 1427


🏁 Script executed:

# More comprehensive search for extensionHost in VideoEditor
rg "extensionHost" src/components/video-editor/VideoEditor.tsx -B 0 -A 0 | wc -l

Repository: webadderallorg/Recordly

Length of output: 70


🏁 Script executed:

# Search for extensionHost across the codebase
rg "extensionHost\s*=" src/ --type=tsx --type=ts -B 2 | head -50

Repository: webadderallorg/Recordly

Length of output: 95


🏁 Script executed:

# Let's check if there are other useEffect hooks with similar async patterns to see if they use cancellation
rg "useEffect.*\(" src/components/video-editor/VideoEditor.tsx -A 10 | head -80

Repository: webadderallorg/Recordly

Length of output: 2366


🏁 Script executed:

# Check for AbortController or cancellation patterns in the file
rg "AbortController|cancelled" src/components/video-editor/VideoEditor.tsx

Repository: webadderallorg/Recordly

Length of output: 344


🏁 Script executed:

# Let's get context around the keystroke effect - 10 lines before and after
sed -n '3335,3360p' src/components/video-editor/VideoEditor.tsx

Repository: webadderallorg/Recordly

Length of output: 900


🏁 Script executed:

# Search for how videoSourcePath is used and if it's truly dynamic
rg "videoSourcePath" src/components/video-editor/VideoEditor.tsx -B 1 -A 1 | head -40

Repository: webadderallorg/Recordly

Length of output: 1310


🏁 Script executed:

# Check if window.electronAPI.getKeystrokeTelemetry is defined/used elsewhere
rg "getKeystrokeTelemetry" . --type=ts --type=tsx -B 2 -A 2

Repository: webadderallorg/Recordly

Length of output: 95


🏁 Script executed:

# Let's look at the actual current code more carefully
sed -n '3340,3355p' src/components/video-editor/VideoEditor.tsx | cat -n

Repository: webadderallorg/Recordly

Length of output: 709


Guard against stale keystroke telemetry responses when videoSourcePath changes.

This async effect can apply out-of-order results (old source overwriting new source), which can show/export wrong keystrokes. Clear events immediately and ignore stale completions via cleanup cancellation.

🔧 Suggested fix
 useEffect(() => {
+  let cancelled = false;
+  extensionHost.setKeystrokeEvents([]);
+
   if (!videoSourcePath) {
-    extensionHost.setKeystrokeEvents([]);
-    return;
+    return () => {
+      cancelled = true;
+    };
   }
-  window.electronAPI
+
+  void window.electronAPI
     .getKeystrokeTelemetry(videoSourcePath)
-    .then((result: { success: boolean; events: Array<{ timeMs: number; key: string; modifiers: string[] }> }) => {
-      extensionHost.setKeystrokeEvents(result.success ? result.events : []);
+    .then((result) => {
+      if (cancelled) return;
+      extensionHost.setKeystrokeEvents(result.success ? result.events : []);
     })
-    .catch(() => extensionHost.setKeystrokeEvents([]));
+    .catch(() => {
+      if (!cancelled) extensionHost.setKeystrokeEvents([]);
+    });
+
+  return () => {
+    cancelled = true;
+  };
 }, [videoSourcePath]);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/video-editor/VideoEditor.tsx` around lines 3341 - 3353, The
useEffect hook fetching keystroke telemetry via
window.electronAPI.getKeystrokeTelemetry() has a race condition where stale
responses from previous videoSourcePath values can overwrite newer results. To
fix this, add a cleanup function that tracks whether the current effect is still
active, immediately call extensionHost.setKeystrokeEvents([]) at the start of
the effect, and modify the promise chain to only apply results if the effect has
not been cancelled (by checking a flag set in the cleanup function before
calling extensionHost.setKeystrokeEvents() in both the .then() and .catch()
handlers).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant