Skip to content

feat: auto-zoom on clicks + exclude OpenScreen windows from display capture#640

Open
realkim93 wants to merge 2 commits into
siddharthvaddem:mainfrom
realkim93:feat/auto-zoom-on-click-and-exclude-self
Open

feat: auto-zoom on clicks + exclude OpenScreen windows from display capture#640
realkim93 wants to merge 2 commits into
siddharthvaddem:mainfrom
realkim93:feat/auto-zoom-on-click-and-exclude-self

Conversation

@realkim93
Copy link
Copy Markdown

@realkim93 realkim93 commented May 22, 2026

Summary

Two small quality-of-life improvements that pair well together for the typical "record fullscreen, then add zooms" workflow.

1. Click-based auto-zoom suggestions

The recording layer already captures click events on CursorRecordingSample.interactionType, but two things were preventing them from reaching the suggestion algorithm:

  • readCursorTelemetryFile was stripping interactionType when loading telemetry off disk.
  • detectZoomDwellCandidates only considers cursor dwell (pause), not clicks.

This PR:

  • Passes interactionType through readCursorTelemetryFile (electron/ipc/handlers.ts).
  • Adds detectZoomClickCandidates in zoomSuggestionUtils.ts with 700ms clustering (so a double/triple click becomes a single candidate).
  • Adds detectZoomCandidates that merges click + dwell candidates, click-sourced ones ranked with higher strength so they win the dedup race.
  • TimelineEditor switches the "Suggest Zooms from Cursor" handler to the combined detector.

End result: clicking the button on a recording with clear UI clicks now places zooms where the user actually interacted, not just where they happened to pause.

2. Exclude OpenScreen's own windows from full-screen capture (macOS / SCK)

SCContentFilter(display:, excludingWindows: []) was leaving the OpenScreen HUD visible in the recorded output. Added an optional excludedApps: [{ bundleIdentifier?, processID? }] field to the helper request and have the renderer pass process.pid for display recordings. The SCK helper then filters content.windows by owningApplication.processID / bundleIdentifier and feeds the result to SCContentFilter.

Window-target capture is unchanged.

Test plan

  • npm test — full suite (156 tests) passes, including 5 new tests in zoomSuggestionUtils.test.ts.
  • tsc --noEmit clean.
  • biome check clean.
  • npm run build:native:mac + npm run dev: clicked around during a fullscreen recording, then pressed "Suggest Zooms from Cursor" — zooms land on each click cluster. The HUD no longer appears in the recorded output.

Notes:

  • The Windows native recording path is not touched in this PR; the click-detection logic is platform-agnostic and Windows already populates interactionType in its telemetry, so it should benefit once the same readCursorTelemetryFile pass-through reaches its loader (it already does — single function for both platforms).
  • No changes to public APIs of the recording helper protocol other than an additive optional field (excludedApps).

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Cursor telemetry now records additional interaction types (double-, right-, middle-click).
    • macOS screen capture can exclude specified apps/processes from display recordings.
    • Video timeline zoom suggestions now include click-based candidates (clusters clicks, orders click before dwell).
  • Tests

    • Added unit tests covering click- and dwell-based zoom candidate behavior.

Review Change Stack

…play capture

Cursor telemetry already records click events (interactionType), but the
suggestion algorithm only looks at cursor dwell. This wires clicks through to
the suggestion pipeline so the "Suggest Zooms from Cursor" button can produce
zooms where the user actually clicked, not just where the cursor paused.

- Pass interactionType through readCursorTelemetryFile (was stripped on load).
- Add detectZoomClickCandidates with 700ms clustering for double/triple clicks.
- detectZoomCandidates combines click + dwell with clicks ranked stronger.
- TimelineEditor switches to the combined detector.

Separately, full-screen recordings include the OpenScreen HUD because the
helper passes excludingWindows: []. Allow the renderer to pass its own pid
through the request and have the SCK helper exclude any windows owned by that
process / bundle identifier from the SCContentFilter.

Tests: add zoomSuggestionUtils.test.ts (5 cases).
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 22, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 2766edad-49d3-4839-b09a-fc0e6f031bc8

📥 Commits

Reviewing files that changed from the base of the PR and between b4ce8a1 and 4e4a8ab.

📒 Files selected for processing (4)
  • electron/ipc/handlers.ts
  • src/components/video-editor/timeline/zoomSuggestionUtils.test.ts
  • src/components/video-editor/timeline/zoomSuggestionUtils.ts
  • src/native/contracts.ts

📝 Walkthrough

Walkthrough

Adds interactionType to cursor telemetry, implements click-clustering zoom candidates (merged with dwell candidates), updates the timeline to use merged candidates, and adds macOS screen-capture exclusions by bundle ID or process ID (electron passes current PID when capturing a display).

Changes

Cursor telemetry enrichment, click detection, and macOS app exclusion

