Skip to content

feat: live camera preview overlay (visible before & during recording)#441

Open
antonioromero1220 wants to merge 1 commit intosiddharthvaddem:mainfrom
antonioromero1220:feat/live-camera-preview-overlay
Open

feat: live camera preview overlay (visible before & during recording)#441
antonioromero1220 wants to merge 1 commit intosiddharthvaddem:mainfrom
antonioromero1220:feat/live-camera-preview-overlay

Conversation

@antonioromero1220
Copy link
Copy Markdown

@antonioromero1220 antonioromero1220 commented Apr 13, 2026

Summary

  • Problem: The webcam feed was recorded and appeared in the final export, but there was no live visual feedback while recording — users couldn't see their face overlay until after the session ended.
  • Adds a floating, always-on-top circular window that shows the live camera feed as soon as the webcam is enabled, both before and during recording.
  • The preview window is draggable, mirrors the feed naturally, and includes a hover-activated collapse button (eye-off icon) so users can tuck it away without disabling the webcam.
Screenshot 2026-04-13 at 12 45 16 PM

Changes

File Change
electron/windows.ts createCameraPreviewWindow() + closeCameraPreviewWindow() — transparent, frameless, 220×220 always-on-top window
electron/main.ts show-camera-preview / hide-camera-preview IPC handlers
electron/preload.ts Exposes both IPC calls to the renderer
src/vite-env.d.ts TypeScript declarations for the two new IPC methods
src/App.tsx Registers camera-preview window type with transparent background
src/components/launch/CameraPreviewWindow.tsx New — live getUserMedia feed in a mirrored draggable circle with hover hide/show toggle
src/hooks/useScreenRecorder.ts Removed misplaced show/hide calls (preview lifecycle now driven by webcam toggle, not recording state)
src/components/launch/LaunchWindow.tsx useEffect triggers showCameraPreview when webcam enabled, hideCameraPreview when disabled

Behavior

  • Toggle webcam ON in the HUD → circular face preview appears immediately (bottom-right, above HUD)
  • Hover the circle → eye-off button appears; click to collapse to a small Show pill
  • Click the pill → expands back (webcam stays on, no recording data lost)
  • Start/stop recording → preview stays visible as long as webcam is enabled
  • Toggle webcam OFF → preview closes

Test plan

  • Enable webcam in HUD — preview circle appears before hitting record
  • Preview shows correct camera device when multiple cameras are connected
  • Hover reveals the hide button; clicking collapses to pill
  • Clicking pill re-expands the preview
  • Start recording — preview remains visible
  • Stop recording — preview remains visible (webcam still on)
  • Disable webcam — preview closes
  • Final exported video still has the webcam overlay composited correctly

🤖 Generated with Claude Code

Summary by CodeRabbit

New Features

  • Added a floating, draggable camera preview window that displays during recording
  • Camera preview can be toggled between expanded and collapsed states via intuitive controls
  • Preview window automatically shows when webcam input is enabled and hides when disabled
  • Circular preview with mirrored video feed for natural self-viewing experience

Shows a floating, always-on-top circular webcam preview window as soon
as the camera is enabled — visible before and during recording so the
user can see their face overlay in real time, matching the final export.

- Add `createCameraPreviewWindow` / `closeCameraPreviewWindow` in
  `electron/windows.ts` — transparent, frameless, always-on-top window
- Wire `show-camera-preview` / `hide-camera-preview` IPC in `main.ts`
- Expose both IPC calls via `preload.ts` and `vite-env.d.ts`
- Register `camera-preview` window type in `App.tsx`
- New `CameraPreviewWindow` component: live `getUserMedia` feed rendered
  in a mirrored circle; hover reveals an eye-off button to collapse it
  to a small pill badge without disabling the webcam
- Trigger `showCameraPreview` from `LaunchWindow` when webcam is toggled
  on (not tied to recording state), and `hideCameraPreview` when off

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 13, 2026

