From 113c3c7b15c9b7fb1a683ac4595c968166baec84 Mon Sep 17 00:00:00 2001 From: InverseZeroSecurity Date: Fri, 25 Sep 2026 15:40:40 +0530 Subject: [PATCH] fix(recording): keep cursor telemetry for the screen recording when a webcam companion is stored The webcam companion is stored before the screen recording, and both went through finalizeStoredVideo(), which persists and then clears the pending cursor telemetry. The webcam file got the telemetry and the screen recording got none, so auto-zoom and cursor effects were empty in the editor. Webcam companions are now only written and validated. Fixes #1038 Co-Authored-By: Claude Opus 5.5 --- electron/ipc/recording/storagePath.test.ts | 22 +++++++++++++++++++++- electron/ipc/recording/storagePath.ts | 5 +++++ electron/ipc/register/recording.ts | 12 +++++++++++- 3 files changed, 37 insertions(+), 2 deletions(-) diff --git a/electron/ipc/recording/storagePath.test.ts b/electron/ipc/recording/storagePath.test.ts index dced4f52e..cab90c909 100644 --- a/electron/ipc/recording/storagePath.test.ts +++ b/electron/ipc/recording/storagePath.test.ts @@ -1,6 +1,6 @@ import path from "node:path"; import { describe, expect, it } from "vitest"; -import { resolveRecordedVideoStoragePath } from "./storagePath"; +import { isWebcamCompanionRecordingPath, resolveRecordedVideoStoragePath } from "./storagePath"; describe("resolveRecordedVideoStoragePath", () => { const recordingsDir = path.resolve("recordings-root"); @@ -51,3 +51,23 @@ describe("resolveRecordedVideoStoragePath", () => { ); }); }); + +describe("isWebcamCompanionRecordingPath", () => { + const recordingsDir = path.resolve("recordings-root"); + + it.each([ + "recording-1720588800000-webcam.webm", + "recording-1720588800000-webcam.mp4", + ])("detects a webcam companion recording: %s", (fileName) => { + expect(isWebcamCompanionRecordingPath(path.join(recordingsDir, fileName))).toBe(true); + }); + + it.each([ + "recording-1720588800000.webm", + "recording-1720588800000.mp4", + "recording-webcam.webm", + "recording-1720588800000-webcam.mov", + ])("does not treat other files as webcam companions: %s", (fileName) => { + expect(isWebcamCompanionRecordingPath(path.join(recordingsDir, fileName))).toBe(false); + }); +}); diff --git a/electron/ipc/recording/storagePath.ts b/electron/ipc/recording/storagePath.ts index 12c3484f6..426c78ef0 100644 --- a/electron/ipc/recording/storagePath.ts +++ b/electron/ipc/recording/storagePath.ts @@ -1,6 +1,7 @@ import path from "node:path"; const RECORDED_VIDEO_FILE_NAME = /^recording-[0-9]+(?:-webcam)?\.(?:webm|mp4)$/; +const WEBCAM_COMPANION_FILE_NAME = /^recording-[0-9]+-webcam\.(?:webm|mp4)$/; export function resolveRecordedVideoStoragePath(recordingsDir: string, fileName: unknown): string { if (typeof fileName !== "string" || RECORDED_VIDEO_FILE_NAME.exec(fileName)?.[0] !== fileName) { @@ -22,3 +23,7 @@ export function resolveRecordedVideoStoragePath(recordingsDir: string, fileName: return candidatePath; } + +export function isWebcamCompanionRecordingPath(filePath: string): boolean { + return WEBCAM_COMPANION_FILE_NAME.test(path.basename(filePath)); +} diff --git a/electron/ipc/register/recording.ts b/electron/ipc/register/recording.ts index 797f8badd..9ae15ae79 100644 --- a/electron/ipc/register/recording.ts +++ b/electron/ipc/register/recording.ts @@ -71,7 +71,10 @@ import { waitForNativeCaptureStart, waitForNativeCaptureStop, } from "../recording/mac"; -import { resolveRecordedVideoStoragePath } from "../recording/storagePath"; +import { + isWebcamCompanionRecordingPath, + resolveRecordedVideoStoragePath, +} from "../recording/storagePath"; import { attachWindowsCaptureLifecycle, isNativeWindowsCaptureAvailable, @@ -1801,6 +1804,13 @@ export function registerRecordingHandlers( const recordingsDir = await getRecordingsDir(); const videoPath = resolveRecordedVideoStoragePath(recordingsDir, fileName); await fs.writeFile(videoPath, Buffer.from(videoData)); + if (isWebcamCompanionRecordingPath(videoPath)) { + // The webcam companion is stored before the screen recording. Finalizing it + // would persist (and clear) the session's cursor telemetry next to the webcam + // file, leaving the screen recording without cursor data. + await validateRecordedVideo(videoPath); + return { success: true, path: videoPath }; + } return await finalizeStoredVideo(videoPath); } catch (error) { console.error("Failed to store video:", error);