Layer / File(s) Summary
Cursor telemetry type enrichment
src/native/contracts.ts, electron/ipc/handlers.ts
CursorTelemetryPoint now has optional interactionType including move, click, double-click, right-click, middle-click, mouseup; normalization and file-reading preserve/propagate this field.
Click-based zoom candidate detection
src/components/video-editor/timeline/zoomSuggestionUtils.ts, src/components/video-editor/timeline/zoomSuggestionUtils.test.ts
Added click-detection constants and detectZoomClickCandidates which clusters click-like events into candidates (source: "click"). detectZoomDwellCandidates outputs source: "dwell". detectZoomCandidates returns clicks then dwells; tests cover clustering, types, and ordering.
Timeline editor zoom logic
src/components/video-editor/timeline/TimelineEditor.tsx
TimelineEditor now imports and calls detectZoomCandidates (replacing detectZoomDwellCandidates) to receive merged click+dwell candidates.
macOS app/process exclusion
src/lib/nativeMacRecording.ts, electron/ipc/handlers.ts, electron/native/screencapturekit/Sources/OpenScreenScreenCaptureKitHelper/main.swift
NativeMacRecordingRequest gains excludedApps?: { bundleIdentifier?: string; processID?: number }[]; electron handler sets exclusion of current PID for display captures; native helper matches windows to excluded bundle IDs / PIDs and passes excluded windows into SCContentFilter.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested reviewers

  • FabLrc
  • siddharthvaddem

Poem

clicks cluster like party guests at 2am
dwell waits, patient, for the camera's eye
macOS quietly steps aside (pid in hand)
timeline stitches click and linger into focus
lowkey elegant, kinda cursed, and functional ✨

🚥 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 Title clearly summarizes both main changes: auto-zoom on clicks and excluding OpenScreen windows from display capture.
Description check ✅ Passed Description covers all template sections with clear motivation, implementation details, test plan, and technical notes.
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.

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint skipped: no ESLint configuration detected in root package.json. To enable, add eslint to devDependencies.


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.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

return {
timeMs: Math.max(0, Math.min(sample.timeMs, totalMs)),
cx: Math.max(0, Math.min(sample.cx, 1)),
cy: Math.max(0, Math.min(sample.cy, 1)),
};

P1 Badge Preserve interactionType during telemetry normalization

detectZoomCandidates now relies on click metadata, but normalizeCursorTelemetry strips interactionType in normalizeTelemetrySample, so every sample passed from TimelineEditor loses click/double-click/right-click markers before detection. In recordings where zoom suggestions should come from clicks, this makes the new click-based path return no click candidates and effectively disables the feature in normal UI flow.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
electron/ipc/handlers.ts (1)

510-520: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

interactionType is still getting flattened before this mapping

nice pass-through here, but lowkey risky: normalizeCursorSample currently downgrades anything outside "click" | "mouseup" | "move" to "move", so "double-click", "right-click", and "middle-click" never survive to the renderer.

Suggested fix
diff --git a/src/native/contracts.ts b/src/native/contracts.ts
@@
 export interface CursorRecordingSample extends CursorTelemetryPoint {
 	assetId?: string | null;
 	visible?: boolean;
 	cursorType?: NativeCursorType | null;
-	interactionType?: "move" | "click" | "mouseup";
+	interactionType?: "move" | "click" | "double-click" | "right-click" | "middle-click" | "mouseup";
 }