📝 Walkthrough

Walkthrough

Introduced IPC-driven camera preview window functionality across the electron and React layers. Added window creation/management utilities, exposed API methods through preload, implemented a frameless draggable preview component with media stream handling, and hooked up the preview toggle in LaunchWindow based on webcam enabled state.

Changes

Cohort / File(s) Summary
IPC & Type Layer
electron/main.ts, electron/preload.ts, src/vite-env.d.ts
Added IPC handlers and preload API methods (showCameraPreview, hideCameraPreview) plus corresponding TypeScript type definitions for window.electronAPI.
Window Management
electron/windows.ts
Implemented camera preview window creation with frameless, transparent, always-on-top rendering; includes singleton behavior and macOS fullscreen/workspace support.
React Components
src/components/launch/CameraPreviewWindow.tsx, src/App.tsx, src/components/launch/LaunchWindow.tsx
New CameraPreviewWindow component with getUserMedia stream handling and draggable UI; App.tsx routes to it; LaunchWindow triggers preview show/hide via effect hook on webcam state changes.
Cleanup
src/hooks/useScreenRecorder.ts
Added blank line after recording state call (formatting).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • siddharthvaddem

Poem

📸 camera floats atop your screen,
dragging smooth like it's a dream,
IPC wires hum & sing,
electron calls, react will bring
the preview dancing, pristine, serene

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 42.86% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change—a live camera preview overlay that appears before and during recording. It's concise and clearly communicates the feature being added.
Description check ✅ Passed PR description covers the problem, motivation, changes, behavior, and testing steps comprehensively with screenshots included.

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


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
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: 5

🧹 Nitpick comments (1)
src/components/launch/CameraPreviewWindow.tsx (1)

57-58: New UI strings are hardcoded (not localized).

"Show", "Show camera preview", and "Hide camera preview" should go through i18n like the rest of launch UI. nit: cleaner for multilingual builds.

Also applies to: 80-80, 135-136

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/launch/CameraPreviewWindow.tsx` around lines 57 - 58, The UI
strings in CameraPreviewWindow are hardcoded; update the component to use the
app i18n API (e.g., useTranslation or t) instead of literal "Show", "Show camera
preview", and "Hide camera preview" so they are localized. Import and call the
translation helper at the top of CameraPreviewWindow, add translation keys
(e.g., launch.cameraPreview.show, launch.cameraPreview.showTitle,
launch.cameraPreview.hideTitle) to replace the button label and title props
wherever "Show"/"Show camera preview"/"Hide camera preview" are used, and ensure
the translated values are passed to the title and visible text in the component.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@electron/main.ts`:
- Around line 371-377: The IPC handlers for "show-camera-preview" and
"hide-camera-preview" must validate the sender before toggling the camera
preview: in the ipcMain.on callbacks for "show-camera-preview" and
"hide-camera-preview" check event.senderFrame?.url (the same HUD overlay check
used elsewhere) and only call createCameraPreviewWindow(deviceId ?? "") or
closeCameraPreviewWindow() when the URL matches the HUD overlay window;
otherwise return early and do not perform any action. Ensure you reference the
existing handlers for "show-camera-preview"/"hide-camera-preview" and reuse the
same URL matching logic used for other HUD-gated IPCs to keep consistent
validation.

In `@electron/windows.ts`:
- Around line 100-101: The current y computation (using workArea, windowSize,
margin and a hardcoded -170) can push the preview off-screen on short displays;
change the logic around the y variable so you compute the desired Y (workArea.y
+ workArea.height - windowSize - margin - 170) and then clamp it within safe
bounds (e.g., at least workArea.y + margin and at most workArea.y +
workArea.height - windowSize - margin) so the preview never extends above or
below the visible workArea; update the y assignment in electron/windows.ts (the
y variable that uses workArea, windowSize, margin and the -170 offset) to use
this clamped value.

