Skip to content

Conversation

@richiemcilroy
Copy link
Member

@richiemcilroy richiemcilroy commented Nov 16, 2025

This PR sets the new and improved recording picker to default. Users can toggle back to the old picker in Settings -> General.

Overview:

  • add the new apps/desktop/src/routes/target-select-overlay.tsx surface that listens to events.targetUnderCursor, drives RecordingOptionsProvider, and lets users jump between display, window, and area capture with inline camera/mic selectors, recording controls, and mode switching
  • overhaul apps/desktop/src/routes/capture-area.tsx to share the picker experience: persisted ratios, snap-to-grid options, validation, and seamless handoff back to the overlay when confirming or cancelling an area target
  • ship the cross-platform SelectionHint overlay plus the new monitor, cursor-windows, and cursor-macos assets in packages/ui-solid/icons, giving users immediate guidance while they drag out a region
  • update the Tauri window plumbing in apps/desktop/src-tauri/src/windows.rs so capture/overlay windows spawn per-display, ignore pointer events until needed, and minimize the main window when a monitor picker is active, keeping the UX distraction-free

Summary by CodeRabbit

  • New Features

    • Selection hint overlay for area capture; richer target-selection UI and device pickers
    • Camera disconnected overlay and input lost/restored notifications
    • Recording settings menu to switch mic/camera and preview controls
  • Improvements

    • New recording flow enabled by default; recording window resized and layout refinements
    • Better device readiness, watchers and automatic restoration; improved error handling
    • Target overlay respects window exclusions; macOS shareable-content refresh/retry
  • Bug Fixes

    • Improved device reconnection and cleanup during recording sessions

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 16, 2025

Warning

Rate limit exceeded

@richiemcilroy has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 2 minutes and 13 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between e6e84ce and 7d86c79.

📒 Files selected for processing (9)
  • apps/desktop/src-tauri/src/general_settings.rs (5 hunks)
  • apps/desktop/src-tauri/src/lib.rs (8 hunks)
  • apps/desktop/src-tauri/src/target_select_overlay.rs (4 hunks)
  • apps/desktop/src/components/selection-hint.tsx (1 hunks)
  • apps/desktop/src/routes/(window-chrome)/new-main/index.tsx (17 hunks)
  • apps/desktop/src/routes/capture-area.tsx (5 hunks)
  • apps/desktop/src/routes/target-select-overlay.tsx (14 hunks)
  • apps/desktop/src/utils/queries.ts (1 hunks)
  • crates/recording/src/feeds/microphone.rs (7 hunks)

Walkthrough

Adds device disconnect/reconnect handling/events for microphone and camera, macOS shareable-content refresh/retry, window-exclusion filtering and selection hints, device-selection controls in overlays, recording-window preference tracking, multiple output-pipeline timing adjustments, feed reconnection messages, and a minor version bump.

Changes

Cohort / File(s) Summary
Version Bump
apps/desktop/src-tauri/Cargo.toml
Updated package version from 0.3.83 to 0.3.84.
General settings & tauri types
apps/desktop/src-tauri/src/general_settings.rs, apps/desktop/src/routes/(window-chrome)/settings/general.tsx, apps/desktop/src/routes/(window-chrome)/settings/experimental.tsx, apps/desktop/src/utils/tauri.ts
Added recording_picker_preference_set (serde default false); made enableNewRecordingFlow default/required true; one-time flip of preference on init; removed experimental UI toggle; extended RecordingEvent and added RecordingInputKind type.
App input state & recording wiring
apps/desktop/src-tauri/src/lib.rs, apps/desktop/src-tauri/src/recording.rs, apps/desktop/src-tauri/src/platform/macos/sc_shareable_content.rs, apps/desktop/src-tauri/src/windows.rs
Added selected_camera_id, camera_in_use, disconnected_inputs; input disconnect/restored handlers and ensure_* helpers; macOS shareable-content prewarm/refresh + retry; recording start uses acquire/retry; recording end clears disconnected state; RecordingEvent gains InputLost/InputRestored; in-progress window size adjusted.
Device watchers & mic error handling
apps/desktop/src-tauri/src/lib.rs
Spawned microphone and camera device watchers; refactored mic error logger → mic error handler using channel; wired watchers and error channel into run/init flows.
Target selection, overlay & capture-area
apps/desktop/src-tauri/src/target_select_overlay.rs, apps/desktop/src/routes/target-select-overlay.tsx, apps/desktop/src/routes/capture-area.tsx, apps/desktop/src/routes/(window-chrome)/new-main/index.tsx
Added window-exclusion filtering and plumbing; replaced debounce with interaction/pending-area reconciliation; added SelectionHint usage and persisted area bounds fallback; integrated CameraSelect/MicrophoneSelect, device queries/mutations, updated overlay UX and target-mode toggling; adjusted layout/sizing.
UI components & styles
apps/desktop/src/components/SelectionHint.tsx, apps/desktop/src/styles/theme.css, packages/ui-solid/src/auto-imports.d.ts
New SelectionHint Solid component; added cap-selection hint CSS rules/animations (block duplicated); added global icon typings for cursor/monitor/alert icons.
In-progress recording UI & camera preview
apps/desktop/src/routes/in-progress-recording.tsx, apps/desktop/src/routes/camera.tsx
Expanded in-progress UI with device menus, mic/camera management, recording-issues flow (tracks InputLost/InputRestored/Failed), CameraDisconnected overlay and event listener wiring, richer control bar and issue panel; preview pages accept disconnected prop.
Recording crate — feed reconnection
crates/recording/src/feeds/camera.rs, crates/recording/src/feeds/microphone.rs
Added locked-state reconnection messages (LockedCameraInputReconnected, LockedInputReconnected), label propagation into attached states, and message handlers to update Locked state during reconnection flows.
Recording crate — studio actor APIs
crates/recording/src/studio_recording.rs
Added actor messages SetMicFeed / SetCameraFeed and public ActorHandle::set_*_feed async APIs; added mutators on SegmentPipelineFactory; made RecordingSegment.pipeline private.
Output pipeline / timing
crates/recording/src/output_pipeline/macos.rs, crates/recording/src/output_pipeline/win.rs
macOS: removed external retry crate, added per-call retry loop and MAX_QUEUE_RETRIES for video queues; Windows: added PauseTracker, added pause: PauseTracker to WindowsMuxer, changed setup to accept pause_flag: Arc<AtomicBool>, and adjusted timestamp handling/relative timing and conversions.
Screen capture error visibility
crates/recording/src/sources/screen_capture/macos.rs
Made SourceError enum pub to expose screen-capture errors to callers.
Misc internal edits / formatting
crates/recording/src/sources/audio_mixer.rs, crates/rendering/src/lib.rs, crates/rendering/src/main.rs
Replaced positional fmt args with named placeholders in ffmpeg filter strings; renamed private method to_uvpoint_to_uv; adjusted format! placeholder usage for output filename (no behavior change).

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant App
    participant DeviceWatcher
    participant RecordingActor
    participant Overlay

    User->>App: Start recording
    App->>RecordingActor: spawn/init recording
    activate RecordingActor

    Note over App,DeviceWatcher: device watchers running
    DeviceWatcher->>App: periodic availability check

    alt device disconnected
        DeviceWatcher->>App: handle_input_disconnect(kind)
        App->>RecordingActor: pause/detach feed
        App->>Overlay: emit RecordingEvent::InputLost(kind)
        App->>User: show issue overlay/notification
    else device restored
        DeviceWatcher->>App: detected available
        App->>App: ensure_selected_mic_ready / ensure_selected_camera_ready
        App->>RecordingActor: reattach/restore feed
        App->>Overlay: emit RecordingEvent::InputRestored(kind)
    end

    deactivate RecordingActor
Loading
sequenceDiagram
    participant Settings
    participant App
    participant User

    Settings->>App: load general settings
    alt first run (recording_picker_preference_set == false)
        App->>App: enable_new_recording_flow = true
        App->>App: recording_picker_preference_set = true
        App->>Settings: persist change
    else subsequent runs
        App->>Settings: keep stored preferences
    end

    User->>Settings: choose Recording window variant
    Settings->>Settings: updateRecordingWindowVariant(variant) sets enableNewRecordingFlow and recordingPickerPreferenceSet
    Settings->>App: persist settings
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

  • Heterogeneous, cross-cutting changes touching frontend UI, tauri bindings, actor/feed state machines, platform capture, and output pipelines.
  • Areas needing focused attention:
    • input disconnect/restore ordering and emitted events in apps/desktop/src-tauri/src/lib.rs and apps/desktop/src-tauri/src/recording.rs
    • locked-state reconnection messages and lifecycle in crates/recording/src/feeds/{camera,microphone}.rs
    • pause/timestamp correctness and pause_flag wiring in crates/recording/src/output_pipeline/win.rs
    • area-target reconciliation and device-selection wiring in apps/desktop/src/routes/target-select-overlay.tsx
    • one-time settings init, serde defaults, and storage persistence in apps/desktop/src-tauri/src/general_settings.rs

Possibly related PRs

Suggested labels

codex

Suggested reviewers

  • oscartbeaumont
  • Brendonovich

Poem

🐇 I hopped through code where cameras sleep,

I nudged the mics that lost their beep,
I warmed the shareables, retried with care,
Hinted selections dancing in the air,
Now feeds reconnect — carrots for the bear! 🥕

Pre-merge checks and finishing touches