diff --git a/electron/ipc/handlers.ts b/electron/ipc/handlers.ts
@@
 function normalizeCursorSample(sample: unknown): CursorRecordingSample | null {
@@
-	const interactionType =
-		point.interactionType === "click" ||
-		point.interactionType === "mouseup" ||
-		point.interactionType === "move"
-			? point.interactionType
-			: "move";
+	const interactionType =
+		point.interactionType === "click" ||
+		point.interactionType === "double-click" ||
+		point.interactionType === "right-click" ||
+		point.interactionType === "middle-click" ||
+		point.interactionType === "mouseup" ||
+		point.interactionType === "move"
+			? point.interactionType
+			: "move";
🤖 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/handlers.ts` around lines 510 - 520, The mapping in
readCursorTelemetryFile is losing non-standard interaction types because
normalizeCursorSample coerces anything outside "click" | "mouseup" | "move" to
"move"; update the code so original interaction types are preserved: either stop
coercing unknown values in normalizeCursorSample or retain the original value
(e.g., rawInteractionType) on the sample and return that in
readCursorTelemetryFile (in function readCursorTelemetryFile use
sample.rawInteractionType or the un-normalized field instead of the coerced
sample.interactionType) so "double-click", "right-click", etc. are passed
through to the renderer.
🧹 Nitpick comments (1)
src/components/video-editor/timeline/zoomSuggestionUtils.test.ts (1)

46-61: ⚡ Quick win

Add one integration-ish test for normalized telemetry path

nit: cleaner coverage would run normalizeCursorTelemetry before detectZoomCandidates, so we catch metadata drop regressions early.

Test addition
-import { detectZoomCandidates, detectZoomClickCandidates } from "./zoomSuggestionUtils";
+import {
+	detectZoomCandidates,
+	detectZoomClickCandidates,
+	normalizeCursorTelemetry,
+} from "./zoomSuggestionUtils";
@@
 describe("detectZoomCandidates", () => {
+	it("keeps click interactions after normalization", () => {
+		const raw: CursorTelemetryPoint[] = [
+			{ timeMs: 100, cx: 0.4, cy: 0.4, interactionType: "click" },
+			{ timeMs: 700, cx: 0.4, cy: 0.4, interactionType: "move" },
+		];
+		const normalized = normalizeCursorTelemetry(raw, 2_000);
+		const candidates = detectZoomCandidates(normalized);
+		expect(candidates.some((c) => c.source === "click")).toBe(true);
+	});
+
 	it("returns click candidates ahead of dwell candidates", () => {
🤖 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/timeline/zoomSuggestionUtils.test.ts` around
lines 46 - 61, The test should feed detectZoomCandidates with telemetry that's
been through normalizeCursorTelemetry to catch metadata-drop regressions: modify
the test to call normalizeCursorTelemetry(samples) (using the same
CursorTelemetryPoint[] format) and pass its output into detectZoomCandidates,
keeping the existing assertions (clickIndex/dwellIndex ordering); ensure any
required metadata produced by normalizeCursorTelemetry is preserved in the
normalized output before calling detectZoomCandidates.
🤖 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 `@src/components/video-editor/timeline/zoomSuggestionUtils.ts`:
- Around line 90-97: detectZoomClickCandidates is filtering by
sample.interactionType but normalizeCursorTelemetry removes that field, so when
TimelineEditor passes normalized CursorTelemetryPoint samples clickSamples is
always empty; fix by preserving the interaction type through normalization or by
changing detectZoomClickCandidates to read the normalized property (e.g.
sample.interaction / sample.eventType / sample.rawInteraction) that
normalizeCursorTelemetry emits. Locate normalizeCursorTelemetry and either add
back interactionType (copy normalized value into interactionType) or update
detectZoomClickCandidates to use CLICK_INTERACTIONS against the normalized
field, and ensure TimelineEditor still passes the normalized samples and
existing tests are updated accordingly.

---

Outside diff comments:
In `@electron/ipc/handlers.ts`:
- Around line 510-520: The mapping in readCursorTelemetryFile is losing
non-standard interaction types because normalizeCursorSample coerces anything
outside "click" | "mouseup" | "move" to "move"; update the code so original
interaction types are preserved: either stop coercing unknown values in
normalizeCursorSample or retain the original value (e.g., rawInteractionType) on
the sample and return that in readCursorTelemetryFile (in function
readCursorTelemetryFile use sample.rawInteractionType or the un-normalized field
instead of the coerced sample.interactionType) so "double-click", "right-click",
etc. are passed through to the renderer.

---

Nitpick comments:
In `@src/components/video-editor/timeline/zoomSuggestionUtils.test.ts`:
- Around line 46-61: The test should feed detectZoomCandidates with telemetry
that's been through normalizeCursorTelemetry to catch metadata-drop regressions:
modify the test to call normalizeCursorTelemetry(samples) (using the same
CursorTelemetryPoint[] format) and pass its output into detectZoomCandidates,
keeping the existing assertions (clickIndex/dwellIndex ordering); ensure any
required metadata produced by normalizeCursorTelemetry is preserved in the
normalized output before calling detectZoomCandidates.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 214a2c36-8524-4391-891b-c6f3cf8ebf8b

📥 Commits

Reviewing files that changed from the base of the PR and between 9f7f498 and b4ce8a1.

📒 Files selected for processing (7)
  • electron/ipc/handlers.ts
  • electron/native/screencapturekit/Sources/OpenScreenScreenCaptureKitHelper/main.swift
  • src/components/video-editor/timeline/TimelineEditor.tsx
  • src/components/video-editor/timeline/zoomSuggestionUtils.test.ts
  • src/components/video-editor/timeline/zoomSuggestionUtils.ts
  • src/lib/nativeMacRecording.ts
  • src/native/contracts.ts

Comment thread src/components/video-editor/timeline/zoomSuggestionUtils.ts
Codex flagged that normalizeCursorTelemetry was stripping interactionType in
normalizeTelemetrySample, so the click-based detector never saw any clicks in
the real UI flow (TimelineEditor always normalizes before detecting).

CodeRabbit additionally pointed out that normalizeCursorSample in handlers.ts
was coercing anything outside "click" | "mouseup" | "move" down to "move", so
"double-click", "right-click", and "middle-click" were being lost upstream
before they could ever reach the renderer.

- Pass interactionType through normalizeTelemetrySample.
- Widen normalizeCursorSample's allow-list to all five click variants.
- Widen CursorRecordingSample.interactionType to the same union.
- Add a normalize -> detect integration test so a future stripping regression
  fails fast.
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.

1 participant