Skip to content

macOS: Cursor telemetry not saved after DMG install (Apple Silicon, macOS 26) #129

@JoshCork

Description

@JoshCork

Bug Description

After installing Recordly v1.1.7 via the DMG on macOS 26.2 (Apple Silicon M3), cursor telemetry .cursor.json files are not being created alongside recordings. The editor shows "No cursor telemetry available — Record a screencast first to generate cursor-based suggestions."

Environment

  • OS: macOS 26.2 (Build 25C56) — Apple Silicon (M3 MacBook Pro)
  • Recordly version: v1.1.7 (DMG install to /Applications/Recordly.app)
  • Permissions granted: Screen Recording ✅, Accessibility ✅
  • Quarantine flags: Cleared via xattr -cr

Reproduction Steps

  1. Install Recordly v1.1.7 via DMG
  2. Grant Screen Recording and Accessibility permissions in System Settings
  3. Record a screencast (any duration, tried multiple 30s+ recordings)
  4. Open recording in editor
  5. Click the zoom suggestions / wand button
  6. See toast: "No cursor telemetry available"

Evidence

Recordings made with npm run dev (dev build) do produce .cursor.json files. Recordings made with the DMG-installed app do not.

# Recordings WITH cursor.json (dev build, same machine, same day):
recording-1774718957877.mp4.cursor.json  (1.5MB, ~10:35am)
recording-1774719579103.mp4.cursor.json  (1.5MB, ~10:45am)
recording-1774743781164.mp4.cursor.json  (77KB,  ~5:23pm)

# Recordings WITHOUT cursor.json (DMG install, 7pm onward):
recording-1774749616308.mp4  ← no .cursor.json
recording-1774750149033.mp4  ← no .cursor.json
recording-1774750394518.mp4  ← no .cursor.json
recording-1774751693887.mp4  ← no .cursor.json

The cursor.json files that exist from the dev build look correct:

{
  "version": 2,
  "samples": [
    { "timeMs": 1, "cx": 0.462, "cy": 0.940, "interactionType": "move", "cursorType": "arrow" }
  ]
}

Analysis

After reviewing the source code, the cursor telemetry flow is:

  1. Recording start (set-recording-state handler, handlers.ts:3901) → starts 33ms interval sampling via sampleCursorPoint()
  2. Recording stop (handlers.ts:3916) → calls snapshotCursorTelemetryForPersistence() to move activeCursorSamplespendingCursorSamples
  3. Finalization (finalizeStoredVideo(), handlers.ts:2604) → calls persistPendingCursorTelemetry() which writes videoPath + ".cursor.json"
  4. Editor load (VideoEditor.tsx:1740) → reads the .cursor.json file via get-cursor-telemetry IPC handler

Likely root causes (in order of probability):

1. persistPendingCursorTelemetry() fails silently or never runs
At handlers.ts:2575-2585, there is no try-catch around the fs.writeFile() call. If the write fails (permissions, path issue, etc.), the error propagates up through finalizeStoredVideo() which also lacks error handling at line 2610. However, since the recording itself saves successfully, the error may be swallowed somewhere upstream or the function may not be called at all in the DMG build's code path.

2. activeCursorSamples is empty when recording stops
If sampleCursorPoint() never executes during recording (interval not starting, or getNormalizedCursorPoint() returning null), then snapshotCursorTelemetryForPersistence() exits early at line 2588 because activeCursorSamples.length === 0, and no file is written.

3. DMG build uses different app data path
The dev build stores data in ~/Library/Application Support/Recordly-dev/ while the DMG uses ~/Library/Application Support/Recordly/. If there's a path resolution mismatch for the cursor file write (e.g., currentVideoPath doesn't match where the recording was actually saved), the .cursor.json could be written to the wrong location or fail.

4. Native cursor monitor binary fails to spawn in DMG context
The startNativeCursorMonitor() function at handlers.ts:2257 spawns the bundled openscreen-native-cursor-monitor binary. While cursor position sampling uses Electron's getScreen().getCursorScreenPoint() (which should work regardless), the overall cursor capture initialization could be affected if the native monitor spawn throws and isn't caught properly.

5. uiohook native module not rebuilt for DMG Electron version
The interaction capture (startInteractionCapture()) uses uiohook-napi for mouse click events. If this native module wasn't properly rebuilt for the DMG's Electron version, it could cause cursor capture to fail at initialization.

Suggested Fix

Add error handling around persistPendingCursorTelemetry() in finalizeStoredVideo(), and add diagnostic logging to identify where the pipeline breaks:

// handlers.ts:2604
async function finalizeStoredVideo(videoPath: string) {
  const validation = await validateRecordedVideo(videoPath)
  snapshotCursorTelemetryForPersistence()
  currentVideoPath = videoPath
  currentProjectPath = null
  
  try {
    await persistPendingCursorTelemetry(videoPath)
  } catch (error) {
    console.error('Failed to persist cursor telemetry:', error)
    // Don't let telemetry failure break video finalization
  }
  
  // ...
}

And add logging in snapshotCursorTelemetryForPersistence():

function snapshotCursorTelemetryForPersistence() {
  console.log(`[cursor] snapshot: ${activeCursorSamples.length} active samples`)
  if (activeCursorSamples.length === 0) {
    console.warn('[cursor] No active cursor samples to snapshot')
    return
  }
  // ...
}

Workaround

Running via npm run dev from source (~/Source/recordly) produces cursor telemetry correctly on the same machine. DMG install does not.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions