WARNING: THIS SITE IS A MIRROR OF GITHUB.COM / IT CANNOT LOGIN OR REGISTER ACCOUNTS / THE CONTENTS ARE PROVIDED AS-IS / THIS SITE ASSUMES NO RESPONSIBILITY FOR ANY DISPLAYED CONTENT OR LINKS / IF YOU FOUND SOMETHING MAY NOT GOOD FOR EVERYONE, CONTACT ADMIN AT ilovescratch@foxmail.com
Skip to content

Conversation

@richiemcilroy
Copy link
Member

@richiemcilroy richiemcilroy commented Nov 17, 2025

Summary by CodeRabbit

  • Style

    • UI shows "Starting..." with inline start progress.
  • New Features

    • Added "initializing" recording state and explicit recording status (pending vs recording).
    • Camera preview: persistent, resizable numeric sizing, scaled controls, smoother corners, and live overlay synchronization with selection.
  • Refactor

    • Project saves are serialized and debounced to coalesce rapid edits and avoid overlapping saves.
    • Recording start/stop flows simplified for cleaner startup and teardown.
  • Performance

    • Faster timeline waveform rendering via precomputed canvas paths.
  • Documentation

    • Added code-formatting guidance (pnpm format, cargo fmt).

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 17, 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 7 minutes and 42 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 6db1d2d and 2f75043.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (13)
  • AGENTS.md (2 hunks)
  • CLAUDE.md (2 hunks)
  • apps/desktop/src-tauri/src/camera.rs (9 hunks)
  • apps/desktop/src/global.d.ts (1 hunks)
  • apps/desktop/src/routes/(window-chrome)/settings/screenshots.tsx (1 hunks)
  • apps/desktop/src/routes/(window-chrome)/setup.tsx (1 hunks)
  • apps/desktop/src/routes/camera.tsx (13 hunks)
  • apps/desktop/src/routes/capture-area.tsx (1 hunks)
  • apps/desktop/src/routes/recordings-overlay.tsx (1 hunks)
  • apps/desktop/src/routes/target-select-overlay.tsx (11 hunks)
  • crates/recording/examples/recording-cli.rs (1 hunks)
  • crates/recording/src/sources/camera.rs (1 hunks)
  • packages/ui-solid/src/auto-imports.d.ts (0 hunks)

Walkthrough

Replaces inline debounced project saves with an explicit scheduler; inlines and relocates tauri camera/mic input logic (adds retries, removes idle restoration); simplifies camera frame forwarding; migrates waveform canvas to Path2D; consolidates muxing/timestamp handling; adds an initializing recording state and RecordingStatus.

Changes