In `@src/components/launch/CameraPreviewWindow.tsx`:
- Around line 44-52: Collapsed outer container in CameraPreviewWindow lacks the
CSS WebkitAppRegion: "drag", so the window becomes non-draggable when collapsed;
update the collapsed outer div (the top-level <div> rendered in collapsed mode
inside the CameraPreviewWindow component) to include WebkitAppRegion: "drag" in
its style object, keeping the existing button style that uses WebkitAppRegion:
"no-drag" intact so the button remains clickable and behavior matches the
expanded state.
- Around line 27-39: The getUserMedia promise can resolve after the component's
cleanup and create tracks that are never stopped; add a disposed guard: declare
a local let disposed = false in the effect, set disposed = true inside the
cleanup, and in the getUserMedia .then handler check if disposed before
assigning stream or videoRef.current.srcObject — if disposed is true immediately
stop any obtained tracks instead of attaching them; keep the existing cleanup
that stops tracks from the outer-scoped stream variable to cover the normal
path.

In `@src/components/launch/LaunchWindow.tsx`:
- Around line 157-166: The effect that calls
window.electronAPI.showCameraPreview/hideCameraPreview only lists webcamEnabled
in its dependency array, so changes to webcamDeviceId or selectedCameraId won't
trigger an update; update the useEffect dependencies to include webcamDeviceId
and selectedCameraId (or the single chosen deviceId variable) so that when
webcamEnabled is true and the selected device changes the effect re-runs and
calls showCameraPreview with the new deviceId; keep the early return on missing
window.electronAPI and preserve calling hideCameraPreview when webcamEnabled is
false.

---

Nitpick comments:
In `@src/components/launch/CameraPreviewWindow.tsx`:
- Around line 57-58: The UI strings in CameraPreviewWindow are hardcoded; update
the component to use the app i18n API (e.g., useTranslation or t) instead of
literal "Show", "Show camera preview", and "Hide camera preview" so they are
localized. Import and call the translation helper at the top of
CameraPreviewWindow, add translation keys (e.g., launch.cameraPreview.show,
launch.cameraPreview.showTitle, launch.cameraPreview.hideTitle) to replace the
button label and title props wherever "Show"/"Show camera preview"/"Hide camera
preview" are used, and ensure the translated values are passed to the title and
visible text in the component.
🪄 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: 2d99762b-428f-4f2b-8357-f54744f7c169

📥 Commits

Reviewing files that changed from the base of the PR and between a6ae0e6 and 3ef1e42.

📒 Files selected for processing (8)
  • electron/main.ts
  • electron/preload.ts
  • electron/windows.ts
  • src/App.tsx
  • src/components/launch/CameraPreviewWindow.tsx
  • src/components/launch/LaunchWindow.tsx
  • src/hooks/useScreenRecorder.ts
  • src/vite-env.d.ts

Comment thread electron/main.ts
Comment on lines +371 to +377
// Camera preview window — shown during recording so user can see their face
ipcMain.on("show-camera-preview", (_, deviceId: string) => {
createCameraPreviewWindow(deviceId ?? "");
});
ipcMain.on("hide-camera-preview", () => {
closeCameraPreviewWindow();
});
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 and read the electron/main.ts file around the specified lines
cd /
find . -name "electron" -type d | head -5

Repository: siddharthvaddem/openscreen

Length of output: 160


🏁 Script executed:

# List files in electron directory
fd . electron --type f --max-depth 1

Repository: siddharthvaddem/openscreen

Length of output: 170


🏁 Script executed:

# Read the main.ts file around lines 371-377
cat -n electron/main.ts | sed -n '360,390p'

Repository: siddharthvaddem/openscreen

Length of output: 1135


🏁 Script executed:

# Check the full context of IPC handler registration
rg "show-camera-preview|hide-camera-preview" -B 5 -A 10