❌ 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%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: Introduce new and improved recording picker' accurately summarizes the main change - a new recording picker interface is being introduced as the default UI for recording capture.

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

Caution

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

⚠️ Outside diff range comments (1)
apps/desktop/src/routes/in-progress-recording.tsx (1)

52-103: Handle the Stopped recording event to auto-close the window

The window-close effect (lines 203–211) correctly watches for state().variant === "stopped", but the recordingEvent listener (lines 139–167) doesn't have a case for the Stopped event. When the backend stops recording normally, it emits RecordingEvent::Stopped (confirmed in recording.rs:749), but this event is unhandled, so state().variant never transitions to "stopped" and the window won't close automatically.

Add a case in the recordingEvent switch:

case "Stopped":
  setState({ variant: "stopped" });
  break;
🧹 Nitpick comments (10)
crates/recording/src/output_pipeline/win.rs (1)

353-365: LGTM: Duration to TimeSpan conversion.

The conversion correctly handles Windows' 100-nanosecond tick representation. Saturating arithmetic and clamping to i64::MAX appropriately handle overflow for extremely large durations.

Consider adding a doc comment explaining the tick rate and overflow behavior for future maintainers:

+/// Converts a [`Duration`] to a Windows [`TimeSpan`].
+///
+/// Windows TimeSpan uses 100-nanosecond ticks (10,000,000 ticks per second).
+/// Overflows are saturated to `i64::MAX`.
 fn duration_to_timespan(duration: Duration) -> TimeSpan {
apps/desktop/src/routes/(window-chrome)/new-main/index.tsx (1)

304-322: Picker-driven main-window hide/show flow is well-contained

Tying the main window’s visibility to rawOptions.targetMode with hasHiddenMainWindowForPicker is a clear way to ensure you only hide/show once per picker session and always restore the window on cleanup. The onCleanup guard is a good safeguard for unexpected unmounts. If you notice any flicker, a minor follow-up would be to cache const currentWindow = getCurrentWindow() instead of re-calling it, but it’s not essential.

apps/desktop/src-tauri/src/target_select_overlay.rs (2)

66-71: Graceful fallback to default exclusions, but settings errors are silently dropped

Using GeneralSettingsStore::get(&app).ok().flatten().map_or_else(...) correctly falls back to default_excluded_windows when settings are missing or invalid, keeping the overlay functional. The downside is deserialization/read errors are silently ignored; consider logging at debug/warn so unexpected corruption of general_settings is diagnosable.


92-95: Window exclusion filter looks correct; consider avoiding duplicate owner name lookups

The should_skip_window logic cleanly matches against bundle identifier, owner name, and title, and the call site short‑circuits correctly to suppress excluded windows. Note that owner_name() is called both inside should_skip_window and again when constructing WindowUnderCursor.app_name, which may duplicate relatively expensive OS calls every 50 ms; if this ever shows up in profiling, you could thread the already-fetched owner_name through instead of recomputing it.

Also applies to: 129-150

crates/recording/src/feeds/camera.rs (1)

176-181: Locked-camera reconnection flow mirrors microphone behavior and looks correct

The LockedCameraInputReconnected message plus the State::Locked branch in SetInput correctly: (1) enforce that only the currently locked camera ID can reconnect, (2) shut down the previous capture via inner.done_tx.send(()) before starting a new one, (3) reuse the same setup_camera pipeline and ready future wiring as the Open path, and (4) update the locked AttachedState with fresh camera_info, video_info, and done_tx. The duplication between the Open and Locked branches is acceptable, but if this grows further you might consider extracting a shared helper to reduce repetition.

Also applies to: 403-468

crates/recording/src/feeds/microphone.rs (1)

333-654: Consider extracting common stream building logic.

The Open and Locked state branches contain substantial code duplication (~140 lines). The stream building logic (lines 386-474 and 540-627) is nearly identical.

While functional, extracting the common stream setup into a helper function would improve maintainability and reduce the risk of inconsistencies when making future changes.

apps/desktop/src/routes/(window-chrome)/settings/general.tsx (1)

66-78: Defaults and handleChange extra payload look consistent, but local store isn’t fully updated

  • Changing enableNewRecordingFlow default to true and adding recordingPickerPreferenceSet: false matches the new “new picker by default” behavior and the backend GeneralSettingsStore shape.
  • handleChange’s extra?: Partial<GeneralSettingsStore> is a nice way to batch related updates into persistence, but you only apply extra to generalSettingsStore.set, not to the local settings store. If you later read any of those extra keys (like recordingPickerPreferenceSet) from settings, they’ll be stale until the window reloads. Consider also merging extra into settings for consistency.

Also applies to: 203-212

apps/desktop/src/routes/in-progress-recording.tsx (1)

182-200: Device-change handling during recording is careful and rollback-safe

  • refreshCameraWindowState plus the periodic timer keep cameraWindowOpen in sync without spamming the backend.
  • pauseRecordingForDeviceChange and its use from updateMicInput / updateCameraInput ensure:
    • Device changes only pause if currently recording,
    • Pause segments are tracked in pauseResumes,
    • The in‑progress UI state remains consistent (variant switched to "paused" and time updated once).
  • updateMicInput/updateCameraInput:
    • Respect startedWithMicrophone/startedWithCameraInput so you can’t enable a new input mid‑recording that wasn’t present at start,
    • No‑op when selecting the same input,
    • Optimistically update optionsQuery.rawOptions, but roll back to the previous value if the underlying set*Input command fails.
  • openRecordingSettingsMenu’s dynamic menu wiring (including “No Microphone”/“No Webcam” items) is consistent with these rules and won’t offer actions that are locked for this recording.

Also applies to: 269-329, 330-425

apps/desktop/src-tauri/src/lib.rs (1)

106-122: Input disconnect/restoration pipeline and device watchers are well-integrated

  • App now tracks selected_camera_id, camera_in_use, and a HashSet<RecordingInputKind> of disconnected_inputs, which is a clean internal model for device state.
  • handle_input_disconnect:
    • Idempotently marks an input as disconnected,
    • Pauses any active recording via current_recording_mut().pause().await,
    • Emits a NewNotification with a clear pause message per input kind,
    • Emits RecordingEvent::InputLost { input } for the frontend.
  • handle_input_restored:
    • Clears the disconnected flag once,
    • Calls ensure_selected_mic_ready / ensure_selected_camera_ready to re‑apply the selected input,
    • Emits RecordingEvent::InputRestored { input }.
  • spawn_mic_error_handler and the microphone/camera watchers poll at 1s intervals only while a recording is active (and, for camera, only when camera_in_use is true), then call the above handlers based on actual device availability. This ties feed‑level errors and physical device add/remove into the same state machine.
  • Minor concern: the watchers and mic error handler currently hold a write lock on ArcLock<App> while awaiting handle_input_disconnect/handle_input_restored, which themselves await (pause recording, set inputs). It works but serializes any other operations needing the app state. You may want to clone just the data needed for the decision, drop the read or write guard, and then call the async handler to reduce lock contention.

Also applies to: 250-327, 492-567, 569-623

apps/desktop/src/routes/target-select-overlay.tsx (1)

152-159: Area-mode state flow works but doesn’t reuse an existing area selection in the cropper

  • For area target mode you now track:
    • pendingAreaTarget (the logical "area" ScreenCaptureTarget),
    • initialAreaBounds (seeded when converting a window to an area via “Adjust recording area”),
    • crop/committedCrop and isValid/committedIsValid to enforce a 150×150 minimum size.
  • When entering area mode:
    • If options.captureTarget is already "area" for this display, pendingAreaTarget is seeded from it.
    • When leaving area mode, any pendingAreaTarget is committed back into captureTarget.
    • While idle (not interacting) and with a valid committedCrop, pendingAreaTarget is kept in sync with the current crop bounds.
  • The cropper visual, though, is only initialized from initialAreaBounds (initialCrop={() => initialAreaBounds() ?? CROP_ZERO}). If you arrive in area mode with an existing area target but without having gone through “Adjust recording area” (so initialAreaBounds is still undefined), the crop starts empty, and the start button stays disabled until the user draws a new area—even though we do have a usable pendingAreaTarget.
  • Consider also seeding initialAreaBounds from the existing "area" capture target when you populate pendingAreaTarget, so the cropper reflects the saved selection and the user can refine instead of redrawing.

Also applies to: 160-183, 184-211, 335-368, 384-405, 504-519, 567-576

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dd4ab52 and a44779e.

⛔ Files ignored due to path filters (4)
  • Cargo.lock is excluded by !**/*.lock
  • packages/ui-solid/icons/cursor-macos.svg is excluded by !**/*.svg
  • packages/ui-solid/icons/cursor-windows.svg is excluded by !**/*.svg
  • packages/ui-solid/icons/monitor.svg is excluded by !**/*.svg
📒 Files selected for processing (24)
  • apps/desktop/src-tauri/Cargo.toml (1 hunks)
  • apps/desktop/src-tauri/src/general_settings.rs (5 hunks)
  • apps/desktop/src-tauri/src/lib.rs (9 hunks)
  • apps/desktop/src-tauri/src/platform/macos/sc_shareable_content.rs (2 hunks)
  • apps/desktop/src-tauri/src/recording.rs (7 hunks)
  • apps/desktop/src-tauri/src/target_select_overlay.rs (4 hunks)
  • apps/desktop/src-tauri/src/windows.rs (1 hunks)
  • apps/desktop/src/components/SelectionHint.tsx (1 hunks)
  • apps/desktop/src/routes/(window-chrome)/new-main/index.tsx (17 hunks)
  • apps/desktop/src/routes/(window-chrome)/settings/experimental.tsx (1 hunks)
  • apps/desktop/src/routes/(window-chrome)/settings/general.tsx (3 hunks)
  • apps/desktop/src/routes/camera.tsx (7 hunks)
  • apps/desktop/src/routes/capture-area.tsx (6 hunks)
  • apps/desktop/src/routes/in-progress-recording.tsx (9 hunks)
  • apps/desktop/src/routes/target-select-overlay.tsx (14 hunks)
  • apps/desktop/src/styles/theme.css (1 hunks)
  • apps/desktop/src/utils/tauri.ts (2 hunks)
  • crates/recording/src/feeds/camera.rs (3 hunks)
  • crates/recording/src/feeds/microphone.rs (5 hunks)
  • crates/recording/src/output_pipeline/macos.rs (1 hunks)
  • crates/recording/src/output_pipeline/win.rs (6 hunks)
  • crates/recording/src/sources/screen_capture/macos.rs (1 hunks)
  • crates/recording/src/studio_recording.rs (4 hunks)
  • packages/ui-solid/src/auto-imports.d.ts (3 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Use a 2-space indent for TypeScript code.
Use Biome for formatting and linting TypeScript/JavaScript files by running pnpm format.

Files:

  • apps/desktop/src/routes/(window-chrome)/settings/experimental.tsx
  • apps/desktop/src/routes/in-progress-recording.tsx
  • apps/desktop/src/routes/capture-area.tsx
  • packages/ui-solid/src/auto-imports.d.ts
  • apps/desktop/src/routes/camera.tsx
  • apps/desktop/src/components/SelectionHint.tsx
  • apps/desktop/src/utils/tauri.ts
  • apps/desktop/src/routes/target-select-overlay.tsx
  • apps/desktop/src/routes/(window-chrome)/settings/general.tsx
  • apps/desktop/src/routes/(window-chrome)/new-main/index.tsx
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx,js,jsx}: Use kebab-case for filenames for TypeScript/JavaScript modules (e.g., user-menu.tsx).
Use PascalCase for React/Solid components.

Files:

  • apps/desktop/src/routes/(window-chrome)/settings/experimental.tsx
  • apps/desktop/src/routes/in-progress-recording.tsx
  • apps/desktop/src/routes/capture-area.tsx
  • packages/ui-solid/src/auto-imports.d.ts
  • apps/desktop/src/routes/camera.tsx
  • apps/desktop/src/components/SelectionHint.tsx
  • apps/desktop/src/utils/tauri.ts
  • apps/desktop/src/routes/target-select-overlay.tsx
  • apps/desktop/src/routes/(window-chrome)/settings/general.tsx
  • apps/desktop/src/routes/(window-chrome)/new-main/index.tsx
**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

**/*.rs: Format Rust code using rustfmt and ensure all Rust code passes workspace-level clippy lints.
Rust modules should be named with snake_case, and crate directories should be in kebab-case.

Files:

  • crates/recording/src/output_pipeline/macos.rs
  • apps/desktop/src-tauri/src/windows.rs
  • crates/recording/src/sources/screen_capture/macos.rs
  • apps/desktop/src-tauri/src/target_select_overlay.rs
  • crates/recording/src/feeds/microphone.rs
  • apps/desktop/src-tauri/src/platform/macos/sc_shareable_content.rs
  • apps/desktop/src-tauri/src/general_settings.rs
  • crates/recording/src/feeds/camera.rs
  • apps/desktop/src-tauri/src/recording.rs
  • apps/desktop/src-tauri/src/lib.rs
  • crates/recording/src/output_pipeline/win.rs
  • crates/recording/src/studio_recording.rs
crates/*/src/**/*

📄 CodeRabbit inference engine (AGENTS.md)

Rust crates should place tests within the src/ and/or a sibling tests/ directory for each crate inside crates/*.

Files:

  • crates/recording/src/output_pipeline/macos.rs
  • crates/recording/src/sources/screen_capture/macos.rs
  • crates/recording/src/feeds/microphone.rs
  • crates/recording/src/feeds/camera.rs
  • crates/recording/src/output_pipeline/win.rs
  • crates/recording/src/studio_recording.rs
**/tauri.ts

📄 CodeRabbit inference engine (AGENTS.md)

Do not edit auto-generated files named tauri.ts.

Files:

  • apps/desktop/src/utils/tauri.ts
🧠 Learnings (4)
📚 Learning: 2025-10-28T08:39:42.230Z
Learnt from: Brendonovich
Repo: CapSoftware/Cap PR: 1305
File: crates/recording/src/output_pipeline/macos.rs:80-90
Timestamp: 2025-10-28T08:39:42.230Z
Learning: In `crates/recording/src/output_pipeline/macos.rs`, the `AVFoundationMp4Muxer` intentionally holds the `Mutex<MP4Encoder>` lock during retry attempts in `send_video_frame()` and `send_audio_frame()`. This blocking behavior is correct because frame processing must happen sequentially to prevent audio and video frames from being interleaved incorrectly in the encoder.

Applied to files:

  • crates/recording/src/output_pipeline/macos.rs
  • crates/recording/src/feeds/camera.rs
  • apps/desktop/src-tauri/src/recording.rs
  • crates/recording/src/output_pipeline/win.rs
📚 Learning: 2025-10-17T05:58:22.586Z
Learnt from: Brendonovich
Repo: CapSoftware/Cap PR: 1219
File: crates/enc-avfoundation/src/mp4.rs:350-373
Timestamp: 2025-10-17T05:58:22.586Z
Learning: In crates/enc-avfoundation/src/mp4.rs, the `finish()` method intentionally skips video extension when `is_paused` is true. This is correct behavior because if recording is paused, the video should not be extended beyond the pause point—the pause is user-initiated, unlike the case where ScreenCaptureKit stops providing frames during static content.

Applied to files:

  • crates/recording/src/output_pipeline/macos.rs
  • crates/recording/src/output_pipeline/win.rs
📚 Learning: 2025-09-22T14:19:56.010Z
Learnt from: CR
Repo: CapSoftware/Cap PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-22T14:19:56.010Z
Learning: Applies to apps/web/**/*.{ts,tsx,js,jsx} : On the client, always use `useEffectQuery` or `useEffectMutation` from `@/lib/EffectRuntime`; never call `EffectRuntime.run*` directly in components.

Applied to files:

  • apps/desktop/src/routes/camera.tsx
📚 Learning: 2025-09-22T14:19:56.010Z
Learnt from: CR
Repo: CapSoftware/Cap PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-22T14:19:56.010Z
Learning: Applies to apps/desktop/**/src-tauri/gen/** : Do not edit auto-generated files in `apps/desktop/src-tauri/gen/`.

Applied to files:

  • apps/desktop/src-tauri/Cargo.toml
🧬 Code graph analysis (15)
crates/recording/src/output_pipeline/macos.rs (3)
crates/recording/src/output_pipeline/core.rs (2)
  • timestamp (838-838)
  • send_audio_frame (861-861)
crates/recording/src/output_pipeline/ffmpeg.rs (3)
  • timestamp (22-24)
  • send_audio_frame (110-116)
  • send_audio_frame (153-155)
crates/recording/src/output_pipeline/win.rs (1)
  • send_audio_frame (340-350)
apps/desktop/src-tauri/src/target_select_overlay.rs (5)
apps/desktop/src/utils/tauri.ts (1)
  • WindowExclusion (497-497)
crates/scap-targets/src/platform/win.rs (3)
  • windows (260-260)
  • windows (1150-1150)
  • owner_name (443-483)
apps/desktop/src-tauri/src/general_settings.rs (2)
  • get (208-219)
  • default_excluded_windows (50-59)
crates/scap-targets/src/platform/macos.rs (2)
  • owner_name (320-332)
  • bundle_identifier (350-378)
crates/scap-targets/src/lib.rs (1)
  • owner_name (134-136)
apps/desktop/src/routes/in-progress-recording.tsx (2)
apps/desktop/src/utils/tauri.ts (6)
  • RecordingInputKind (452-452)
  • events (297-341)
  • commands (7-292)
  • CameraInfo (365-365)
  • LogicalPosition (425-425)
  • DeviceOrModelID (394-394)
apps/desktop/src/utils/createEventListener.ts (1)
  • createTauriEventListener (30-44)
apps/desktop/src/routes/capture-area.tsx (2)
apps/desktop/src/components/SelectionHint.tsx (1)
  • SelectionHint (10-47)
apps/desktop/src/components/Cropper.tsx (1)
  • CROP_ZERO (32-32)
crates/recording/src/feeds/microphone.rs (2)
crates/timestamp/src/lib.rs (1)
  • from_cpal (35-44)
crates/recording/src/feeds/camera.rs (11)
  • handle (327-470)
  • handle (476-492)
  • handle (498-500)
  • handle (506-523)
  • handle (529-535)
  • handle (541-557)
  • handle (573-609)
  • handle (615-639)
  • handle (645-661)
  • handle (667-679)
  • handle (685-698)
apps/desktop/src/routes/camera.tsx (2)
apps/desktop/src/utils/createEventListener.ts (1)
  • createTauriEventListener (30-44)
apps/desktop/src/utils/tauri.ts (1)
  • events (297-341)
apps/desktop/src-tauri/src/general_settings.rs (3)
apps/desktop/src/utils/tauri.ts (1)
  • GeneralSettingsStore (405-405)
apps/desktop/src-tauri/src/auth.rs (1)
  • get (51-57)
apps/desktop/src-tauri/src/presets.rs (1)
  • get (24-38)
crates/recording/src/feeds/camera.rs (1)
crates/recording/src/feeds/microphone.rs (10)
  • new (88-98)
  • handle (330-654)
  • handle (660-672)
  • handle (678-680)
  • handle (686-706)
  • handle (722-758)
  • handle (764-776)
  • handle (782-798)
  • handle (804-817)
  • handle (823-836)
apps/desktop/src-tauri/src/recording.rs (5)
apps/desktop/src/utils/tauri.ts (3)
  • ScreenCaptureTarget (469-469)
  • RecordingInputKind (452-452)
  • RecordingEvent (451-451)
apps/desktop/src-tauri/src/platform/macos/sc_shareable_content.rs (3)
  • get_shareable_content (67-83)
  • refresh_shareable_content (30-32)
  • state (22-24)
crates/recording/src/sources/screen_capture/mod.rs (1)
  • display (66-72)
crates/scap-targets/src/platform/macos.rs (1)
  • display (519-539)
crates/scap-targets/src/platform/win.rs (1)
  • display (1044-1051)
apps/desktop/src-tauri/src/lib.rs (5)
apps/desktop/src/utils/tauri.ts (5)
  • RecordingEvent (451-451)
  • RecordingInputKind (452-452)
  • DeviceOrModelID (394-394)
  • Camera (364-364)
  • NewNotification (432-432)
crates/recording/src/feeds/microphone.rs (7)
  • handle (330-654)
  • handle (660-672)
  • handle (678-680)
  • handle (686-706)
  • handle (722-758)
  • drop (262-266)
  • list (105-127)
crates/recording/src/studio_recording.rs (6)
  • handle (110-142)
  • handle (150-173)
  • handle (181-205)
  • handle (213-221)
  • handle (231-242)
  • handle (252-267)
crates/camera/src/lib.rs (4)
  • drop (238-242)
  • list_cameras (42-44)
  • device_id (29-31)
  • model_id (33-35)
apps/desktop/src-tauri/src/recording.rs (1)
  • list_cameras (291-293)
crates/recording/src/output_pipeline/win.rs (3)
crates/recording/src/output_pipeline/core.rs (7)
  • new (307-325)
  • new (675-677)
  • new (718-720)
  • new (751-753)
  • new (795-812)
  • timestamp (838-838)
  • send_audio_frame (861-861)
crates/recording/src/output_pipeline/ffmpeg.rs (3)
  • timestamp (22-24)
  • send_audio_frame (110-116)
  • send_audio_frame (153-155)
crates/recording/src/sources/screen_capture/windows.rs (1)
  • timestamp (63-65)
apps/desktop/src/routes/target-select-overlay.tsx (5)
apps/desktop/src/components/Cropper.tsx (3)
  • CropBounds (26-31)
  • CROP_ZERO (32-32)
  • Cropper (233-1266)
apps/desktop/src/components/SelectionHint.tsx (1)
  • SelectionHint (10-47)
apps/desktop/src/utils/queries.ts (3)
  • listVideoDevices (81-86)
  • listAudioDevices (100-107)
  • createCameraMutation (195-228)
apps/desktop/src/routes/(window-chrome)/new-main/CameraSelect.tsx (1)
  • CameraSelect (13-27)
apps/desktop/src/routes/(window-chrome)/new-main/MicrophoneSelect.tsx (1)
  • MicrophoneSelect (21-36)
apps/desktop/src/routes/(window-chrome)/settings/general.tsx (3)
apps/desktop/src/utils/tauri.ts (1)
  • GeneralSettingsStore (405-405)
apps/desktop/src/store.ts (1)
  • generalSettingsStore (61-62)
apps/desktop/src/routes/(window-chrome)/settings/Setting.tsx (1)
  • SettingItem (3-22)
crates/recording/src/studio_recording.rs (2)
crates/recording/src/feeds/camera.rs (2)
  • handle (327-470)
  • handle (476-492)
crates/recording/src/feeds/microphone.rs (9)
  • handle (330-654)
  • handle (660-672)
  • handle (678-680)
  • handle (686-706)
  • handle (722-758)
  • handle (764-776)
  • handle (782-798)
  • handle (804-817)
  • handle (823-836)
apps/desktop/src/routes/(window-chrome)/new-main/index.tsx (2)
apps/desktop/src/store.ts (1)
  • authStore (59-59)
apps/desktop/src/utils/tauri.ts (2)
  • CaptureDisplay (377-377)
  • CaptureWindow (379-379)
🪛 GitHub Actions: CI
crates/recording/src/feeds/microphone.rs

[error] 809-816: this if statement can be collapsed

crates/recording/src/feeds/camera.rs

[error] 672-678: this if statement can be collapsed

🪛 GitHub Check: Clippy (aarch64-apple-darwin, macos-latest)
crates/recording/src/feeds/microphone.rs

[failure] 809-816: this if statement can be collapsed
error: this if statement can be collapsed
--> crates/recording/src/feeds/microphone.rs:809:9
|
809 | / if let State::Locked { inner } = &mut self.state {
810 | | if inner.label == msg.label {
811 | | inner.id = msg.id;
812 | | inner.config = msg.config;
... |
816 | | }
| |_________^
|
= help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.91.0/index.html#collapsible_if
help: collapse nested if block
|
809 ~ if let State::Locked { inner } = &mut self.state
810 ~ && inner.label == msg.label {
811 | inner.id = msg.id;
...
814 | inner.done_tx = msg.done_tx;
815 ~ }
|

crates/recording/src/feeds/camera.rs

[failure] 672-678: this if statement can be collapsed
error: this if statement can be collapsed
--> crates/recording/src/feeds/camera.rs:672:9
|
672 | / if let State::Locked { inner } = &mut self.state {
673 | | if inner.id == msg.id {
674 | | inner.camera_info = msg.camera_info;
675 | | inner.video_info = msg.video_info;
... |
678 | | }
| |_________^
|
= help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.91.0/index.html#collapsible_if
= note: requested on the command line with -D clippy::collapsible-if
help: collapse nested if block
|
672 ~ if let State::Locked { inner } = &mut self.state
673 ~ && inner.id == msg.id {
674 | inner.camera_info = msg.camera_info;
675 | inner.video_info = msg.video_info;
676 | inner.done_tx = msg.done_tx;
677 ~ }
|

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (rust)
🔇 Additional comments (46)
apps/desktop/src-tauri/src/windows.rs (1)

628-629: LGTM! Window dimensions updated to accommodate enhanced recording controls.

The increased dimensions (320×150 vs. the previous 250×40) appropriately support the new inline camera/mic selectors and recording controls mentioned in the PR objectives. The positioning calculations at lines 646-647 correctly adapt to these new values, maintaining proper centering and bottom margin.

crates/recording/src/output_pipeline/macos.rs (2)

66-110: Explicit retry logic is clearer, but fix the unbounded loops first.

The shift from retry-crate macros to explicit loops improves readability and maintains the intentional blocking behavior required to prevent frame interleaving. However, the unbounded retry loops must be fixed before merging.

Based on learnings: The lock-holding during retries is correct and intentional to ensure sequential frame processing.


98-109: Add a retry limit to prevent infinite blocking.

Similar to send_video_frame, this loop will retry indefinitely when NotReadyForMore is returned. Apply the same fix to prevent thread hangs.

+        const MAX_RETRIES: u32 = 500; // 500 * 2ms = 1 second max wait
+        let mut retry_count = 0;
+
         loop {
             match mp4.queue_audio_frame(&frame.inner, timestamp) {
                 Ok(()) => break,
                 Err(QueueFrameError::NotReadyForMore) => {
+                    retry_count += 1;
+                    if retry_count >= MAX_RETRIES {
+                        return Err(anyhow!("send_audio_frame/timeout after {} retries", MAX_RETRIES));
+                    }
                     std::thread::sleep(Duration::from_millis(2));
                     continue;
                 }
                 Err(e) => return Err(anyhow!("send_audio_frame/{e}")),
             }
         }
⛔ Skipped due to learnings
Learnt from: Brendonovich
Repo: CapSoftware/Cap PR: 1305
File: crates/recording/src/output_pipeline/macos.rs:80-90
Timestamp: 2025-10-28T08:39:42.230Z
Learning: In `crates/recording/src/output_pipeline/macos.rs`, the `AVFoundationMp4Muxer` intentionally holds the `Mutex<MP4Encoder>` lock during retry attempts in `send_video_frame()` and `send_audio_frame()`. This blocking behavior is correct because frame processing must happen sequentially to prevent audio and video frames from being interleaved incorrectly in the encoder.
packages/ui-solid/src/auto-imports.d.ts (1)

28-29: All icon files verified to exist—no issues found.

The referenced icon files are confirmed: cursor-macos.svg, cursor-windows.svg, and monitor.svg exist in packages/ui-solid/icons/, and lucide includes the alert-triangle icon. The auto-generated declarations correctly reflect the discoverable icons from both the local cap collection and the lucide package.

apps/desktop/src-tauri/src/platform/macos/sc_shareable_content.rs (4)

26-32: LGTM! Clean delegation pattern.

The refactoring to extract common logic into prewarm_shareable_content_inner with a force_refresh parameter follows the DRY principle while maintaining a clear public API surface.


34-40: Approve the force-refresh logic with a verification note.

The cache-clearing logic is correct: the write lock on Line 36 ensures exclusive access when clearing, and the early-return path on Line 37 uses a read lock for concurrent access. The function properly coordinates with in-flight warmup tasks via the async Mutex acquired later (Line 43).

One subtle point: when force_refresh=true and an in-flight warmup exists, this function waits for that warmup to complete. Since sc::ShareableContent::current() is fetched at execution time (Line 87 in run_warmup), not spawn time, the data will be fresh even if the warmup task was spawned before the force-refresh request.

Please verify the concurrent force-refresh behavior with a test or manual review, particularly the scenario where:

  1. Thread A starts a prewarm (spawns warmup task)
  2. Thread B calls refresh (clears cache, waits for A's warmup)
  3. Both threads receive the fresh fetch result

Expected: Both get fresh data from the system at warmup execution time.


174-178: LGTM! Correct conditional delegation.

The logic correctly routes to refresh_shareable_content() when a forced refresh is requested, otherwise falls back to the standard prewarm_shareable_content() path.


1-200: Verify rustfmt and clippy compliance manually.

The sandbox environment cannot execute cargo commands. Please verify locally before merging:

cargo fmt -- --check apps/desktop/src-tauri/src/platform/macos/sc_shareable_content.rs
cargo clippy --workspace -- -D warnings

Both commands must pass without errors per the coding guidelines.

crates/recording/src/output_pipeline/win.rs (4)

232-248: LGTM: Native encoder timestamp handling.

The refactored timestamp logic correctly computes relative frame times starting from zero. Since timestamps are already pause-adjusted when sent through video_tx (line 332), the encoder receives a continuous timeline with pause durations removed.

The unwrap_or(Duration::ZERO) fallback on line 243 handles potential non-monotonic timestamps gracefully, though such cases should be rare in practice.


326-336: LGTM: Pause-aware video frame dispatch.

Correctly routes frames through PauseTracker.adjust and only sends to the encoder when an adjusted timestamp is available. Frames arriving during pause are appropriately skipped.


340-350: LGTM: Pause-aware audio frame dispatch.

Correctly integrates with PauseTracker to skip frames during pause and adjust timestamps. The let-chain syntax (Rust 1.65+) cleanly handles the multiple conditions.

Note that if output.lock() fails, the audio frame is silently dropped—consistent with the video path (line 266), but consider whether poisoned-mutex cases warrant additional logging or monitoring.


60-60: Verification confirmed: All Muxer implementations updated consistently.

All four Muxer trait implementations have been correctly updated with the pause_flag: Arc<AtomicBool> parameter:

  • Windows (win.rs:80)
  • macOS (macos.rs:33)
  • Mp4Muxer (ffmpeg.rs:41)
  • OggMuxer (ffmpeg.rs:128)

The trait definition in core.rs:849 matches all implementations. The PauseTracker integration is complete and consistent across the codebase.

apps/desktop/src/routes/(window-chrome)/new-main/index.tsx (9)

32-35: Mode/auth integration and header restructuring look consistent

Using authStore.createQuery() to drive the dashboard link and introducing <Mode /> in the header follows existing store/query patterns and keeps concerns localized to this route. The new top bar grouping (settings, recordings, changelog, license badge, Mode) is coherent and the conditional dashboard URL based on auth.data reads cleanly. I don’t see functional issues here.

Also applies to: 770-865


76-80: Window size bump appears safe

Updating getWindowSize() to return 290×310 should give the new layout more breathing room. Given that you also clamp the window size on focus/resize elsewhere in this file, this change looks self‑consistent.


195-229: TargetMenuPanel flex/min-h-0 adjustments improve scrolling

The new flex/flex-1/min-h-0 structure on the panel container should resolve nested flex overflow issues and make the list area’s scrolling more reliable in constrained heights. No behavioral concerns from this change.


398-416: Overlay opening logic: verify idempotence when toggling modes

Passing { variant: "display" | "window", id: target.id } into commands.openTargetSelectOverlays from the select handlers aligns with the ScreenCaptureTarget shape and should give the overlay a precise initial focus. toggleTargetMode then uses openTargetSelectOverlays(null)/closeTargetSelectOverlays() to manage overlay visibility based on targetMode.

One thing to double‑check: when switching between modes while the picker is already active, toggleTargetMode will call openTargetSelectOverlays(null) again. As long as the underlying Tauri command is idempotent (reusing or updating existing overlays rather than spawning duplicates), this is fine; if not, you might want to only call openTargetSelectOverlays on transitions from null -> mode.

Also applies to: 585-591


531-554: Explicit CaptureDisplay/CaptureWindow locals improve type safety

Introducing let screen: CaptureDisplay | undefined and let win: CaptureWindow | undefined clarifies the return types of options.screen() and options.window() and makes the undefined cases explicit. The control flow and fallbacks remain the same as before, so behavior should be unchanged.


585-592: toggleTargetMode wiring into target buttons is sensible

Centralizing target-mode changes in toggleTargetMode and reusing it from the Display/Window/Area TargetTypeButton components keeps the logic consistent and ensures you respect the isRecording() guard everywhere. The dropdown buttons still control the inline menus independently, which matches the intent of separating picker overlays from the menu UI.

Also applies to: 671-745


780-838: Confirm imports or global registration for icon components

In this file I see usages of IconCapSettings, IconLucideSquarePlay, IconLucideBug, IconCapLogoFullDark, and IconCapLogoFull, but I don’t see corresponding imports. If these aren’t globally registered components via your tooling, TypeScript/TSX will fail at compile time.

Please confirm they are provided globally; if not, add the appropriate imports for these icons.


824-865: License badge + dashboard link behavior looks good

The conditional href for the logo (dashboard vs marketing site) and the license badge click-through to "Upgrade" for non‑pro plans are straightforward and gated correctly on license.data?.type. Wrapping the badge in ErrorBoundary+Suspense keeps failures and loading states from impacting the rest of the header. This all reads cleanly.


867-918: Sign-in overlay and cancel flow are generally solid; verify abort wiring

The full-screen “Signing In…” overlay is correctly layered via absolute inset-0 on a relative root, and gating the underlying content on !signIn.isPending avoids accidental interaction. Using signIn.variables?.abort() plus signIn.reset() on the cancel button is a reasonable pattern assuming start-sign-in always calls signIn.mutateAsync with an AbortController.

Please double‑check that:

  • createSignInMutation consistently sets variables to the AbortController passed from the "start-sign-in" handler, and
  • there are no other call sites where signIn is used with different variables shapes.

If those assumptions hold, this flow should behave as expected.

apps/desktop/src-tauri/Cargo.toml (1)

3-3: Version bump to 0.3.84 looks fine

Package metadata change only; no functional impact from this file.

crates/recording/src/sources/screen_capture/macos.rs (1)

48-53: Exposing SourceError publicly is appropriate

Making SourceError pub so other crates (e.g. tauri layer) can express macOS screen capture failures is reasonable and does not alter internal behavior.

crates/recording/src/studio_recording.rs (2)

400-412: ActorHandle feed setters are thin, well-scoped wrappers

ActorHandle::set_mic_feed and set_camera_feed simply ask the actor with the new messages and bubble up anyhow::Result<()>, which keeps the external API minimal while reusing the actor’s validation logic.


719-725: SegmentPipelineFactory setters correctly feed into future segments

Updating self.base_inputs.mic_feed and .camera_feed here ensures subsequent create_next calls use the new feeds without disturbing the currently running segment, which matches the paused‑then‑resume semantics.

crates/recording/src/feeds/microphone.rs (1)

291-297: LGTM: Reconnection handling follows established patterns.

The LockedInputReconnected message and its handler correctly implement input reconnection for locked feeds. The label-based matching ensures the correct device is being reconnected, and the implementation parallels the camera feed pattern shown in camera.rs.

Also applies to: 801-818

apps/desktop/src/routes/(window-chrome)/settings/experimental.tsx (1)

20-30: LGTM: Feature successfully graduated from experimental.

The new recording flow is now enabled by default and the experimental toggle has been appropriately removed. This aligns with the PR objectives to make the new recording picker the default experience.

apps/desktop/src/styles/theme.css (1)

264-375: LGTM: Selection hint animations properly implemented.

The CSS for the new selection hint overlay is well-structured with appropriate animations and timing functions. The animations provide a smooth user experience to guide area selection.

apps/desktop/src-tauri/src/general_settings.rs (1)

243-263: LGTM: One-time migration logic correctly implemented.

The initialization logic properly implements a one-time opt-in mechanism for the new recording flow. Existing users who haven't explicitly set their preference will be migrated to the new flow, while the flag prevents this from running on subsequent launches.

apps/desktop/src/utils/tauri.ts (1)

1-3: Auto-generated file - no manual review required.

This file is auto-generated by tauri-specta and should not be manually edited. The type updates correctly reflect the Rust-side changes.

apps/desktop/src/components/SelectionHint.tsx (1)

1-47: LGTM: Selection hint component properly implemented.

The component correctly implements a platform-aware selection hint overlay with appropriate accessibility attributes and fallback message. The OS detection approach is suitable since the platform doesn't change at runtime.

apps/desktop/src/routes/camera.tsx (3)

50-61: LGTM: Camera disconnect detection properly implemented.

The event listener correctly tracks camera disconnect/reconnect events using the new InputLost and InputRestored event variants. The createTauriEventListener utility ensures proper cleanup.


98-100: LGTM: Disconnect overlay consistently integrated.

The CameraDisconnectedOverlay is consistently integrated into both native and legacy camera preview pages, appearing when the camera is disconnected.

Also applies to: 291-293


414-425: LGTM: Disconnect overlay component well-designed.

The overlay component is appropriately styled with backdrop blur, proper z-index, and inherits the camera window's border radius for visual consistency. The pointer-events-none allows interaction with controls while showing the disconnect state.

apps/desktop/src/routes/capture-area.tsx (3)

74-81: LGTM: Selection hint conditions properly implemented.

The logic correctly shows the selection hint to guide users who haven't made a selection yet. The conditions ensure the hint only appears when relevant (UI visible, no stored selection, minimal crop bounds).

Also applies to: 129-134


275-275: LGTM: Selection hint properly integrated.

The SelectionHint component is correctly placed and uses the reactive showSelectionHint() signal to control visibility. The default message is appropriate for this context.


289-298: LGTM: Initial crop logic safely handles stored selections.

The initialCrop function correctly retrieves stored bounds when available and safely falls back to CROP_ZERO. This ensures the cropper always has a valid initial value.

apps/desktop/src/routes/(window-chrome)/settings/general.tsx (1)

214-225: Recording window variant toggle wiring is correct and avoids redundant writes

  • recordingWindowVariant() correctly maps enableNewRecordingFlow to "new"/"old", and updateRecordingWindowVariant early‑returns when the requested variant matches the current state, which prevents unnecessary writes.
  • The segmented control UI uses aria-pressed, highlights the active option, and persists via updateRecordingWindowVariant, which also marks recordingPickerPreferenceSet: true for analytics/one‑time‑prompt logic. This all looks sound.

Also applies to: 438-465

apps/desktop/src-tauri/src/recording.rs (2)

327-343: Recording input events and camera state tie-in are coherent

  • Adding RecordingInputKind and extending RecordingEvent with InputLost / InputRestored provides a clean, typed bridge to the frontend for device issues.
  • Marking state.camera_in_use = camera_feed.is_some() at actor spawn and resetting both app.disconnected_inputs and app.camera_in_use in handle_recording_end keeps the watcher logic in lib.rs from running against stale state after recordings finish.
  • The macOS actor retry branch that checks is_shareable_content_error(&err) and then reacquires shareable_content before retrying is integrated correctly into the mic‑retry loop.
  • The conditional cleanup of instant recording directories based on delete_instant_recordings_after_upload is guarded by both successful upload and filesystem error logging, which is safe.

Also applies to: 558-563, 691-702, 985-987, 1154-1156


11-14: macOS shareable-content acquisition and retry are well-scoped

  • Wrapping ShareableContent in SendableShareableContent with explicit Send/Sync and a retained() helper keeps the actor interface simple while still using the cached content.
  • acquire_shareable_content_for_target correctly:
    • Fetches cached content (or prewarms),
    • Validates that the target's display is representable in that content via shareable_content_missing_target_display,
    • Performs at most one refresh_shareable_content() before failing with a clear error.
  • is_shareable_content_error's downcast to SourceError and match on AsContentFilter is a narrow, explicit retry condition. AsContentFilter is the only variant indicating transient stale content (returned when content filter creation fails on an already-resolved display), while NoDisplay is a permanent error (display doesn't exist) and should not trigger retry.
  • The error‑loop in start_recording that re‑acquires shareable_content only when is_shareable_content_error is true looks correct and won't spin indefinitely.
apps/desktop/src/routes/in-progress-recording.tsx (1)

479-642: Control bar UX for issues, mic status, and camera preview is coherent

  • The new issue panel, mic icon with live level indicator, and issue button tied to hasRecordingIssue/issuePanelVisible give clear, low‑friction visibility into input problems and backend failures.
  • Disabling pause/resume while hasDisconnectedInput() is true prevents resuming a recording when inputs are in a broken state.
  • Camera preview toggle correctly uses isCameraWindowOpen plus showWindow("Camera")/WebviewWindow.getByLabel("camera") and re‑reads state afterward, so the button reflects the actual window state.
  • The added settings button that opens openRecordingSettingsMenu rounds out the in‑recording device management story without cluttering the main stop/pause/restart controls.
apps/desktop/src-tauri/src/lib.rs (3)

330-401: set_mic_input correctly coordinates feed changes with studio recordings and disconnection state

  • The function now:
    • Reads mic_feed, any active studio handle, and the current selected label without holding the lock across awaits.
    • Short‑circuits when the requested label equals the current selection.
    • If a studio recording is active, first detaches the mic feed (set_mic_feed(None)), then reconfigures the underlying MicFeed (SetInput or RemoveInput), and finally re‑attaches a locked feed to the studio actor when a label is present.
    • Updates selected_mic_label and, if the mic was previously marked as disconnected, clears that flag and emits an InputRestored event.
  • Device‑not‑found errors on restart are handled explicitly and logged via restart_mic_feed, so transient unavailability doesn’t crash the app.

409-490: set_camera_input mirrors mic semantics and keeps camera_in_use consistent

  • Similar to set_mic_input, set_camera_input:
    • Avoids unnecessary work when id equals current_id and camera_in_use is already true.
    • Detaches any studio camera feed (set_camera_feed(None)), then applies RemoveInput or SetInput on the CameraFeed, showing the camera preview window when a camera is selected.
    • For studio recordings, locks the camera feed and re‑attaches it to the actor.
    • Updates selected_camera_id and camera_in_use, clears any Camera entry in disconnected_inputs, and emits InputRestored when appropriate.
  • This ensures that studio and instant recordings see the same camera state, and that the frontend’s input‑issue UI stays in sync with backend state.

178-214: Mic error handling and watcher bootstrapping are wired correctly at startup and restart

  • restart_mic_feed now creates a fresh error channel and spawns spawn_mic_error_handler for the new feed, so error handling continues to work after restarts.
  • In run, mic errors are also wired once the App state is created (spawn_mic_error_handler(app.clone(), mic_error_rx)), and device watchers are started via spawn_device_watchers(app.clone()).
  • AudioInputLevel emission continues to use the same mic_samples_rx channel, so the existing level meter UI stays functional.

Also applies to: 2301-2317, 2477-2492, 2499-2501

apps/desktop/src/routes/target-select-overlay.tsx (2)

387-405: Area-mode RecordingControls correctly guard start and sync capture target

  • In area mode, isValid and committedIsValid enforce the minimum crop size, and startDisabled is wired to props.disabled, so the “Start Recording” button is effectively disabled when the crop is too small.
  • Before starting, the click handler for the primary button checks props.target.variant === "area" and then writes the current area bounds into captureTarget via setOptions, ensuring the global options reflect what’s actually sent to commands.startRecording.
  • The min-size error callout and the SelectionHint overlay provide clear feedback when the selection is too small or hasn’t been drawn yet, which should keep users from accidentally starting a tiny or empty capture.

Also applies to: 520-577, 585-642


5-6: Serialization shapes match exactly—no risk of mismatch

The verification is confirmed:

  • Rust DeviceOrModelID enum uses default externally-tagged representation with serde::Serialize
  • ModelID variant contains a struct that custom-serializes to string format "vid:pid"
  • With externally-tagged enums, this produces JSON: {"DeviceID": "..."} | {"ModelID": "vid:pid"}
  • TypeScript type is { DeviceID: string } | { ModelID: ModelIDType } where ModelIDType = string
  • These match exactly; Specta generates the TS types correctly from the Rust definitions

No serialization mismatch risk exists.

Copy link
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

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a44779e and e0fab01.

📒 Files selected for processing (5)
  • crates/recording/src/feeds/camera.rs (3 hunks)
  • crates/recording/src/feeds/microphone.rs (5 hunks)
  • crates/recording/src/sources/audio_mixer.rs (2 hunks)
  • crates/rendering/src/lib.rs (2 hunks)
  • crates/rendering/src/main.rs (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • crates/recording/src/feeds/camera.rs
🧰 Additional context used
📓 Path-based instructions (2)
**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

**/*.rs: Format Rust code using rustfmt and ensure all Rust code passes workspace-level clippy lints.
Rust modules should be named with snake_case, and crate directories should be in kebab-case.

Files:

  • crates/rendering/src/lib.rs
  • crates/recording/src/sources/audio_mixer.rs
  • crates/recording/src/feeds/microphone.rs
  • crates/rendering/src/main.rs
crates/*/src/**/*

📄 CodeRabbit inference engine (AGENTS.md)

Rust crates should place tests within the src/ and/or a sibling tests/ directory for each crate inside crates/*.

Files:

  • crates/rendering/src/lib.rs
  • crates/recording/src/sources/audio_mixer.rs
  • crates/recording/src/feeds/microphone.rs
  • crates/rendering/src/main.rs
🧬 Code graph analysis (1)
crates/recording/src/feeds/microphone.rs (1)
crates/recording/src/feeds/camera.rs (11)
  • handle (327-470)
  • handle (476-492)
  • handle (498-500)
  • handle (506-523)
  • handle (529-535)
  • handle (541-557)
  • handle (573-609)
  • handle (615-639)
  • handle (645-661)
  • handle (667-679)
  • handle (685-698)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (rust)
🔇 Additional comments (7)
crates/recording/src/sources/audio_mixer.rs (2)

102-104: LGTM: Refactoring improves readability.

The switch from positional to inline named placeholders in the format string makes the code more readable and less error-prone.


119-121: LGTM: Consistent refactoring.

The inline format placeholders maintain consistency with the aresample filter configuration above and improve code clarity.

crates/rendering/src/main.rs (1)

152-152: LGTM! Modern Rust formatting syntax.

The inline variable formatting format!("frame.{extension}") is more readable and idiomatic than the positional placeholder approach. This is a good modernization.

crates/rendering/src/lib.rs (1)

430-430: Verification complete—rename is fully applied.

All to_uv references have been updated to point_to_uv in the MotionBounds implementation. The method definition (line 430) and its only call site (line 516) are consistent with no remaining stale references.

crates/recording/src/feeds/microphone.rs (3)

64-64: LGTM: Label tracking enables proper device reconnection.

The addition of label fields to AttachedState and InputConnected allows tracking device identity across reconnection events, which is essential for the new reconnection logic.

Also applies to: 81-81, 285-285


291-297: LGTM: Reconnection message struct follows established pattern.

The LockedInputReconnected struct mirrors the design from camera.rs and includes all necessary fields for updating the locked feed state during reconnection.


801-818: LGTM: Reconnection handler correctly implements the guard pattern.

The handler properly uses the guard pattern (&& inner.label == msg.label) to avoid nested if statements, which satisfies the clippy lint mentioned in previous reviews. The logic correctly updates the locked state when a device with a matching label reconnects.

Copy link
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: 0

🧹 Nitpick comments (3)
crates/recording/src/feeds/camera.rs (1)

335-498: Locked SetInput reconnection flow is sound; consider reducing duplication with Open path

The Locked branch correctly:

  • Rejects mismatched ids while locked.
  • Spins a new camera setup, asks the actor to update locked state, and only resolves the ready future on success.
  • Interprets Ok(false) and Err(_) from LockedCameraInputReconnected as failures, returning BuildStreamCrashed and stopping the new handle.

This matches the existing Open-path behavior while adding the necessary locking semantics. The Open and Locked branches now duplicate a fair amount of setup (channels, runtime/thread spawn, setup_camera call); extracting this into a shared helper would reduce surface for future drift but isn’t strictly required.

crates/recording/src/studio_recording.rs (1)

719-725: SegmentPipelineFactory mutators correctly propagate new feeds to future segments

Updating self.base_inputs.mic_feed / camera_feed inside SegmentPipelineFactory ensures that subsequent create_next calls use the latest feeds after a pause. The implementation is minimal and precise; if you later add more mutable recording inputs, a shared helper for updating base_inputs could keep this pattern DRY, but it’s fine as‑is.

crates/recording/src/output_pipeline/win.rs (1)

258-263: Consider logging when timestamps go backward.

The defensive fallback to Duration::ZERO on line 259 prevents panics if frames arrive out of order, but duplicate zero timestamps could degrade encoding quality. Consider logging a warning to aid troubleshooting.

 let relative = if let Some(first) = first_timestamp {
-    timestamp.checked_sub(first).unwrap_or(Duration::ZERO)
+    timestamp.checked_sub(first).unwrap_or_else(|| {
+        warn!("Frame timestamp out of order: timestamp={timestamp:?}, first={first:?}");
+        Duration::ZERO
+    })
 } else {
     first_timestamp = Some(timestamp);
     Duration::ZERO
 };
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e0fab01 and ff746c0.

📒 Files selected for processing (4)
  • crates/recording/src/feeds/camera.rs (3 hunks)
  • crates/recording/src/output_pipeline/macos.rs (2 hunks)
  • crates/recording/src/output_pipeline/win.rs (6 hunks)
  • crates/recording/src/studio_recording.rs (4 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

**/*.rs: Format Rust code using rustfmt and ensure all Rust code passes workspace-level clippy lints.
Rust modules should be named with snake_case, and crate directories should be in kebab-case.

Files:

  • crates/recording/src/output_pipeline/win.rs
  • crates/recording/src/studio_recording.rs
  • crates/recording/src/feeds/camera.rs
  • crates/recording/src/output_pipeline/macos.rs
crates/*/src/**/*

📄 CodeRabbit inference engine (AGENTS.md)

Rust crates should place tests within the src/ and/or a sibling tests/ directory for each crate inside crates/*.

Files:

  • crates/recording/src/output_pipeline/win.rs
  • crates/recording/src/studio_recording.rs
  • crates/recording/src/feeds/camera.rs
  • crates/recording/src/output_pipeline/macos.rs
🧠 Learnings (2)
📚 Learning: 2025-10-28T08:39:42.230Z
Learnt from: Brendonovich
Repo: CapSoftware/Cap PR: 1305
File: crates/recording/src/output_pipeline/macos.rs:80-90
Timestamp: 2025-10-28T08:39:42.230Z
Learning: In `crates/recording/src/output_pipeline/macos.rs`, the `AVFoundationMp4Muxer` intentionally holds the `Mutex<MP4Encoder>` lock during retry attempts in `send_video_frame()` and `send_audio_frame()`. This blocking behavior is correct because frame processing must happen sequentially to prevent audio and video frames from being interleaved incorrectly in the encoder.

Applied to files:

  • crates/recording/src/output_pipeline/win.rs
  • crates/recording/src/studio_recording.rs
  • crates/recording/src/feeds/camera.rs
  • crates/recording/src/output_pipeline/macos.rs
📚 Learning: 2025-10-17T05:58:22.586Z
Learnt from: Brendonovich
Repo: CapSoftware/Cap PR: 1219
File: crates/enc-avfoundation/src/mp4.rs:350-373
Timestamp: 2025-10-17T05:58:22.586Z
Learning: In crates/enc-avfoundation/src/mp4.rs, the `finish()` method intentionally skips video extension when `is_paused` is true. This is correct behavior because if recording is paused, the video should not be extended beyond the pause point—the pause is user-initiated, unlike the case where ScreenCaptureKit stops providing frames during static content.

Applied to files:

  • crates/recording/src/output_pipeline/win.rs
  • crates/recording/src/output_pipeline/macos.rs
🧬 Code graph analysis (4)
crates/recording/src/output_pipeline/win.rs (2)
crates/recording/src/output_pipeline/core.rs (7)
  • new (307-325)
  • new (675-677)
  • new (718-720)
  • new (751-753)
  • new (795-812)
  • timestamp (838-838)
  • send_audio_frame (861-861)
crates/recording/src/output_pipeline/ffmpeg.rs (3)
  • timestamp (22-24)
  • send_audio_frame (110-116)
  • send_audio_frame (153-155)
crates/recording/src/studio_recording.rs (2)
crates/recording/src/feeds/camera.rs (2)
  • handle (327-499)
  • handle (505-521)
crates/recording/src/feeds/microphone.rs (9)
  • handle (330-654)
  • handle (660-672)
  • handle (678-680)
  • handle (686-706)
  • handle (722-758)
  • handle (764-776)
  • handle (782-798)
  • handle (804-817)
  • handle (823-836)
crates/recording/src/feeds/camera.rs (2)
apps/desktop/src/utils/tauri.ts (1)
  • DeviceOrModelID (394-394)
crates/recording/src/feeds/microphone.rs (9)
  • handle (330-654)
  • handle (660-672)
  • handle (678-680)
  • handle (686-706)
  • handle (722-758)
  • handle (764-776)
  • handle (782-798)
  • handle (804-817)
  • handle (823-836)
crates/recording/src/output_pipeline/macos.rs (2)
crates/recording/src/output_pipeline/core.rs (2)
  • timestamp (838-838)
  • send_audio_frame (861-861)
crates/recording/src/output_pipeline/ffmpeg.rs (3)
  • timestamp (22-24)
  • send_audio_frame (110-116)
  • send_audio_frame (153-155)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Clippy (aarch64-apple-darwin, macos-latest)
  • GitHub Check: Analyze (rust)
🔇 Additional comments (9)
crates/recording/src/output_pipeline/macos.rs (2)

66-101: Well done implementing the retry limit for video frames.

The bounded retry loop properly addresses the previous review concern about infinite blocking. The timeout of ~1 second (500 retries × 2ms) is reasonable for a queue operation, and the error message clearly indicates the failure mode.

Based on learnings: The approach of holding the mutex lock during retries remains correct for maintaining sequential frame processing.


105-120: Critical: Add a retry limit to prevent infinite blocking in audio path.

The audio frame queueing has the same infinite retry issue that was fixed in the video path (lines 81-98). If the encoder becomes stuck returning NotReadyForMore, this loop will block indefinitely and hang the recording thread.

Apply a retry limit consistent with the video path:

 impl AudioMuxer for AVFoundationMp4Muxer {
     fn send_audio_frame(&mut self, frame: AudioFrame, timestamp: Duration) -> anyhow::Result<()> {
         let mut mp4 = self.0.lock().map_err(|e| anyhow!("{e}"))?;
 
+        const MAX_AUDIO_QUEUE_RETRIES: u32 = 500;
+        let mut retry_count = 0;
+
         loop {
             match mp4.queue_audio_frame(&frame.inner, timestamp) {
                 Ok(()) => break,
                 Err(QueueFrameError::NotReadyForMore) => {
+                    retry_count += 1;
+                    if retry_count >= MAX_AUDIO_QUEUE_RETRIES {
+                        return Err(anyhow!(
+                            "send_audio_frame/timeout after {} retries",
+                            MAX_AUDIO_QUEUE_RETRIES
+                        ));
+                    }
                     std::thread::sleep(Duration::from_millis(2));
                     continue;
                 }
                 Err(e) => return Err(anyhow!("send_audio_frame/{e}")),
             }
         }
 
         Ok(())
     }
 }
⛔ Skipped due to learnings
Learnt from: Brendonovich
Repo: CapSoftware/Cap PR: 1305
File: crates/recording/src/output_pipeline/macos.rs:80-90
Timestamp: 2025-10-28T08:39:42.230Z
Learning: In `crates/recording/src/output_pipeline/macos.rs`, the `AVFoundationMp4Muxer` intentionally holds the `Mutex<MP4Encoder>` lock during retry attempts in `send_video_frame()` and `send_audio_frame()`. This blocking behavior is correct because frame processing must happen sequentially to prevent audio and video frames from being interleaved incorrectly in the encoder.
Learnt from: Brendonovich
Repo: CapSoftware/Cap PR: 1219
File: crates/enc-avfoundation/src/mp4.rs:350-373
Timestamp: 2025-10-17T05:58:22.586Z
Learning: In crates/enc-avfoundation/src/mp4.rs, the `finish()` method intentionally skips video extension when `is_paused` is true. This is correct behavior because if recording is paused, the video should not be extended beyond the pause point—the pause is user-initiated, unlike the case where ScreenCaptureKit stops providing frames during static content.
crates/recording/src/feeds/camera.rs (2)

176-181: LockedCameraInputReconnected event wiring looks consistent with existing feed patterns

The new private event cleanly carries the id, camera/video info, and done_tx needed to refresh the locked camera state, mirroring the microphone’s reconnection message shape. No functional issues here.


693-713: LockedCameraInputReconnected handler correctly updates state and tears down the old capture

The handler’s if let chain on State::Locked and matching id is appropriate, and the sequence:

  • Updates camera_info/video_info,
  • Swaps in the new done_tx and signals the previous one,
  • Returns true/false to reflect whether the locked state still matched,

gives the caller precise feedback and ensures the old capture handle is cleanly stopped. This matches the intended reconnection semantics and aligns with the existing done_tx usage elsewhere in this feed.

crates/recording/src/studio_recording.rs (2)

13-13: Mic/camera feed change messages and error handling are well‑gated

Importing bail and anyhow supports the new SetMicFeed/SetCameraFeed handlers. The handlers:

  • Forbid changing feeds while Recording, with clear bail! messages.
  • Allow updates while Paused by mutating segment_factory’s base inputs.
  • Return a clear error once the recording actor is inactive.

This cleanly enforces the intended “pause before changing inputs” policy without mutating active pipelines.

Also applies to: 224-268


400-412: ActorHandle APIs for changing mic/camera feeds match existing handle patterns

The new set_mic_feed and set_camera_feed methods mirror the existing stop/pause/resume/cancel APIs by wrapping an ask(...) call and propagating the inner anyhow::Result. This keeps the public surface consistent and makes it straightforward for callers to react to state‑gating errors.

crates/recording/src/output_pipeline/win.rs (3)

22-69: Excellent error handling for timestamp adjustments.

The new PauseTracker implementation properly addresses the overflow and underflow concerns from the previous review. The error propagation with descriptive context (lines 46-50, 52-57, 60-65) will aid debugging if timing issues occur, and the shared instance ensures audio/video remain synchronized during pause/unpause operations.


369-381: LGTM: Correct Duration to TimeSpan conversion.

The conversion logic correctly handles Windows TimeSpan's 100-nanosecond tick rate. Saturating arithmetic and clamping to i64::MAX prevent overflow, ensuring valid timestamps even for very long recordings.


96-96: No issues found—pause_flag is correctly shared and synchronized across the pipeline.

The pause_flag is properly shared as an Arc<AtomicBool> instance created in BuildCtx, stored in OutputPipeline, and passed to the muxer. The control layer exposes public pause() and resume() methods that atomically modify the flag with store(), while the muxer's PauseTracker monitors it with atomic load() operations. Both audio and video frames respect the pause state through PauseTracker::adjust(), ensuring consistent synchronization across the pipeline.

Copy link
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: 0

♻️ Duplicate comments (1)
crates/recording/src/feeds/camera.rs (1)

752-777: Locked reconnection handler correctly stages and overwrites state

The LockedCameraInputReconnected handler only proceeds when still locked on the same id, stages the existing stream for pending release, overwrites attachment data, and returns a bool so the caller can decide whether to proceed or tear down. This neatly guards against races where the state changes between reconnect start and completion.

🧹 Nitpick comments (3)
crates/recording/src/feeds/camera.rs (3)

80-132: Pending-release management in AttachedState is coherent; minor cleanup possible

The new, overwrite, stage_pending_release, and finalize_pending_release helpers correctly encapsulate state transitions and ensure previous streams are signaled exactly once. Now that id is read in FinalizePendingRelease, the #[allow(dead_code)] attribute is stale and could be removed.


248-358: spawn_camera_setup unifies setup nicely; consider runtime reuse

The function cleanly centralizes camera setup, ready/error signaling, and locked vs open behavior (including reconnection and cleanup), and the ReadyFuture wiring looks correct. One thing to watch: creating a fresh tokio::Runtime per setup call may be heavier than necessary; if SetInput can be invoked frequently, consider reusing a shared runtime/handle rather than constructing a new one each time.


714-718: InputConnected handler relies solely on the shared ready future

Ignoring the InputConnected payload and instead awaiting the shared ready future from state.connecting is a bit unusual but internally consistent: it keeps all data flow via ReadyFuture and ensures pending releases are finalized via the same helper as in Lock.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5727d98 and e6e84ce.

📒 Files selected for processing (1)
  • crates/recording/src/feeds/camera.rs (9 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

**/*.rs: Format Rust code using rustfmt and ensure all Rust code passes workspace-level clippy lints.
Rust modules should be named with snake_case, and crate directories should be in kebab-case.

Files:

  • crates/recording/src/feeds/camera.rs
crates/*/src/**/*

📄 CodeRabbit inference engine (AGENTS.md)

Rust crates should place tests within the src/ and/or a sibling tests/ directory for each crate inside crates/*.

Files:

  • crates/recording/src/feeds/camera.rs
🧠 Learnings (1)
📚 Learning: 2025-10-28T08:39:42.230Z
Learnt from: Brendonovich
Repo: CapSoftware/Cap PR: 1305
File: crates/recording/src/output_pipeline/macos.rs:80-90
Timestamp: 2025-10-28T08:39:42.230Z
Learning: In `crates/recording/src/output_pipeline/macos.rs`, the `AVFoundationMp4Muxer` intentionally holds the `Mutex<MP4Encoder>` lock during retry attempts in `send_video_frame()` and `send_audio_frame()`. This blocking behavior is correct because frame processing must happen sequentially to prevent audio and video frames from being interleaved incorrectly in the encoder.

Applied to files:

  • crates/recording/src/feeds/camera.rs
🧬 Code graph analysis (1)
crates/recording/src/feeds/camera.rs (1)
crates/recording/src/feeds/microphone.rs (11)
  • handle_input_connected (58-71)
  • new (88-98)
  • handle (330-654)
  • handle (660-672)
  • handle (678-680)
  • handle (686-706)
  • handle (722-758)
  • handle (764-776)
  • handle (782-798)
  • handle (804-817)
  • handle (823-836)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Clippy (aarch64-apple-darwin, macos-latest)
  • GitHub Check: Analyze (rust)
🔇 Additional comments (9)
crates/recording/src/feeds/camera.rs (9)

6-9: Futures imports align with new readiness flow

FutureExt, BoxFuture, and Shared are all used correctly for the shared readiness future pattern; no issues here.


54-71: OpenState::handle_input_connected logic looks sound

Returning bool and handling attach vs reconnect here keeps callers simple, and the stage_pending_release + attach logic is coherent with later finalization calls in Lock and InputConnected.


221-227: Readiness alias and flow enum are a good abstraction

ReadyFuture plus CameraSetupFlow cleanly separate open vs locked setup paths while sharing the underlying machinery; matches the microphone feed pattern well.


233-238: LockedCameraInputReconnected payload is appropriately scoped

Carrying just id, {camera,video}_info, and done_tx is sufficient for reconnection and keeps this message tightly focused on locked-state updates.


244-246: FinalizePendingRelease message keeps cleanup explicit

Using an explicit message keyed by DeviceOrModelID for deferred cleanup is a clear way to finish pending releases after a successful locked reconnect.


508-549: Refactored SetInput handler correctly routes through spawn_camera_setup

Open and locked flows both now use the same setup path and return a future resolving to (CameraInfo, VideoInfo), with appropriate locked-id validation and error propagation. The mapping from ReadyFuture to the public reply type is straightforward and consistent.


563-566: RemoveInput now also cleans up pending releases

Finalizing any pending_release before sending on done_tx ensures both the current and any previously staged streams are signaled, avoiding leaked capture handles.


664-668: Lock path correctly finalizes previous stream after successful connect

Using state.handle_input_connected(..)’s boolean plus a chained let Some(attached) to call finalize_pending_release mirrors the InputConnected flow and ensures we don’t leave the prior camera stream running when locking while a connection is in progress.


779-802: FinalizePendingRelease safely handles both open and locked states

Matching on State::Open vs State::Locked and checking id before calling finalize_pending_release means the pending-release signal is only sent when the attachment still corresponds to the expected device/model, avoiding accidental teardown after unrelated state changes.

@richiemcilroy richiemcilroy merged commit 19cbc64 into main Nov 16, 2025
15 of 16 checks passed
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