Cohort / File(s) Summary
Editor: debounced project save
apps/desktop/src/routes/editor/context.ts
Replace debounce with explicit scheduler/flush state (PROJECT_SAVE_DEBOUNCE_MS, projectSaveTimeout, saveInFlight, shouldResave, hasPendingProjectSave); add scheduleProjectConfigSave/flushProjectConfig and onCleanup flush; trackStore effects call scheduler.
Waveform canvas & ClipTrack
apps/desktop/src/routes/editor/Timeline/ClipTrack.tsx
Add CANVAS_HEIGHT and waveform scaling, implement Path2D-based createWaveformPath and memoized drawing helpers, precompute segmentOffsets, remove secsPerPixel prop and per-frame waveform math.
Tauri input helpers & recording state
apps/desktop/src-tauri/src/lib.rs, apps/desktop/src-tauri/src/deeplink_actions.rs, apps/desktop/src-tauri/src/windows.rs, apps/desktop/src-tauri/src/recording.rs
Remove apply_* helpers and RecordingSettingsStore; inline logic into set_camera_input/set_mic_input tauri handlers with retry/error handling; remove idle restoration; update camera init/cleanup and CurrentRecording to include RecordingStatus.
Recording UI: initialize/countdown
apps/desktop/src/routes/in-progress-recording.tsx
Add initializing state, transition initializing→recording on status update, adjust adjustedTime/countdown logic, disable controls during initializing/countdown, inline starting UI and countdown display.
Camera preview pages & mutation
apps/desktop/src/routes/camera.tsx
Replace enum-size preview with numeric size state and CameraWindowShape/CameraWindowState; add createCameraMutation, persisted numeric size, mouse-driven resize handlers, scale-based UI transforms and presets.
Camera frame forwarding simplification
crates/recording/src/sources/camera.rs
Replace reattach/retry forwarding with a perpetual async forwarding loop (rx -> video_tx) using wider channel and LogDrop wrapper; remove reattachment logic.
Output pipeline: muxing & timing
crates/recording/src/output_pipeline/core.rs, crates/recording/src/output_pipeline/ffmpeg.rs
Consolidate muxing loop under stop_token.run_until_cancelled; make video_rx mutable in spawn_video_encoder signature; remove Mp4Muxer internal timing fields and frame_duration helper; simplify encoder init and send_video_frame signature.
Examples cleanup
crates/recording/examples/camera_stream.rs
Remove example camera_stream.rs that demonstrated camera feed acquisition and frame reading.
Client typing update
apps/desktop/src/utils/tauri.ts
Replace CameraPreviewSize with numeric size: number; add RecordingStatus type and extend CurrentRecording with status: RecordingStatus; add updateCameraOverlayBounds method.
Camera shader radius & sizing (wgsl / tauri camera)
apps/desktop/src-tauri/src/camera.wgsl, apps/desktop/src-tauri/src/camera.rs, apps/desktop/src/routes/camera.tsx
Change corner radius calc to mix interpolation; clamp numeric camera size with MIN/MAX/DEFAULT constants; update shader uniforms and sizing logic; adapt get_state/set_state to clamp size.
Recording readiness adjustments
crates/recording/src/feeds/camera.rs
Broaden OpenState readiness predicate, add logging on sender add/remove, and ensure awaiting listeners are flushed on connect failure.
Target-select overlay & camera overlay sync
apps/desktop/src/routes/target-select-overlay.tsx, apps/desktop/src-tauri/src/target_select_overlay.rs
Add camera window discovery and live overlay bounds sync (requestAnimationFrame loop + update_camera_overlay_bounds Tauri command); add onRecordingStart prop to RecordingControls; revertCamera logic and cleanup integration.
Camera preview Tauri API sizing
apps/desktop/src-tauri/src/camera.rs
Replace CameraPreviewSize enum with numeric sizing constants and CameraPreviewState.size: f32; add clamp helper, Default impl, clamp on get/set, and adjust resize calculations.
Settings UI conditional toggle
apps/desktop/src/routes/(window-chrome)/settings/experimental.tsx
Render "Native camera preview" toggle only when OS type() is not "windows".
Cursor animation default
crates/project/src/configuration.rs
Change default CursorAnimationStyle from Slow to Mellow (#[default] moved).
Docs: formatting guidance
AGENTS.md, CLAUDE.md
Add "Code Formatting" guidance recommending pnpm format and cargo fmt.
Auto-imports typing removal
packages/ui-solid/src/auto-imports.d.ts
Remove many global icon auto-import typings from the declaration file.
Timestamps: checked_duration_since
crates/timestamp/src/lib.rs, crates/timestamp/src/macos.rs, crates/timestamp/src/win.rs
Add checked_duration_since helpers for Timestamp and platform variants to return Option on underflow.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant Editor as Editor Context
    participant Scheduler as Save Scheduler
    participant Commands as commands.setProjectConfig

    Editor->>Scheduler: trackStore change -> scheduleProjectConfigSave()
    Scheduler->>Scheduler: start/refresh timeout (250ms)
    alt no saveInFlight
        Scheduler->>Commands: flushProjectConfig()
        Commands-->>Scheduler: save result
        Scheduler->>Scheduler: saveInFlight = false
    else saveInFlight
        Scheduler->>Scheduler: shouldResave = true
    end
    alt shouldResave
        Scheduler->>Scheduler: scheduleProjectConfigSave()
    end
    Note over Editor,Scheduler: onCleanup -> flushProjectConfig()
Loading
sequenceDiagram
    autonumber
    participant App as Tauri App
    participant Lib as lib.rs (set_* handlers)
    participant Camera as Camera Feed

    App->>Lib: start_recording()
    Lib->>Camera: set_camera_input (init with up-to-3 retries)
    alt camera init succeeds
        Camera-->>Lib: ready
        Lib->>App: recording started (status: Recording)
    else fail
        Lib-->>App: error
    end
Loading
sequenceDiagram
    autonumber
    participant Pipeline as Output Pipeline
    participant Encoder as spawn_video_encoder
    participant Muxer as Mp4Muxer

    Pipeline->>Encoder: spawn_video_encoder(mut video_rx, stop_token)
    loop until stop_token cancelled
        Encoder->>Encoder: recv frame from video_rx
        alt frame received
            Encoder->>Muxer: send_video_frame(frame, timestamp)
        end
    end
    Note over Encoder: cancellation -> clean shutdown (stop muxer)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Areas needing extra attention:
    • apps/desktop/src-tauri/src/lib.rs & deeplink_actions.rs — inlined set_* logic, retry loops, and exported surface changes.
    • apps/desktop/src/routes/editor/context.ts — scheduling/serialization, timer cleanup, and potential race conditions.
    • crates/recording/src/output_pipeline/{core.rs,ffmpeg.rs} — timestamp semantics, altered signatures, and encoder shutdown path.
    • crates/recording/src/sources/camera.rs — forwarding loop behavior and backpressure handling.
    • apps/desktop/src/routes/camera.tsx & tauri camera sizing/shader — persisted numeric sizing, resize handlers, and shader uniform changes.

Possibly related PRs

Suggested reviewers

  • oscartbeaumont
  • Brendonovich

Poem

🐇 I debounce no more — I schedule and save,
I set the cam, make waves on a canvas brave,
Frames hop through loops and muxers hum along,
The overlay shifts to fit the chosen song,
Hop, flush, retry — the rabbit nods, well done.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 44.07% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title check ❓ Inconclusive The title 'Various features + performance bits' is vague and generic, using non-descriptive terms that don't convey specific information about the changeset. Provide a more specific title that highlights the primary change. For example: 'refactor: Improve recording initialization flow and camera overlay handling' or similar, reflecting the main focus of the substantial changes across multiple systems.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

🧹 Nitpick comments (3)
apps/desktop/src-tauri/src/recording.rs (3)

34-35: Pruning helper is sound; consider documenting semantics

prune_excluded_windows_without_shareable_content is implemented safely: using mem::take avoids in-place mutation pitfalls, and dropping any IDs that can’t be resolved or don’t appear in the current ShareableContent (with a removed counter) is exactly what you want for keeping the exclusion list in sync.

As this will be subtle to reason about later (it removes both “never-resolvable” and “no longer present” windows), a short doc comment explaining that behavior would help future maintainers.

Also applies to: 68-69, 191-230


695-715: macOS excluded-window initialization and pruning fit well into the actor setup

Initializing excluded_windows from general settings, resolving them via resolve_window_ids, and immediately calling prune_excluded_windows_without_shareable_content against the freshly acquired shareable_content ensures the pipeline starts with a minimal, valid exclusion set. That should reduce the chance of later ScreenCaptureKit filter errors and keep the occlusion behavior aligned with the UI settings.

One thing to keep in mind for future performance work: all of this currently runs while the state write lock is held inside the actor-spawn path. If lock contention around recording startup ever shows up in profiling, this block (resolving/pruning exclusions and acquiring shareable content) would be a good candidate to move outside the critical section.

Also applies to: 717-721


840-851: Guard against unbounded retries on shareable-content errors

On macOS, is_shareable_content_error triggers a re-acquire of shareable_content and re-pruning of excluded_windows, then continue-s the loop. If SourceError::AsContentFilter keeps occurring even after refresh (for example due to a persistent OS-level issue), this loop can theoretically retry forever.

Consider adding a small attempt counter or time-based cutoff for this specific retry path, and after exceeding it, surface a more explicit error to the user instead of looping indefinitely. This keeps the behavior robust even when ScreenCaptureKit misbehaves.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bd1fc73 and 5d95f24.

📒 Files selected for processing (1)
  • apps/desktop/src-tauri/src/recording.rs (8 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.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:

  • apps/desktop/src-tauri/src/recording.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:

  • apps/desktop/src-tauri/src/recording.rs
🧬 Code graph analysis (1)
apps/desktop/src-tauri/src/recording.rs (3)
crates/recording/src/sources/screen_capture/mod.rs (1)
  • window (74-79)
crates/scap-targets/src/platform/macos.rs (1)
  • from_id (48-51)
crates/scap-targets/src/lib.rs (2)
  • from_id (32-34)
  • from_id (122-124)
⏰ 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 (1)
apps/desktop/src-tauri/src/recording.rs (1)

111-147: Shareable content acquisition and missing-window handling look correct

The new acquire_shareable_content_for_target logic plus shareable_content_missing_windows gives a clear, bounded retry: one refresh on stale ScreenCaptureKit content, with DisplayMissing surfaced cleanly and excluded-window mismatches tolerated after a refresh. The distinction between display-missing and window-missing cases, and only treating known-but-unresolved windows as “missing”, matches the later pruning behavior and should avoid spurious failures.

Also applies to: 164-189

@richiemcilroy richiemcilroy changed the title feat: Editor performance improvements feat: Misc performance and UX fixes Nov 17, 2025
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 (1)
apps/desktop/src/routes/editor/Timeline/ClipTrack.tsx (1)

46-94: Consider defensive bounds checking and add documentation.

The waveform path generation logic is sophisticated but could be clearer:

  1. Line 82: amplitudeAt(index - 1) on the first iteration may access waveform[-1] when segment.start is 0. While safely handled by the amplitudeAt function's type checks, consider clamping: Math.max(index - 1, 0).

  2. The bezier curve construction with control points and the coordinate normalization could benefit from inline comments explaining the approach.

Apply this diff to add bounds checking:

 		const prevY = 1 - amplitudeAt(index - 1);
+		const prevY = 1 - amplitudeAt(Math.max(index - 1, 0));
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5d95f24 and dbe8ae1.

📒 Files selected for processing (1)
  • apps/desktop/src/routes/editor/Timeline/ClipTrack.tsx (4 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{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/editor/Timeline/ClipTrack.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/editor/Timeline/ClipTrack.tsx
🧬 Code graph analysis (1)
apps/desktop/src/routes/editor/Timeline/ClipTrack.tsx (3)
crates/editor/src/audio.rs (1)
  • gain (63-65)
crates/project/src/configuration.rs (2)
  • duration (526-528)
  • duration (591-593)
crates/editor/src/segments.rs (1)
  • segments (7-36)
⏰ 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: Analyze (javascript-typescript)
  • GitHub Check: Analyze (rust)
🔇 Additional comments (5)
apps/desktop/src/routes/editor/Timeline/ClipTrack.tsx (5)

33-44: LGTM! Good constants and scaling logic.

The waveform constants are well-defined and the gainToScale function correctly converts decibel gain to a linear scale factor for visualization purposes.


110-171: Excellent Path2D-based rendering optimization!

The refactor to use memoized Path2D objects with transform-based scaling is a solid performance improvement over per-frame waveform calculations. The reactive dependencies are correctly tracked.

Minor note: Line 136's canvas.width = canvasWidth already clears the canvas, making line 138's clearRect redundant (though harmless).


196-205: LGTM! Excellent performance optimization.

Precomputing segment offsets eliminates redundant calculations across multiple segments. The cumulative offset logic correctly accounts for timescale and provides O(1) lookups.


233-233: LGTM! Clean integration with segmentOffsets.

Using the precomputed offsets is more efficient and the nullish coalescing provides good safety.


489-495: LGTM! Appropriate conditional rendering.

Limiting waveform rendering to segments with timescale === 1 is sensible, as time-stretched segments would require different waveform handling.

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

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

53-66: Consider adding logging when reattachment occurs.

The reattachment logic provides good resilience when the camera sender disconnects, but there's no info-level log when reattachment succeeds. This makes it difficult to diagnose intermittent disconnection issues in production.

Consider adding an info log after successful reattachment:

                        Err(_) => {
                            let (new_tx, new_rx) = flume::bounded(8);

                            if let Err(err) = feed_lock.ask(camera::AddSender(new_tx)).await {
                                warn!(
                                    ?err,
                                    "Camera sender disconnected and could not be reattached"
                                );
                                break Err(err);
                            }

+                           info!(
+                               frame_count,
+                               "Camera sender disconnected; successfully reattached"
+                           );
                            receiver = new_rx;
                        }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dbe8ae1 and 37eb7c8.

📒 Files selected for processing (2)
  • .claude/settings.local.json (1 hunks)
  • crates/recording/src/sources/camera.rs (2 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/sources/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/sources/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/sources/camera.rs
🧬 Code graph analysis (1)
crates/recording/src/sources/camera.rs (1)
crates/recording/src/feeds/camera.rs (1)
  • drop (175-179)
⏰ 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 (3)
.claude/settings.local.json (1)

7-8: Approve: Configuration update is consistent.

The addition of "Bash(cargo build:*)" follows the established pattern of wildcard permissions for build tool operations and complements the existing "Bash(cargo check:*)" entry.

crates/recording/src/sources/camera.rs (2)

37-37: LGTM! Good observability improvement.

The frame counting and enhanced error logging provide valuable diagnostics for tracking camera forwarding behavior. Including the frame count in error logs will help identify whether issues occur at startup or during sustained operation.

Also applies to: 42-50


68-76: LGTM! Explicit cleanup with observable logging.

The explicit drop statements and final info log provide clear lifecycle boundaries and telemetry. This makes it easier to diagnose camera forwarding issues and ensures resources are cleaned up promptly.

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

Caution

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

⚠️ Outside diff range comments (1)
crates/recording/src/output_pipeline/core.rs (1)

429-482: mux-video now swallows video encoder errors; consider logging or propagating them

The new mux-video task:

stop_token
    .run_until_cancelled(async {
        while let Some(frame) = video_rx.next().await {
            let timestamp = frame.timestamp();
            if let Some(first_tx) = first_tx.take() {
                let _ = first_tx.send(timestamp);
            }

            muxer
                .lock()
                .await
                .send_video_frame(frame, timestamp.duration_since(timestamps))
                .map_err(|e| anyhow!("Error queueing video frame: {e}"))?;
        }

        Ok::<(), anyhow::Error>(())
    })
    .await;

muxer.lock().await.stop();

Ok(())

turns any send_video_frame failure into an Err, but that error is discarded because the run_until_cancelled result is ignored and the outer future always returns Ok(()). This means:

  • The TaskPool will log “Task finished successfully” even if the video encoder failed mid-stream.
  • finish_build will only see muxer-level errors from finish(...), not from the streaming side, and you lose visibility into where things went wrong.

For parity with the audio path (which logs Audio encoder: {e}) and better diagnosability, I’d recommend at least logging and breaking on error, e.g.:

-    setup_ctx.tasks().spawn("mux-video", async move {
-        use futures::StreamExt;
-
-        let mut first_tx = Some(first_tx);
-
-        stop_token
-            .run_until_cancelled(async {
-                while let Some(frame) = video_rx.next().await {
-                    let timestamp = frame.timestamp();
-
-                    if let Some(first_tx) = first_tx.take() {
-                        let _ = first_tx.send(timestamp);
-                    }
-
-                    muxer
-                        .lock()
-                        .await
-                        .send_video_frame(frame, timestamp.duration_since(timestamps))
-                        .map_err(|e| anyhow!("Error queueing video frame: {e}"))?;
-                }
-
-                Ok::<(), anyhow::Error>(())
-            })
-            .await;
-
-        muxer.lock().await.stop();
-
-        Ok(())
-    });
+    setup_ctx.tasks().spawn("mux-video", async move {
+        use futures::StreamExt;
+
+        let mut first_tx = Some(first_tx);
+
+        stop_token
+            .run_until_cancelled(async {
+                while let Some(frame) = video_rx.next().await {
+                    let timestamp = frame.timestamp();
+
+                    if let Some(first_tx) = first_tx.take() {
+                        let _ = first_tx.send(timestamp);
+                    }
+
+                    let duration = timestamp.duration_since(timestamps);
+                    if let Err(e) = muxer.lock().await.send_video_frame(frame, duration) {
+                        error!("Video encoder: {e:#}");
+                        break;
+                    }
+                }
+            })
+            .await;
+
+        muxer.lock().await.stop();
+
+        Ok(())
+    });

This keeps the cancellation-driven structure while ensuring video encoder failures are visible in logs and will still cause the pipeline to stop cleanly. It also aligns behavior with the audio mux loop, which logs but does not silently ignore errors. Based on learnings.

🧹 Nitpick comments (3)
apps/desktop/src/routes/camera.tsx (1)

25-25: Verify that the new camera mutation-based close behavior matches the intended UX

Switching the “X” controls to setCamera.mutate(null) decouples them from getCurrentWindow().close() and makes them purely “clear the selected camera” actions.

Two things to double-check:

  • Native preview window: NativeCameraPreviewPage no longer has any logic that closes the Tauri window when the camera is cleared. If the desired behavior is still “close the floating camera window” when the user clicks X, you may want to either:

    • explicitly close the window after a successful mutate(null), or
    • add a small effect that observes the relevant recording option (e.g. camera ID/label) and closes on null, similar to the legacy path.
  • Legacy preview window: the X button now relies on createCameraMutation indirectly driving rawOptions.cameraLabel to null for getCurrentWindow().close() to fire (lines 273–281). Please confirm that the options store wiring actually clears cameraLabel when cameraID is set to null; otherwise the X button will stop closing the legacy window as well.

If the new UX is intentionally “keep the window around but just stop using the camera”, consider also updating the legacy window behavior to avoid the asymmetric handling between the two code paths.

Also applies to: 91-91, 104-104, 271-271, 297-297

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

25-35: Break out of the forwarding loop when video_tx.send fails to avoid silent drops

The new forwarding task keeps reading from rx even if video_tx.send(frame).await starts failing (e.g., muxing side is shut down). Because the send result is ignored, frames are silently dropped and the task stays alive until the flume sender is dropped.

Consider treating a send error as an exit condition:

tokio::spawn(async move {
-    while let Ok(frame) = rx.recv_async().await {
-        let _ = video_tx.send(frame).await;
-    }
+    while let Ok(frame) = rx.recv_async().await {
+        if video_tx.send(frame).await.is_err() {
+            // Downstream is gone; stop forwarding.
+            break;
+        }
+    }
});

This keeps the behavior simple while avoiding unnecessary work and making frame loss semantics more predictable.

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

2540-2549: Reusing set_mic_input / set_camera_input for startup is a good unification

Calling set_mic_input(app.state(), settings.mic_name) and set_camera_input(app.clone(), app.state(), settings.camera_id) here makes the “start recording from event” path share the exact same logic as the explicit commands, which is nice for consistency.

You currently discard the Result with let _ = …;. If failures here should be visible (e.g., to help debug why a recording started without the expected mic/camera), consider at least logging the error:

if let Err(e) = set_mic_input(app.state(), settings.mic_name).await {
    warn!("Failed to apply mic input before recording: {e}");
}

Same idea for set_camera_input. If best-effort behavior is intentional, the current code is fine.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8b403a8 and 184ad59.

📒 Files selected for processing (9)
  • apps/desktop/src-tauri/src/deeplink_actions.rs (2 hunks)
  • apps/desktop/src-tauri/src/lib.rs (1 hunks)
  • apps/desktop/src-tauri/src/recording.rs (0 hunks)
  • apps/desktop/src-tauri/src/windows.rs (1 hunks)
  • apps/desktop/src/routes/camera.tsx (5 hunks)
  • crates/recording/examples/camera_stream.rs (0 hunks)
  • crates/recording/src/output_pipeline/core.rs (2 hunks)
  • crates/recording/src/output_pipeline/ffmpeg.rs (2 hunks)
  • crates/recording/src/sources/camera.rs (1 hunks)
💤 Files with no reviewable changes (2)
  • apps/desktop/src-tauri/src/recording.rs
  • crates/recording/examples/camera_stream.rs
🧰 Additional context used
📓 Path-based instructions (4)
**/*.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/sources/camera.rs
  • crates/recording/src/output_pipeline/ffmpeg.rs
  • apps/desktop/src-tauri/src/windows.rs
  • apps/desktop/src-tauri/src/lib.rs
  • crates/recording/src/output_pipeline/core.rs
  • apps/desktop/src-tauri/src/deeplink_actions.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/sources/camera.rs
  • crates/recording/src/output_pipeline/ffmpeg.rs
  • crates/recording/src/output_pipeline/core.rs
**/*.{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/camera.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/camera.tsx
🧠 Learnings (3)
📓 Common 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.
📚 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/sources/camera.rs
  • crates/recording/src/output_pipeline/ffmpeg.rs
  • crates/recording/src/output_pipeline/core.rs
  • apps/desktop/src-tauri/src/deeplink_actions.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/ffmpeg.rs
  • crates/recording/src/output_pipeline/core.rs
🧬 Code graph analysis (4)
crates/recording/src/output_pipeline/ffmpeg.rs (1)
crates/enc-ffmpeg/src/video/h264.rs (1)
  • builder (234-236)
crates/recording/src/output_pipeline/core.rs (3)
crates/recording/src/output_pipeline/ffmpeg.rs (1)
  • timestamp (22-24)
crates/recording/src/sources/screen_capture/windows.rs (1)
  • timestamp (63-65)
crates/recording/src/sources/screen_capture/macos.rs (1)
  • timestamp (61-63)
apps/desktop/src-tauri/src/deeplink_actions.rs (1)
apps/desktop/src-tauri/src/lib.rs (8)
  • set_camera_input (413-490)
  • app (1522-1523)
  • app (2582-2582)
  • app (2613-2613)
  • app (2674-2674)
  • app (2680-2680)
  • app (2904-2905)
  • set_mic_input (333-401)
apps/desktop/src/routes/camera.tsx (1)
apps/desktop/src/utils/queries.ts (1)
  • createCameraMutation (195-236)
⏰ 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 (3)
apps/desktop/src-tauri/src/windows.rs (1)

23-30: Imports look consistent with current usages

The updated use crate::{ App, ArcLock, RequestScreenCapturePrewarm, fake_window, …, recording_settings::RecordingTargetMode, … } set matches how these types are used later in the file. Nothing to fix here.

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

9-10: Deep link start-recording now correctly reuses the shared input setters

Using App/ArcLock<App> plus:

let state = app.state::<ArcLock<App>>();
crate::set_camera_input(app.clone(), state.clone(), camera).await?;
crate::set_mic_input(state.clone(), mic_label).await?;

before building StartRecordingInputs cleanly aligns deep-link initiated recordings with the same mic/camera selection logic used elsewhere in the app. This should reduce divergence and subtle bugs between entry points.

The error propagation via ? also keeps handling consistent: if either setter fails, the deep link action surfaces a Result::Err back to the caller.

Also applies to: 117-145

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

49-52: MP4 muxer setup and timestamp handling look consistent with the refactor

  • The new video encoder initialization:

    let video_encoder = video_config
        .map(|video_config| H264Encoder::builder(video_config).build(&mut output))
        .transpose()
        .context("video encoder")?;

    correctly mirrors the audio path (AACEncoder::init) and yields a Result<Option<H264Encoder>>.

  • send_video_frame now simply forwards the Duration it’s given into queue_frame, with timestamp derivation handled by the caller (see spawn_video_encoder in core.rs), which keeps muxer responsibilities narrow and consistent with send_audio_frame.

No changes needed from this diff.

Also applies to: 96-103

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

🧹 Nitpick comments (2)
apps/desktop/src/routes/camera.tsx (2)

25-25: Remove unused camera mutation declaration.

The createCameraMutation import and setCamera variable are declared but never used in NativeCameraPreviewPage. This appears to be dead code.

Apply this diff to remove the unused code:

-import { createCameraMutation } from "~/utils/queries";
-	const setCamera = createCameraMutation();
-

Also applies to: 91-91


271-271: Remove unused camera mutation declaration.

The setCamera variable is declared but never used in LegacyCameraPreviewPage. This appears to be dead code.

Apply this diff to remove the unused code:

-	const setCamera = createCameraMutation();
-
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 184ad59 and 41fbc5c.

📒 Files selected for processing (7)
  • apps/desktop/src-tauri/src/lib.rs (7 hunks)
  • apps/desktop/src-tauri/src/recording.rs (2 hunks)
  • apps/desktop/src-tauri/src/windows.rs (2 hunks)
  • apps/desktop/src/routes/camera.tsx (5 hunks)
  • apps/desktop/src/routes/in-progress-recording.tsx (9 hunks)
  • apps/desktop/src/utils/tauri.ts (2 hunks)
  • crates/recording/src/feeds/camera.rs (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/desktop/src-tauri/src/windows.rs
  • apps/desktop/src-tauri/src/lib.rs
🧰 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/utils/tauri.ts
  • apps/desktop/src/routes/camera.tsx
  • apps/desktop/src/routes/in-progress-recording.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/utils/tauri.ts
  • apps/desktop/src/routes/camera.tsx
  • apps/desktop/src/routes/in-progress-recording.tsx
**/tauri.ts

📄 CodeRabbit inference engine (AGENTS.md)

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

Files:

  • apps/desktop/src/utils/tauri.ts
**/*.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
  • apps/desktop/src-tauri/src/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/feeds/camera.rs
🧠 Learnings (2)
📓 Common 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.
📚 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:

  • apps/desktop/src-tauri/src/recording.rs
🧬 Code graph analysis (3)
apps/desktop/src/routes/camera.tsx (1)
apps/desktop/src/utils/queries.ts (1)
  • createCameraMutation (195-236)
apps/desktop/src-tauri/src/recording.rs (1)
apps/desktop/src-tauri/src/windows.rs (6)
  • id (779-805)
  • app (215-215)
  • app (372-372)
  • app (462-462)
  • app (785-785)
  • app (1006-1006)
apps/desktop/src/routes/in-progress-recording.tsx (1)
apps/desktop/src/utils/tauri.ts (3)
  • stopRecording (20-22)
  • restartRecording (29-31)
  • deleteRecording (32-34)
⏰ 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 (9)
apps/desktop/src/routes/camera.tsx (1)

104-104: LGTM!

Removing the void operator from getCurrentWindow().close() is cleaner and doesn't change behavior.

Also applies to: 297-297

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

386-386: LGTM!

The addition of RecordingStatus type and status field to CurrentRecording correctly extends the type system to support the new pending/recording state tracking.

Also applies to: 459-459

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

729-753: LGTM!

The addition of explicit cleanup in InputConnectFailed (clearing connecting and draining on_ready) ensures that listeners are properly notified when a connection attempt fails, making the feed available for new operations.


592-603: This is a correct and intentional semantic change — no issues found.

The relaxed pattern matching at lines 592-603 is a legitimate improvement, not a race condition risk. The change allows ListenForReady to signal readiness once a connection attempt completes (connecting: None), rather than requiring the feed to be completely idle (connected: None AND attached: None).

This aligns with the semantics: the only direct caller (await_camera_preview_ready) is a Tauri UI command waiting for the camera preview to become displayable, not for the feed to be completely disconnected. The function name itself clarifies the intent—"preview ready," not "feed idle."

The code correctly fires ready notifications (line 721-722) after connection finalization, and no callers make assumptions about the attached state being None when the signal fires.

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

550-599: LGTM! Camera initialization logic is more robust.

The new staged initialization correctly handles the case where a camera is selected but not yet initialized. The flow is:

  1. Attempt to lock the camera (optimistic path)
  2. If it fails with NoInput and a camera ID is selected, initialize via SetInput and retry the lock
  3. Handle and log errors appropriately at each stage

The nested matching is complex but correct. The logic properly sets camera_in_use based on whether a camera feed lock was obtained.


1074-1082: LGTM! Input state cleanup is correct.

Clearing selected_mic_label, selected_camera_id, and camera_in_use when the main window is absent ensures clean state management. This correctly handles the case where the recording session is ending without the main window being available to retain the input selections.

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

40-40: LGTM! Initializing state improves UX.

The addition of an "initializing" state variant provides better feedback during the transition from countdown to actual recording, improving the user experience.

Also applies to: 60-68


481-489: LGTM! Helper functions improve readability.

The helper functions (isInitializing, isCountdown, countdownCurrent) and the updated adjustedTime logic correctly handle the new initializing state.

Also applies to: 518-523


547-547: LGTM! UI updates properly reflect initialization states.

The UI changes correctly:

  • Add visual polish with border and shadow styling
  • Disable controls appropriately during initializing and countdown states
  • Show contextual feedback ("Starting...", countdown progress)
  • Improve user experience by preventing premature actions

Also applies to: 552-575, 625-660

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)
apps/desktop/src/routes/in-progress-recording.tsx (1)

193-203: Remove the unsafe type cast (duplicate issue).

This is the same issue flagged in the previous review. The as CurrentRecording cast on line 195 bypasses TypeScript's type safety and could lead to runtime errors if the data structure changes.

Apply this diff to use safer optional chaining:

 createEffect(() => {
   if (state().variant === "initializing") {
-    const recording = currentRecording.data as CurrentRecording;
-    if (recording?.status === "recording") {
+    if (currentRecording.data?.status === "recording") {
       setDisconnectedInputs({ microphone: false, camera: false });
       setRecordingFailure(null);
       setState({ variant: "recording" });
       setStart(Date.now());
     }
   }
 });
🧹 Nitpick comments (1)
apps/desktop/src/routes/in-progress-recording.tsx (1)

552-554: Consider disabling pause, restart, and delete during initializing state.

The stop button is disabled during both initializing and countdown states (lines 552-554), but the pause (lines 626-630), restart (line 652), and delete (line 660) buttons only check for countdown. For consistency and to prevent potential issues during the initialization phase, consider also disabling these buttons when isInitializing() is true.

Apply this diff to add initializing checks:

 <ActionButton
   disabled={
     togglePause.isPending ||
     hasDisconnectedInput() ||
-    isCountdown()
+    isCountdown() ||
+    isInitializing()
   }
   onClick={() => togglePause.mutate()}
 <ActionButton
-  disabled={restartRecording.isPending || isCountdown()}
+  disabled={restartRecording.isPending || isCountdown() || isInitializing()}
   onClick={() => restartRecording.mutate()}
 <ActionButton
-  disabled={deleteRecording.isPending || isCountdown()}
+  disabled={deleteRecording.isPending || isCountdown() || isInitializing()}
   onClick={() => deleteRecording.mutate()}

Also applies to: 626-630, 652-652, 660-660

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 41fbc5c and 44a5348.

📒 Files selected for processing (1)
  • apps/desktop/src/routes/in-progress-recording.tsx (9 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{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/in-progress-recording.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/in-progress-recording.tsx
🧬 Code graph analysis (1)
apps/desktop/src/routes/in-progress-recording.tsx (1)
apps/desktop/src/utils/tauri.ts (4)
  • CurrentRecording (386-386)
  • stopRecording (20-22)
  • restartRecording (29-31)
  • deleteRecording (32-34)
⏰ 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)

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

🧹 Nitpick comments (7)
apps/desktop/src/routes/(window-chrome)/settings/experimental.tsx (1)

66-75: OS‑gating the native camera preview toggle

Conditionally hiding the “Native camera preview” toggle when the OS type is "windows" is a good UX guard for a feature that’s unreliable on that platform. The inline type() call is cheap, but if this component re-renders frequently you could cache the OS type at module scope or in a small helper to avoid repeated calls.

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

44-59: State clamping and serialization migration behavior

The new numeric CameraPreviewState::size plus clamp_size usage in get_state, set_state, and CursorConfiguration::default give a clear, bounded size model and ensure uniforms see a normalized [0, 1] value. That part looks solid.

One side effect: existing JSON in the "cameraPreview" store that was serialized with the old enum‑based size will fail to deserialize into the new struct and will fall back to CameraPreviewState::default() via unwrap_or_default(). That means any previously stored shape/mirrored overrides will be lost on first read.

If preserving previous user‑chosen shape/mirroring is important, consider a small migration path (e.g., read the raw JSON and map legacy string values into the new struct) instead of relying solely on unwrap_or_default(). Otherwise, the current behavior is acceptable as a reset to sane defaults.

Also applies to: 82-92, 96-102, 463-488

apps/desktop/src-tauri/src/camera.wgsl (1)

102-106: Continuous corner-radius interpolation matches normalized size but comment is stale

Using mix(0.10, 0.14, size) for the square shape and mix(0.08, 0.12, size) for the full shape is a nice continuous generalization of the previous small/large corner radii and lines up with the host side now sending a normalized [0, 1] size uniform.

The top comment still says // Size constants: 0 = Sm, 1 = Lg, which no longer reflects the continuous range semantics. Consider updating that comment to describe size as a normalized 0‑1 value derived from [MIN_CAMERA_SIZE, MAX_CAMERA_SIZE] to avoid confusion for future shader changes.

Also applies to: 127-129, 142-144

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

121-173: Consider extracting resize logic to reduce duplication.

The resize handlers (handleResizeStart, handleResizeMove, handleResizeEnd) and the effect that manages event listeners are duplicated in LegacyCameraPreviewPage (lines 355-407). This represents significant code duplication that could be extracted into a shared hook or utility function.

Consider creating a custom hook like createResizeHandler that returns these handlers and manages the state:

function createResizeHandler(
  state: { size: number },
  setState: (key: "size", value: number) => void,
  minSize: number,
  maxSize: number
) {
  const [isResizing, setIsResizing] = createSignal(false);
  const [resizeStart, setResizeStart] = createSignal({
    size: 0,
    x: 0,
    y: 0,
    corner: "",
  });

  // ... rest of the logic
  
  return { handleResizeStart, isResizing };
}

260-272: Clarify or remove unused active prop.

The active prop is defined in the component signature but never used. If it's intended for future use or external access, consider adding a comment. Otherwise, remove it to avoid confusion.


349-353: Extract duplicate scale function.

This scale function is identical to the one in NativeCameraPreviewPage (lines 115-119). Consider extracting it to a shared location to eliminate duplication.

Define it once at the module level:

function calculateScale(
  size: number,
  minSize: number = CAMERA_MIN_SIZE,
  maxSize: number = CAMERA_MAX_SIZE
) {
  const normalized = (size - minSize) / (maxSize - minSize);
  return 0.7 + normalized * 0.3;
}

306-323: Simplify imageDataHandler to reduce side effects.

The function has multiple responsibilities: updating frame state, updating dimensions, and rendering to canvas. Consider separating concerns:

  1. Frame data updates
  2. Dimension tracking
  3. Canvas rendering (could be handled by a separate effect)

This would improve testability and make the data flow clearer.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 44a5348 and dd39425.

📒 Files selected for processing (6)
  • apps/desktop/src-tauri/src/camera.rs (5 hunks)
  • apps/desktop/src-tauri/src/camera.wgsl (2 hunks)
  • apps/desktop/src/routes/(window-chrome)/settings/experimental.tsx (2 hunks)
  • apps/desktop/src/routes/camera.tsx (1 hunks)
  • apps/desktop/src/utils/tauri.ts (3 hunks)
  • crates/project/src/configuration.rs (1 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
**/*.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/project/src/configuration.rs
  • apps/desktop/src-tauri/src/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/project/src/configuration.rs
**/*.{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/utils/tauri.ts
  • apps/desktop/src/routes/camera.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/utils/tauri.ts
  • apps/desktop/src/routes/camera.tsx
**/tauri.ts

📄 CodeRabbit inference engine (AGENTS.md)

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

Files:

  • apps/desktop/src/utils/tauri.ts
🧠 Learnings (1)
📚 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 **/tauri.ts : Do not edit auto-generated files named `tauri.ts`.

Applied to files:

  • apps/desktop/src/utils/tauri.ts
🧬 Code graph analysis (3)
apps/desktop/src/routes/(window-chrome)/settings/experimental.tsx (1)
apps/desktop/src/routes/(window-chrome)/settings/Setting.tsx (1)
  • ToggleSettingItem (24-40)
apps/desktop/src/routes/camera.tsx (6)
apps/desktop/src/store.ts (1)
  • generalSettingsStore (61-62)
apps/desktop/src/utils/createEventListener.ts (1)
  • createTauriEventListener (30-44)
apps/desktop/src/utils/tauri.ts (4)
  • events (297-341)
  • commands (7-292)
  • LogicalSize (425-425)
  • LogicalPosition (424-424)
apps/desktop/src/routes/(window-chrome)/OptionsContext.tsx (2)
  • RecordingOptionsProvider (18-18)
  • useRecordingOptions (9-16)
apps/desktop/src/utils/queries.ts (1)
  • createCameraMutation (195-236)
apps/desktop/src/utils/socket.ts (2)
  • createLazySignal (88-109)
  • createImageDataWS (4-86)
apps/desktop/src-tauri/src/camera.rs (1)
apps/desktop/src/utils/tauri.ts (2)
  • CameraPreviewState (368-368)
  • CameraPreviewShape (367-367)
🪛 GitHub Actions: CI
apps/desktop/src/routes/(window-chrome)/settings/experimental.tsx

[error] 1-1: Biome: File content differs from formatting output. The imports and exports are not sorted.

apps/desktop/src/routes/camera.tsx

[error] 1-1: Biome: File content differs from formatting output. The imports and exports are not sorted.

apps/desktop/src-tauri/src/camera.rs

[error] 62-62: clamp-like pattern without using clamp function


[note] 62-62: clamp will panic if max < min, min.is_nan(), or max.is_nan()


[note] 62-62: clamp returns NaN if the input is NaN


[help] 62-62: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.91.0/index.html#manual_clamp


[help] 62-62: replace with clamp

🪛 GitHub Check: Clippy (aarch64-apple-darwin, macos-latest)
apps/desktop/src-tauri/src/camera.rs

[failure] 62-62: clamp-like pattern without using clamp function
error: clamp-like pattern without using clamp function
--> apps/desktop/src-tauri/src/camera.rs:62:5
|
62 | size.max(MIN_CAMERA_SIZE).min(MAX_CAMERA_SIZE)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with clamp: size.clamp(MIN_CAMERA_SIZE, MAX_CAMERA_SIZE)
|
= note: clamp will panic if max < min, min.is_nan(), or max.is_nan()
= note: clamp returns NaN if the input is NaN
= help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.91.0/index.html#manual_clamp
= note: requested on the command line with -D clippy::manual-clamp

⏰ 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 (9)
crates/project/src/configuration.rs (1)

401-407: Confirm intentional change of default cursor animation style

CursorAnimationStyle now defaults to Mellow instead of Slow, and the serde aliases ("regular", "quick", "rapid", "fast") all deserialize to Mellow. This also changes the default CursorConfiguration smoothing parameters via preset(). If this UX change is intentional, the implementation looks fine; just be aware this will subtly alter existing projects’ default cursor feel.

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

368-369: Generated API surface changes look consistent; ensure callers are updated

The generated bindings now expose:

  • CameraPreviewState.size as a number instead of a "sm" | "lg"-style union.
  • CurrentRecording with a new status: RecordingStatus field.
  • A new RecordingStatus = "pending" | "recording" type.

These changes align with the tauri-side API evolution and look consistent for a tauri-specta output. Just make sure any hand-written TS/JS that references these types (e.g., assumes size is a string enum or that recordings lack status) is updated accordingly. Since this file is auto-generated, any further tweaks should be done by changing the Rust definitions and regenerating, not by editing this file directly.

Also applies to: 385-386, 458-458

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

623-637: Normalized size for shader uniforms matches new sizing model

Using clamp_size followed by a normalized size (clamped_size - MIN_CAMERA_SIZE) / (MAX_CAMERA_SIZE - MIN_CAMERA_SIZE) is a good fit for driving the shader’s size uniform as a 0‑1 interpolation parameter. This keeps GPU behavior stable even if the stored size drifts, and matches the continuous corner-radius interpolation added in camera.wgsl.


676-693: Window resize logic correctly reuses clamped base size

resize_window now uses let base = clamp_size(state.size); and derives window_width/window_height from it, including the toolbar height. This keeps OS window sizing consistent with the clamped range and the normalized shader size, without introducing any obvious edge cases (aspect ratio handling for Full still behaves as before).

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

34-45: LGTM!

The type definitions and constants are well-structured and provide clear boundaries for camera window sizing and configuration.


51-53: Verify handling of undefined generalSettings during initial load.

The condition accesses generalSettings.data?.enableNativeCameraPreview which might be undefined during the initial query load. While the optional chaining handles the undefined case, the fallback to false means native preview will be disabled until settings load.

Confirm this behavior is intentional and doesn't cause UI flickering or state issues during the initial render.


452-460: Verify window close behavior on camera removal.

When cameraLabel becomes null, the window is immediately closed. Ensure this is the desired behavior and doesn't interrupt user workflows or cause issues when the camera is temporarily unavailable.

Consider adding a grace period or user notification before closing:

createEffect(
  on(
    () => rawOptions.cameraLabel,
    (label) => {
      if (label === null) {
        // Consider: Show a notification or delay before closing
        setTimeout(() => getCurrentWindow().close(), 1000);
      }
    },
    { defer: true }
  )
);

620-634: LGTM!

The helper functions are well-implemented. cameraBorderRadius provides smooth interpolation between min/max sizes for non-round shapes, which will create a nice visual effect.


636-647: LGTM!

Clean overlay implementation. Inheriting the parent's border radius ensures visual consistency across different camera shapes.

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

Caution

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

⚠️ Outside diff range comments (1)
crates/recording/src/sources/camera.rs (1)

37-37: Remove unnecessary mut from parameter.

The video_tx parameter at line 37 doesn't need to be mutable since it's immediately reassigned when wrapped in LogDrop at line 50. Move the mut to the reassignment only.

Apply this diff:

     async fn setup(
         feed_lock: Self::Config,
-        mut video_tx: mpsc::Sender<Self::Frame>,
+        video_tx: mpsc::Sender<Self::Frame>,
         _: &mut SetupCtx,
     ) -> anyhow::Result<Self>
     where
         Self: Sized,
     {

Also applies to: 50-50

♻️ Duplicate comments (3)
apps/desktop/src/routes/camera.tsx (3)

325-347: Fix WebSocket reconnection logic and unsafe property access.

Multiple issues remain in this segment:

  1. Line 325: Unsafe access to window.__CAP__ with type assertion
  2. Lines 331-342: The reconnection logic attempts to mutate the existing WebSocket via Object.assign(ws, newWs[0]), which won't properly establish the new connection
  3. The reconnectInterval continues running even after component unmount if cleanup happens before the next interval tick

Consider refactoring to properly manage WebSocket lifecycle:

- const { cameraWsPort } = (window as any).__CAP__;
+ const { cameraWsPort } = (window as { __CAP__: { cameraWsPort: number } }).__CAP__;
  
- const [ws, isConnected] = createImageDataWS(
-   `ws://localhost:${cameraWsPort}`,
-   imageDataHandler,
- );
-
- const reconnectInterval = setInterval(() => {
-   if (!isConnected()) {
-     console.log("Attempting to reconnect...");
-     ws.close();
-
-     const newWs = createImageDataWS(
-       `ws://localhost:${cameraWsPort}`,
-       imageDataHandler,
-     );
-     Object.assign(ws, newWs[0]);
-   }
- }, 5000);
-
- onCleanup(() => {
-   clearInterval(reconnectInterval);
-   ws.close();
- });
+ const [wsSignal, setWsSignal] = createSignal(
+   createImageDataWS(`ws://localhost:${cameraWsPort}`, imageDataHandler)
+ );
+ 
+ const ws = () => wsSignal()[0];
+ const isConnected = () => wsSignal()[1]();
+
+ const reconnectInterval = setInterval(() => {
+   if (!isConnected()) {
+     console.log("Attempting to reconnect...");
+     ws().close();
+     setWsSignal(
+       createImageDataWS(`ws://localhost:${cameraWsPort}`, imageDataHandler)
+     );
+   }
+ }, 5000);
+
+ onCleanup(() => {
+   clearInterval(reconnectInterval);
+   ws().close();
+ });

113-113: Remove unused variable.

The setCamera mutation is created but never used in NativeCameraPreviewPage.

Apply this diff:

-  const setCamera = createCameraMutation();
-

450-450: Remove unused variable.

The setCamera mutation is created but never used in LegacyCameraPreviewPage.

Apply this diff:

-  const setCamera = createCameraMutation();
-
🧹 Nitpick comments (4)
crates/recording/src/studio_recording.rs (1)

67-67: Minor typo in log message.

"shuting" should be "shutting".

Apply this diff to fix the typo:

-        tracing::info!("pipeline shuting down");
+        tracing::info!("pipeline shutting down");
crates/recording/src/sources/camera.rs (1)

52-75: Simplified forwarding logic is correct.

The removal of retry/reattachment logic aligns with the PR's goal of more explicit control flows. Breaking on pipeline closure (line 65) is the correct behavior—it ensures proper cleanup and disconnection from the camera feed.

Consider removing the uncertain comments.

The comments at lines 61-65 express uncertainty about the control flow decision, but the implementation already makes the correct choice by breaking. Consider removing these comments since the decision is final.

Apply this diff to clean up the comments:

                     Ok(frame) => {
                         // tracing::trace!("Sending camera frame");
                         if let Err(e) = video_tx.send(frame).await {
                             tracing::warn!("Failed to send to video pipeline: {e}");
-                            // If pipeline is closed, we should stop?
-                            // But lets continue to keep rx alive for now to see if it helps,
-                            // or maybe break?
-                            // If we break, we disconnect from CameraFeed.
-                            // If pipeline is closed, we SHOULD disconnect.
                             break;
                         }
                     }
apps/desktop/src/routes/camera.tsx (2)

115-173: Consider extracting duplicated resize logic.

The resize handlers (handleResizeStart, handleResizeMove, handleResizeEnd), the scale function, and the resize effect are duplicated between NativeCameraPreviewPage (lines 115-173) and LegacyCameraPreviewPage (lines 349-407). This duplication makes maintenance harder and increases the risk of inconsistencies.

Consider extracting this logic into a shared custom hook or utility function:

function useResizeLogic(state: { size: number }, setState: (key: string, value: any) => void) {
  const [isResizing, setIsResizing] = createSignal(false);
  const [resizeStart, setResizeStart] = createSignal({
    size: 0,
    x: 0,
    y: 0,
    corner: "",
  });

  const scale = () => {
    const normalized =
      (state.size - CAMERA_MIN_SIZE) / (CAMERA_MAX_SIZE - CAMERA_MIN_SIZE);
    return 0.7 + normalized * 0.3;
  };

  const handleResizeStart = (corner: string) => (e: MouseEvent) => {
    e.preventDefault();
    e.stopPropagation();
    setIsResizing(true);
    setResizeStart({ size: state.size, x: e.clientX, y: e.clientY, corner });
  };

  const handleResizeMove = (e: MouseEvent) => {
    if (!isResizing()) return;
    const start = resizeStart();
    const deltaX = e.clientX - start.x;
    const deltaY = e.clientY - start.y;

    let delta = 0;
    if (start.corner.includes("e") && start.corner.includes("s")) {
      delta = Math.max(deltaX, deltaY);
    } else if (start.corner.includes("e") && start.corner.includes("n")) {
      delta = Math.max(deltaX, -deltaY);
    } else if (start.corner.includes("w") && start.corner.includes("s")) {
      delta = Math.max(-deltaX, deltaY);
    } else if (start.corner.includes("w") && start.corner.includes("n")) {
      delta = Math.max(-deltaX, -deltaY);
    } else if (start.corner.includes("e")) {
      delta = deltaX;
    } else if (start.corner.includes("w")) {
      delta = -deltaX;
    } else if (start.corner.includes("s")) {
      delta = deltaY;
    } else if (start.corner.includes("n")) {
      delta = -deltaY;
    }

    const newSize = Math.max(
      CAMERA_MIN_SIZE,
      Math.min(CAMERA_MAX_SIZE, start.size + delta),
    );
    setState("size", newSize);
  };

  const handleResizeEnd = () => {
    setIsResizing(false);
  };

  createEffect(() => {
    if (isResizing()) {
      window.addEventListener("mousemove", handleResizeMove);
      window.addEventListener("mouseup", handleResizeEnd);
      onCleanup(() => {
        window.removeEventListener("mousemove", handleResizeMove);
        window.removeEventListener("mouseup", handleResizeEnd);
      });
    }
  });

  return { scale, handleResizeStart };
}

229-248: Consider extracting resize handles into a shared component.

The four corner resize handles are duplicated between NativeCameraPreviewPage (lines 229-248) and LegacyCameraPreviewPage (lines 518-537).

You could extract them into a reusable component:

function ResizeHandles(props: { onResizeStart: (corner: string) => (e: MouseEvent) => void }) {
  return (
    <>
      <div
        class="absolute top-0 left-0 w-4 h-4 cursor-nw-resize opacity-0 group-hover:opacity-100 transition-opacity z-10"
        style={{ "pointer-events": "auto" }}
        onMouseDown={props.onResizeStart("nw")}
      />
      <div
        class="absolute top-0 right-0 w-4 h-4 cursor-ne-resize opacity-0 group-hover:opacity-100 transition-opacity z-10"
        style={{ "pointer-events": "auto" }}
        onMouseDown={props.onResizeStart("ne")}
      />
      <div
        class="absolute bottom-0 left-0 w-4 h-4 cursor-sw-resize opacity-0 group-hover:opacity-100 transition-opacity z-10"
        style={{ "pointer-events": "auto" }}
        onMouseDown={props.onResizeStart("sw")}
      />
      <div
        class="absolute bottom-0 right-0 w-4 h-4 cursor-se-resize opacity-0 group-hover:opacity-100 transition-opacity z-10"
        style={{ "pointer-events": "auto" }}
        onMouseDown={props.onResizeStart("se")}
      />
    </>
  );
}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dd39425 and e6e9320.

📒 Files selected for processing (8)
  • AGENTS.md (1 hunks)
  • CLAUDE.md (1 hunks)
  • apps/desktop/src/routes/(window-chrome)/settings/experimental.tsx (2 hunks)
  • apps/desktop/src/routes/camera.tsx (13 hunks)
  • crates/recording/src/feeds/camera.rs (4 hunks)
  • crates/recording/src/output_pipeline/core.rs (2 hunks)
  • crates/recording/src/sources/camera.rs (2 hunks)
  • crates/recording/src/studio_recording.rs (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • AGENTS.md
🚧 Files skipped from review as they are similar to previous changes (2)
  • crates/recording/src/feeds/camera.rs
  • apps/desktop/src/routes/(window-chrome)/settings/experimental.tsx
🧰 Additional context used
📓 Path-based instructions (4)
**/*.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/studio_recording.rs
  • crates/recording/src/sources/camera.rs
  • crates/recording/src/output_pipeline/core.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/studio_recording.rs
  • crates/recording/src/sources/camera.rs
  • crates/recording/src/output_pipeline/core.rs
**/*.{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/camera.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/camera.tsx
🧠 Learnings (5)
📚 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 **/*.rs : Format Rust code using `rustfmt` and ensure all Rust code passes workspace-level clippy lints.

Applied to files:

  • CLAUDE.md
📚 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 **/*.{ts,tsx} : Use Biome for formatting and linting TypeScript/JavaScript files by running `pnpm format`.

Applied to files:

  • CLAUDE.md
  • 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 **/*.rs : Rust modules should be named with snake_case, and crate directories should be in kebab-case.

Applied to files:

  • CLAUDE.md
📚 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/sources/camera.rs
  • crates/recording/src/output_pipeline/core.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/core.rs
🧬 Code graph analysis (3)
crates/recording/src/sources/camera.rs (1)
crates/recording/src/feeds/camera.rs (2)
  • drop (175-179)
  • deref (169-171)
apps/desktop/src/routes/camera.tsx (2)
apps/desktop/src/store.ts (1)
  • generalSettingsStore (61-62)
apps/desktop/src/utils/queries.ts (1)
  • createCameraMutation (195-236)
crates/recording/src/output_pipeline/core.rs (3)
crates/recording/src/output_pipeline/ffmpeg.rs (1)
  • timestamp (22-24)
crates/recording/src/sources/screen_capture/windows.rs (1)
  • timestamp (63-65)
crates/recording/src/sources/screen_capture/macos.rs (1)
  • timestamp (61-63)
🪛 GitHub Actions: CI
crates/recording/src/sources/camera.rs

[warning] 37-37: variable does not need to be mutable

⏰ 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 (6)
CLAUDE.md (1)

417-424: Excellent addition—formatting guidance aligns with repo standards.

The new "Code Formatting" section documents the formatting practices correctly and complements the existing development guidelines. The commands match the repository learnings: pnpm format for TypeScript/JavaScript (Biome) and cargo fmt for Rust (rustfmt). Placing this guidance at the end of the document, after the references section, is logical and serves as a helpful reminder for developers completing their work.

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

858-858: LGTM! Error message now matches the pipeline being set up.

The error context correctly identifies this as "system audio pipeline setup" instead of the misleading "microphone pipeline setup". This improves debugging accuracy.

crates/recording/src/sources/camera.rs (2)

13-29: LGTM: Clean debugging utility.

The LogDrop wrapper provides transparent access while instrumenting drop events, which will help diagnose channel lifetime issues.


43-43: Capacity increase improves buffering.

Increasing the frame channel from 8 to 32 provides better buffering against temporary backpressure, reducing the likelihood of dropped frames.

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

628-634: LGTM: Dynamic border radius calculation.

The updated cameraBorderRadius function signature and implementation provide a smooth, size-dependent radius calculation. The normalized approach (3rem + up to 1.5rem based on size) is more elegant than fixed mappings.

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

432-432: LGTM!

The mut modifier is necessary because StreamExt::next() requires a mutable reference to the receiver.

@richiemcilroy richiemcilroy changed the title feat: Misc performance and UX fixes feat: Various features + performance bits Nov 18, 2025
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
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 (2)
apps/desktop/src-tauri/src/camera.rs (2)

31-33: Numeric sizing + clamping pipeline looks good; consider a small robustness guard

The constants + clamp_size + normalized size + window base usage form a consistent pipeline, and switching to f32::clamp should keep clippy happy.

One edge case to consider is defensive handling of non‑finite or future degenerate values (e.g. if someone later “helpfully” tweaks the constants):

  • clamp will panic if MIN_CAMERA_SIZE >= MAX_CAMERA_SIZE.
  • NaN inputs propagate through to uniforms/window sizing.

If you want extra robustness, you could add a debug assertion and treat non‑finite sizes as the default:

 fn clamp_size(size: f32) -> f32 {
-    size.clamp(MIN_CAMERA_SIZE, MAX_CAMERA_SIZE)
+    debug_assert!(MIN_CAMERA_SIZE < MAX_CAMERA_SIZE);
+    if !size.is_finite() {
+        DEFAULT_CAMERA_SIZE
+    } else {
+        size.clamp(MIN_CAMERA_SIZE, MAX_CAMERA_SIZE)
+    }
 }

Totally optional, but it makes future changes to the constants and any corrupted persisted state safer.

Also applies to: 61-63, 625-628, 635-635, 683-683


44-49: Persisted CameraPreviewState now fully resets to defaults on legacy/type‑mismatched data

The move from an enum‑based size to f32 plus:

.get("state")
.and_then(|v| serde_json::from_value::<CameraPreviewState>(v).ok())
.unwrap_or_default();

means that any legacy store entry (e.g. size previously serialized as a string enum) or any type mismatch will cause deserialization to fail and the entire state to fall back to CameraPreviewState::default().

Consequence: existing users with stored camera prefs will likely lose not just their old size, but also shape/mirrored on upgrade, with no logging.

If you want smoother migration, consider:

  • Trying to deserialize into the new struct first.
  • On failure, attempting a lightweight “upgrade” from the raw serde_json::Value (e.g. salvage shape/mirrored, map legacy size variants to numeric values), and only then falling back to Default.
  • Optionally logging when a fallback/upgrade path is taken so unexpected corruption is visible.

This isn’t a correctness bug, but it is a user‑visible behavioral change worth double‑checking is intentional.

Also applies to: 51-59, 83-93, 96-98

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e6e9320 and 5de394f.

📒 Files selected for processing (1)
  • apps/desktop/src-tauri/src/camera.rs (5 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.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:

  • apps/desktop/src-tauri/src/camera.rs

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

♻️ Duplicate comments (1)
apps/desktop/src/routes/in-progress-recording.tsx (1)

36-37: Leverage RecordingStatus without casting currentRecording.data

The new "initializing" state plus RecordingStatus integration and adjustedTime clamping look good and should avoid negative/early time calculations. The remaining currentRecording.data as CurrentRecording | undefined cast in the effect is still unnecessary if createCurrentRecordingQuery is typed to expose .status – you can mirror existing usage like currentRecording.data?.mode and write:

createEffect(() => {
  const s = state();
  if (s.variant === "initializing" || s.variant === "countdown") {
    if (currentRecording.data?.status === "recording") {
      setDisconnectedInputs({ microphone: false, camera: false });
      setRecordingFailure(null);
      setState({ variant: "recording" });
      setStart(Date.now());
    }
  }
});

This avoids the cast altogether and aligns with the prior feedback about removing as any in this spot.

Also applies to: 43-47, 64-71, 195-206, 485-493, 522-527

🧹 Nitpick comments (2)
apps/desktop/src-tauri/src/target_select_overlay.rs (1)

151-179: Validate and clamp overlay bounds before casting to physical coordinates

update_camera_overlay_bounds currently casts f64 values directly to u32/i32. If the frontend ever passes negative, NaN, or extremely large values, these casts will silently clamp/convert and could result in a 0-sized or off-screen window.

Consider validating/clamping width/height (e.g., > 0 and within a reasonable max) and maybe rounding x/y before casting so failures are explicit and easier to diagnose.

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

401-415: Camera overlay sizing/positioning logic is solid; consider minor cleanups

The camera-window discovery, original-bounds/scale caching, and placement math (using logical coordinates and scaleFactor to compute physical x/y/width/height) are coherent and match the new TAURI command surface. revertCamera plus onRecordingStart={() => setOriginalCameraBounds(null)} also correctly distinguish “cancel selection” (revert) from “start recording” (persist new camera position).

Two small non-blocking follow-ups you might consider:

  • Using the generated commands.updateCameraOverlayBounds instead of raw invoke("update_camera_overlay_bounds", ...) for consistency with the rest of the TAURI calls in this file.
  • If you ever add more camera-labelled windows, the getAll().find((w) => w.label.includes("camera")) fallback could become ambiguous; a stricter label check (e.g. equality or prefix) would avoid accidental matches.

Neither affects correctness today, but they would tighten the integration a bit.

Also applies to: 480-561, 563-573, 588-589, 704-710, 715-716

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5de394f and 05f39bb.

📒 Files selected for processing (7)
  • apps/desktop/src-tauri/src/lib.rs (8 hunks)
  • apps/desktop/src-tauri/src/target_select_overlay.rs (1 hunks)
  • apps/desktop/src-tauri/src/windows.rs (3 hunks)
  • apps/desktop/src/routes/in-progress-recording.tsx (10 hunks)
  • apps/desktop/src/routes/target-select-overlay.tsx (11 hunks)
  • apps/desktop/src/utils/tauri.ts (4 hunks)
  • packages/ui-solid/src/auto-imports.d.ts (0 hunks)
💤 Files with no reviewable changes (1)
  • packages/ui-solid/src/auto-imports.d.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/desktop/src-tauri/src/windows.rs
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{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/in-progress-recording.tsx
  • apps/desktop/src/routes/target-select-overlay.tsx
  • apps/desktop/src/utils/tauri.ts
**/*.{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/in-progress-recording.tsx
  • apps/desktop/src/routes/target-select-overlay.tsx
  • apps/desktop/src/utils/tauri.ts
**/*.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:

  • apps/desktop/src-tauri/src/target_select_overlay.rs
  • apps/desktop/src-tauri/src/lib.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 (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:

  • apps/desktop/src-tauri/src/lib.rs
🧬 Code graph analysis (4)
apps/desktop/src/routes/in-progress-recording.tsx (1)
apps/desktop/src/utils/tauri.ts (4)
  • CurrentRecording (388-388)
  • stopRecording (20-22)
  • restartRecording (29-31)
  • deleteRecording (32-34)
apps/desktop/src/routes/target-select-overlay.tsx (3)
apps/desktop/src/utils/tauri.ts (1)
  • PhysicalSize (442-442)
crates/scap-targets/src/bounds.rs (2)
  • size (19-21)
  • size (46-48)
crates/scap-targets/src/platform/macos.rs (1)
  • scale (111-113)
apps/desktop/src-tauri/src/lib.rs (4)
apps/desktop/src/utils/tauri.ts (4)
  • RecordingStatus (461-461)
  • CurrentRecording (388-388)
  • CurrentRecordingTarget (390-390)
  • RecordingMode (457-457)
apps/desktop/src-tauri/src/recording.rs (2)
  • mode (234-239)
  • capture_target (164-169)
apps/desktop/src-tauri/src/target_select_overlay.rs (2)
  • None (140-140)
  • update_camera_overlay_bounds (154-179)
apps/desktop/src-tauri/src/camera.rs (1)
  • new (72-79)
apps/desktop/src/utils/tauri.ts (3)
crates/camera-windows/src/lib.rs (2)
  • width (350-352)
  • height (354-356)
crates/rendering/src/decoder/mod.rs (2)
  • width (24-26)
  • height (28-30)
crates/camera/src/lib.rs (2)
  • width (57-59)
  • height (61-63)
⏰ 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 (5)
apps/desktop/src/utils/tauri.ts (1)

280-282: Generated bindings for camera overlay and recording status look consistent

The new updateCameraOverlayBounds command and the CameraPreviewState, CurrentRecording, and RecordingStatus type changes match the Rust definitions (RecordingStatus::{Pending, Recording} with camelCase serialization and the extra status field on CurrentRecording). Given this file is specta-generated, further changes should go through the Rust side and regeneration rather than manual edits.

Also applies to: 371-372, 388-389, 461-462

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

223-233: Comment-only note about camera revert behaviour

This block only adds commentary explaining why camera revert is handled via onCleanup inside the Match rather than the Escape handler; no code behaviour change here, so nothing to action.

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

447-479: Camera input retry logic is robust and state-safe

The new set_camera_input flow avoids holding the App lock across awaits, cleanly retries SetInput up to three times with logging, and only re-locks and updates selected_camera_id/camera_in_use once the camera is ready. This should make camera startup much less flaky while keeping the recording state and RecordingEvent::InputRestored notifications consistent.

Also applies to: 489-515


756-769: RecordingStatus addition and get_current_recording wiring look correct

The new RecordingStatus enum and corresponding status field on CurrentRecording correctly mirror RecordingState::{None, Pending, Active}: Pending uses the stored mode/target, and Active delegates to inner.mode() and inner.capture_target(). The TS bindings ("pending" | "recording") now have a precise backend source of truth, which matches how in-progress-recording.tsx drives its "initializing"/"recording" UI.

Also applies to: 771-809


2590-2592: Recording start and window-destruction paths now keep mic/camera state coherent

  • RequestStartRecording::listen_any_spawn now runs set_mic_input and set_camera_input with the persisted RecordingSettingsStore before calling start_recording, which ensures backend and UI agree on selected devices for hotkey-driven starts.
  • When the main window is destroyed and no recording is active or pending, you now remove mic/camera inputs and clear selected_mic_label/selected_camera_id/camera_in_use, avoiding stale “in use” flags.
  • On camera window close, camera_preview.on_window_close() is called and the camera input is removed when not recording, resetting camera_in_use appropriately.
  • The TAURI command list includes target_select_overlay::update_camera_overlay_bounds, so the new TS bindings and camera overlay sync can actually invoke it.

These changes keep device state, recording state, and window lifecycle much better aligned.

Also applies to: 2660-2671, 2728-2739, 2215-2221, 2295-2298

Comment on lines +555 to +557
disabled={
stopRecording.isPending || isInitializing() || isCountdown()
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

Disable pause/restart/delete during the new “initializing” phase as well

You correctly disable the Stop button during isInitializing() and isCountdown(), but Pause/Restart/Delete are only gated on isCountdown() (plus other conditions). This means users can trigger pauseRecording/restartRecording/deleteRecording while the overlay is still in the "initializing" state, before the recording is actually active.

To keep the control surface consistent and avoid odd backend calls, consider extending the disabled conditions, e.g.:

- disabled={
-   togglePause.isPending ||
-   hasDisconnectedInput() ||
-   isCountdown()
- }
+ disabled={
+   togglePause.isPending ||
+   hasDisconnectedInput() ||
+   isCountdown() ||
+   isInitializing()
+ }

- disabled={restartRecording.isPending || isCountdown()}
+ disabled={
+   restartRecording.isPending ||
+   isCountdown() ||
+   isInitializing()
+ }

- disabled={deleteRecording.isPending || isCountdown()}
+ disabled={
+   deleteRecording.isPending ||
+   isCountdown() ||
+   isInitializing()
+ }

That keeps all destructive actions unavailable until the recording has really started.

Also applies to: 566-623, 674-678, 700-709


🏁 Script executed:

#!/bin/bash
# Extract the relevant code sections from the file
echo "=== Lines 555-557 (Stop button) ==="
sed -n '555,557p' apps/desktop/src/routes/in-progress-recording.tsx

echo ""
echo "=== Lines 566-623 (Pause button area) ==="
sed -n '566,623p' apps/desktop/src/routes/in-progress-recording.tsx

echo ""
echo "=== Lines 674-678 (Restart button area) ==="
sed -n '674,678p' apps/desktop/src/routes/in-progress-recording.tsx

echo ""
echo "=== Lines 700-709 (Delete button area) ==="
sed -n '700,709p' apps/desktop/src/routes/in-progress-recording.tsx

echo ""
echo "=== Search for isInitializing definition and usage ==="
rg -n 'isInitializing' apps/desktop/src/routes/in-progress-recording.tsx -C 2

echo ""
echo "=== Search for isCountdown definition and usage ==="
rg -n 'isCountdown' apps/desktop/src/routes/in-progress-recording.tsx -C 2

Length of output: 4843


Add isInitializing() check to Pause, Restart, and Delete button disabled conditions for consistency with Stop button

Verification confirms the review comment is accurate. The Stop button correctly checks isInitializing() (line 556), but the Pause button (line 677), Restart button (line 700), and Delete button (line 708) each check only isCountdown(). This creates an inconsistency in the control surface.

The fix should extend the disabled conditions as suggested:

  • Pause button: add isInitializing() to the condition at line 677
  • Restart button: add isInitializing() to the condition at line 700
  • Delete button: add isInitializing() to the condition at line 708

This ensures all destructive/state-changing actions are consistently unavailable until the recording fully transitions past the initialization phase.

🤖 Prompt for AI Agents
In apps/desktop/src/routes/in-progress-recording.tsx around lines 555–708, the
Pause (line 677), Restart (line 700), and Delete (line 708) buttons only check
isCountdown() when deciding disabled state, creating an inconsistency with the
Stop button which also checks isInitializing(); update each of those three
disabled conditions to include isInitializing() alongside isCountdown() (i.e.,
disable the button when stopRecording.isPending || isInitializing() ||
isCountdown()) so all destructive/state-changing actions remain disabled during
initialization.

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

Caution

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

⚠️ Outside diff range comments (1)
crates/recording/src/output_pipeline/core.rs (1)

510-538: Fix formatting in audio timestamp calculation.

The audio muxing task uses the same checked_duration_since(...).unwrap_or(Duration::ZERO) pattern as the video path (line 521). While this ensures consistency between audio and video handling, CI reports formatting differences on lines 518-523.

Run cargo fmt to fix the formatting issue.

This calculation shares the same zero-duration fallback behavior as the video path reviewed above. The verification script there will check both occurrences.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 05f39bb and fd30b14.

📒 Files selected for processing (4)
  • crates/recording/src/output_pipeline/core.rs (3 hunks)
  • crates/timestamp/src/lib.rs (1 hunks)
  • crates/timestamp/src/macos.rs (1 hunks)
  • crates/timestamp/src/win.rs (1 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/timestamp/src/win.rs
  • crates/timestamp/src/macos.rs
  • crates/timestamp/src/lib.rs
  • crates/recording/src/output_pipeline/core.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/timestamp/src/win.rs
  • crates/timestamp/src/macos.rs
  • crates/timestamp/src/lib.rs
  • crates/recording/src/output_pipeline/core.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/core.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/core.rs
🧬 Code graph analysis (4)
crates/timestamp/src/win.rs (2)
crates/timestamp/src/lib.rs (1)
  • checked_duration_since (35-44)
crates/timestamp/src/macos.rs (2)
  • checked_duration_since (33-40)
  • new (14-16)
crates/timestamp/src/macos.rs (2)
crates/timestamp/src/lib.rs (1)
  • checked_duration_since (35-44)
crates/timestamp/src/win.rs (2)
  • checked_duration_since (49-66)
  • new (26-28)
crates/timestamp/src/lib.rs (2)
crates/timestamp/src/macos.rs (1)
  • checked_duration_since (33-40)
crates/timestamp/src/win.rs (1)
  • checked_duration_since (49-66)
crates/recording/src/output_pipeline/core.rs (3)
crates/recording/src/output_pipeline/ffmpeg.rs (1)
  • timestamp (22-24)
crates/recording/src/sources/screen_capture/windows.rs (1)
  • timestamp (63-65)
crates/recording/src/sources/screen_capture/macos.rs (1)
  • timestamp (61-63)
🪛 GitHub Actions: CI
crates/timestamp/src/lib.rs

[error] 37-40: cargo fmt --check failed: formatting differences in PerformanceCounter branch. Run 'cargo fmt' to fix formatting in this region.

crates/recording/src/output_pipeline/core.rs

[error] 464-468: cargo fmt --check failed: formatting differences detected in duration calculation. Run 'cargo fmt' to fix formatting in this region.


[error] 518-523: cargo fmt --check failed: formatting differences detected in timestamp calculation. Run 'cargo fmt' to fix formatting in this region.

⏰ 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 (4)
crates/timestamp/src/macos.rs (1)

33-40: LGTM!

The checked_duration_since implementation correctly mirrors duration_since while returning Option<Duration> for safer duration arithmetic. The use of checked_sub with the ? operator provides clean underflow handling, and the frequency calculation is consistent with the existing method.

crates/timestamp/src/win.rs (1)

49-66: LGTM!

The Windows implementation of checked_duration_since correctly handles negative timestamp differences by returning None, and the duration calculation logic matches the existing duration_since method. The cross-platform API is now consistent between Windows and macOS.

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

432-432: LGTM!

Adding mut to video_rx is necessary for the .next() calls in the refactored mux-video task below.


453-492: Address formatting and document duration fallback intent.

Error handling improvement is confirmed good—send_video_frame errors are now properly propagated (lines 483-485) via the run_until_cancelled pattern. However, two issues require attention:

  1. Formatting: CI already flagged differences on lines 464-468. Run cargo fmt locally to fix before pushing.

  2. Duration fallback: Both video (line 467) and audio (line 521) encoders use identical unwrap_or(Duration::ZERO) fallback when checked_duration_since returns None. This occurs during clock skew or timestamp monotonicity violations. The pattern is consistent across both tasks, suggesting intentional design, but it lacks documentation. Verify this is not masking real timing bugs. If intentional, add a comment explaining the fallback rationale. Otherwise, consider using saturating_duration_since (Rust's standard approach for this scenario).

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

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/camera.tsx (1)

80-407: Eliminate code duplication between Native and Legacy components.

The resize logic (lines 90-173 vs 288-407), scale function (lines 115-119 vs 349-353), control panel rendering (lines 185-225 vs 473-513), and resize handles (lines 229-248 vs 516-535) are duplicated between NativeCameraPreviewPage and LegacyCameraPreviewPage.

Extract shared logic into reusable hooks and components:

// Extract resize logic
function useCameraResize(state: CameraWindowState, setState: SetStoreFunction<CameraWindowState>) {
  const [isResizing, setIsResizing] = createSignal(false);
  const [resizeStart, setResizeStart] = createSignal({
    size: 0,
    x: 0,
    y: 0,
    corner: "",
  });

  const handleResizeStart = (corner: string) => (e: MouseEvent) => {
    e.preventDefault();
    e.stopPropagation();
    setIsResizing(true);
    setResizeStart({ size: state.size, x: e.clientX, y: e.clientY, corner });
  };

  const handleResizeMove = (e: MouseEvent) => {
    if (!isResizing()) return;
    const start = resizeStart();
    const deltaX = e.clientX - start.x;
    const deltaY = e.clientY - start.y;

    let delta = 0;
    if (start.corner.includes("e") && start.corner.includes("s")) {
      delta = Math.max(deltaX, deltaY);
    } else if (start.corner.includes("e") && start.corner.includes("n")) {
      delta = Math.max(deltaX, -deltaY);
    } else if (start.corner.includes("w") && start.corner.includes("s")) {
      delta = Math.max(-deltaX, deltaY);
    } else if (start.corner.includes("w") && start.corner.includes("n")) {
      delta = Math.max(-deltaX, -deltaY);
    } else if (start.corner.includes("e")) {
      delta = deltaX;
    } else if (start.corner.includes("w")) {
      delta = -deltaX;
    } else if (start.corner.includes("s")) {
      delta = deltaY;
    } else if (start.corner.includes("n")) {
      delta = -deltaY;
    }

    const newSize = Math.max(
      CAMERA_MIN_SIZE,
      Math.min(CAMERA_MAX_SIZE, start.size + delta),
    );
    setState("size", newSize);
  };

  const handleResizeEnd = () => {
    setIsResizing(false);
  };

  createEffect(() => {
    if (isResizing()) {
      window.addEventListener("mousemove", handleResizeMove);
      window.addEventListener("mouseup", handleResizeEnd);
      onCleanup(() => {
        window.removeEventListener("mousemove", handleResizeMove);
        window.removeEventListener("mouseup", handleResizeEnd);
      });
    }
  });

  return { handleResizeStart };
}

// Extract scale calculation
function useCameraScale(state: CameraWindowState) {
  return () => {
    const normalized =
      (state.size - CAMERA_MIN_SIZE) / (CAMERA_MAX_SIZE - CAMERA_MIN_SIZE);
    return 0.7 + normalized * 0.3;
  };
}

// Extract control panel and resize handles into components
function CameraControls(props: { state: CameraWindowState, setState: SetStoreFunction<CameraWindowState>, scale: Accessor<number> }) {
  // ... control panel JSX
}

function ResizeHandles(props: { onResizeStart: (corner: string) => (e: MouseEvent) => void }) {
  // ... resize handle JSX
}

Then use these in both Native and Legacy components to eliminate duplication.

♻️ Duplicate comments (1)
apps/desktop/src/routes/camera.tsx (1)

113-113: Remove unused variable.

The setCamera mutation is created but never used in NativeCameraPreviewPage.

Apply this diff:

-  const setCamera = createCameraMutation();
-
🧹 Nitpick comments (3)
crates/recording/src/output_pipeline/core.rs (2)

467-469: Consider logging when timestamp fallback occurs.

Using Duration::ZERO when checked_duration_since returns None gracefully handles timing anomalies (clock adjustments, time going backwards), but silently applying the fallback could make debugging timing issues difficult. Consider logging a warning when this occurs.

Apply this diff to add logging:

-                    let duration = timestamp
-                        .checked_duration_since(timestamps)
-                        .unwrap_or(Duration::ZERO);
+                    let duration = timestamp
+                        .checked_duration_since(timestamps)
+                        .unwrap_or_else(|| {
+                            warn!("Video frame timestamp before recording start, using ZERO");
+                            Duration::ZERO
+                        });

523-526: Consider logging when timestamp fallback occurs.

Similar to the video path, using Duration::ZERO when checked_duration_since returns None gracefully handles timing anomalies but could make debugging difficult. Consider logging a warning when the fallback is applied.

Apply this diff to add logging:

-                            let timestamp = frame
-                                .timestamp
-                                .checked_duration_since(timestamps)
-                                .unwrap_or(Duration::ZERO);
+                            let timestamp = frame
+                                .timestamp
+                                .checked_duration_since(timestamps)
+                                .unwrap_or_else(|| {
+                                    warn!("Audio frame timestamp before recording start, using ZERO");
+                                    Duration::ZERO
+                                });
apps/desktop/src/routes/camera.tsx (1)

550-565: Consider consolidating window size calculation logic.

The canvas sizing logic duplicates the window size calculation from the resource (lines 417-444) for immediate feedback during resize. While this provides better UX, maintaining two copies of the same logic increases the risk of inconsistency.

Consider extracting the calculation into a shared function:

function calculateWindowDimensions(
  size: number,
  shape: CameraWindowShape,
  aspectRatio: number
) {
  const base = Math.max(CAMERA_MIN_SIZE, Math.min(CAMERA_MAX_SIZE, size));
  const windowWidth =
    shape === "full" ? (aspectRatio >= 1 ? base * aspectRatio : base) : base;
  const windowHeight =
    shape === "full" ? (aspectRatio >= 1 ? base : base / aspectRatio) : base;
  return { base, windowWidth, windowHeight };
}

Then use it in both the resource calculation and the style function to ensure consistency.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fd30b14 and 6db1d2d.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (4)
  • apps/desktop/src/routes/camera.tsx (12 hunks)
  • crates/recording/src/output_pipeline/core.rs (3 hunks)
  • crates/timestamp/src/lib.rs (1 hunks)
  • packages/ui-solid/src/auto-imports.d.ts (0 hunks)
💤 Files with no reviewable changes (1)
  • packages/ui-solid/src/auto-imports.d.ts
🧰 Additional context used
📓 Path-based instructions (4)
**/*.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/timestamp/src/lib.rs
  • crates/recording/src/output_pipeline/core.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/timestamp/src/lib.rs
  • crates/recording/src/output_pipeline/core.rs
**/*.{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/camera.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/camera.tsx
🧠 Learnings (3)
📚 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/core.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/core.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 **/*.{ts,tsx} : Use Biome for formatting and linting TypeScript/JavaScript files by running `pnpm format`.

Applied to files:

  • apps/desktop/src/routes/camera.tsx
🧬 Code graph analysis (2)
crates/timestamp/src/lib.rs (2)
crates/timestamp/src/macos.rs (1)
  • checked_duration_since (33-40)
crates/timestamp/src/win.rs (1)
  • checked_duration_since (49-66)
apps/desktop/src/routes/camera.tsx (3)
apps/desktop/src/store.ts (1)
  • generalSettingsStore (61-62)
apps/desktop/src/utils/tauri.ts (1)
  • commands (7-295)
apps/desktop/src/utils/queries.ts (1)
  • createCameraMutation (195-236)
⏰ 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 (3)
crates/timestamp/src/lib.rs (1)

35-46: LGTM! Safe duration calculation implemented correctly.

The checked_duration_since method properly delegates to platform-specific implementations and handles the SystemTime variant by converting errors to None. The formatting issue noted in the previous review has been resolved.

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

432-432: Mutability correctly added.

Declaring video_rx as mutable is necessary for calling .next().await in the loop below.


485-491: Error propagation correctly implemented.

The error handling properly propagates errors from send_video_frame and distinguishes between cancellation and failure cases, addressing the critical issue from the previous review.

@richiemcilroy richiemcilroy merged commit 563e238 into main Nov 19, 2025
15 of 16 checks passed
vgadodia added a commit to InFlight-Software/inflight-recorder that referenced this pull request Dec 2, 2025
commit bdf23c8
Merge: 87ffbc2 d654add
Author: Richie McIlroy <[email protected]>
Date:   Sun Nov 30 21:38:31 2025 +0800

    Merge pull request CapSoftware#1417 from CapSoftware/cursor/optimize-editor-performance-for-large-videos-claude-4.5-opus-high-thinking-fb23

    Optimize editor performance for large videos

commit d654add
Author: Richie McIlroy <[email protected]>
Date:   Sun Nov 30 21:35:01 2025 +0800

    fmt

commit 3213283
Author: Richie McIlroy <[email protected]>
Date:   Sun Nov 30 21:27:29 2025 +0800

    clippy

commit 35f9380
Author: Richie McIlroy <[email protected]>
Date:   Sun Nov 30 21:13:41 2025 +0800

    fmt

commit d1e708a
Author: Cursor Agent <[email protected]>
Date:   Sun Nov 30 10:47:39 2025 +0000

    Refactor playback prefetching to use tokio mpsc and futures

    Co-authored-by: richiemcilroy1 <[email protected]>

commit 23f8516
Author: Cursor Agent <[email protected]>
Date:   Sun Nov 30 10:45:29 2025 +0000

    Checkpoint before follow-up message

    Co-authored-by: richiemcilroy1 <[email protected]>

commit 9bb14bb
Author: Cursor Agent <[email protected]>
Date:   Sun Nov 30 10:23:27 2025 +0000

    Refactor: Improve editor performance and UI responsiveness

    Co-authored-by: richiemcilroy1 <[email protected]>

commit 87ffbc2
Merge: 3cb22ad 0e16f7c
Author: Richie McIlroy <[email protected]>
Date:   Sat Nov 29 16:30:29 2025 +0530

    Merge pull request CapSoftware#1410 from p-delorme/fix-camera-init

    Handles legacy camera size (strings)

commit 0e16f7c
Merge: 21f197e 3cb22ad
Author: Richie McIlroy <[email protected]>
Date:   Sat Nov 29 18:48:41 2025 +0800

    Merge branch 'main' into pr/1410

commit 3cb22ad
Merge: 2330c53 48f7aeb
Author: Richie McIlroy <[email protected]>
Date:   Sat Nov 29 10:00:30 2025 +0530

    Merge pull request CapSoftware#1395 from CapSoftware/screenshots

commit 48f7aeb
Author: Richie McIlroy <[email protected]>
Date:   Sat Nov 29 12:02:29 2025 +0800

    fmt

commit 126421d
Author: Richie McIlroy <[email protected]>
Date:   Sat Nov 29 11:59:07 2025 +0800

    types/clippy bits

commit cdc91c0
Author: Richie McIlroy <[email protected]>
Date:   Sat Nov 29 11:47:16 2025 +0800

    Throw SilentError on export cancellation

commit 980720e
Author: Richie McIlroy <[email protected]>
Date:   Sat Nov 29 11:47:06 2025 +0800

    Add image dimension and data validation to editor

commit 860ccd7
Author: Richie McIlroy <[email protected]>
Date:   Sat Nov 29 11:46:57 2025 +0800

    Add camera_cleanup_done flag to prevent redundant cleanup

commit 38cc696
Author: Richie McIlroy <[email protected]>
Date:   Sat Nov 29 00:02:07 2025 +0800

    claude settings

commit e2545ee
Author: Richie McIlroy <[email protected]>
Date:   Fri Nov 28 23:58:31 2025 +0800

    Improve screenshot capture reliability and performance

commit ba9ba53
Author: Richie McIlroy <[email protected]>
Date:   Fri Nov 28 23:58:17 2025 +0800

    Add validation for annotation and camera config

commit 641a99a
Author: Richie McIlroy <[email protected]>
Date:   Fri Nov 28 23:58:03 2025 +0800

    Show toast on screenshot failure in overlay

commit ba901dd
Author: Richie McIlroy <[email protected]>
Date:   Fri Nov 28 22:48:55 2025 +0800

    Add keyboard shortcuts for export actions in Header

commit c73f735
Author: Richie McIlroy <[email protected]>
Date:   Fri Nov 28 21:47:48 2025 +0800

    Add stride check in FFmpeg frame conversion

commit 6825eb3
Merge: 4d73f14 2330c53
Author: Richie McIlroy <[email protected]>
Date:   Fri Nov 28 21:23:41 2025 +0800

    Merge branch 'main' into screenshots

commit 2330c53
Author: Richie McIlroy <[email protected]>
Date:   Fri Nov 28 20:22:42 2025 +0800

    fix: margin on mobile nav

commit 0765fee
Author: Richie McIlroy <[email protected]>
Date:   Fri Nov 28 20:16:06 2025 +0800

    feat: Cap Friday

commit 4d73f14
Author: Richie McIlroy <[email protected]>
Date:   Fri Nov 28 04:55:51 2025 +0530

    Add fast screenshot capture and shared GPU context

commit f8f1d01
Author: Richie McIlroy <[email protected]>
Date:   Fri Nov 28 04:54:15 2025 +0530

    Add toast notifications for screenshot actions

commit bb0310d
Author: Richie McIlroy <[email protected]>
Date:   Fri Nov 28 01:16:47 2025 +0530

    misc bits

commit ed64349
Author: Richie McIlroy <[email protected]>
Date:   Fri Nov 28 01:16:42 2025 +0530

    Refactor camera window cleanup logic

commit 2b1eb9e
Author: Richie McIlroy <[email protected]>
Date:   Fri Nov 28 01:16:25 2025 +0530

    Handle errors during titlebar initialization

commit eafc7d9
Author: Richie McIlroy <[email protected]>
Date:   Fri Nov 28 01:16:12 2025 +0530

    Refactor import and error handling in screenshot_editor.rs

commit d776857
Author: Richie McIlroy <[email protected]>
Date:   Fri Nov 28 01:15:59 2025 +0530

    Optimize PNG screenshot encoding settings

commit 737b89a
Author: Richie McIlroy <[email protected]>
Date:   Fri Nov 28 01:15:42 2025 +0530

    Remove unused mutable binding in camera_legacy.rs

commit 37456d3
Author: Richie McIlroy <[email protected]>
Date:   Fri Nov 28 00:39:08 2025 +0530

    Disable cursor capture in screenshot functionality

commit 694de05
Author: Richie McIlroy <[email protected]>
Date:   Fri Nov 28 00:24:07 2025 +0530

    Refactor camera preview to use broadcast channel

commit 7c68831
Author: Richie McIlroy <[email protected]>
Date:   Fri Nov 28 00:23:58 2025 +0530

    Switch frame channel from flume to tokio broadcast

commit 3a39c9c
Author: Richie McIlroy <[email protected]>
Date:   Fri Nov 28 00:23:25 2025 +0530

    Refactor WebSocket frame handling to use broadcast channel

commit f12c13e
Author: Richie McIlroy <[email protected]>
Date:   Fri Nov 28 00:23:14 2025 +0530

    Refactor render shutdown handling in screenshot editor

commit 3c4a124
Author: Richie McIlroy <[email protected]>
Date:   Fri Nov 28 00:23:05 2025 +0530

    Remove timestamp from screenshot src

commit 032b819
Author: Richie McIlroy <[email protected]>
Date:   Fri Nov 28 00:21:25 2025 +0530

    Handle shutdown in screenshot editor render loop

commit 7dd146a
Author: Richie McIlroy <[email protected]>
Date:   Fri Nov 28 00:15:59 2025 +0530

    Handle missing ScreenshotEditor instance gracefully

commit 21f197e
Author: Patrick Delorme <[email protected]>
Date:   Wed Nov 26 22:18:33 2025 -0500

    Handles legacy camera size strings

commit 9d22e01
Author: Richie McIlroy <[email protected]>
Date:   Sat Nov 22 09:24:47 2025 +0000

    0.4.0

commit af1cc60
Author: Richie McIlroy <[email protected]>
Date:   Sat Nov 22 10:45:45 2025 +0530

    feat: Screenshots V1 - Enhance screenshot editor with mask tool and export cancel

commit 61982eb
Author: Richie McIlroy <[email protected]>
Date:   Thu Nov 20 16:03:26 2025 +0000

    Add screenshot export improvements and new icon

commit 1996ab6
Author: Richie McIlroy <[email protected]>
Date:   Thu Nov 20 15:28:16 2025 +0000

    Implement Windows screenshot capture support

commit 7bd9644
Merge: 6dcb6f2 f272bbf
Author: Richie McIlroy <[email protected]>
Date:   Thu Nov 20 15:22:15 2025 +0000

    Merge branch 'main' into screenshots

commit a292974
Merge: f272bbf 4a41f99
Author: Richie McIlroy <[email protected]>
Date:   Thu Nov 20 15:21:54 2025 +0000

    Merge pull request CapSoftware#1381 from phuocithcmus/fix/window-memory-leak-processout

    fix: window memory leak - memory increase while recording

commit 6dcb6f2
Author: Richie McIlroy <[email protected]>
Date:   Thu Nov 20 15:03:01 2025 +0000

    Refactor mask blur logic for screenshot editor

commit b4c8a17
Author: Richie McIlroy <[email protected]>
Date:   Thu Nov 20 12:51:21 2025 +0000

    feat: Screenshot editor styling/layout + Mask annotation

commit f272bbf
Merge: 563e238 bab0348
Author: Richie McIlroy <[email protected]>
Date:   Wed Nov 19 19:01:40 2025 +0000

    Merge pull request CapSoftware#1396 from ItsEeleeya/new-theme-previews

    New theme preview icons

commit bab0348
Author: Ilya <[email protected]>
Date:   Wed Nov 19 21:54:35 2025 +0330

    Remove unused

commit ef4a54f
Author: Ilya <[email protected]>
Date:   Wed Nov 19 21:45:33 2025 +0330

    New theme preview icons

commit f92090a
Author: Richie McIlroy <[email protected]>
Date:   Wed Nov 19 17:57:11 2025 +0000

    Add screenshot support to target selection UI

commit 849b702
Author: Richie McIlroy <[email protected]>
Date:   Wed Nov 19 17:20:53 2025 +0000

    Replace Button with native button in TargetMenuPanel

commit f28cffd
Author: Richie McIlroy <[email protected]>
Date:   Wed Nov 19 17:15:52 2025 +0000

    Add recordings grid and menu to main window

commit 84d1baa
Author: Richie McIlroy <[email protected]>
Date:   Wed Nov 19 16:55:52 2025 +0000

    Refactor screenshot saving and editor window management

commit f9450c8
Author: Richie McIlroy <[email protected]>
Date:   Wed Nov 19 16:55:35 2025 +0000

    Add screenshot mode and image icon support

commit 11007c2
Author: Richie McIlroy <[email protected]>
Date:   Wed Nov 19 16:55:23 2025 +0000

    Refactor screenshot editor and add screenshots tab

commit 77cac64
Author: Richie McIlroy <[email protected]>
Date:   Wed Nov 19 02:45:50 2025 +0000

    Integrate live preview for screenshot editor

commit c982991
Author: Richie McIlroy <[email protected]>
Date:   Wed Nov 19 02:45:40 2025 +0000

    Add screenshot editor integration and DecodedFrame constructor

commit 50ca057
Author: Richie McIlroy <[email protected]>
Date:   Wed Nov 19 02:45:24 2025 +0000

    Add screenshot editor backend implementation

commit 4c3b942
Author: Richie McIlroy <[email protected]>
Date:   Wed Nov 19 02:31:13 2025 +0000

    Allow Slider to accept custom history prop

commit f7047a0
Author: Richie McIlroy <[email protected]>
Date:   Wed Nov 19 02:29:18 2025 +0000

    feat: v1 of screenshots

commit 563e238
Merge: 9edf1ce 2f75043
Author: Richie McIlroy <[email protected]>
Date:   Wed Nov 19 00:33:18 2025 +0000

    Merge pull request CapSoftware#1390 from CapSoftware/editor-perf

    feat: Various features + performance bits

commit 2f75043
Author: Richie McIlroy <[email protected]>
Date:   Wed Nov 19 00:30:49 2025 +0000

    clippy bits

commit ef920b2
Author: Richie McIlroy <[email protected]>
Date:   Wed Nov 19 00:20:04 2025 +0000

    gen'd files

commit 695bb66
Author: Richie McIlroy <[email protected]>
Date:   Wed Nov 19 00:19:52 2025 +0000

    Fix requestAnimationFrame cleanup in overlay

commit 0510be8
Author: Richie McIlroy <[email protected]>
Date:   Wed Nov 19 00:19:44 2025 +0000

    Add __CAP__ property to Window interface

commit 756d296
Author: Richie McIlroy <[email protected]>
Date:   Wed Nov 19 00:19:29 2025 +0000

    Improve camera window resizing and positioning logic

commit ff78eba
Author: Richie McIlroy <[email protected]>
Date:   Wed Nov 19 00:19:09 2025 +0000

    Clarify and emphasize no code comments policy

commit 6db1d2d
Author: Richie McIlroy <[email protected]>
Date:   Tue Nov 18 23:41:03 2025 +0000

    Remove unused setCamera mutation in camera page

commit 51e2f6e
Author: Richie McIlroy <[email protected]>
Date:   Tue Nov 18 23:38:43 2025 +0000

    misc packages

commit a0aeffa
Author: Richie McIlroy <[email protected]>
Date:   Tue Nov 18 23:38:32 2025 +0000

    Refactor duration calculation for readability

commit fd30b14
Author: Richie McIlroy <[email protected]>
Date:   Tue Nov 18 21:52:38 2025 +0000

    Add checked_duration_since for timestamp types

commit 05f39bb
Author: Richie McIlroy <[email protected]>
Date:   Tue Nov 18 21:46:20 2025 +0000

    Improve countdown animation and fix recording logic

commit dec0d8e
Merge: 0066eb1 5de394f
Author: Richie McIlroy <[email protected]>
Date:   Tue Nov 18 21:44:52 2025 +0000

    Merge branch 'editor-perf' of https://github.com/CapSoftware/Cap into editor-perf

commit 0066eb1
Author: Richie McIlroy <[email protected]>
Date:   Tue Nov 18 21:36:48 2025 +0000

    Add camera overlay bounds update and revert logic

commit 5de394f
Author: Richie McIlroy <[email protected]>
Date:   Tue Nov 18 20:51:15 2025 +0000

    Update apps/desktop/src-tauri/src/camera.rs

    Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

commit e6e9320
Author: Richie McIlroy <[email protected]>
Date:   Tue Nov 18 18:26:21 2025 +0000

    Improve logging and error context in recording pipeline

commit d24e92c
Author: Richie McIlroy <[email protected]>
Date:   Tue Nov 18 17:10:19 2025 +0000

    Add code formatting guidelines to documentation

commit 2b504dd
Author: Richie McIlroy <[email protected]>
Date:   Tue Nov 18 16:54:29 2025 +0000

    Update camera.tsx

commit 891b122
Author: Richie McIlroy <[email protected]>
Date:   Tue Nov 18 16:54:02 2025 +0000

    Improve camera feed sender handling and logging

commit 831fb76
Author: Richie McIlroy <[email protected]>
Date:   Tue Nov 18 16:53:42 2025 +0000

    Fix import order in experimental settings route

commit dd39425
Author: Richie McIlroy <[email protected]>
Date:   Tue Nov 18 16:41:22 2025 +0000

    Set Mellow as default for CursorAnimationStyle

commit ce63e02
Author: Richie McIlroy <[email protected]>
Date:   Tue Nov 18 16:41:11 2025 +0000

    Add resizing to camera window

commit 6dd3ff1
Author: Richie McIlroy <[email protected]>
Date:   Tue Nov 18 16:40:46 2025 +0000

    Hide native camera preview toggle on Windows

commit 44a5348
Author: Richie McIlroy <[email protected]>
Date:   Tue Nov 18 15:14:03 2025 +0000

    Refactor type casting for currentRecording data

commit 41fbc5c
Author: Richie McIlroy <[email protected]>
Date:   Tue Nov 18 14:51:41 2025 +0000

    Improve camera initialization and recording state handling

commit 6aca28e
Author: Richie McIlroy <[email protected]>
Date:   Tue Nov 18 14:51:33 2025 +0000

    Add initializing state to recording flow

commit d2b8fd4
Author: Richie McIlroy <[email protected]>
Date:   Tue Nov 18 14:50:45 2025 +0000

    Update close button to use window close action

commit fee3391
Author: Richie McIlroy <[email protected]>
Date:   Tue Nov 18 12:00:28 2025 +0000

    Reset camera and mic state on window close and recording end

commit 184ad59
Author: Richie McIlroy <[email protected]>
Date:   Tue Nov 18 10:49:45 2025 +0000

    lost a days work (I F'D UP)

commit 8b403a8
Author: Richie McIlroy <[email protected]>
Date:   Tue Nov 18 10:40:07 2025 +0000

    Update recording UI container styles

commit 37eb7c8
Author: Richie McIlroy <[email protected]>
Date:   Mon Nov 17 20:52:24 2025 +0000

    Update settings.local.json

commit cced653
Author: Richie McIlroy <[email protected]>
Date:   Mon Nov 17 20:52:18 2025 +0000

    Improve camera frame forwarding and logging

commit dbe8ae1
Author: Richie McIlroy <[email protected]>
Date:   Mon Nov 17 20:50:12 2025 +0000

    Refactor waveform rendering in ClipTrack

commit 5d95f24
Author: Richie McIlroy <[email protected]>
Date:   Mon Nov 17 20:15:25 2025 +0000

    Handle excluded windows in macOS screen capture

commit b61958c
Merge: bd1fc73 9edf1ce
Author: Richie McIlroy <[email protected]>
Date:   Mon Nov 17 17:12:38 2025 +0000

    Merge branch 'main' into editor-perf

commit bd1fc73
Author: Richie McIlroy <[email protected]>
Date:   Mon Nov 17 13:07:30 2025 +0000

    fmt

commit 0a5fea6
Author: Richie McIlroy <[email protected]>
Date:   Mon Nov 17 13:07:10 2025 +0000

    Refactor project config save with custom debounce

commit 4a41f99
Author: phuocnd <[email protected]>
Date:   Wed Nov 12 16:15:42 2025 +0700

    fix: window memory leak - memory increase while streaming
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