Repository: siddharthvaddem/openscreen

Length of output: 1883


🏁 Script executed:

# Search for createCameraPreviewWindow and closeCameraPreviewWindow implementation
rg "createCameraPreviewWindow|closeCameraPreviewWindow" -B 3 -A 8

Repository: siddharthvaddem/openscreen

Length of output: 2689


🏁 Script executed:

# Check the createCameraPreviewWindow implementation details
cat -n electron/windows.ts | sed -n '1,150p'

Repository: siddharthvaddem/openscreen

Length of output: 5193


🏁 Script executed:

# Look at createHudOverlayWindow and related window creation
rg "createHudOverlayWindow|createKalturaBrowseWindow" -B 2 -A 20

Repository: siddharthvaddem/openscreen

Length of output: 3287


🏁 Script executed:

# Check preload configuration for each window type
rg "preload:" electron/windows.ts -B 5 -A 2

Repository: siddharthvaddem/openscreen

Length of output: 920


🏁 Script executed:

# Check if there are any other references to showCameraPreview or hideCameraPreview
rg "showCameraPreview|hideCameraPreview" -B 3 -A 3

Repository: siddharthvaddem/openscreen

Length of output: 1851


🏁 Script executed:

# Check which windows load preload and what their purposes are
rg "loadURL|loadFile" electron/windows.ts | head -20

Repository: siddharthvaddem/openscreen

Length of output: 583


🏁 Script executed:

# Verify preload.mjs exports and what electronAPI contains
cat -n electron/preload.ts | head -100

Repository: siddharthvaddem/openscreen

Length of output: 4381


🏁 Script executed:

# Check for createKalturaBrowseWindow to verify if it has preload attached
rg "createKalturaBrowseWindow" -B 5 -A 30

Repository: siddharthvaddem/openscreen

Length of output: 52


🏁 Script executed:

# Also check the KalturaBrowseDialog mentioned in learnings
rg "KalturaBrowseDialog" --type tsx -B 3 -A 15

Repository: siddharthvaddem/openscreen

Length of output: 97


🏁 Script executed:

# Search for Kaltura-related files
fd Kaltura . --type f

Repository: siddharthvaddem/openscreen

Length of output: 52


🏁 Script executed:

# Try searching for KalturaBrowseDialog without type specification
rg "KalturaBrowseDialog" -B 3 -A 15

Repository: siddharthvaddem/openscreen

Length of output: 52


🏁 Script executed:

# Check if there are any window creation functions that load remote URLs
rg "loadURL.*https" electron/

Repository: siddharthvaddem/openscreen

Length of output: 52


🏁 Script executed:

# List all TypeScript files to understand the structure
fd . src --type f --extension ts --extension tsx | head -20

Repository: siddharthvaddem/openscreen

Length of output: 700


🏁 Script executed:

# Check what windows can call these IPC handlers - verify all windows have preload
rg "windowType=" electron/windows.ts

Repository: siddharthvaddem/openscreen

Length of output: 355


🏁 Script executed:

# Check how LaunchWindow is loaded and in which window
rg "LaunchWindow" src/ -B 3 -A 3

Repository: siddharthvaddem/openscreen

Length of output: 1892


🏁 Script executed:

# Look at all IPC listeners registered and check which ones lack sender validation
rg "ipcMain.on\(|ipcMain.handle\(" electron/main.ts -A 2 | head -50

Repository: siddharthvaddem/openscreen

Length of output: 542


IPC handlers should validate sender before toggling camera preview window.

These listeners currently accept calls from any renderer with access to window.electronAPI. Since all windows share the same preload bridge, an attacker who compromises any window could spam open/close on the camera preview—kinda cursed to have that persist across the app without validation.

The hardening is straightforward: check event.senderFrame?.url to gate these to the HUD overlay window only, same pattern already established elsewhere.

