Skip to content

feat(launch): add draggable floating webcam preview overlay#163

Open
longmaba wants to merge 4 commits intowebadderall:mainfrom
longmaba:feat/webcam-preview-floating-overlay
Open

feat(launch): add draggable floating webcam preview overlay#163
longmaba wants to merge 4 commits intowebadderall:mainfrom
longmaba:feat/webcam-preview-floating-overlay

Conversation

@longmaba
Copy link
Copy Markdown

@longmaba longmaba commented Apr 3, 2026

Pull Request Template

Description

Adds a larger floating webcam preview to the launch HUD that appears as soon as webcam is enabled, remains visible during recording, and can be dragged to reposition for live expression monitoring.

Motivation

The previous webcam preview was too small and only visible in limited contexts, making it hard to check framing and expression while recording. This change improves usability by making preview visibility immediate, enlarging the preview, and allowing flexible placement.

Type of Change

  • New Feature
  • Bug Fix
  • Refactor / Code Cleanup
  • Documentation Update
  • Other (please specify)

Related Issue(s)

N/A

Screenshots / Video

Please attach before merge.

Screenshot (if applicable):

image

Testing Guide

  1. Launch Recordly.
  2. Enable webcam from the launch HUD.
  3. Verify webcam preview appears immediately (without starting recording).
  4. Verify preview is visually larger than before.
  5. Drag the preview around and verify it can be repositioned freely without unexpected cutoff.
  6. Start recording and confirm preview remains visible and movable.
  7. Verify rounded-corner masking is applied to the webcam video (no square corner bleed).
  8. Disable and re-enable webcam and confirm preview behavior remains correct.

Checklist

  • I have performed a self-review of my code.
  • I have added any necessary screenshots or videos.
  • I have linked related issue(s) and updated the changelog if applicable.

Summary by CodeRabbit

  • New Features

    • Draggable floating on-screen webcam preview during recording with mirrored live video.
  • Style

    • Updated preview appearance: background, border radius, shadow, and grab/grabbing cursors; video fills container cleanly.
  • Behavior

    • Preview position is draggable and resets when webcam is disabled; preview stream mounts/unmounts and cleans up reliably; HUD layout adapts while preview is active.

Show a live webcam preview as soon as webcam is enabled, keep it visible as a larger floating overlay, and allow drag repositioning across the HUD overlay area for expression monitoring while recording.

Made-with: Cursor
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 3, 2026

Warning

.coderabbit.yaml has a parsing error

The CodeRabbit configuration file in this repository has a parsing error and default settings were used instead. Please fix the error(s) in the configuration file. You can initialize chat with CodeRabbit to get help with the configuration file.

💥 Parsing errors (1)
Validation error: Invalid regex pattern for base branch. Received: "*" at "reviews.auto_review.base_branches[0]"
⚙️ Configuration instructions
  • Please see the configuration documentation for more information.
  • You can also validate your configuration using the online YAML validator.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json
📝 Walkthrough

Walkthrough

Adds a draggable on-screen webcam preview: new CSS classes for a fixed preview container and video; component state, refs, pointer handlers for drag offset; shared MediaStream wiring to attach preview and main video nodes; UI gating and HUD sizing updates; offset reset when webcam is disabled.

Changes

Cohort / File(s) Summary
Draggable Webcam Preview Styling
src/components/launch/LaunchWindow.module.css
Added .recordingWebcamPreview, .recordingWebcamPreview:active, and .recordingWebcamPreviewVideo to style a fixed, elevated, draggable preview container and its video (object-fit: cover, grab/grabbing cursors, rounded corner overflow handling).
Recording Webcam Preview Logic & UI
src/components/launch/LaunchWindow.tsx
Added webcamPreviewOffset state, recordingWebcamPreviewRef, previewStreamRef, drag start tracking and pointer handlers to move the preview; replaced direct ref-based stream attachment with callback ref functions to attach same MediaStream to both preview and main nodes; introduced showRecordingWebcamPreview / shouldStreamWebcamPreview, reset offset when webcam disabled; adjusted HUD overlay expansion/height logic to account for floating preview; changed webcam dropdown preview to use callback ref; added conditional mirrored, muted preview element; refined HUD drag-end handling to consider actual dragging.

Sequence Diagram(s)

sequenceDiagram
  participant User as User (pointer)
  participant Preview as RecordingWebcamPreview
  participant Component as LaunchWindow
  participant Stream as MediaStream

  User->>Preview: pointerdown (start drag)
  Preview->>Component: handleWebcamPreviewPointerDown (store start, set pointer capture)
  loop dragging
    User->>Preview: pointermove
    Preview->>Component: handleWebcamPreviewPointerMove (compute delta)
    Component->>Preview: update transform (apply webcamPreviewOffset)
  end
  User->>Preview: pointerup / pointercancel
  Preview->>Component: handleWebcamPreviewPointerUp (release capture, finalize offset)
  Component->>Stream: attachPreviewStreamToNode() (assign stream to preview & main video nodes)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐇 I hop and nudge a tiny view,

A mirrored pane that follows you,
Drag my ear, then let me roam,
A fluffy frame to call your own,
Sniffing pixels, soft and true 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 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 (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main feature addition: a draggable floating webcam preview overlay. It is specific, directly related to the changeset, and summarizes the primary change effectively.
Description check ✅ Passed The PR description follows the template well with complete sections: Description, Motivation, Type of Change, Related Issues, Screenshots, Testing Guide, and Checklist. All key sections are filled out appropriately with detailed testing steps and a screenshot included.

✏️ 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.

Copy link
Copy Markdown

@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

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

273-285: Consider adding optional boundary awareness to prevent dragging completely off-screen.

The preview can currently be dragged entirely out of the viewport. While unbounded dragging provides maximum flexibility, users may accidentally lose the preview. Consider:

  • Clamping to keep at least a portion visible
  • Adding a "reset position" action
  • Storing position in persisted state for convenience

This is optional since the PR testing guide indicates "repositioned freely" is expected behavior.

🤖 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 273 - 285,
handleWebcamPreviewPointerMove currently updates position without bounds; clamp
the computed new coordinates before calling setWebcamPreviewOffset by computing
viewport and preview dimensions and limiting x/y so at least a portion remains
on-screen, using webcamPreviewDragStartRef to derive originX/originY and the
delta, add an optional reset action (e.g., resetWebcamPreviewPosition) to
restore a default location, and persist offsets (e.g., via localStorage or a
parent state setter) when dragging ends so position survives reloads; update
references to handleWebcamPreviewPointerMove, webcamPreviewDragStartRef, and
setWebcamPreviewOffset accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/components/launch/LaunchWindow.tsx`:
- Around line 354-361: The cleanup reads webcamPreviewRef.current and
recordingWebcamPreviewRef.current directly which can be stale; capture their
current values (e.g., const initialWebcam = webcamPreviewRef.current and const
initialRecordingWebcam = recordingWebcamPreviewRef.current) and also capture
previewStream into a local variable at the start of the effect, then use those
locals inside the cleanup to pause and null srcObject and to stop tracks; update
the cleanup in the effect that defines these refs/previewStream accordingly to
reference the captured locals instead of the ref.current properties.

---

Nitpick comments:
In `@src/components/launch/LaunchWindow.tsx`:
- Around line 273-285: handleWebcamPreviewPointerMove currently updates position
without bounds; clamp the computed new coordinates before calling
setWebcamPreviewOffset by computing viewport and preview dimensions and limiting
x/y so at least a portion remains on-screen, using webcamPreviewDragStartRef to
derive originX/originY and the delta, add an optional reset action (e.g.,
resetWebcamPreviewPosition) to restore a default location, and persist offsets
(e.g., via localStorage or a parent state setter) when dragging ends so position
survives reloads; update references to handleWebcamPreviewPointerMove,
webcamPreviewDragStartRef, and setWebcamPreviewOffset accordingly.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 6acd0aee-b568-4771-8e86-79c1bead0b4e

📥 Commits

Reviewing files that changed from the base of the PR and between 4d29baa and 68aa2a9.

📒 Files selected for processing (2)
  • src/components/launch/LaunchWindow.module.css
  • src/components/launch/LaunchWindow.tsx

@mahdyarief
Copy link
Copy Markdown
Contributor

Woow, that what i will doo, it means it basic functionality.. appreciate it

Copy link
Copy Markdown

@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)
src/components/launch/LaunchWindow.tsx (1)

303-346: ⚠️ Potential issue | 🟡 Minor

Late-mounted preview nodes miss the active stream.

initialWebcam and initialRecordingWebcam are now used for the attach path as well as cleanup. If either <video> mounts after this effect starts—for example, reopening the webcam dropdown while the webcam stays enabled—the new node never gets srcObject because the effect does not rerun on ref changes. Please keep the live MediaStream in a ref and reattach it from a callback ref or a small follow-up effect when either preview element mounts.

🤖 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 303 - 346, The effect
currently captures local variables initialWebcam/initialRecordingWebcam and
assigns srcObject only once, so any <video> that mounts later won't receive the
active MediaStream; fix by storing the live stream in a stable ref (e.g.,
previewStreamRef) inside startPreview (set previewStreamRef.current = stream),
stop using the captured initial* vars for attach/cleanup, and add a small
follow-up: either use callback refs on
webcamPreviewRef/recordingWebcamPreviewRef or a useEffect that watches those
ref.current values to reattach when they mount by setting videoElement.srcObject
= previewStreamRef.current and calling play() with a catch; also update cleanup
to stop tracks from previewStreamRef.current and clear previewStreamRef.current
when unmounting or stopping.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/components/launch/LaunchWindow.tsx`:
- Around line 287-297: The pointer-up handler handleWebcamPreviewPointerUp
currently clears drag state but doesn't restore click-through when the drag ends
outside the HUD, leaving the expanded transparent hudContent intercepting
clicks; update handleWebcamPreviewPointerUp (and the equivalent code around
lines 527-564) to call hudOverlaySetIgnoreMouse(true) when a drag finishes
(i.e., when webcamPreviewDragStartRef.current was set and isDraggingRef.current
is true) and ensure pointer capture is released and isDraggingRef/current are
cleared; also ensure onMouseLeave logic remains consistent by only skipping
hudOverlaySetIgnoreMouse while isDraggingRef is true, but restore ignore-mouse
on drag end so the HUD becomes click-through again.

---

Outside diff comments:
In `@src/components/launch/LaunchWindow.tsx`:
- Around line 303-346: The effect currently captures local variables
initialWebcam/initialRecordingWebcam and assigns srcObject only once, so any
<video> that mounts later won't receive the active MediaStream; fix by storing
the live stream in a stable ref (e.g., previewStreamRef) inside startPreview
(set previewStreamRef.current = stream), stop using the captured initial* vars
for attach/cleanup, and add a small follow-up: either use callback refs on
webcamPreviewRef/recordingWebcamPreviewRef or a useEffect that watches those
ref.current values to reattach when they mount by setting videoElement.srcObject
= previewStreamRef.current and calling play() with a catch; also update cleanup
to stop tracks from previewStreamRef.current and clear previewStreamRef.current
when unmounting or stopping.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 76394bc4-7d2d-4684-818d-c616ccc6de0f

📥 Commits

Reviewing files that changed from the base of the PR and between 68aa2a9 and 5c6f6a2.

📒 Files selected for processing (1)
  • src/components/launch/LaunchWindow.tsx

Store the live webcam preview stream in a stable ref and reattach it through callback refs so late-mounted preview videos receive srcObject immediately while preserving drag-end click-through restoration.

Made-with: Cursor
@longmaba
Copy link
Copy Markdown
Author

longmaba commented Apr 3, 2026

Implemented in 8ffebef.

  • Restored click-through on drag end (both floating preview pointer drag and HUD bar drag paths).
  • Fixed late-mount stream attachment by storing the active stream in previewStreamRef and reattaching via callback refs for both preview video nodes.
  • Cleanup now consistently detaches/pause nodes and stops + clears previewStreamRef.
  • Kept unbounded dragging behavior as-is because that is expected in this PR’s UX/testing scope.

Copy link
Copy Markdown

@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: 2

🧹 Nitpick comments (2)
src/components/launch/LaunchWindow.tsx (2)

222-224: Three identical derived variables are redundant.

showWebcamControls, showRecordingWebcamPreview, and shouldStreamWebcamPreview all resolve to webcamEnabled. While this might be intentional for future flexibility, currently they add indirection without benefit.

Consider consolidating to a single derived variable or using webcamEnabled directly where appropriate, unless you anticipate these conditions diverging (e.g., showRecordingWebcamPreview becoming webcamEnabled && recording).

🤖 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 222 - 224, The three
derived booleans showWebcamControls, showRecordingWebcamPreview, and
shouldStreamWebcamPreview in LaunchWindow.tsx are redundant because they all
equal webcamEnabled; consolidate by removing the extra variables and either use
webcamEnabled directly at call sites or replace them with a single shared
derived name (e.g., showWebcam) so all references (showWebcamControls,
showRecordingWebcamPreview, shouldStreamWebcamPreview) are updated to the single
variable or webcamEnabled; keep separate variables only if you intentionally
plan to diverge conditions (e.g., showRecordingWebcamPreview => webcamEnabled &&
recording).

568-588: Magic number 100000 for full work area expansion lacks documentation.

Using 100000 as a sentinel value for "expand to full work area" is non-obvious. Future maintainers may not understand the intent without examining the Electron-side handler.

📝 Proposed fix to add a named constant
+const FULL_WORK_AREA_SIZE = 100000;
+
 const reportHudSize = () => {
 	frameId = 0;
 	const hasFloatingWebcamPreview = showRecordingWebcamPreview;
 	const measuredWidth = hasFloatingWebcamPreview
-		? 100000
+		? FULL_WORK_AREA_SIZE
 		: Math.ceil(
 			Math.max(
 				hudBar.getBoundingClientRect().width,
 				hudBar.scrollWidth,
 				hudContent.getBoundingClientRect().width,
 				hudContent.scrollWidth,
 			) + 24,
 		);
 	const measuredHeight = hasFloatingWebcamPreview
-		? 100000
+		? FULL_WORK_AREA_SIZE
 		: Math.ceil(
 			Math.max(hudContent.getBoundingClientRect().height, hudContent.scrollHeight) + 24,
 		);
🤖 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 568 - 588, The code uses
a magic sentinel value 100000 to indicate "expand to full work area" when
showRecordingWebcamPreview is true (affecting measuredWidth/measuredHeight and
calls to window.electronAPI.setHudOverlayCompactWidth and
setHudOverlayMeasuredHeight); replace the literal with a well-named constant
(e.g., FULL_WORKAREA_SENTINEL or MAX_FULL_WORKAREA) declared near the top of the
module, add a short comment explaining its purpose (that it signals the Electron
handler to expand to the full work area), and update references of
hasFloatingWebcamPreview / measuredWidth / measuredHeight to use this constant
so intent is clear and maintainable.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/components/launch/LaunchWindow.tsx`:
- Line 60: The locale mapping in LaunchWindow.tsx has the "zh-CN" entry set to
the corrupted placeholder "??"; locate the locale object (the mapping that
includes the "zh-CN" key) and restore the proper Chinese label by replacing "??"
with "中文" (or the desired display string such as "简体中文") so the language
selector shows the correct label for Chinese users; update any related UI
snapshots or tests that expect the previous label if present.
- Around line 640-643: The window name parsing in LaunchWindow.tsx (inside the
block using isWindow, appName, displayName and s.name) uses the wrong separator
" ? "; change it to match the rest of the codebase by using " — " (em-dash) for
both the includes() check and the split/join operations so appName =
parts[0]?.trim() and displayName = parts.slice(1).join(" — ").trim() behave
consistently with SourceSelector.tsx and handlers.ts.

---

Nitpick comments:
In `@src/components/launch/LaunchWindow.tsx`:
- Around line 222-224: The three derived booleans showWebcamControls,
showRecordingWebcamPreview, and shouldStreamWebcamPreview in LaunchWindow.tsx
are redundant because they all equal webcamEnabled; consolidate by removing the
extra variables and either use webcamEnabled directly at call sites or replace
them with a single shared derived name (e.g., showWebcam) so all references
(showWebcamControls, showRecordingWebcamPreview, shouldStreamWebcamPreview) are
updated to the single variable or webcamEnabled; keep separate variables only if
you intentionally plan to diverge conditions (e.g., showRecordingWebcamPreview
=> webcamEnabled && recording).
- Around line 568-588: The code uses a magic sentinel value 100000 to indicate
"expand to full work area" when showRecordingWebcamPreview is true (affecting
measuredWidth/measuredHeight and calls to
window.electronAPI.setHudOverlayCompactWidth and setHudOverlayMeasuredHeight);
replace the literal with a well-named constant (e.g., FULL_WORKAREA_SENTINEL or
MAX_FULL_WORKAREA) declared near the top of the module, add a short comment
explaining its purpose (that it signals the Electron handler to expand to the
full work area), and update references of hasFloatingWebcamPreview /
measuredWidth / measuredHeight to use this constant so intent is clear and
maintainable.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: aefbd788-436f-4b7a-ac24-f003e705119c

📥 Commits

Reviewing files that changed from the base of the PR and between 5c6f6a2 and 8ffebef.

📒 Files selected for processing (1)
  • src/components/launch/LaunchWindow.tsx

Copy link
Copy Markdown

@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: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/components/launch/LaunchWindow.tsx`:
- Around line 196-198: The UI shows webcam controls based only on webcamEnabled
which leaves the HUD in an "on" state when getUserMedia fails; update the webcam
startup flow (the code that calls getUserMedia / acquires the stream) to only
set showWebcamControls, showRecordingWebcamPreview and shouldStreamWebcamPreview
true after a successful stream acquisition, or alternatively revert
webcamEnabled to false inside the catch path where the getUserMedia error is
handled; locate the stream acquisition function (the method that calls
navigator.mediaDevices.getUserMedia / acquireWebcamStream / startPreview) and
either gate rendering on the actual acquired MediaStream state or ensure the
catch block resets webcamEnabled and clears any partial state so the HUD and
both preview surfaces are not shown when opening the camera fails.
- Around line 1161-1179: The current mouse-based drag handlers (onMouseDown ->
handleMove/handleUp) can get stuck if mouseup occurs outside the renderer;
replace them with pointer events and pointer capture so release is guaranteed.
On the element's pointerdown (instead of onMouseDown) call
e.currentTarget.setPointerCapture(e.pointerId), set isDraggingRef.current =
true, and call window.electronAPI?.hudOverlayDrag?.("start", e.screenX,
e.screenY); attach pointermove and pointerup handlers that call
hudOverlayDrag("move"/"end") and clear isDraggingRef; in pointermove check
ev.buttons (or rely on pointer events state) and in pointerup release capture
via e.currentTarget.releasePointerCapture(e.pointerId) and call
window.electronAPI?.hudOverlaySetIgnoreMouse?.(true) if a drag occurred; remove
the pointer listeners in pointerup to avoid stuck listeners (update references
to handleMove, handleUp, isDraggingRef, hudOverlayDrag, hudOverlaySetIgnoreMouse
accordingly).
- Around line 575-583: The outside-click guard is using dropdownRef which is
attached to the full 100vh HUD wrapper so contains(...) always returns true;
update the logic so the click test targets the actual dropdown/project browser
element instead of the overlay wrapper (e.g., create/use a separate ref like
projectDropdownRef or previewDropdownRef or query the specific dropdown node
inside the component) in the useEffect handler that removes the dropdown:
replace dropdownRef.current.contains(e.target) with a check against the specific
dropdown DOM node and keep calling setActiveDropdown("none") and
setProjectBrowserOpen(false) when the click is outside that specific node.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: fd789b5f-2fe7-40d8-8402-53f1ad5a67e7

📥 Commits

Reviewing files that changed from the base of the PR and between 8ffebef and 3fa1aa1.

📒 Files selected for processing (1)
  • src/components/launch/LaunchWindow.tsx

Comment on lines +196 to +198
const showWebcamControls = webcamEnabled;
const showRecordingWebcamPreview = webcamEnabled;
const shouldStreamWebcamPreview = webcamEnabled;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Don't keep the webcam UI "on" after preview startup fails.

Both preview surfaces are rendered from webcamEnabled, but the getUserMedia failure path only logs. If permission is denied or the selected camera cannot be opened, the HUD still expands and both previews show an active webcam state with no stream attached. Gate these sections on successful stream acquisition, or revert the enabled flag in the catch path.

Also applies to: 307-342, 1043-1055, 1214-1234

🤖 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 196 - 198, The UI shows
webcam controls based only on webcamEnabled which leaves the HUD in an "on"
state when getUserMedia fails; update the webcam startup flow (the code that
calls getUserMedia / acquires the stream) to only set showWebcamControls,
showRecordingWebcamPreview and shouldStreamWebcamPreview true after a successful
stream acquisition, or alternatively revert webcamEnabled to false inside the
catch path where the getUserMedia error is handled; locate the stream
acquisition function (the method that calls navigator.mediaDevices.getUserMedia
/ acquireWebcamStream / startPreview) and either gate rendering on the actual
acquired MediaStream state or ensure the catch block resets webcamEnabled and
clears any partial state so the HUD and both preview surfaces are not shown when
opening the camera fails.

Comment on lines +575 to +583
useEffect(() => {
const handleClick = (e: MouseEvent) => {
if (dropdownRef.current && !dropdownRef.current.contains(e.target as Node)) {
setActiveDropdown("none");
setProjectBrowserOpen(false);
}
};
document.addEventListener("mousedown", handleClick);
return () => document.removeEventListener("mousedown", handleClick);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

The outside-click guard can never fire inside the enlarged HUD window.

dropdownRef is attached to the 100vh wrapper, so dropdownRef.current.contains(e.target) is true for every click inside the overlay window. With the larger expanded HUD used for the floating preview, clicks on the bar or preview won't dismiss an open menu/project browser anymore.

💡 Simple fix
-    <div className="w-full flex items-end justify-center bg-transparent overflow-visible pb-5" style={{ height: "100vh" }} ref={dropdownRef}>
+    <div className="w-full flex items-end justify-center bg-transparent overflow-visible pb-5" style={{ height: "100vh" }}>
-        <div className={styles.menuArea}>
+        <div className={styles.menuArea} ref={dropdownRef}>

Also applies to: 911-912, 924-924

🤖 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 575 - 583, The
outside-click guard is using dropdownRef which is attached to the full 100vh HUD
wrapper so contains(...) always returns true; update the logic so the click test
targets the actual dropdown/project browser element instead of the overlay
wrapper (e.g., create/use a separate ref like projectDropdownRef or
previewDropdownRef or query the specific dropdown node inside the component) in
the useEffect handler that removes the dropdown: replace
dropdownRef.current.contains(e.target) with a check against the specific
dropdown DOM node and keep calling setActiveDropdown("none") and
setProjectBrowserOpen(false) when the click is outside that specific node.

Comment on lines +1161 to +1179
onMouseDown={(e) => {
e.preventDefault();
isDraggingRef.current = true;
window.electronAPI?.hudOverlayDrag?.("start", e.screenX, e.screenY);
const handleMove = (ev: MouseEvent) => {
window.electronAPI?.hudOverlayDrag?.("move", ev.screenX, ev.screenY);
};
const handleUp = () => {
const wasDragging = isDraggingRef.current;
isDraggingRef.current = false;
window.electronAPI?.hudOverlayDrag?.("end", 0, 0);
if (wasDragging) {
window.electronAPI?.hudOverlaySetIgnoreMouse?.(true);
}
document.removeEventListener("mousemove", handleMove);
document.removeEventListener("mouseup", handleUp);
};
document.addEventListener("mousemove", handleMove);
document.addEventListener("mouseup", handleUp);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

The HUD-bar drag can get stuck after an off-window mouseup.

This path relies on document mousemove/mouseup, and handleMove never checks ev.buttons. If the button is released outside the renderer, hudOverlayDrag("end") never fires, the listeners stay attached, and a later plain mousemove can keep moving the HUD because electron/windows.ts:187-201 still has hudDragOffset. Reusing the floating preview's pointer-capture approach here would close that hole.

🤖 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 1161 - 1179, The current
mouse-based drag handlers (onMouseDown -> handleMove/handleUp) can get stuck if
mouseup occurs outside the renderer; replace them with pointer events and
pointer capture so release is guaranteed. On the element's pointerdown (instead
of onMouseDown) call e.currentTarget.setPointerCapture(e.pointerId), set
isDraggingRef.current = true, and call
window.electronAPI?.hudOverlayDrag?.("start", e.screenX, e.screenY); attach
pointermove and pointerup handlers that call hudOverlayDrag("move"/"end") and
clear isDraggingRef; in pointermove check ev.buttons (or rely on pointer events
state) and in pointerup release capture via
e.currentTarget.releasePointerCapture(e.pointerId) and call
window.electronAPI?.hudOverlaySetIgnoreMouse?.(true) if a drag occurred; remove
the pointer listeners in pointerup to avoid stuck listeners (update references
to handleMove, handleUp, isDraggingRef, hudOverlayDrag, hudOverlaySetIgnoreMouse
accordingly).

@webadderall
Copy link
Copy Markdown
Owner

Will review as soon as I have time

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

Labels

Projects

None yet

3 participants