Hardening patch (recommended)
 import {
 	app,
 	BrowserWindow,
 	dialog,
+	IpcMainEvent,
 	ipcMain,
@@
+function isHudOverlaySender(event: IpcMainEvent): boolean {
+	const url = event.senderFrame?.url ?? "";
+	return url.includes("windowType=hud-overlay");
+}
+
 // Camera preview window — shown during recording so user can see their face
-	ipcMain.on("show-camera-preview", (_, deviceId: string) => {
+	ipcMain.on("show-camera-preview", (event, deviceId: string) => {
+		if (!isHudOverlaySender(event)) return;
 		createCameraPreviewWindow(deviceId ?? "");
 	});
-	ipcMain.on("hide-camera-preview", () => {
+	ipcMain.on("hide-camera-preview", (event) => {
+		if (!isHudOverlaySender(event)) return;
 		closeCameraPreviewWindow();
 	});
📝 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
// Camera preview window — shown during recording so user can see their face
ipcMain.on("show-camera-preview", (_, deviceId: string) => {
createCameraPreviewWindow(deviceId ?? "");
});
ipcMain.on("hide-camera-preview", () => {
closeCameraPreviewWindow();
});
function isHudOverlaySender(event: IpcMainEvent): boolean {
const url = event.senderFrame?.url ?? "";
return url.includes("windowType=hud-overlay");
}
// Camera preview window — shown during recording so user can see their face
ipcMain.on("show-camera-preview", (event, deviceId: string) => {
if (!isHudOverlaySender(event)) return;
createCameraPreviewWindow(deviceId ?? "");
});
ipcMain.on("hide-camera-preview", (event) => {
if (!isHudOverlaySender(event)) return;
closeCameraPreviewWindow();
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@electron/main.ts` around lines 371 - 377, The IPC handlers for
"show-camera-preview" and "hide-camera-preview" must validate the sender before
toggling the camera preview: in the ipcMain.on callbacks for
"show-camera-preview" and "hide-camera-preview" check event.senderFrame?.url
(the same HUD overlay check used elsewhere) and only call
createCameraPreviewWindow(deviceId ?? "") or closeCameraPreviewWindow() when the
URL matches the HUD overlay window; otherwise return early and do not perform
any action. Ensure you reference the existing handlers for
"show-camera-preview"/"hide-camera-preview" and reuse the same URL matching
logic used for other HUD-gated IPCs to keep consistent validation.

Comment thread electron/windows.ts
Comment on lines +100 to +101
const x = Math.floor(workArea.x + workArea.width - windowSize - margin);
const y = Math.floor(workArea.y + workArea.height - windowSize - margin - 170); // above HUD
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

Window position math can place preview off-screen on small displays.

Line 101 uses a fixed -170 offset with no clamp, so the preview can become partially inaccessible on low-height work areas.

nit: cleaner bounds-safe positioning
-const x = Math.floor(workArea.x + workArea.width - windowSize - margin);
-const y = Math.floor(workArea.y + workArea.height - windowSize - margin - 170); // above HUD
+const desiredX = Math.floor(workArea.x + workArea.width - windowSize - margin);
+const desiredY = Math.floor(workArea.y + workArea.height - windowSize - margin - 170); // above HUD
+const x = Math.max(workArea.x, Math.min(desiredX, workArea.x + workArea.width - windowSize));
+const y = Math.max(workArea.y, Math.min(desiredY, workArea.y + workArea.height - windowSize));
📝 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
const x = Math.floor(workArea.x + workArea.width - windowSize - margin);
const y = Math.floor(workArea.y + workArea.height - windowSize - margin - 170); // above HUD
const desiredX = Math.floor(workArea.x + workArea.width - windowSize - margin);
const desiredY = Math.floor(workArea.y + workArea.height - windowSize - margin - 170); // above HUD
const x = Math.max(workArea.x, Math.min(desiredX, workArea.x + workArea.width - windowSize));
const y = Math.max(workArea.y, Math.min(desiredY, workArea.y + workArea.height - windowSize));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@electron/windows.ts` around lines 100 - 101, The current y computation (using
workArea, windowSize, margin and a hardcoded -170) can push the preview
off-screen on short displays; change the logic around the y variable so you
compute the desired Y (workArea.y + workArea.height - windowSize - margin - 170)
and then clamp it within safe bounds (e.g., at least workArea.y + margin and at
most workArea.y + workArea.height - windowSize - margin) so the preview never
extends above or below the visible workArea; update the y assignment in
electron/windows.ts (the y variable that uses workArea, windowSize, margin and
the -170 offset) to use this clamped value.

Comment on lines +27 to +39
navigator.mediaDevices
.getUserMedia(constraints)
.then((s) => {
stream = s;
if (videoRef.current) {
videoRef.current.srcObject = s;
}
})
.catch(console.error);

return () => {
stream?.getTracks().forEach((t) => t.stop());
};
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:

cat -n src/components/launch/CameraPreviewWindow.tsx | head -60

Repository: siddharthvaddem/openscreen

Length of output: 2097


getUserMedia cleanup has an async unmount race — camera won't release.

If the component unmounts before the promise resolves, the .then() handler runs after cleanup and creates tracks that never get stopped. lowkey cursed for resource lifecycle.

Fix with disposed guard
 	let stream: MediaStream | null = null;
+	let disposed = false;
 
 		navigator.mediaDevices
 			.getUserMedia(constraints)
 			.then((s) => {
+				if (disposed) {
+					s.getTracks().forEach((t) => t.stop());
+					return;
+				}
 				stream = s;
 				if (videoRef.current) {
 					videoRef.current.srcObject = s;
 				}
 			})
 			.catch(console.error);
 
 		return () => {
+			disposed = true;
 			stream?.getTracks().forEach((t) => t.stop());
 		};
📝 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
navigator.mediaDevices
.getUserMedia(constraints)
.then((s) => {
stream = s;
if (videoRef.current) {
videoRef.current.srcObject = s;
}
})
.catch(console.error);
return () => {
stream?.getTracks().forEach((t) => t.stop());
};
let stream: MediaStream | null = null;
let disposed = false;
navigator.mediaDevices
.getUserMedia(constraints)
.then((s) => {
if (disposed) {
s.getTracks().forEach((t) => t.stop());
return;
}
stream = s;
if (videoRef.current) {
videoRef.current.srcObject = s;
}
})
.catch(console.error);
return () => {
disposed = true;
stream?.getTracks().forEach((t) => t.stop());
};
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/launch/CameraPreviewWindow.tsx` around lines 27 - 39, The
getUserMedia promise can resolve after the component's cleanup and create tracks
that are never stopped; add a disposed guard: declare a local let disposed =
false in the effect, set disposed = true inside the cleanup, and in the
getUserMedia .then handler check if disposed before assigning stream or
videoRef.current.srcObject — if disposed is true immediately stop any obtained
tracks instead of attaching them; keep the existing cleanup that stops tracks
from the outer-scoped stream variable to cover the normal path.

Comment on lines +44 to +52
<div
style={{
width: "100vw",
height: "100vh",
display: "flex",
alignItems: "center",
justifyContent: "center",
background: "transparent",
}}
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:

fd "CameraPreviewWindow.tsx" --type f

Repository: siddharthvaddem/openscreen

Length of output: 115


🏁 Script executed:

cat -n src/components/launch/CameraPreviewWindow.tsx | head -120

Repository: siddharthvaddem/openscreen

Length of output: 4139


Collapsed state loses drag behavior — add WebkitAppRegion: "drag" to outer container.

Expanded mode sets WebkitAppRegion: "drag" on the outer div (line 96), but collapsed mode's outer container (lines 44-52) is missing this property. The button correctly has WebkitAppRegion: "no-drag" (line 72) to stay clickable, but the surrounding area won't be draggable after collapse. Users won't be able to reposition the window until expanding it again — kinda cursed UX. Add WebkitAppRegion: "drag" to the collapsed outer div to match the expanded state.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/launch/CameraPreviewWindow.tsx` around lines 44 - 52,
Collapsed outer container in CameraPreviewWindow lacks the CSS WebkitAppRegion:
"drag", so the window becomes non-draggable when collapsed; update the collapsed
outer div (the top-level <div> rendered in collapsed mode inside the
CameraPreviewWindow component) to include WebkitAppRegion: "drag" in its style
object, keeping the existing button style that uses WebkitAppRegion: "no-drag"
intact so the button remains clickable and behavior matches the expanded state.

Comment on lines +157 to +166
useEffect(() => {
if (!window.electronAPI) return;
if (webcamEnabled) {
const deviceId = webcamDeviceId || selectedCameraId || "";
window.electronAPI.showCameraPreview(deviceId);
} else {
window.electronAPI.hideCameraPreview();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [webcamEnabled]);
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:

fd "LaunchWindow.tsx" --type f

Repository: siddharthvaddem/openscreen

Length of output: 108


🏁 Script executed:

if [ -f "src/components/launch/LaunchWindow.tsx" ]; then
  wc -l "src/components/launch/LaunchWindow.tsx"
fi

Repository: siddharthvaddem/openscreen

Length of output: 112


🏁 Script executed:

if [ -f "src/components/launch/LaunchWindow.tsx" ]; then
  sed -n '150,175p' "src/components/launch/LaunchWindow.tsx"
fi

Repository: siddharthvaddem/openscreen

Length of output: 885


Camera preview stays stale when you switch devices while recording (missing deps at line 165).

Rn the eslint-disable-next-line silences a legit warning — you're using webcamDeviceId and selectedCameraId but only have [webcamEnabled] in the dependency array. so if someone picks a different camera while the preview is live, they won't see it update until they toggle the webcam off/on. lowkey breaks the device-selection flow.

Proposed fix
-import { useEffect, useState } from "react";
+import { useEffect, useRef, useState } from "react";
@@
+const lastPreviewDeviceIdRef = useRef<string | null>(null);
+
 useEffect(() => {
 	if (!window.electronAPI) return;
-	if (webcamEnabled) {
-		const deviceId = webcamDeviceId || selectedCameraId || "";
-		window.electronAPI.showCameraPreview(deviceId);
-	} else {
+	if (!webcamEnabled) {
 		window.electronAPI.hideCameraPreview();
+		lastPreviewDeviceIdRef.current = null;
+		return;
 	}
-	// eslint-disable-next-line react-hooks/exhaustive-deps
-}, [webcamEnabled]);
+	const deviceId = webcamDeviceId || selectedCameraId || "";
+	if (lastPreviewDeviceIdRef.current === deviceId) return;
+	window.electronAPI.showCameraPreview(deviceId);
+	lastPreviewDeviceIdRef.current = deviceId;
+}, [webcamEnabled, webcamDeviceId, selectedCameraId]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/launch/LaunchWindow.tsx` around lines 157 - 166, The effect
that calls window.electronAPI.showCameraPreview/hideCameraPreview only lists
webcamEnabled in its dependency array, so changes to webcamDeviceId or
selectedCameraId won't trigger an update; update the useEffect dependencies to
include webcamDeviceId and selectedCameraId (or the single chosen deviceId
variable) so that when webcamEnabled is true and the selected device changes the
effect re-runs and calls showCameraPreview with the new deviceId; keep the early
return on missing window.electronAPI and preserve calling hideCameraPreview when
webcamEnabled is false.

@siddharthvaddem
Copy link
Copy Markdown
Owner

@antonioromero1220 whats the behvaiour if i enable webcam and am recoridng the whole screen?

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.

2 participants