Skip to content

Conversation

@ItsEeleeya
Copy link
Contributor

@ItsEeleeya ItsEeleeya commented Nov 22, 2025

Note

This is part of a preparation for bigger changes that are work-in-progress. These are smaller, but more frequent and should be done as early as possible to avoid future conflicts and unnecessary work.

This PR includes a small refactor in windows.rs and bumps some crate versions.
CapWindowId -> CapWindowDef and ShowCapWindow -> CapWindow.

We can now give the traffic light position to tauri instead of somewhat subclassing Tauri's NSWindow delegate. This is still not ideal for macOS 26+, that will be fixed with a later PR.

permissions.rs now uses objc2_application_services instead. This crate will be useful in the near future for determining the possible shape of a window.

Summary by CodeRabbit

  • New Features

    • Recordings can optionally include a camera feed.
    • Project saves now include mask and text segments in timelines.
  • Improvements

    • Upgraded desktop runtime, tooling, and native macOS integrations (including macOS platform refactor).
    • Refined macOS window behavior and traffic-light controls.
    • Enhanced Cap project file association metadata on macOS.
  • Bug Fixes

    • Safer macOS accessibility permission handling.
    • Screenshot editor: removed rounded corners and hid crop bounds overlay.

✏️ Tip: You can customize this high-level summary in your review settings.


Note

Refactors desktop windowing (CapWindowDef/CapWindow), switches macOS window/permissions to objc2, and upgrades Tauri/objc2 and plugins; also tweaks editor Zoom track and bundling metadata.

  • Desktop (windowing):
    • Rename CapWindowIdCapWindowDef and ShowCapWindowCapWindow; centralize window building/levels/buttons/fullscreen in windows.rs.
    • Remove custom macOS NSWindow delegate; use Tauri APIs with traffic_light_position and new WebviewWindowExt to access NSWindow and toggle traffic lights.
    • Improve camera window lifecycle/cleanup and overlay management.
  • Permissions (macOS):
    • Replace CoreFoundation bridges with objc2_application_services (AXIsProcessTrusted(WithOptions)), update request flow.
  • Frontend:
    • Simplify ZoomTrack.tsx segment rendering/selection logic.
  • Config/Bundling:
    • Add richer macOS file association metadata (rank/description/exportedType) in tauri.conf.json.
  • Dependencies:
    • Upgrade tauri to 2.9.3 and multiple tauri-* plugins; bump objc2*, block2, wry, tao, tauri-runtime(-wry), and serde; set @tauri-apps/cli to ^2.9.4.
    • Add objc2-application-services and related objc2-* crates; update lockfile accordingly.

Written by Cursor Bugbot for commit a598262. This will update automatically on new commits. Configure here.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 22, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Workspace Tauri and macOS ObjC dependencies bumped and unified; macOS raw FFI/delegate code removed and replaced with objc2-based APIs and a WebviewWindowExt trait; window enums/types renamed (CapWindowId → CapWindowDef, ShowCapWindow → CapWindow); recording and window payloads extended.

Changes

Cohort / File(s) Change Summary
Workspace & manifests
Cargo.toml, apps/desktop/package.json, apps/desktop/src-tauri/Cargo.toml, crates/.../Cargo.toml, apps/desktop/src-tauri/tauri.conf.json
Bump workspace tauri and tauri plugins; add workspace objc2; convert per-crate objc2 deps to workspace-scoped; update package.json tauri packages; add .cap file association metadata.
Window API rename & wiring
apps/desktop/src-tauri/src/windows.rs, apps/desktop/src-tauri/src/lib.rs, apps/desktop/src-tauri/src/tray.rs, apps/desktop/src-tauri/src/deeplink_actions.rs, apps/desktop/src-tauri/src/hotkeys.rs, apps/desktop/src-tauri/src/screenshot_editor.rs, apps/desktop/src/utils/tauri.ts
Systematic rename: CapWindowIdCapWindowDef, ShowCapWindowCapWindow; update variants, FromStr/Display/label/title helpers, imports, matches, and TypeScript CapWindow type and showWindow signature.
Recording, screenshot & overlays
apps/desktop/src-tauri/src/recording.rs, apps/desktop/src-tauri/src/target_select_overlay.rs, apps/desktop/src/routes/screenshot-editor/Editor.tsx
Add camera_feed: Option<Arc<CameraFeedLock>> to InProgressRecording; extend timeline/project config segment fields and DisplayInformation.logical_size; update window lookups to CapWindowDef; minor UI cropper adjustments.
macOS platform migration
apps/desktop/src-tauri/src/platform/macos/mod.rs, apps/desktop/src-tauri/src/platform/mod.rs, apps/desktop/src-tauri/src/permissions.rs, (deleted) apps/desktop/src-tauri/src/platform/macos/delegates.rs
Remove legacy delegates.rs; migrate macOS FFI/permissions/haptic code to objc2 crates; add WebviewWindowExt trait with objc2_nswindow and set_traffic_lights_visible; simplify unsafe blocks.
Per-crate Cargo adjustments
crates/cursor-info/Cargo.toml, crates/scap-screencapturekit/Cargo.toml
Change objc2 dependency declarations to workspace-scoped ({ workspace = true }) for macOS targets.

Sequence Diagram(s)

mermaid
sequenceDiagram
participant UI as Browser / React
participant TS as utils/tauri.ts
participant Rust as Tauri (Rust backend)
participant Windows as windows.rs / App state
participant macOS as objc2 / NSWindow
UI->>TS: showWindow(CapWindow)
TS->>Rust: invoke Tauri command (CapWindow payload)
Rust->>Windows: resolve CapWindowDef.from_str / def()
Windows->>Rust: build/show window (main-thread)
Rust->>macOS: call objc2 helpers (objc2_nswindow / set_traffic_lights_visible)
macOS-->>Rust: window events / responses
Rust->>UI: emit event / IPC callback

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Areas needing extra attention:
    • apps/desktop/src-tauri/src/platform/macos/* — deleted delegate implementation and objc2 migration: verify macOS lifecycle events, window controls, and event emission.
    • apps/desktop/src-tauri/src/windows.rs, lib.rs — widespread rename and behavioral hooks via CapWindowDef (FromStr/label/title/activates_dock); ensure all call sites updated and serialization compatibility preserved.
    • apps/desktop/src-tauri/src/recording.rs — new camera_feed field: verify ownership, Arc usage, initialization, and cleanup across threads.
    • JS ↔ Rust IPC types: apps/desktop/src/utils/tauri.ts vs Rust enums — confirm specta/command bindings and runtime serialization remain aligned.

Possibly related PRs

Suggested labels

codex

Suggested reviewers

  • oscartbeaumont
  • Brendonovich

Poem

🐰 I hopped through enums and ObjC glue,

CapWindow donned a tidy hue.
objc2 hummed, the traffic lights hid,
camera feeds snuggled safe and did.
A little rabbit cheers — code anew.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 56.79% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the two main changes: refactors in windows.rs (CapWindowId/ShowCapWindow renaming, delegate removal, centralized window building) and version bumps for tauri and objc2-related crates.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

📜 Recent review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3967e3a and 25c4f89.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (3)
  • apps/desktop/src-tauri/src/lib.rs (23 hunks)
  • apps/desktop/src-tauri/src/windows.rs (30 hunks)
  • apps/desktop/src/utils/tauri.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/desktop/src/utils/tauri.ts
🧰 Additional context used
📓 Path-based instructions (2)
**/*.rs

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.rs: Never use dbg!() macro; use proper logging (tracing::debug!, etc.) instead
Never write let _ = async_fn() which silently drops futures; await or explicitly handle them
Use duration.saturating_sub(other) instead of duration - other to avoid panics on underflow
Merge nested if statements: write 'if a && b { }' instead of 'if a { if b { } }'
Don't call .clone() on Copy types (integers, bools, etc.); copy them directly
Use function references directly: iter.map(foo) instead of iter.map(|x| foo(x))
Accept &[T] or &str instead of &Vec or &String in function parameters for flexibility
Use .is_empty() instead of .len() == 0 or .len() > 0 / .len() != 0
Don't assign () to a variable: write foo(); instead of let _ = foo(); or let x = foo(); when return is unit
Use .unwrap_or(val) instead of .unwrap_or_else(|| val) when the default is a simple/cheap value
Use 'for item in &collection' or 'for (i, item) in collection.iter().enumerate()' instead of 'for i in 0..collection.len()'
Use value.clamp(min, max) instead of manual if chains or .min(max).max(min) patterns
Always handle Result/Option or types marked #[must_use]; never ignore them

**/*.rs: Use rustfmt and workspace clippy lints for Rust code formatting and linting
Use snake_case for Rust module names and kebab-case for crate names
Never use dbg!() macro in Rust code; use proper logging instead (Clippy: dbg_macro = deny)
Always handle Result/Option or types marked #[must_use]; never ignore them (Rust compiler lint: unused_must_use = deny)
Never write let _ = async_fn() which silently drops futures; await or explicitly handle them (Clippy: let_underscore_future = deny)
Use saturating_sub instead of - for Duration to avoid panics (Clippy: unchecked_duration_subtraction = deny)
Merge nested if statements: use if a && b { } instead of if a { if b { } } (Clippy: collapsible_if = deny)
Don't call .clone() on Copy types; just copy them directly (Clippy: clone_on_copy = deny)
U...

Files:

  • apps/desktop/src-tauri/src/windows.rs
  • apps/desktop/src-tauri/src/lib.rs
**/*.{ts,tsx,js,jsx,rs}

📄 CodeRabbit inference engine (AGENTS.md)

Never add comments to code (//, /* */, ///, //!, #, etc.); code must be self-explanatory through naming, types, and structure

Files:

  • apps/desktop/src-tauri/src/windows.rs
  • apps/desktop/src-tauri/src/lib.rs
🧠 Learnings (5)
📚 Learning: 2025-12-07T14:29:40.743Z
Learnt from: CR
Repo: CapSoftware/Cap PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-07T14:29:40.743Z
Learning: Applies to **/*.rs : Use `.unwrap_or(val)` instead of `.unwrap_or_else(|| val)` for cheap values (Clippy: `unnecessary_lazy_evaluations` = deny)

Applied to files:

  • apps/desktop/src-tauri/src/windows.rs
📚 Learning: 2025-12-07T14:29:40.743Z
Learnt from: CR
Repo: CapSoftware/Cap PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-07T14:29:40.743Z
Learning: Applies to **/*.rs : Never write `let _ = async_fn()` which silently drops futures; await or explicitly handle them (Clippy: `let_underscore_future` = deny)

Applied to files:

  • apps/desktop/src-tauri/src/windows.rs
📚 Learning: 2025-12-07T14:29:19.180Z
Learnt from: CR
Repo: CapSoftware/Cap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-07T14:29:19.180Z
Learning: Applies to **/*.rs : Never write let _ = async_fn() which silently drops futures; await or explicitly handle them

Applied to files:

  • apps/desktop/src-tauri/src/windows.rs
📚 Learning: 2025-12-07T14:29:40.743Z
Learnt from: CR
Repo: CapSoftware/Cap PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-07T14:29:40.743Z
Learning: Applies to **/*.rs : Always handle `Result`/`Option` or types marked `#[must_use]`; never ignore them (Rust compiler lint: `unused_must_use` = deny)

Applied to files:

  • apps/desktop/src-tauri/src/windows.rs
📚 Learning: 2025-12-07T14:29:19.180Z
Learnt from: CR
Repo: CapSoftware/Cap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-07T14:29:19.180Z
Learning: Applies to **/*.rs : Always handle Result/Option or types marked #[must_use]; never ignore them

Applied to files:

  • apps/desktop/src-tauri/src/windows.rs
🧬 Code graph analysis (2)
apps/desktop/src-tauri/src/windows.rs (2)
apps/desktop/src/routes/(window-chrome)/settings.tsx (1)
  • Settings (12-114)
apps/desktop/src/utils/tauri.ts (3)
  • Camera (371-371)
  • LogicalPosition (434-434)
  • CapWindow (379-379)
apps/desktop/src-tauri/src/lib.rs (1)
apps/desktop/src-tauri/src/windows.rs (13)
  • from_str (60-99)
  • app (262-262)
  • app (281-281)
  • app (445-445)
  • app (620-620)
  • app (964-964)
  • app (982-982)
  • app (1122-1122)
  • app (1134-1134)
  • label (128-130)
  • get (169-172)
  • get (1121-1123)
  • get (1133-1135)
⏰ 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: Clippy (x86_64-pc-windows-msvc, windows-latest)

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.

.await;
app_state.camera_in_use = false;
}
});
Copy link

Choose a reason for hiding this comment

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

Bug: Camera cleanup misses double-execution guard

The new inline camera cleanup code in the Destroyed event handler is missing the camera_cleanup_done guard that exists in cleanup_camera_window. The CloseRequested event still calls cleanup_camera_window which checks and sets camera_cleanup_done, but the Destroyed event now runs inline code that lacks this check. When both events fire (which happens during normal window close), cleanup runs twice - calling on_window_close() and RemoveInput a second time after the guard was already set.

Additional Locations (1)

Fix in Cursor Fix in Web

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 (2)
apps/desktop/src-tauri/src/windows.rs (2)

377-407: Critical: window_builder is undefined in the Windows TargetSelectOverlay branch.

In the #[cfg(windows)] block, builder = window_builder.inner_size(... refers to a non‑existent window_builder variable; only builder is in scope. This will not compile and should use builder instead. This was already noted in a previous review and still applies.

-                #[cfg(windows)]
-                {
-                    builder = window_builder.inner_size(100.0, 100.0).position(0.0, 0.0);
-                }
+                #[cfg(windows)]
+                {
+                    builder = builder.inner_size(100.0, 100.0).position(0.0, 0.0);
+                }

823-851: Avoid potential panics when resolving Editor/ScreenshotEditor IDs from registries.

Both the Editor and ScreenshotEditor arms in def() use find(...).unwrap(), which will panic if the path is missing from their respective registries. The current show() flow registers paths before calling def(), but any future caller or inconsistent state would turn into a crash. A small change can make this more robust while preserving the same API surface:

-            CapWindow::Editor { project_path } => {
-                let state = app.state::<EditorWindowIds>();
-                let s = state.ids.lock().unwrap();
-                let id = s.iter().find(|(path, _)| path == project_path).unwrap().1;
-                CapWindowDef::Editor { id }
-            }
+            CapWindow::Editor { project_path } => {
+                let state = app.state::<EditorWindowIds>();
+                let s = state.ids.lock().unwrap();
+                let id = s
+                    .iter()
+                    .find(|(path, _)| path == project_path)
+                    .map(|(_, id)| *id)
+                    .unwrap_or_default();
+                CapWindowDef::Editor { id }
+            }
@@
-            CapWindow::ScreenshotEditor { path } => {
-                let state = app.state::<ScreenshotEditorWindowIds>();
-                let s = state.ids.lock().unwrap();
-                let id = s.iter().find(|(p, _)| p == path).unwrap().1;
-                CapWindowDef::ScreenshotEditor { id }
-            }
+            CapWindow::ScreenshotEditor { path } => {
+                let state = app.state::<ScreenshotEditorWindowIds>();
+                let s = state.ids.lock().unwrap();
+                let id = s
+                    .iter()
+                    .find(|(p, _)| p == path)
+                    .map(|(_, id)| *id)
+                    .unwrap_or_default();
+                CapWindowDef::ScreenshotEditor { id }
+            }

This mirrors earlier feedback about these unwraps while avoiding a hard panic if the registry ever gets out of sync.

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

264-281: Reduce duplicate scans when registering editor window IDs.

The editor registration block scans state.ids twice (once with any, once with find). You can simplify and avoid the second search by deriving the ID from a single find result and creating/pushing only on None:

-            let window_id = {
-                let mut s = state.ids.lock().unwrap();
-                if !s.iter().any(|(path, _)| path == project_path) {
-                    let id = state
-                        .counter
-                        .fetch_add(1, std::sync::atomic::Ordering::SeqCst);
-                    s.push((project_path.clone(), id));
-                    id
-                } else {
-                    s.iter().find(|(path, _)| path == project_path).unwrap().1
-                }
-            };
+            let window_id = {
+                let mut s = state.ids.lock().unwrap();
+                if let Some((_, id)) = s.iter().find(|(path, _)| path == project_path) {
+                    *id
+                } else {
+                    let id = state
+                        .counter
+                        .fetch_add(1, std::sync::atomic::Ordering::SeqCst);
+                    s.push((project_path.clone(), id));
+                    id
+                }
+            };

212-224: Check Editor min_size vs explicit inner_size to avoid conflicting constraints.

CapWindowDef::min_size for Editor is (1275.0, 800.0), but the Editor branch later calls .inner_size(1240.0, 800.0) on the builder. Since window_builder already sets both inner_size and min_inner_size from min_size, the explicit .inner_size(1240.0, 800.0) may be clamped or overridden by the larger min size, depending on Tauri/winit behavior. Consider aligning these (either reduce the min_size width or drop the explicit inner_size) so the initial size and minimum are consistent.

Also applies to: 467-472, 789-803


197-211: Align RecordingsOverlay window level between CapWindowDef and NSPanel usage.

CapWindowDef::window_level returns NSScreenSaverWindowLevel for RecordingsOverlay, but the NSPanel configuration still calls panel.set_level(NSMainMenuWindowLevel as i32). Since the post‑match macOS block also sets the NSWindow level from def.window_level(), whichever runs last wins; having two different levels for the same window is confusing and may be misleading for future changes. It would be clearer to pick one level for RecordingsOverlay and apply it consistently (either by adjusting window_level() or by updating/removing the panel.set_level call).

Also applies to: 724-735

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a598262 and 51b78b0.

📒 Files selected for processing (1)
  • apps/desktop/src-tauri/src/windows.rs (28 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx,js,jsx,rs,py,sh}

📄 CodeRabbit inference engine (CLAUDE.md)

Never add any form of comments to code (single-line //, multi-line /* /, documentation ///, //!, /* */, or any other comment syntax). Code must be self-explanatory through naming, types, and structure.

Files:

  • apps/desktop/src-tauri/src/windows.rs
**/*.rs

📄 CodeRabbit inference engine (CLAUDE.md)

Use Rust 2024 edition and run cargo fmt to format all Rust code with rustfmt

**/*.rs: Format all Rust code using rustfmt and apply workspace clippy lints
Use snake_case for Rust module names and kebab-case for Rust crate names

Files:

  • apps/desktop/src-tauri/src/windows.rs
apps/desktop/**/*.rs

📄 CodeRabbit inference engine (CLAUDE.md)

Follow architectural decision to use tauri_specta for strongly typed Rust-to-TypeScript IPC bindings; do not modify generated bindings

Files:

  • apps/desktop/src-tauri/src/windows.rs
**/*.{ts,tsx,js,jsx,rs}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx,js,jsx,rs}: Use kebab-case for file names (e.g., user-menu.tsx)
Never add comments to code in any language (no //, /* */, ///, //!, #, etc.). Code must be self-explanatory through naming, types, and structure

Files:

  • apps/desktop/src-tauri/src/windows.rs
🧠 Learnings (1)
📚 Learning: 2025-11-29T04:31:05.289Z
Learnt from: CR
Repo: CapSoftware/Cap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-29T04:31:05.289Z
Learning: Applies to apps/desktop/**/*.rs : Follow architectural decision to use tauri_specta for strongly typed Rust-to-TypeScript IPC bindings; do not modify generated bindings

Applied to files:

  • apps/desktop/src-tauri/src/windows.rs
⏰ 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: Clippy (aarch64-apple-darwin, macos-latest)
🔇 Additional comments (3)
apps/desktop/src-tauri/src/windows.rs (3)

40-56: CapWindowDef metadata wiring looks internally consistent.

The enum variants, FromStr/Display label mapping, titles, dock-activation, window levels, and min sizes all line up and give a single source of truth for window identity and behavior, which should simplify future changes and avoid label/title drift.

Also applies to: 58-101, 103-225


759-779: Centralized macOS window customization via CapWindowDef is a solid refactor.

Using def.disables_window_buttons(), def.disables_fullscreen(), and def.window_level() from a single run_on_main_thread block cleanly replaces scattered NSWindow logic and ties macOS‑specific behavior directly to the declarative window definition.


885-889: Content protection refresh now correctly uses CapWindowDef.

Iterating over webview_windows(), parsing labels with CapWindowDef::from_str, and feeding id.title() into should_protect_window gives a consistent, type‑checked way to keep content protection in sync with the user’s exclusion list.

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

♻️ Duplicate comments (1)
apps/desktop/src-tauri/src/windows.rs (1)

401-404: Critical bug: undefined variable window_builder.

On line 403, window_builder is referenced but the variable is named builder (defined on line 377). This will cause a compilation error.

Apply this diff to fix:

                 #[cfg(windows)]
                 {
-                    builder = window_builder.inner_size(100.0, 100.0).position(0.0, 0.0);
+                    builder = builder.inner_size(100.0, 100.0).position(0.0, 0.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 51b78b0 and b16cd8c.

📒 Files selected for processing (1)
  • apps/desktop/src-tauri/src/windows.rs (28 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx,js,jsx,rs,py,sh}

📄 CodeRabbit inference engine (CLAUDE.md)

Never add any form of comments to code (single-line //, multi-line /* /, documentation ///, //!, /* */, or any other comment syntax). Code must be self-explanatory through naming, types, and structure.

Files:

  • apps/desktop/src-tauri/src/windows.rs
**/*.rs

📄 CodeRabbit inference engine (CLAUDE.md)

Use Rust 2024 edition and run cargo fmt to format all Rust code with rustfmt

**/*.rs: Format all Rust code using rustfmt and apply workspace clippy lints
Use snake_case for Rust module names and kebab-case for Rust crate names

Files:

  • apps/desktop/src-tauri/src/windows.rs
apps/desktop/**/*.rs

📄 CodeRabbit inference engine (CLAUDE.md)

Follow architectural decision to use tauri_specta for strongly typed Rust-to-TypeScript IPC bindings; do not modify generated bindings

Files:

  • apps/desktop/src-tauri/src/windows.rs
**/*.{ts,tsx,js,jsx,rs}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx,js,jsx,rs}: Use kebab-case for file names (e.g., user-menu.tsx)
Never add comments to code in any language (no //, /* */, ///, //!, #, etc.). Code must be self-explanatory through naming, types, and structure

Files:

  • apps/desktop/src-tauri/src/windows.rs
🧠 Learnings (2)
📓 Common learnings
Learnt from: CR
Repo: CapSoftware/Cap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-29T04:31:05.289Z
Learning: Applies to apps/desktop/**/*.rs : Follow architectural decision to use tauri_specta for strongly typed Rust-to-TypeScript IPC bindings; do not modify generated bindings
📚 Learning: 2025-11-29T04:31:05.289Z
Learnt from: CR
Repo: CapSoftware/Cap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-29T04:31:05.289Z
Learning: Applies to apps/desktop/**/*.rs : Follow architectural decision to use tauri_specta for strongly typed Rust-to-TypeScript IPC bindings; do not modify generated bindings

Applied to files:

  • apps/desktop/src-tauri/src/windows.rs
⏰ 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: Clippy (aarch64-apple-darwin, macos-latest)
🔇 Additional comments (6)
apps/desktop/src-tauri/src/windows.rs (6)

10-56: LGTM!

The enum refactor from CapWindowId to CapWindowDef is clean and the new import additions support the macOS window management functionality introduced in this PR.


58-225: LGTM!

The FromStr and Display implementations are correctly inverse operations, and the new window property methods are well-structured with appropriate const qualifiers and platform-specific gating.


436-676: LGTM!

The window building logic is consistent across variants, and the macOS-specific NSWindow manipulation using dispatch2::run_on_main and objc2_nswindow() properly ensures main-thread execution for AppKit operations.


758-779: LGTM!

The consolidated post-build window configuration using def-based methods (disables_window_buttons(), disables_fullscreen(), window_level()) with proper main-thread dispatch is a clean architectural improvement.


867-894: LGTM!

The should_protect_window function and refresh_window_content_protection command correctly use the new CapWindowDef API to determine window protection based on title matching.


823-853: Verify safety of public def() method with unwraps.

The def() method is public and contains unwraps at lines 831 and 849 when looking up Editor and ScreenshotEditor paths. While show() may ensure paths are registered before calling def(), external callers could invoke def() directly without registration, causing a panic. Verify whether def() is called only from show() or from other contexts where path registration isn't guaranteed.

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 (3)
apps/desktop/src-tauri/src/windows.rs (3)

260-297: Editor/ScreenshotEditor ID registration is sound; fmt::Write import can be dropped

Pre‑registering Editor and ScreenshotEditor paths in EditorWindowIds/ScreenshotEditorWindowIds before calling def() gives stable labels and enables the fast‑path to reuse existing windows, which is a solid pattern. The local use std::fmt::Write; inside show() is now unused and can be removed to avoid unnecessary lints.

-        use std::fmt::Write;
-

370-407: Fix undefined window_builder in Windows TargetSelectOverlay branch

In the TargetSelectOverlay arm, the Windows‑only block assigns to builder = window_builder..., but only builder is in scope. This will fail to compile on Windows; the mutation should use builder itself.

-                #[cfg(windows)]
-                {
-                    builder = window_builder.inner_size(100.0, 100.0).position(0.0, 0.0);
-                }
+                #[cfg(windows)]
+                {
+                    builder = builder.inner_size(100.0, 100.0).position(0.0, 0.0);
+                }

823-852: Potential panic in def() when Editor/ScreenshotEditor IDs are missing

CapWindow::def looks up Editor and ScreenshotEditor IDs using .iter().find(...).unwrap(). In the normal flow, show() pre‑registers paths so these unwraps succeed, but def() is public and could be called with a project_path/path that was never registered or whose entry was removed, which would panic. To harden against inconsistent state, consider either restricting def() to module‑private use or handling the None case explicitly (e.g. by returning a fallback CapWindowDef or signaling an error upstream instead of unwrapping).

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

212-224: Align Editor min_size() with explicit .inner_size() to avoid conflicting constraints

CapWindowDef::min_size() returns (1275.0, 800.0) for Editor and is applied in window_builder() as both inner_size and min_inner_size, but the Editor arm in show() later calls .inner_size(1240.0, 800.0). Depending on platform behavior, that smaller inner_size may just be clamped up to the min, but the two constraints are slightly contradictory. Consider either removing the explicit .inner_size(1240.0, 800.0) and relying on min_size(), or updating min_size() to reflect the intended initial Editor dimensions.

Also applies to: 463-483


724-735: Centralized macOS NSWindow tweaks are good; reconcile duplicate level settings for RecordingsOverlay

The final run_on_main_thread block that toggles traffic lights, fullscreen behavior, and window level based on CapWindowDef centralizes macOS window policy nicely across all variants. One nuance: in the RecordingsOverlay arm you also call panel.set_level(NSMainMenuWindowLevel as i32), while CapWindowDef::window_level() returns NSScreenSaverWindowLevel for that variant, so whichever call happens last determines the actual level. It would be safer to pick one source of truth for that window’s level to avoid surprising differences between panel and raw NSWindow behavior.

Also applies to: 758-779

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b16cd8c and eca16fd.

📒 Files selected for processing (2)
  • apps/desktop/src-tauri/src/windows.rs (28 hunks)
  • apps/desktop/src/routes/screenshot-editor/Editor.tsx (2 hunks)
🧰 Additional context used
📓 Path-based instructions (8)
**/*.{ts,tsx,js,jsx,rs,py,sh}

📄 CodeRabbit inference engine (CLAUDE.md)

Never add any form of comments to code (single-line //, multi-line /* /, documentation ///, //!, /* */, or any other comment syntax). Code must be self-explanatory through naming, types, and structure.

Files:

  • apps/desktop/src/routes/screenshot-editor/Editor.tsx
  • apps/desktop/src-tauri/src/windows.rs
apps/desktop/src/**/*.{ts,tsx,solid}

📄 CodeRabbit inference engine (CLAUDE.md)

apps/desktop/src/**/*.{ts,tsx,solid}: Use @tanstack/solid-query for server state in desktop app; call generated commands and events from tauri_specta
Never manually import desktop app icons; use unplugin-icons auto-import instead

Files:

  • apps/desktop/src/routes/screenshot-editor/Editor.tsx
**/*.{tsx,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use PascalCase for component names; use camelCase starting with 'use' for hook names

Use PascalCase for component names in TypeScript/JavaScript

Files:

  • apps/desktop/src/routes/screenshot-editor/Editor.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Enforce strict TypeScript; avoid 'any' type; leverage shared types from packages/utils and other shared packages

Files:

  • apps/desktop/src/routes/screenshot-editor/Editor.tsx
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use Biome for linting and formatting; run pnpm format and pnpm lint regularly and at the end of each coding session

**/*.{ts,tsx,js,jsx}: Use 2-space indentation in TypeScript files
Format all TypeScript and JavaScript code using Biome (via pnpm format)

Files:

  • apps/desktop/src/routes/screenshot-editor/Editor.tsx
**/*.{ts,tsx,js,jsx,rs}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx,js,jsx,rs}: Use kebab-case for file names (e.g., user-menu.tsx)
Never add comments to code in any language (no //, /* */, ///, //!, #, etc.). Code must be self-explanatory through naming, types, and structure

Files:

  • apps/desktop/src/routes/screenshot-editor/Editor.tsx
  • apps/desktop/src-tauri/src/windows.rs
**/*.rs

📄 CodeRabbit inference engine (CLAUDE.md)

Use Rust 2024 edition and run cargo fmt to format all Rust code with rustfmt

**/*.rs: Format all Rust code using rustfmt and apply workspace clippy lints
Use snake_case for Rust module names and kebab-case for Rust crate names

Files:

  • apps/desktop/src-tauri/src/windows.rs
apps/desktop/**/*.rs

📄 CodeRabbit inference engine (CLAUDE.md)

Follow architectural decision to use tauri_specta for strongly typed Rust-to-TypeScript IPC bindings; do not modify generated bindings

Files:

  • apps/desktop/src-tauri/src/windows.rs
🧠 Learnings (2)
📓 Common learnings
Learnt from: CR
Repo: CapSoftware/Cap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-29T04:31:05.289Z
Learning: Applies to apps/desktop/**/*.rs : Follow architectural decision to use tauri_specta for strongly typed Rust-to-TypeScript IPC bindings; do not modify generated bindings
📚 Learning: 2025-11-29T04:31:05.289Z
Learnt from: CR
Repo: CapSoftware/Cap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-29T04:31:05.289Z
Learning: Applies to apps/desktop/**/*.rs : Follow architectural decision to use tauri_specta for strongly typed Rust-to-TypeScript IPC bindings; do not modify generated bindings

Applied to files:

  • apps/desktop/src-tauri/src/windows.rs
🧬 Code graph analysis (1)
apps/desktop/src-tauri/src/windows.rs (1)
apps/desktop/src/utils/tauri.ts (2)
  • Camera (357-357)
  • CapWindow (365-365)
⏰ 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: Clippy (aarch64-apple-darwin, macos-latest)
🔇 Additional comments (4)
apps/desktop/src/routes/screenshot-editor/Editor.tsx (1)

350-351: Crop dialog visual change and hidden bounds – please confirm UX

The crop container no longer clips/rounds (removed rounded/overflow-hidden) and the Cropper now hides bounds (showBounds is false). Implementation looks fine, but this will noticeably change how the crop region is presented; please double-check that this matches the intended design and that selection handles remain clear in common cases.

Also applies to: 386-387

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

39-258: CapWindowDef/CapWindow enum refactor looks consistent and typesafe

The new CapWindowDef and CapWindow enums, plus FromStr/Display/label()/title()/activates_dock()/min_size(), line up consistently and match the CapWindow union shape on the TS side. This centralization of labels, titles, dock behavior, and sizing should make window handling less error‑prone going forward.


436-461: Settings window behavior and window-hiding logic look good

Using CapWindowDef::from_str on each webview window label and hiding Main, TargetSelectOverlay, and Camera when Settings opens is a clean way to prevent clashing UI layers. The subsequent builder configuration for Settings (resizable, centered, non‑maximized) is straightforward and consistent with that intent.


637-677: CaptureArea sizing/positioning and main-window minimization logic look correct

The CaptureArea window is sized and positioned from per‑display logical/physical bounds, and the call to Display::intersects with outer_position/outer_size/scale_factor to decide whether to minimize the Main window gives predictable behavior on multi‑monitor setups. This should reduce cases where the main window stays visible under a capture overlay on the same screen.

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 (5)
apps/desktop/src-tauri/Cargo.toml (1)

114-114: Remove the trailing comment.

The trailing comment #{ version = , features = ["http-proto", "reqwest-client"] } should be removed. As per coding guidelines, comments should not be added to code.

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

262-262: Remove unused import.

The use std::fmt::Write; import is not used in this method. No write formatting operations are present that require this trait.

     pub async fn show(&self, app: &AppHandle<Wry>) -> tauri::Result<WebviewWindow> {
-        use std::fmt::Write;
-
         if let Self::Editor { project_path } = &self {

401-404: Critical bug: undefined variable window_builder.

On line 403, window_builder is referenced but the variable is named builder (defined on line 377). This will cause a compilation error.

                 #[cfg(windows)]
                 {
-                    builder = window_builder.inner_size(100.0, 100.0).position(0.0, 0.0);
+                    builder = builder.inner_size(100.0, 100.0).position(0.0, 0.0);
                 }
apps/desktop/src-tauri/src/lib.rs (2)

2970-2974: Critical bug: CapWindow::from_str does not exist.

CapWindow does not implement FromStr. The FromStr trait is implemented on CapWindowDef, not CapWindow. This will cause a compilation error.

 #[cfg(target_os = "windows")]
 fn has_open_editor_window(app: &AppHandle) -> bool {
     app.webview_windows()
         .keys()
-        .any(|label| matches!(CapWindow::from_str(label), Ok(CapWindow::Editor { .. })))
+        .any(|label| matches!(CapWindowDef::from_str(label), Ok(CapWindowDef::Editor { .. })))
 }

2860-2876: Camera cleanup missing double-execution guard.

The inline camera cleanup in the Destroyed event handler is missing the camera_cleanup_done guard that exists in cleanup_camera_window() (lines 588-603). When the camera window closes, both CloseRequested and Destroyed events fire. The CloseRequested handler calls cleanup_camera_window which sets the guard, but the Destroyed handler runs its own inline cleanup without checking the guard, causing on_window_close() and RemoveInput to potentially execute twice.

                             CapWindowDef::Camera => {
                                 let app = app.clone();
                                 tokio::spawn(async move {
                                     let state = app.state::<ArcLock<App>>();
                                     let mut app_state = state.write().await;

+                                    if app_state.camera_cleanup_done {
+                                        return;
+                                    }
+                                    app_state.camera_cleanup_done = true;
+
                                     app_state.camera_preview.on_window_close();

                                     if !app_state.is_recording_active_or_pending() {
                                         let _ = app_state
                                             .camera_feed
                                             .ask(feeds::camera::RemoveInput)
                                             .await;
                                         app_state.camera_in_use = false;
                                     }
                                 });
                             }
🧹 Nitpick comments (1)
apps/desktop/src-tauri/src/windows.rs (1)

10-10: Remove unused f64 import.

The f64 type is a primitive and doesn't require explicit importing. This import appears to be dead code.

 use std::{
-    f64,
     ops::Deref,
     path::PathBuf,
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 96d33d9 and 8c10410.

⛔ Files ignored due to path filters (2)
  • Cargo.lock is excluded by !**/*.lock
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (9)
  • Cargo.toml (2 hunks)
  • apps/desktop/src-tauri/Cargo.toml (2 hunks)
  • apps/desktop/src-tauri/src/lib.rs (23 hunks)
  • apps/desktop/src-tauri/src/recording.rs (9 hunks)
  • apps/desktop/src-tauri/src/screenshot_editor.rs (2 hunks)
  • apps/desktop/src-tauri/src/tray.rs (5 hunks)
  • apps/desktop/src-tauri/src/windows.rs (28 hunks)
  • apps/desktop/src/routes/screenshot-editor/Editor.tsx (2 hunks)
  • apps/desktop/src/utils/tauri.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/desktop/src/utils/tauri.ts
  • apps/desktop/src-tauri/src/screenshot_editor.rs
🧰 Additional context used
📓 Path-based instructions (5)
**/*.tsx

📄 CodeRabbit inference engine (CLAUDE.md)

Use React Query hooks with Server Actions for mutations and perform precise cache updates using setQueryData/setQueriesData instead of broad invalidations

Files:

  • apps/desktop/src/routes/screenshot-editor/Editor.tsx
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx,js,jsx}: Use strict TypeScript; avoid any type; leverage shared types from @cap/* packages
Follow camelCase naming for variables and functions; PascalCase for components; hooks must start with 'use' prefix
Use Biome for linting and formatting; match existing formatting conventions in the codebase
Use Tailwind CSS for styling in web components; stay consistent with spacing and tokens
Use static skeletons for loading states that mirror content; avoid bouncing animations
Memoize expensive work, code-split naturally, and use Next/Image for remote assets

**/*.{ts,tsx,js,jsx}: Use 2-space indent for TypeScript files; format with Biome using pnpm format
Use Biome for code formatting and linting; run pnpm format regularly
Use kebab-case for file names (e.g., user-menu.tsx); use PascalCase for components

Files:

  • apps/desktop/src/routes/screenshot-editor/Editor.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Never add any form of comments to code (single-line //, multi-line /* /, JSDoc /* */, or any other comment syntax); code must be self-explanatory through naming, types, and structure
Directory naming must use lowercase-dashed convention

Files:

  • apps/desktop/src/routes/screenshot-editor/Editor.tsx
**/*.{ts,tsx,js,jsx,rs}

📄 CodeRabbit inference engine (AGENTS.md)

Never add comments to code (//, /* */, ///, //!, #, etc.); code must be self-explanatory through naming, types, and structure

Files:

  • apps/desktop/src/routes/screenshot-editor/Editor.tsx
  • apps/desktop/src-tauri/src/recording.rs
  • apps/desktop/src-tauri/src/tray.rs
  • apps/desktop/src-tauri/src/windows.rs
  • apps/desktop/src-tauri/src/lib.rs
**/*.rs

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.rs: Never use dbg!() macro; use proper logging (tracing::debug!, etc.) instead
Never write let _ = async_fn() which silently drops futures; await or explicitly handle them
Use duration.saturating_sub(other) instead of duration - other to avoid panics on underflow
Merge nested if statements: write 'if a && b { }' instead of 'if a { if b { } }'
Don't call .clone() on Copy types (integers, bools, etc.); copy them directly
Use function references directly: iter.map(foo) instead of iter.map(|x| foo(x))
Accept &[T] or &str instead of &Vec or &String in function parameters for flexibility
Use .is_empty() instead of .len() == 0 or .len() > 0 / .len() != 0
Don't assign () to a variable: write foo(); instead of let _ = foo(); or let x = foo(); when return is unit
Use .unwrap_or(val) instead of .unwrap_or_else(|| val) when the default is a simple/cheap value
Use 'for item in &collection' or 'for (i, item) in collection.iter().enumerate()' instead of 'for i in 0..collection.len()'
Use value.clamp(min, max) instead of manual if chains or .min(max).max(min) patterns
Always handle Result/Option or types marked #[must_use]; never ignore them

**/*.rs: Use rustfmt and workspace clippy lints for Rust code formatting and linting
Use snake_case for Rust module names and kebab-case for crate names
Never use dbg!() macro in Rust code; use proper logging instead (Clippy: dbg_macro = deny)
Always handle Result/Option or types marked #[must_use]; never ignore them (Rust compiler lint: unused_must_use = deny)
Never write let _ = async_fn() which silently drops futures; await or explicitly handle them (Clippy: let_underscore_future = deny)
Use saturating_sub instead of - for Duration to avoid panics (Clippy: unchecked_duration_subtraction = deny)
Merge nested if statements: use if a && b { } instead of if a { if b { } } (Clippy: collapsible_if = deny)
Don't call .clone() on Copy types; just copy them directly (Clippy: clone_on_copy = deny)
U...

Files:

  • apps/desktop/src-tauri/src/recording.rs
  • apps/desktop/src-tauri/src/tray.rs
  • apps/desktop/src-tauri/src/windows.rs
  • apps/desktop/src-tauri/src/lib.rs
🧠 Learnings (2)
📚 Learning: 2025-12-07T14:29:40.721Z
Learnt from: CR
Repo: CapSoftware/Cap PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-07T14:29:40.721Z
Learning: Applies to **/*.{ts,tsx,js,jsx,rs} : Never add comments to code (`//`, `/* */`, `///`, `//!`, `#`, etc.); code must be self-explanatory through naming, types, and structure

Applied to files:

  • apps/desktop/src-tauri/Cargo.toml
📚 Learning: 2025-12-07T14:29:19.166Z
Learnt from: CR
Repo: CapSoftware/Cap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-07T14:29:19.166Z
Learning: Regenerate auto-generated Tauri bindings by restarting the dev server when Rust types change

Applied to files:

  • apps/desktop/src-tauri/Cargo.toml
🧬 Code graph analysis (2)
apps/desktop/src-tauri/src/recording.rs (2)
apps/desktop/src/utils/tauri.ts (1)
  • CapWindow (368-368)
apps/desktop/src-tauri/src/windows.rs (11)
  • app (265-265)
  • app (284-284)
  • app (431-431)
  • app (531-531)
  • app (837-837)
  • app (855-855)
  • app (995-995)
  • app (1007-1007)
  • show (261-790)
  • label (129-131)
  • from_str (61-100)
apps/desktop/src-tauri/src/lib.rs (2)
apps/desktop/src/utils/tauri.ts (2)
  • CapWindow (368-368)
  • Camera (360-360)
apps/desktop/src-tauri/src/windows.rs (13)
  • from_str (61-100)
  • app (265-265)
  • app (284-284)
  • app (431-431)
  • app (531-531)
  • app (837-837)
  • app (855-855)
  • app (995-995)
  • app (1007-1007)
  • label (129-131)
  • get (170-173)
  • get (994-996)
  • get (1006-1008)
⏰ 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: Clippy (x86_64-pc-windows-msvc, windows-latest)
🔇 Additional comments (14)
Cargo.toml (1)

25-25: LGTM! Dependency updates align with the migration goals.

The tauri bump to 2.9.3 and addition of objc2 = "0.6.1" as a workspace dependency support the broader migration from legacy macOS FFI to the objc2 ecosystem.

Also applies to: 62-62

apps/desktop/src/routes/screenshot-editor/Editor.tsx (1)

372-372: LGTM! Minor UI refinements.

The removal of rounded overflow-hidden from the crop dialog wrapper and disabling showBounds are straightforward UI adjustments.

Also applies to: 408-408

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

122-133: LGTM! Clean migration to objc2 ecosystem.

The macOS dependencies have been properly restructured to use the objc2 family of crates with consistent versioning (0.3.2 for objc2-* crates, 0.6.2 for block2). The workspace-level objc2 reference ensures version consistency across the project.


30-46: LGTM! Tauri plugin versions aligned.

The tauri-plugin-* dependencies are bumped consistently to the 2.3.x-2.5.x range, aligning with the tauri 2.9.3 workspace dependency.

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

4-4: LGTM! Import updated to new window type.

The import change from ShowCapWindow to CapWindow aligns with the codebase-wide refactoring.


408-412: LGTM! Window invocations consistently updated.

All tray window invocations have been properly migrated from ShowCapWindow to CapWindow variants (ScreenshotEditor, Editor, Main, Settings) while preserving the existing behavior and .show(&app).await pattern.

Also applies to: 430-431, 471-476, 511-511

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

67-67: LGTM! Import updated to new window types.

The import correctly brings in both CapWindow (for creating/showing windows) and CapWindowDef (for querying existing windows).


77-90: LGTM! Camera feed tracking added to both recording variants.

The camera_feed: Option<Arc<CameraFeedLock>> field is now consistently present in both Instant and Studio variants of InProgressRecording, enabling proper camera lifecycle management during recordings.


479-481: LGTM! Window queries use CapWindowDef pattern correctly.

The CapWindowDef::Camera.get(&app) and CapWindowDef::Main.get(&app) calls correctly use the definition enum for querying existing windows.

Also applies to: 599-604


564-577: LGTM! Window creation uses CapWindow pattern correctly.

CapWindow::WindowCaptureOccluder and CapWindow::InProgressRecording are correctly used with .show(&app).await for creating/displaying windows.

Also applies to: 595-597


589-593: LGTM! Window label parsing updated to CapWindowDef.

The filtering logic correctly uses CapWindowDef::from_str(label) for parsing window labels and matching against CapWindowDef::TargetSelectOverlay.


1519-1526: LGTM! Post-recording window handling updated.

Both CapWindow::Editor and CapWindow::RecordingsOverlay are correctly used for post-recording navigation based on user settings.

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

854-859: Same potential panic for screenshot editor path lookup.

Similar to the Editor variant, this .unwrap() could panic if the path is not found.

             CapWindow::ScreenshotEditor { path } => {
                 let state = app.state::<ScreenshotEditorWindowIds>();
                 let s = state.ids.lock().unwrap();
-                let id = s.iter().find(|(p, _)| p == path).unwrap().1;
+                let id = s
+                    .iter()
+                    .find(|(p, _)| p == path)
+                    .map(|(_, id)| *id)
+                    .unwrap_or(0);
                 CapWindowDef::ScreenshotEditor { id }
             }

836-841: Potential panic on missing editor path.

The .unwrap() at line 839 will panic if project_path is not found in the EditorWindowIds state. While the show() method registers the path before calling def(), this could still fail if called from a different code path or if state becomes inconsistent. Consider using defensive error handling:

             CapWindow::Editor { project_path } => {
                 let state = app.state::<EditorWindowIds>();
                 let s = state.ids.lock().unwrap();
-                let id = s.iter().find(|(path, _)| path == project_path).unwrap().1;
+                let id = s
+                    .iter()
+                    .find(|(path, _)| path == project_path)
+                    .map(|(_, id)| *id)
+                    .unwrap_or(0);
                 CapWindowDef::Editor { id }
             }

Verify that def() is only ever called after the path has been registered in EditorWindowIds to ensure this is not a masking fix for a deeper state management issue.

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 (2)
apps/desktop/src-tauri/src/lib.rs (2)

1295-1307: Avoid unwrap() when parsing editor window labels into CapWindowDef

In create_editor_instance:

let CapWindowDef::Editor { id } = CapWindowDef::from_str(window.label()).unwrap() else {
    return Err("Invalid window".to_string());
};

If window.label() is ever not parseable as a CapWindowDef (e.g. mislabelled window, unexpected call from a non‑editor window), this will panic before reaching the else branch.

Safer pattern:

-    let CapWindowDef::Editor { id } = CapWindowDef::from_str(window.label()).unwrap() else {
-        return Err("Invalid window".to_string());
-    };
+    let id = match CapWindowDef::from_str(window.label()) {
+        Ok(CapWindowDef::Editor { id }) => id,
+        _ => return Err("Invalid window".to_string()),
+    };

This preserves the intended validation without risking a panic on malformed labels.


2882-2892: hide_dock_icon logic can panic on unknown window labels via from_str(...).unwrap()

In the WindowEvent::Destroyed handler you have:

&& app
    .webview_windows()
    .keys()
    .all(|label| !CapWindowDef::from_str(label).unwrap().activates_dock())

If any open window has a label that CapWindowDef::from_str does not recognize (e.g. "signin", which is explicitly referenced later in RunEvent::Reopen), this .unwrap() will panic during destruction.

Consider treating unknown labels as non‑dock‑activating instead of panicking, for example:

-    .all(|label| !CapWindowDef::from_str(label).unwrap().activates_dock())
+    .all(|label| {
+        CapWindowDef::from_str(label)
+            .map(|id| !id.activates_dock())
+            .unwrap_or(true)
+    })

so unrecognized labels simply don’t block hiding the dock icon.

♻️ Duplicate comments (4)
apps/desktop/src-tauri/src/windows.rs (2)

374-404: TargetSelectOverlay builder fix correctly uses the existing builder variable

The refactored TargetSelectOverlay path now consistently mutates the builder returned from self.window_builder(...) on both macOS and Windows, resolving the previous undefined window_builder variable and ensuring platform‑specific sizing/positioning is applied before .build().


763-784: Avoid panics in def() for Editor/ScreenshotEditor window IDs

CapWindow::def currently does:

let id = s.iter().find(|(path, _)| path == project_path).unwrap().1;

and similarly for ScreenshotEditor, which will panic if the ID registry is ever out of sync (e.g. def() called before registration, or after an entry was pruned). Prior reviews raised the same concern on these lookups.

Consider making this resilient by handling the None case explicitly, for example:

-            CapWindow::Editor { project_path } => {
-                let state = app.state::<EditorWindowIds>();
-                let s = state.ids.lock().unwrap();
-                let id = s.iter().find(|(path, _)| path == project_path).unwrap().1;
-                CapWindowDef::Editor { id }
-            }
+            CapWindow::Editor { project_path } => {
+                let state = app.state::<EditorWindowIds>();
+                let s = state.ids.lock().unwrap();
+                let id = s
+                    .iter()
+                    .find(|(path, _)| path == project_path)
+                    .map(|(_, id)| *id)
+                    .unwrap_or(0);
+                CapWindowDef::Editor { id }
+            }
...
-            CapWindow::ScreenshotEditor { path } => {
-                let state = app.state::<ScreenshotEditorWindowIds>();
-                let s = state.ids.lock().unwrap();
-                let id = s.iter().find(|(p, _)| p == path).unwrap().1;
-                CapWindowDef::ScreenshotEditor { id }
-            }
+            CapWindow::ScreenshotEditor { path } => {
+                let state = app.state::<ScreenshotEditorWindowIds>();
+                let s = state.ids.lock().unwrap();
+                let id = s
+                    .iter()
+                    .find(|(p, _)| p == path)
+                    .map(|(_, id)| *id)
+                    .unwrap_or(0);
+                CapWindowDef::ScreenshotEditor { id }
+            }

or by changing def() to return a Result/Option so callers can decide how to handle a missing mapping instead of panicking.

Also applies to: 828-858

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

2972-2978: has_open_editor_window now correctly uses CapWindowDef parsing

Replacing the non‑existent CapWindow::from_str call with CapWindowDef::from_str(label) and matching on CapWindowDef::Editor { .. } fixes the earlier critical bug and matches how labels are parsed elsewhere.


2757-2759: Camera cleanup still runs twice (CloseRequested + Destroyed) without a shared guard

WindowEvent::CloseRequested for the camera window spawns cleanup_camera_window(app.clone()), which correctly uses camera_cleanup_done to ensure idempotent cleanup. However, WindowEvent::Destroyed also has a CapWindowDef::Camera arm that runs inline cleanup (calling on_window_close() and RemoveInput) without consulting or setting camera_cleanup_done.

When both events fire (the normal close path), cleanup now runs twice again, matching the previously reported issue.

One straightforward fix is to route the Destroyed arm through the same helper:

-                            CapWindowDef::Camera => {
-                                let app = app.clone();
-                                tokio::spawn(async move {
-                                    let state = app.state::<ArcLock<App>>();
-                                    let mut app_state = state.write().await;
-
-                                    app_state.camera_preview.on_window_close();
-
-                                    if !app_state.is_recording_active_or_pending() {
-                                        let _ = app_state
-                                            .camera_feed
-                                            .ask(feeds::camera::RemoveInput)
-                                            .await;
-                                        app_state.camera_in_use = false;
-                                    }
-                                });
-                            }
+                            CapWindowDef::Camera => {
+                                let app = app.clone();
+                                tokio::spawn(cleanup_camera_window(app));
+                            }

so both events share the same guarded logic.

Also applies to: 2861-2876

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

426-545: Handle or consistently log errors from CapWindow::show instead of silently discarding them

Across several call sites (e.g. set_camera_input, show_window, single‑instance handler, startup logic, RequestOpenRecordingPicker, RequestOpenSettings, macOS RunEvent::Reopen, and open_project_from_path), the result of CapWindow::... .show(&app).await is ignored via let _ = ... or .ok(). This means window‑creation failures are effectively invisible to the JS side and often only loosely logged.

Given that show_window and event handlers already return Result in some cases, consider a more consistent pattern such as:

CapWindow::Camera
    .show(&app_handle)
    .await
    .map_err(|err| {
        error!("Failed to show camera preview window: {err}");
        err.to_string()
    })?;

for commands that return Result<_, String>, and at least logging the error (without let _ =) for fire‑and‑forget event handlers. This will make diagnosing window‑creation failures much easier.

Also applies to: 2166-2169, 2485-2490, 2681-2689, 2724-2727, 2732-2735, 2953-2957, 3249-3250

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e8dd3d0 and 84cb2f1.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (3)
  • Cargo.toml (2 hunks)
  • apps/desktop/src-tauri/src/lib.rs (23 hunks)
  • apps/desktop/src-tauri/src/windows.rs (27 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • Cargo.toml
🧰 Additional context used
📓 Path-based instructions (2)
**/*.rs

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.rs: Never use dbg!() macro; use proper logging (tracing::debug!, etc.) instead
Never write let _ = async_fn() which silently drops futures; await or explicitly handle them
Use duration.saturating_sub(other) instead of duration - other to avoid panics on underflow
Merge nested if statements: write 'if a && b { }' instead of 'if a { if b { } }'
Don't call .clone() on Copy types (integers, bools, etc.); copy them directly
Use function references directly: iter.map(foo) instead of iter.map(|x| foo(x))
Accept &[T] or &str instead of &Vec or &String in function parameters for flexibility
Use .is_empty() instead of .len() == 0 or .len() > 0 / .len() != 0
Don't assign () to a variable: write foo(); instead of let _ = foo(); or let x = foo(); when return is unit
Use .unwrap_or(val) instead of .unwrap_or_else(|| val) when the default is a simple/cheap value
Use 'for item in &collection' or 'for (i, item) in collection.iter().enumerate()' instead of 'for i in 0..collection.len()'
Use value.clamp(min, max) instead of manual if chains or .min(max).max(min) patterns
Always handle Result/Option or types marked #[must_use]; never ignore them

**/*.rs: Use rustfmt and workspace clippy lints for Rust code formatting and linting
Use snake_case for Rust module names and kebab-case for crate names
Never use dbg!() macro in Rust code; use proper logging instead (Clippy: dbg_macro = deny)
Always handle Result/Option or types marked #[must_use]; never ignore them (Rust compiler lint: unused_must_use = deny)
Never write let _ = async_fn() which silently drops futures; await or explicitly handle them (Clippy: let_underscore_future = deny)
Use saturating_sub instead of - for Duration to avoid panics (Clippy: unchecked_duration_subtraction = deny)
Merge nested if statements: use if a && b { } instead of if a { if b { } } (Clippy: collapsible_if = deny)
Don't call .clone() on Copy types; just copy them directly (Clippy: clone_on_copy = deny)
U...

Files:

  • apps/desktop/src-tauri/src/windows.rs
  • apps/desktop/src-tauri/src/lib.rs
**/*.{ts,tsx,js,jsx,rs}

📄 CodeRabbit inference engine (AGENTS.md)

Never add comments to code (//, /* */, ///, //!, #, etc.); code must be self-explanatory through naming, types, and structure

Files:

  • apps/desktop/src-tauri/src/windows.rs
  • apps/desktop/src-tauri/src/lib.rs
🧬 Code graph analysis (1)
apps/desktop/src-tauri/src/lib.rs (2)
apps/desktop/src/utils/tauri.ts (2)
  • CapWindow (368-368)
  • Camera (360-360)
apps/desktop/src-tauri/src/windows.rs (13)
  • from_str (60-99)
  • app (262-262)
  • app (281-281)
  • app (428-428)
  • app (528-528)
  • app (834-834)
  • app (852-852)
  • app (992-992)
  • app (1004-1004)
  • label (128-130)
  • get (169-172)
  • get (991-993)
  • get (1003-1005)
⏰ 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: Clippy (x86_64-pc-windows-msvc, windows-latest)
🔇 Additional comments (14)
apps/desktop/src-tauri/src/windows.rs (7)

38-224: CapWindowDef enum and helper methods look consistent and well‑factored

The FromStr/Display implementations, label/title, activates_dock, window_level, and min_size helpers appear internally consistent and keep window metadata centralized, which should simplify future changes to window behavior across platforms. No issues spotted here.


259-307: Editor/ScreenshotEditor window ID registration integrates cleanly with new CapWindowDef flow

The logic that assigns stable numeric IDs for Editor { project_path } and ScreenshotEditor { path } before computing CapWindowDef labels and prewarming editor instances looks sound and should avoid the previous path‑registration issues. The lock/atomic usage is straightforward and appropriate for this low‑contention registry.


520-605: Camera window creation and macOS collection behavior wiring look correct

The camera window builder settings (always‑on‑top, workspace visibility, taskbar skip, transparent, hidden until initialized) and the macOS dispatch2::run_on_main call that adds FullScreenAuxiliary collection behavior align with the intended “floating auxiliary camera preview” behavior. There are no obvious race conditions around the state lock or feed initialization in this block.


639-680: CaptureArea sizing/minimization behavior is coherent across platforms

The new CaptureArea path correctly derives bounds from Display::from_id(screen_id) via logical/physical bounds per platform, then minimizes the main window only when its outer rect intersects the target display (using MonitorExt::intersects). This seems like a sensible consolidation of logic and should avoid spurious minimization.


789-826: Centralized window_builder correctly applies title, protection, min size, and decorations

The new window_builder helper uses self.def(app) to assign labels, titles, min sizes, and content protection in one place, and applies macOS‑specific decorations/traffic‑light positions based on CapWindowDef flags. This is a solid simplification over per‑callsite configuration and should make future window variants easier to manage.


885-899: refresh_window_content_protection now safely uses CapWindowDef parsing

Using CapWindowDef::from_str(&label) to recompute titles before re‑evaluating should_protect_window is a good fit with the new enum; the if let Ok(id) guard means unknown/legacy labels will simply be skipped without panicking.


984-1005: Editor/ScreenshotEditorWindowIds helper structs are simple and appropriate

Wrapping the (PathBuf, u32) registries and AtomicU32 counters in clonable structs with get(app: &AppHandle) helpers keeps the state access ergonomic while preserving synchronization via Arc<Mutex<...>>. No issues identified here.

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

97-99: Windows module imports match the new CapWindow/CapWindowDef API surface

Importing CapWindow, CapWindowDef, the window ID registries, and set_window_transparent from windows cleanly aligns this module with the refactored window management API. No issues here.


1444-1470: Recordings overlay panel helpers correctly use CapWindowDef labels

Both close_recordings_overlay_window and focus_captures_panel now derive the recordings overlay label via CapWindowDef::RecordingsOverlay.label(), keeping the nspanel interactions in sync with the centralized label mapping. The non‑macOS path correctly falls back to CapWindowDef::RecordingsOverlay.get(&app).


2084-2089: is_camera_window_open correctly reuses CapWindowDef lookup

Using CapWindowDef::Camera.get(&app).is_some() to implement is_camera_window_open is simple and consistent with the window‑management abstractions in windows.rs. No issues here.


2526-2534: Window‑state denylist now driven by CapWindowDef labels

Feeding CapWindowDef::<variant>.label().as_str() into tauri_plugin_window_state’s with_denylist keeps these entries aligned with the canonical label mapping and avoids drifting string literals. This looks correct and future‑proof for these variants.


2570-2585: Camera feed disconnect handler cleanly closes the camera window when present

The OnFeedDisconnect handler now uses CapWindowDef::Camera.get(&app) to look up and close the camera window if it exists. This is a good use of the centralized window lookup and ensures graceful UI cleanup when the camera feed drops.


2982-2993: reopen_main_window correctly reuses CapWindowDef/CapWindow abstractions

The Windows‑only reopen_main_window helper now first tries to show/focus an existing CapWindowDef::Main window via get(&app), and only spawns a new CapWindow::Main if none exists. This is a clean separation between definition and creation, consistent with the rest of the refactor.


3249-3261: open_project_from_path uses CapWindow::Editor/Main consistently with new window API

When opening a studio recording, spawning CapWindow::Editor { project_path }.show(&app) matches the new CapWindow surface; for instant recordings, closing an existing CapWindowDef::Main window via get(&app) is also consistent with the centralized window management. No issues found in this integration.

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

♻️ Duplicate comments (1)
apps/desktop/src-tauri/src/windows.rs (1)

345-350: Handle the Result from run_on_main_thread.

The _ = window.run_on_main_thread(...) pattern silently drops the Result. Per coding guidelines, Result types should be handled. Use .ok() to explicitly acknowledge ignoring the error.

                     if new_recording_flow {
-                        _ = window.run_on_main_thread({
+                        window.run_on_main_thread({
                             let window = window.clone();
                             move || window.objc2_nswindow().setLevel(50)
-                        });
+                        }).ok();
                     }
🧹 Nitpick comments (10)
apps/desktop/src-tauri/src/windows.rs (10)

67-68: Remove the inline comment.

As per coding guidelines, comments should not be added to code. The "legacy identifier" comment should be removed; the mapping itself is self-explanatory.

-            // legacy identifier
             "in-progress-recording" => Self::RecordingControls,

116-116: Remove the inline comment.

Same as above - remove the "legacy identifier" comment to comply with coding guidelines.

-            Self::RecordingControls => write!(f, "in-progress-recording"), // legacy identifier
+            Self::RecordingControls => write!(f, "in-progress-recording"),

196-210: Consider extracting the magic window level constant.

Line 204 uses a hardcoded value 45 for the window level. While the other levels use named constants from objc2_app_kit, this one is a magic number. Consider defining a named constant for clarity, or add a brief reference to what this level represents.

+        const TARGET_SELECT_WINDOW_LEVEL: objc2_app_kit::NSWindowLevel = 45;
+
         match self {
             Self::RecordingControls => Some(NSMainMenuWindowLevel),
-            Self::TargetSelectOverlay { .. } | Self::CaptureArea => Some(45),
+            Self::TargetSelectOverlay { .. } | Self::CaptureArea => Some(TARGET_SELECT_WINDOW_LEVEL),
             Self::RecordingsOverlay | Self::WindowCaptureOccluder { .. } => {
                 Some(NSScreenSaverWindowLevel)
             }
             _ => None,
         }

433-446: Remove the inline comment.

The comment at line 434 violates coding guidelines. The code's intent is clear from the logic itself.

             Self::Settings { page } => {
-                // Hide main window and target select overlays when settings window opens
                 for (label, window) in app.webview_windows() {

487-518: Remove the inline comments.

Lines 488 and 503 contain comments that violate coding guidelines. The code behavior is self-explanatory.

             Self::Upgrade => {
-                // Hide main window when upgrade window opens
                 if let Some(main) = CapWindowDef::Main.get(app) {
                     let _ = main.hide();
                 }
...
             Self::ModeSelect => {
-                // Hide main window when mode select window opens
                 if let Some(main) = CapWindowDef::Main.get(app) {
                     let _ = main.hide();

564-567: Remove the inline comment.

Line 565 contains a comment that violates coding guidelines.

-                        .visible(false); // We set this true in `CameraWindowState::init_window`
+                        .visible(false);

669-677: Remove the inline comment.

Line 669 contains a comment that violates coding guidelines.

-                // Hide the main window if the target monitor is the same
                 if let Some(main_window) = CapWindowDef::Main.get(app)

748-751: Remove the comment and track the issue properly.

The comment indicates a known issue with the panel style mask. Per coding guidelines, comments should be removed. If this is a known limitation, consider creating a tracking issue instead of leaving a comment in code.

-                            // seems like this doesn't work properly -_-
                             #[allow(non_upper_case_globals)]
                             const NSWindowStyleMaskNonActivatingPanel: i32 = 1 << 7;
                             panel.set_style_mask(NSWindowStyleMaskNonActivatingPanel);

763-784: Use .ok() instead of let _ = for clarity.

Similar to the earlier pattern, using .ok() explicitly acknowledges ignoring the Result rather than silently dropping it with let _ =.

         #[cfg(target_os = "macos")]
-        let _ = window.run_on_main_thread({
+        window.run_on_main_thread({
             let window = window.clone();
             move || {
                 // ... closure body ...
             }
-        });
+        }).ok();

901-901: Consider removing the attribution comment.

Per coding guidelines, comments should be avoided. If attribution is needed for licensing purposes, consider adding it to a NOTICES or LICENSE file instead.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 84cb2f1 and 3dd643c.

⛔ Files ignored due to path filters (2)
  • Cargo.lock is excluded by !**/*.lock
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (4)
  • Cargo.toml (2 hunks)
  • apps/desktop/package.json (3 hunks)
  • apps/desktop/src-tauri/Cargo.toml (2 hunks)
  • apps/desktop/src-tauri/src/windows.rs (27 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/desktop/package.json
  • Cargo.toml
🧰 Additional context used
📓 Path-based instructions (2)
**/*.rs

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.rs: Never use dbg!() macro; use proper logging (tracing::debug!, etc.) instead
Never write let _ = async_fn() which silently drops futures; await or explicitly handle them
Use duration.saturating_sub(other) instead of duration - other to avoid panics on underflow
Merge nested if statements: write 'if a && b { }' instead of 'if a { if b { } }'
Don't call .clone() on Copy types (integers, bools, etc.); copy them directly
Use function references directly: iter.map(foo) instead of iter.map(|x| foo(x))
Accept &[T] or &str instead of &Vec or &String in function parameters for flexibility
Use .is_empty() instead of .len() == 0 or .len() > 0 / .len() != 0
Don't assign () to a variable: write foo(); instead of let _ = foo(); or let x = foo(); when return is unit
Use .unwrap_or(val) instead of .unwrap_or_else(|| val) when the default is a simple/cheap value
Use 'for item in &collection' or 'for (i, item) in collection.iter().enumerate()' instead of 'for i in 0..collection.len()'
Use value.clamp(min, max) instead of manual if chains or .min(max).max(min) patterns
Always handle Result/Option or types marked #[must_use]; never ignore them

**/*.rs: Use rustfmt and workspace clippy lints for Rust code formatting and linting
Use snake_case for Rust module names and kebab-case for crate names
Never use dbg!() macro in Rust code; use proper logging instead (Clippy: dbg_macro = deny)
Always handle Result/Option or types marked #[must_use]; never ignore them (Rust compiler lint: unused_must_use = deny)
Never write let _ = async_fn() which silently drops futures; await or explicitly handle them (Clippy: let_underscore_future = deny)
Use saturating_sub instead of - for Duration to avoid panics (Clippy: unchecked_duration_subtraction = deny)
Merge nested if statements: use if a && b { } instead of if a { if b { } } (Clippy: collapsible_if = deny)
Don't call .clone() on Copy types; just copy them directly (Clippy: clone_on_copy = deny)
U...

Files:

  • apps/desktop/src-tauri/src/windows.rs
**/*.{ts,tsx,js,jsx,rs}

📄 CodeRabbit inference engine (AGENTS.md)

Never add comments to code (//, /* */, ///, //!, #, etc.); code must be self-explanatory through naming, types, and structure

Files:

  • apps/desktop/src-tauri/src/windows.rs
🧠 Learnings (6)
📚 Learning: 2025-12-07T14:29:40.721Z
Learnt from: CR
Repo: CapSoftware/Cap PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-07T14:29:40.721Z
Learning: Applies to **/*.{ts,tsx,js,jsx,rs} : Never add comments to code (`//`, `/* */`, `///`, `//!`, `#`, etc.); code must be self-explanatory through naming, types, and structure

Applied to files:

  • apps/desktop/src-tauri/Cargo.toml
📚 Learning: 2025-12-07T14:29:19.166Z
Learnt from: CR
Repo: CapSoftware/Cap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-07T14:29:19.166Z
Learning: Regenerate auto-generated Tauri bindings by restarting the dev server when Rust types change

Applied to files:

  • apps/desktop/src-tauri/Cargo.toml
📚 Learning: 2025-12-07T14:29:40.721Z
Learnt from: CR
Repo: CapSoftware/Cap PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-07T14:29:40.721Z
Learning: Applies to **/*.rs : Never write `let _ = async_fn()` which silently drops futures; await or explicitly handle them (Clippy: `let_underscore_future` = deny)

Applied to files:

  • apps/desktop/src-tauri/src/windows.rs
📚 Learning: 2025-12-07T14:29:19.165Z
Learnt from: CR
Repo: CapSoftware/Cap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-07T14:29:19.165Z
Learning: Applies to **/*.rs : Never write let _ = async_fn() which silently drops futures; await or explicitly handle them

Applied to files:

  • apps/desktop/src-tauri/src/windows.rs
📚 Learning: 2025-12-07T14:29:40.721Z
Learnt from: CR
Repo: CapSoftware/Cap PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-07T14:29:40.721Z
Learning: Applies to **/*.rs : Always handle `Result`/`Option` or types marked `#[must_use]`; never ignore them (Rust compiler lint: `unused_must_use` = deny)

Applied to files:

  • apps/desktop/src-tauri/src/windows.rs
📚 Learning: 2025-12-07T14:29:40.721Z
Learnt from: CR
Repo: CapSoftware/Cap PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-07T14:29:40.721Z
Learning: Applies to **/*.rs : Don't assign `()` to a variable: write `foo();` instead of `let _ = foo();` when return is unit (Clippy: `let_unit_value` = deny)

Applied to files:

  • apps/desktop/src-tauri/src/windows.rs
⏰ 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: Clippy (x86_64-pc-windows-msvc, windows-latest)
🔇 Additional comments (4)
apps/desktop/src-tauri/src/windows.rs (3)

38-55: LGTM!

The CapWindowDef enum is well-structured with clear variant names. The use of DisplayId for overlay-related variants provides type safety.


259-299: LGTM!

The window ID registration logic for both Editor and ScreenshotEditor variants correctly registers the path-to-ID mapping before calling def(). This ensures the def() method can find the ID when building the window label.


459-486: LGTM!

The Editor and ScreenshotEditor window creation correctly closes Main and Camera windows before opening, preventing conflicting window states.

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

121-133: Verify objc2 workspace version compatibility with 0.3.2 crates.

The objc2-* crates at version 0.3.2 require objc2 >=0.6.2 and <0.8.0. Confirm that the workspace-managed objc2 version falls within this range. Additionally, block2 0.6.2 satisfies the requirement (>=0.6.1, <0.8.0) for objc2-app-kit 0.3.2 and related crates.

Comment on lines +833 to 838
CapWindow::Editor { project_path } => {
let state = app.state::<EditorWindowIds>();
let s = state.ids.lock().unwrap();
let id = s.iter().find(|(path, _)| path == project_path).unwrap().1;
CapWindowId::Editor { id }
CapWindowDef::Editor { id }
}
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

Potential panic if def() is called without prior registration.

The def() method is public and uses .unwrap() when looking up Editor/ScreenshotEditor IDs. While show() registers the ID before calling def(), external callers of def() could trigger a panic if the path isn't registered.

Consider returning Option<CapWindowDef> or Result<CapWindowDef, _>, or documenting the precondition if this method should only be called after registration.

-            CapWindow::Editor { project_path } => {
-                let state = app.state::<EditorWindowIds>();
-                let s = state.ids.lock().unwrap();
-                let id = s.iter().find(|(path, _)| path == project_path).unwrap().1;
-                CapWindowDef::Editor { id }
-            }
+            CapWindow::Editor { project_path } => {
+                let state = app.state::<EditorWindowIds>();
+                let s = state.ids.lock().unwrap();
+                let id = s
+                    .iter()
+                    .find(|(path, _)| path == project_path)
+                    .map(|(_, id)| *id)
+                    .unwrap_or(0);
+                CapWindowDef::Editor { id }
+            }

Also applies to: 851-856

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

♻️ Duplicate comments (3)
apps/desktop/src-tauri/src/windows.rs (3)

833-838: Potential panic if path not found in registry.

The .unwrap() on line 836 can panic if project_path is not found in the registry. While show() registers the ID before calling def(), this public method could be called externally without prior registration.

Consider returning Result<CapWindowDef, _> or documenting that def() must only be called after show():

             CapWindow::Editor { project_path } => {
                 let state = app.state::<EditorWindowIds>();
                 let s = state.ids.lock().unwrap();
-                let id = s.iter().find(|(path, _)| path == project_path).unwrap().1;
+                let id = s
+                    .iter()
+                    .find(|(path, _)| path == project_path)
+                    .map(|(_, id)| *id)
+                    .unwrap_or(0);
                 CapWindowDef::Editor { id }
             }

851-856: Same potential panic for ScreenshotEditor path lookup.

Similar to the Editor variant, the .unwrap() on line 854 can panic if the path is not found in the registry.

Apply the same fix as for Editor:

             CapWindow::ScreenshotEditor { path } => {
                 let state = app.state::<ScreenshotEditorWindowIds>();
                 let s = state.ids.lock().unwrap();
-                let id = s.iter().find(|(p, _)| p == path).unwrap().1;
+                let id = s
+                    .iter()
+                    .find(|(p, _)| p == path)
+                    .map(|(_, id)| *id)
+                    .unwrap_or(0);
                 CapWindowDef::ScreenshotEditor { id }
             }

346-350: Must handle Result from run_on_main_thread.

Using let _ = window.run_on_main_thread(...) violates coding guidelines that require handling Result/#[must_use] types. The underscore assignment silently ignores the Result.

Apply this diff to properly handle the Result:

                 #[cfg(target_os = "macos")]
                 {
                     if new_recording_flow {
-                        let _ = window.run_on_main_thread({
+                        window.run_on_main_thread({
                             let window = window.clone();
                             move || window.objc2_nswindow().setLevel(50)
-                        });
+                        }).ok();
                     }

Based on coding guidelines: "Always handle Result/Option or types marked #[must_use]" and "Don't assign () to a variable".

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3dd643c and 4440ccd.

📒 Files selected for processing (1)
  • apps/desktop/src-tauri/src/windows.rs (27 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.rs

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.rs: Never use dbg!() macro; use proper logging (tracing::debug!, etc.) instead
Never write let _ = async_fn() which silently drops futures; await or explicitly handle them
Use duration.saturating_sub(other) instead of duration - other to avoid panics on underflow
Merge nested if statements: write 'if a && b { }' instead of 'if a { if b { } }'
Don't call .clone() on Copy types (integers, bools, etc.); copy them directly
Use function references directly: iter.map(foo) instead of iter.map(|x| foo(x))
Accept &[T] or &str instead of &Vec or &String in function parameters for flexibility
Use .is_empty() instead of .len() == 0 or .len() > 0 / .len() != 0
Don't assign () to a variable: write foo(); instead of let _ = foo(); or let x = foo(); when return is unit
Use .unwrap_or(val) instead of .unwrap_or_else(|| val) when the default is a simple/cheap value
Use 'for item in &collection' or 'for (i, item) in collection.iter().enumerate()' instead of 'for i in 0..collection.len()'
Use value.clamp(min, max) instead of manual if chains or .min(max).max(min) patterns
Always handle Result/Option or types marked #[must_use]; never ignore them

**/*.rs: Use rustfmt and workspace clippy lints for Rust code formatting and linting
Use snake_case for Rust module names and kebab-case for crate names
Never use dbg!() macro in Rust code; use proper logging instead (Clippy: dbg_macro = deny)
Always handle Result/Option or types marked #[must_use]; never ignore them (Rust compiler lint: unused_must_use = deny)
Never write let _ = async_fn() which silently drops futures; await or explicitly handle them (Clippy: let_underscore_future = deny)
Use saturating_sub instead of - for Duration to avoid panics (Clippy: unchecked_duration_subtraction = deny)
Merge nested if statements: use if a && b { } instead of if a { if b { } } (Clippy: collapsible_if = deny)
Don't call .clone() on Copy types; just copy them directly (Clippy: clone_on_copy = deny)
U...

Files:

  • apps/desktop/src-tauri/src/windows.rs
**/*.{ts,tsx,js,jsx,rs}

📄 CodeRabbit inference engine (AGENTS.md)

Never add comments to code (//, /* */, ///, //!, #, etc.); code must be self-explanatory through naming, types, and structure

Files:

  • apps/desktop/src-tauri/src/windows.rs
🧠 Learnings (4)
📚 Learning: 2025-12-07T14:29:40.721Z
Learnt from: CR
Repo: CapSoftware/Cap PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-07T14:29:40.721Z
Learning: Applies to **/*.rs : Never write `let _ = async_fn()` which silently drops futures; await or explicitly handle them (Clippy: `let_underscore_future` = deny)

Applied to files:

  • apps/desktop/src-tauri/src/windows.rs
📚 Learning: 2025-12-07T14:29:19.165Z
Learnt from: CR
Repo: CapSoftware/Cap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-07T14:29:19.165Z
Learning: Applies to **/*.rs : Never write let _ = async_fn() which silently drops futures; await or explicitly handle them

Applied to files:

  • apps/desktop/src-tauri/src/windows.rs
📚 Learning: 2025-12-07T14:29:40.721Z
Learnt from: CR
Repo: CapSoftware/Cap PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-07T14:29:40.721Z
Learning: Applies to **/*.rs : Always handle `Result`/`Option` or types marked `#[must_use]`; never ignore them (Rust compiler lint: `unused_must_use` = deny)

Applied to files:

  • apps/desktop/src-tauri/src/windows.rs
📚 Learning: 2025-12-07T14:29:40.721Z
Learnt from: CR
Repo: CapSoftware/Cap PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-07T14:29:40.721Z
Learning: Applies to **/*.rs : Don't assign `()` to a variable: write `foo();` instead of `let _ = foo();` when return is unit (Clippy: `let_unit_value` = deny)

Applied to files:

  • apps/desktop/src-tauri/src/windows.rs
⏰ 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: Clippy (x86_64-pc-windows-msvc, windows-latest)

Comment on lines +764 to +784
let _ = window.run_on_main_thread({
let window = window.clone();
move || {
if def.disables_window_buttons() {
window.set_traffic_lights_visible(false);
}

let nswindow = window.objc2_nswindow();

if def.disables_fullscreen() {
nswindow.setCollectionBehavior(
nswindow.collectionBehavior()
| objc2_app_kit::NSWindowCollectionBehavior::FullScreenNone,
);
}

if let Some(level) = def.window_level() {
nswindow.setLevel(level)
}
}
});
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 | 🔴 Critical

Must handle Result from run_on_main_thread.

Using let _ = window.run_on_main_thread(...) violates coding guidelines requiring proper handling of #[must_use] types.

Apply this diff:

         #[cfg(target_os = "macos")]
-        let _ = window.run_on_main_thread({
+        window.run_on_main_thread({
             let window = window.clone();
             move || {
                 if def.disables_window_buttons() {
                     window.set_traffic_lights_visible(false);
                 }
 
                 let nswindow = window.objc2_nswindow();
 
                 if def.disables_fullscreen() {
                     nswindow.setCollectionBehavior(
                         nswindow.collectionBehavior()
                             | objc2_app_kit::NSWindowCollectionBehavior::FullScreenNone,
                     );
                 }
 
                 if let Some(level) = def.window_level() {
                     nswindow.setLevel(level)
                 }
             }
-        });
+        }).ok();

Based on coding guidelines: "Always handle Result/Option or types marked #[must_use]".

🤖 Prompt for AI Agents
In apps/desktop/src-tauri/src/windows.rs around lines 764 to 784, the call "let
_ = window.run_on_main_thread(...)" ignores a #[must_use] Result; replace the
discard with explicit handling: capture the Result, and on Err branch either
propagate the error (return/?) or log it and continue. Concretely, assign the
call to a variable and then use match or if let Err(e) = result to call an
appropriate logger (e.g., error!("run_on_main_thread failed: {:?}", e)) or
return the error from the enclosing function if that fits the function's
signature; ensure no #[must_use] value is silently dropped.

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 (3)
apps/desktop/src-tauri/src/windows.rs (2)

801-822: Result from run_on_main_thread should be handled.

Same issue as above - let _ = window.run_on_main_thread(...) discards the Result. Use .ok() to explicitly acknowledge the result.


866-896: Potential panic in def() if called before registration.

The def() method uses .unwrap() when looking up Editor and ScreenshotEditor IDs (lines 874 and 892). While show() registers the ID before calling def(), external callers could trigger a panic if the path isn't registered.

Past review flagged this - consider returning Option<CapWindowDef> or documenting the precondition clearly.

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

2889-2904: Critical: Camera cleanup runs twice without guard check.

The inline camera cleanup in the Destroyed event handler doesn't check the camera_cleanup_done guard. When a window closes normally, both CloseRequested (line 2786) and Destroyed fire. CloseRequested calls cleanup_camera_window which checks and sets camera_cleanup_done, but this inline Destroyed code lacks the check, causing duplicate cleanup: on_window_close() and RemoveInput execute twice.

🔎 Apply this diff to add the guard check:
 CapWindowDef::Camera => {
     let app = app.clone();
     tokio::spawn(async move {
         let state = app.state::<ArcLock<App>>();
         let mut app_state = state.write().await;
 
+        if app_state.camera_cleanup_done {
+            return;
+        }
+        app_state.camera_cleanup_done = true;
+
         app_state.camera_preview.on_window_close();
 
         if !app_state.is_recording_active_or_pending() {
             let _ = app_state
                 .camera_feed
                 .ask(feeds::camera::RemoveInput)
                 .await;
             app_state.camera_in_use = false;
         }
     });
 }
🧹 Nitpick comments (1)
apps/desktop/src-tauri/src/windows.rs (1)

345-350: Result from run_on_main_thread should be handled.

The let _ = window.run_on_main_thread(...) pattern silently discards the Result. Based on coding guidelines, #[must_use] types should be handled explicitly.

🔎 Apply this diff to handle the result:
                 if new_recording_flow {
-                    let _ = window.run_on_main_thread({
+                    window.run_on_main_thread({
                         let window = window.clone();
                         move || window.objc2_nswindow().setLevel(50)
-                    });
+                    }).ok();
                 }
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0ded5a9 and 983a44e.

⛔ Files ignored due to path filters (2)
  • Cargo.lock is excluded by !**/*.lock
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (11)
  • Cargo.toml (2 hunks)
  • apps/desktop/package.json (3 hunks)
  • apps/desktop/src-tauri/Cargo.toml (2 hunks)
  • apps/desktop/src-tauri/src/hotkeys.rs (2 hunks)
  • apps/desktop/src-tauri/src/lib.rs (23 hunks)
  • apps/desktop/src-tauri/src/recording.rs (9 hunks)
  • apps/desktop/src-tauri/src/screenshot_editor.rs (2 hunks)
  • apps/desktop/src-tauri/src/target_select_overlay.rs (5 hunks)
  • apps/desktop/src-tauri/src/tray.rs (5 hunks)
  • apps/desktop/src-tauri/src/windows.rs (28 hunks)
  • apps/desktop/src/utils/tauri.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/desktop/package.json
  • apps/desktop/src-tauri/src/screenshot_editor.rs
🧰 Additional context used
📓 Path-based instructions (6)
**/*.rs

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.rs: Never use dbg!() macro; use proper logging (tracing::debug!, etc.) instead
Never write let _ = async_fn() which silently drops futures; await or explicitly handle them
Use duration.saturating_sub(other) instead of duration - other to avoid panics on underflow
Merge nested if statements: write 'if a && b { }' instead of 'if a { if b { } }'
Don't call .clone() on Copy types (integers, bools, etc.); copy them directly
Use function references directly: iter.map(foo) instead of iter.map(|x| foo(x))
Accept &[T] or &str instead of &Vec or &String in function parameters for flexibility
Use .is_empty() instead of .len() == 0 or .len() > 0 / .len() != 0
Don't assign () to a variable: write foo(); instead of let _ = foo(); or let x = foo(); when return is unit
Use .unwrap_or(val) instead of .unwrap_or_else(|| val) when the default is a simple/cheap value
Use 'for item in &collection' or 'for (i, item) in collection.iter().enumerate()' instead of 'for i in 0..collection.len()'
Use value.clamp(min, max) instead of manual if chains or .min(max).max(min) patterns
Always handle Result/Option or types marked #[must_use]; never ignore them

**/*.rs: Use rustfmt and workspace clippy lints for Rust code formatting and linting
Use snake_case for Rust module names and kebab-case for crate names
Never use dbg!() macro in Rust code; use proper logging instead (Clippy: dbg_macro = deny)
Always handle Result/Option or types marked #[must_use]; never ignore them (Rust compiler lint: unused_must_use = deny)
Never write let _ = async_fn() which silently drops futures; await or explicitly handle them (Clippy: let_underscore_future = deny)
Use saturating_sub instead of - for Duration to avoid panics (Clippy: unchecked_duration_subtraction = deny)
Merge nested if statements: use if a && b { } instead of if a { if b { } } (Clippy: collapsible_if = deny)
Don't call .clone() on Copy types; just copy them directly (Clippy: clone_on_copy = deny)
U...

Files:

  • apps/desktop/src-tauri/src/hotkeys.rs
  • apps/desktop/src-tauri/src/tray.rs
  • apps/desktop/src-tauri/src/target_select_overlay.rs
  • apps/desktop/src-tauri/src/recording.rs
  • apps/desktop/src-tauri/src/lib.rs
  • apps/desktop/src-tauri/src/windows.rs
**/*.{ts,tsx,js,jsx,rs}

📄 CodeRabbit inference engine (AGENTS.md)

Never add comments to code (//, /* */, ///, //!, #, etc.); code must be self-explanatory through naming, types, and structure

Files:

  • apps/desktop/src-tauri/src/hotkeys.rs
  • apps/desktop/src-tauri/src/tray.rs
  • apps/desktop/src/utils/tauri.ts
  • apps/desktop/src-tauri/src/target_select_overlay.rs
  • apps/desktop/src-tauri/src/recording.rs
  • apps/desktop/src-tauri/src/lib.rs
  • apps/desktop/src-tauri/src/windows.rs
**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Use TanStack Query v5 for all client-side server state and data fetching in TypeScript files

Files:

  • apps/desktop/src/utils/tauri.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx,js,jsx}: Use strict TypeScript; avoid any type; leverage shared types from @cap/* packages
Follow camelCase naming for variables and functions; PascalCase for components; hooks must start with 'use' prefix
Use Biome for linting and formatting; match existing formatting conventions in the codebase
Use Tailwind CSS for styling in web components; stay consistent with spacing and tokens
Use static skeletons for loading states that mirror content; avoid bouncing animations
Memoize expensive work, code-split naturally, and use Next/Image for remote assets

**/*.{ts,tsx,js,jsx}: Use 2-space indent for TypeScript files; format with Biome using pnpm format
Use Biome for code formatting and linting; run pnpm format regularly
Use kebab-case for file names (e.g., user-menu.tsx); use PascalCase for components

Files:

  • apps/desktop/src/utils/tauri.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Never add any form of comments to code (single-line //, multi-line /* /, JSDoc /* */, or any other comment syntax); code must be self-explanatory through naming, types, and structure
Directory naming must use lowercase-dashed convention

Files:

  • apps/desktop/src/utils/tauri.ts
apps/desktop/**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

apps/desktop/**/*.ts: Use @tanstack/solid-query for server state management in SolidJS components
Use generated commands and events from tauri_specta for IPC; never manually construct IPC calls
Listen directly to generated events from tauri_specta and use typed event interfaces

Files:

  • apps/desktop/src/utils/tauri.ts
🧠 Learnings (7)
📚 Learning: 2025-12-07T14:29:19.180Z
Learnt from: CR
Repo: CapSoftware/Cap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-07T14:29:19.180Z
Learning: Applies to apps/desktop/**/*.ts : Use generated commands and events from tauri_specta for IPC; never manually construct IPC calls

Applied to files:

  • apps/desktop/src/utils/tauri.ts
📚 Learning: 2025-12-07T14:29:40.743Z
Learnt from: CR
Repo: CapSoftware/Cap PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-07T14:29:40.743Z
Learning: Applies to **/*.rs : Never write `let _ = async_fn()` which silently drops futures; await or explicitly handle them (Clippy: `let_underscore_future` = deny)

Applied to files:

  • apps/desktop/src-tauri/src/windows.rs
📚 Learning: 2025-12-07T14:29:19.180Z
Learnt from: CR
Repo: CapSoftware/Cap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-07T14:29:19.180Z
Learning: Applies to **/*.rs : Never write let _ = async_fn() which silently drops futures; await or explicitly handle them

Applied to files:

  • apps/desktop/src-tauri/src/windows.rs
📚 Learning: 2025-12-07T14:29:40.743Z
Learnt from: CR
Repo: CapSoftware/Cap PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-07T14:29:40.743Z
Learning: Applies to **/*.rs : Always handle `Result`/`Option` or types marked `#[must_use]`; never ignore them (Rust compiler lint: `unused_must_use` = deny)

Applied to files:

  • apps/desktop/src-tauri/src/windows.rs
📚 Learning: 2025-12-07T14:29:19.180Z
Learnt from: CR
Repo: CapSoftware/Cap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-07T14:29:19.180Z
Learning: Applies to **/*.rs : Always handle Result/Option or types marked #[must_use]; never ignore them

Applied to files:

  • apps/desktop/src-tauri/src/windows.rs
📚 Learning: 2025-12-07T14:29:40.743Z
Learnt from: CR
Repo: CapSoftware/Cap PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-07T14:29:40.743Z
Learning: Applies to **/*.{ts,tsx,js,jsx,rs} : Never add comments to code (`//`, `/* */`, `///`, `//!`, `#`, etc.); code must be self-explanatory through naming, types, and structure

Applied to files:

  • apps/desktop/src-tauri/Cargo.toml
📚 Learning: 2025-12-07T14:29:19.180Z
Learnt from: CR
Repo: CapSoftware/Cap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-07T14:29:19.180Z
Learning: Regenerate auto-generated Tauri bindings by restarting the dev server when Rust types change

Applied to files:

  • apps/desktop/src-tauri/Cargo.toml
🧬 Code graph analysis (4)
apps/desktop/src-tauri/src/hotkeys.rs (1)
apps/desktop/src/utils/tauri.ts (1)
  • CapWindow (376-376)
apps/desktop/src-tauri/src/tray.rs (1)
apps/desktop/src/utils/tauri.ts (1)
  • CapWindow (376-376)
apps/desktop/src-tauri/src/target_select_overlay.rs (1)
apps/desktop/src-tauri/src/windows.rs (9)
  • from_str (60-99)
  • app (262-262)
  • app (281-281)
  • app (428-428)
  • app (528-528)
  • app (872-872)
  • app (890-890)
  • app (1030-1030)
  • app (1042-1042)
apps/desktop/src-tauri/src/lib.rs (2)
apps/desktop/src-tauri/src/windows.rs (13)
  • from_str (60-99)
  • app (262-262)
  • app (281-281)
  • app (428-428)
  • app (528-528)
  • app (872-872)
  • app (890-890)
  • app (1030-1030)
  • app (1042-1042)
  • label (128-130)
  • get (169-172)
  • get (1029-1031)
  • get (1041-1043)
apps/desktop/src-tauri/src/main.rs (1)
  • main (9-138)
⏰ 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 (x86_64-pc-windows-msvc, windows-latest)
  • GitHub Check: Clippy (aarch64-apple-darwin, macos-latest)
🔇 Additional comments (23)
apps/desktop/src-tauri/src/windows.rs (6)

38-55: LGTM!

The CapWindowDef enum is well-structured with clear variants for each window type. The parameterized variants (Editor, ScreenshotEditor, WindowCaptureOccluder, TargetSelectOverlay) appropriately use their respective ID types.


57-100: LGTM!

The FromStr implementation correctly parses all window label formats including parameterized variants with proper error handling via map_err.


102-125: LGTM!

The Display implementation mirrors the FromStr logic correctly, maintaining the legacy identifier for RecordingControls.


127-224: LGTM!

The CapWindowDef helper methods (label, title, activates_dock, pre_solarium_traffic_lights_position, get, undecorated, disables_window_buttons, disables_fullscreen, window_level, min_size) are well-organized const fn where possible and provide clean window configuration access.


597-604: Good use of dispatch2::run_on_main for synchronous main-thread work.

Using dispatch2::run_on_main for camera window's NSWindow configuration is appropriate for synchronous operations that don't return a Result.


827-864: LGTM!

The window_builder method cleanly centralizes window construction with proper handling of min_size, decorations, and traffic light positioning based on CapWindowDef properties.

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

5-5: LGTM!

Import correctly updated from ShowCapWindow to CapWindow to align with the refactored window types.


98-102: LGTM!

Usage correctly updated to CapWindow::Settings. The let _ here is acceptable since this is a fire-and-forget spawn for opening settings on hotkey press, and the user will see the window (or not) regardless of logging the error.

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

5-5: LGTM!

Import correctly updated to CapWindow.


458-464: LGTM!

All tray menu handler usages correctly migrated from ShowCapWindow to CapWindow variants. The async spawn patterns are appropriate for tray menu actions.

Also applies to: 479-482, 555-561, 594-597

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

191-193: LGTM!

The generated showWindow command correctly uses the CapWindow type parameter, aligning with the Rust backend changes.


376-376: LGTM!

The generated CapWindow type correctly mirrors the Rust CapWindow enum with all variants including their nested properties. The type union accurately represents all window configurations.

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

14-14: LGTM!

Import correctly includes both CapWindow (for showing windows) and CapWindowDef (for label parsing and lookups).


61-65: LGTM!

Correctly uses CapWindow::TargetSelectOverlay with show() to open overlay windows for each display.


185-193: LGTM!

Correctly uses CapWindowDef::from_str for pattern matching window labels when closing overlays. The pattern match with CapWindowDef::TargetSelectOverlay { .. } cleanly identifies target overlay windows.


240-244: Clean migration to objc2 APIs.

The macOS focus window activation code now uses NSRunningApplication::runningApplicationWithProcessIdentifier and activateWithOptions from objc2-app-kit, which is cleaner than the previous raw FFI approach.


297-300: LGTM!

The WindowFocusManager correctly uses CapWindowDef::Main.get(app) and CapWindowDef::Settings.get(app) for window lookups, aligning with the new API.

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

3000-3006: LGTM: Previous bug fixed.

The past review flagged CapWindow::from_str not existing. This has been correctly fixed to use CapWindowDef::from_str with the CapWindowDef::Editor variant.

Cargo.toml (3)

30-30: LGTM!

Adding the "chrono" feature to the specta workspace dependency is a low-risk change that extends serialization support.


68-68: LGTM!

The objc2 workspace dependency addition aligns with the macOS platform migration from legacy Cocoa/objc APIs to the objc2-based stack described in the PR objectives.


25-25: Verify the tauri version bump from 2.5.0 to 2.9.4.

Tauri 2.9.5 is the current version on crates.io, and version 2.9.4 is available on docs.rs. Tauri 2 is considered stabilized and no longer expects breaking changes. The bump from 2.5.0 to 2.9.4 is a minor version update within the stable 2.x series and maintains backward compatibility. No unpatched security advisories specific to tauri 2.9.4 were identified; earlier advisories have been addressed in the current release.

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

123-134: LGTM!

The macOS dependency migration from legacy Cocoa/objc/swift-rs APIs to the objc2-based ecosystem (objc2, dispatch2, block2, objc2-foundation, objc2-application-services, objc2-core-foundation, objc2-core-graphics, objc2-app-kit) aligns with the PR objectives. The objc2-app-kit features (NSWindow, NSResponder, NSHapticFeedback) support the window management refactor described in the PR.


30-46: Plugin versions are compatible with tauri 2.9.4.

All specified versions are official Tauri v2 plugins maintained within the stable 2.x branch. No breaking changes or security advisories found for the versions listed. The tauri-plugin-shell 2.3.3 is unaffected by CVE-2025-31477 (fixed in 2.2.1+), and tauri-plugin-oauth from the FabianLars v2 branch is actively maintained for Tauri v2 compatibility.

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

♻️ Duplicate comments (4)
apps/desktop/src-tauri/src/windows.rs (4)

346-350: Must handle Result from run_on_main_thread.

Using let _ = to discard the Result from run_on_main_thread violates the coding guideline requiring proper handling of #[must_use] types.

🔎 Apply this diff to handle the Result:
                 if new_recording_flow {
-                    let _ = window.run_on_main_thread({
+                    window.run_on_main_thread({
                         let window = window.clone();
                         move || window.objc2_nswindow().setLevel(50)
-                    });
+                    }).ok();
                 }

Based on coding guidelines: "Always handle Result/Option or types marked #[must_use]; never ignore them."


802-822: Must handle Result from run_on_main_thread.

Using let _ = to discard the Result from run_on_main_thread violates the coding guideline requiring proper handling of #[must_use] types.

🔎 Apply this diff to handle the Result:
         #[cfg(target_os = "macos")]
-        let _ = window.run_on_main_thread({
+        window.run_on_main_thread({
             let window = window.clone();
             move || {
                 if def.disables_window_buttons() {
                     window.set_traffic_lights_visible(false);
                 }
 
                 let nswindow = window.objc2_nswindow();
 
                 if def.disables_fullscreen() {
                     nswindow.setCollectionBehavior(
                         nswindow.collectionBehavior()
                             | objc2_app_kit::NSWindowCollectionBehavior::FullScreenNone,
                     );
                 }
 
                 if let Some(level) = def.window_level() {
                     nswindow.setLevel(level)
                 }
             }
-        });
+        }).ok();

Based on coding guidelines: "Always handle Result/Option or types marked #[must_use]; never ignore them."


871-876: Potential panic if def() called without prior registration.

Line 874 uses .unwrap() when looking up the Editor ID. While show() registers the ID before calling def(), external callers of def() could trigger a panic if the path isn't registered.

🔎 Apply this diff to handle the missing-path case gracefully:
             CapWindow::Editor { project_path } => {
                 let state = app.state::<EditorWindowIds>();
                 let s = state.ids.lock().unwrap();
-                let id = s.iter().find(|(path, _)| path == project_path).unwrap().1;
+                let id = s
+                    .iter()
+                    .find(|(path, _)| path == project_path)
+                    .map(|(_, id)| *id)
+                    .unwrap_or(0);
                 CapWindowDef::Editor { id }
             }

889-894: Potential panic if def() called without prior registration.

Line 892 uses .unwrap() when looking up the ScreenshotEditor ID. Similar to the Editor variant, external callers of def() could trigger a panic if the path isn't registered.

🔎 Apply this diff to handle the missing-path case gracefully:
             CapWindow::ScreenshotEditor { path } => {
                 let state = app.state::<ScreenshotEditorWindowIds>();
                 let s = state.ids.lock().unwrap();
-                let id = s.iter().find(|(p, _)| p == path).unwrap().1;
+                let id = s
+                    .iter()
+                    .find(|(p, _)| p == path)
+                    .map(|(_, id)| *id)
+                    .unwrap_or(0);
                 CapWindowDef::ScreenshotEditor { id }
             }
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 983a44e and eecbd18.

📒 Files selected for processing (2)
  • apps/desktop/src-tauri/src/lib.rs (23 hunks)
  • apps/desktop/src-tauri/src/windows.rs (28 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.rs

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.rs: Never use dbg!() macro; use proper logging (tracing::debug!, etc.) instead
Never write let _ = async_fn() which silently drops futures; await or explicitly handle them
Use duration.saturating_sub(other) instead of duration - other to avoid panics on underflow
Merge nested if statements: write 'if a && b { }' instead of 'if a { if b { } }'
Don't call .clone() on Copy types (integers, bools, etc.); copy them directly
Use function references directly: iter.map(foo) instead of iter.map(|x| foo(x))
Accept &[T] or &str instead of &Vec or &String in function parameters for flexibility
Use .is_empty() instead of .len() == 0 or .len() > 0 / .len() != 0
Don't assign () to a variable: write foo(); instead of let _ = foo(); or let x = foo(); when return is unit
Use .unwrap_or(val) instead of .unwrap_or_else(|| val) when the default is a simple/cheap value
Use 'for item in &collection' or 'for (i, item) in collection.iter().enumerate()' instead of 'for i in 0..collection.len()'
Use value.clamp(min, max) instead of manual if chains or .min(max).max(min) patterns
Always handle Result/Option or types marked #[must_use]; never ignore them

**/*.rs: Use rustfmt and workspace clippy lints for Rust code formatting and linting
Use snake_case for Rust module names and kebab-case for crate names
Never use dbg!() macro in Rust code; use proper logging instead (Clippy: dbg_macro = deny)
Always handle Result/Option or types marked #[must_use]; never ignore them (Rust compiler lint: unused_must_use = deny)
Never write let _ = async_fn() which silently drops futures; await or explicitly handle them (Clippy: let_underscore_future = deny)
Use saturating_sub instead of - for Duration to avoid panics (Clippy: unchecked_duration_subtraction = deny)
Merge nested if statements: use if a && b { } instead of if a { if b { } } (Clippy: collapsible_if = deny)
Don't call .clone() on Copy types; just copy them directly (Clippy: clone_on_copy = deny)
U...

Files:

  • apps/desktop/src-tauri/src/lib.rs
  • apps/desktop/src-tauri/src/windows.rs
**/*.{ts,tsx,js,jsx,rs}

📄 CodeRabbit inference engine (AGENTS.md)

Never add comments to code (//, /* */, ///, //!, #, etc.); code must be self-explanatory through naming, types, and structure

Files:

  • apps/desktop/src-tauri/src/lib.rs
  • apps/desktop/src-tauri/src/windows.rs
🧠 Learnings (5)
📚 Learning: 2025-12-07T14:29:40.743Z
Learnt from: CR
Repo: CapSoftware/Cap PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-07T14:29:40.743Z
Learning: Applies to **/*.rs : Use `.unwrap_or(val)` instead of `.unwrap_or_else(|| val)` for cheap values (Clippy: `unnecessary_lazy_evaluations` = deny)

Applied to files:

  • apps/desktop/src-tauri/src/windows.rs
📚 Learning: 2025-12-07T14:29:40.743Z
Learnt from: CR
Repo: CapSoftware/Cap PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-07T14:29:40.743Z
Learning: Applies to **/*.rs : Never write `let _ = async_fn()` which silently drops futures; await or explicitly handle them (Clippy: `let_underscore_future` = deny)

Applied to files:

  • apps/desktop/src-tauri/src/windows.rs
📚 Learning: 2025-12-07T14:29:19.180Z
Learnt from: CR
Repo: CapSoftware/Cap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-07T14:29:19.180Z
Learning: Applies to **/*.rs : Never write let _ = async_fn() which silently drops futures; await or explicitly handle them

Applied to files:

  • apps/desktop/src-tauri/src/windows.rs
📚 Learning: 2025-12-07T14:29:40.743Z
Learnt from: CR
Repo: CapSoftware/Cap PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-07T14:29:40.743Z
Learning: Applies to **/*.rs : Always handle `Result`/`Option` or types marked `#[must_use]`; never ignore them (Rust compiler lint: `unused_must_use` = deny)

Applied to files:

  • apps/desktop/src-tauri/src/windows.rs
📚 Learning: 2025-12-07T14:29:19.180Z
Learnt from: CR
Repo: CapSoftware/Cap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-07T14:29:19.180Z
Learning: Applies to **/*.rs : Always handle Result/Option or types marked #[must_use]; never ignore them

Applied to files:

  • apps/desktop/src-tauri/src/windows.rs
⏰ 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: Clippy (x86_64-pc-windows-msvc, windows-latest)

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

♻️ Duplicate comments (1)
apps/desktop/src-tauri/src/lib.rs (1)

2889-2904: Duplicate: Camera cleanup missing double-execution guard.

This issue was already flagged by cursor[bot]. The inline cleanup code in the Destroyed event handler lacks the camera_cleanup_done guard that exists in cleanup_camera_window (lines 588-603). When both CloseRequested and Destroyed events fire during normal window close, cleanup runs twice—calling on_window_close() and RemoveInput a second time after the guard was already set.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between eecbd18 and 3967e3a.

📒 Files selected for processing (1)
  • apps/desktop/src-tauri/src/lib.rs (23 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.rs

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.rs: Never use dbg!() macro; use proper logging (tracing::debug!, etc.) instead
Never write let _ = async_fn() which silently drops futures; await or explicitly handle them
Use duration.saturating_sub(other) instead of duration - other to avoid panics on underflow
Merge nested if statements: write 'if a && b { }' instead of 'if a { if b { } }'
Don't call .clone() on Copy types (integers, bools, etc.); copy them directly
Use function references directly: iter.map(foo) instead of iter.map(|x| foo(x))
Accept &[T] or &str instead of &Vec or &String in function parameters for flexibility
Use .is_empty() instead of .len() == 0 or .len() > 0 / .len() != 0
Don't assign () to a variable: write foo(); instead of let _ = foo(); or let x = foo(); when return is unit
Use .unwrap_or(val) instead of .unwrap_or_else(|| val) when the default is a simple/cheap value
Use 'for item in &collection' or 'for (i, item) in collection.iter().enumerate()' instead of 'for i in 0..collection.len()'
Use value.clamp(min, max) instead of manual if chains or .min(max).max(min) patterns
Always handle Result/Option or types marked #[must_use]; never ignore them

**/*.rs: Use rustfmt and workspace clippy lints for Rust code formatting and linting
Use snake_case for Rust module names and kebab-case for crate names
Never use dbg!() macro in Rust code; use proper logging instead (Clippy: dbg_macro = deny)
Always handle Result/Option or types marked #[must_use]; never ignore them (Rust compiler lint: unused_must_use = deny)
Never write let _ = async_fn() which silently drops futures; await or explicitly handle them (Clippy: let_underscore_future = deny)
Use saturating_sub instead of - for Duration to avoid panics (Clippy: unchecked_duration_subtraction = deny)
Merge nested if statements: use if a && b { } instead of if a { if b { } } (Clippy: collapsible_if = deny)
Don't call .clone() on Copy types; just copy them directly (Clippy: clone_on_copy = deny)
U...

Files:

  • apps/desktop/src-tauri/src/lib.rs
**/*.{ts,tsx,js,jsx,rs}

📄 CodeRabbit inference engine (AGENTS.md)

Never add comments to code (//, /* */, ///, //!, #, etc.); code must be self-explanatory through naming, types, and structure

Files:

  • apps/desktop/src-tauri/src/lib.rs
🧬 Code graph analysis (1)
apps/desktop/src-tauri/src/lib.rs (2)
apps/desktop/src/utils/tauri.ts (2)
  • CapWindow (376-376)
  • Camera (368-368)
apps/desktop/src-tauri/src/windows.rs (13)
  • from_str (60-99)
  • app (262-262)
  • app (281-281)
  • app (428-428)
  • app (528-528)
  • app (872-872)
  • app (890-890)
  • app (1030-1030)
  • app (1042-1042)
  • label (128-130)
  • get (169-172)
  • get (1029-1031)
  • get (1041-1043)
⏰ 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: Clippy (x86_64-pc-windows-msvc, windows-latest)

.webview_windows()
.keys()
.all(|label| !CapWindowId::from_str(label).unwrap().activates_dock())
.all(|label| !CapWindowDef::from_str(label).unwrap().activates_dock())
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 | 🔴 Critical

Critical: Unwrap on from_str result can panic.

The code calls .unwrap() on CapWindowDef::from_str(label) without handling potential parsing errors. If any window label doesn't match the expected pattern, this will panic and crash the application during the Destroyed event handler.

🔎 Apply this diff to handle parsing errors gracefully:
                     if let Some(settings) = GeneralSettingsStore::get(app).unwrap_or(None)
                         && settings.hide_dock_icon
                         && app
                             .webview_windows()
                             .keys()
-                            .all(|label| !CapWindowDef::from_str(label).unwrap().activates_dock())
+                            .all(|label| {
+                                CapWindowDef::from_str(label)
+                                    .map(|w| !w.activates_dock())
+                                    .unwrap_or(false)
+                            })
                     {

Based on coding guidelines: "Always handle Result/Option or types marked #[must_use]; never ignore them."

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
.all(|label| !CapWindowDef::from_str(label).unwrap().activates_dock())
if let Some(settings) = GeneralSettingsStore::get(app).unwrap_or(None)
&& settings.hide_dock_icon
&& app
.webview_windows()
.keys()
.all(|label| {
CapWindowDef::from_str(label)
.map(|w| !w.activates_dock())
.unwrap_or(false)
})
{
🤖 Prompt for AI Agents
In apps/desktop/src-tauri/src/lib.rs around line 2915, the code calls
CapWindowDef::from_str(label).unwrap() which can panic on parse errors; replace
the unwrap with proper Result handling by mapping or matching the parse result,
logging or tracing any parse error, and treating a failed parse as
non-activating for the purposes of the .all check (e.g., use
from_str(...).ok().map_or(false, |def| def.activates_dock()) and invert
accordingly, or match and continue), so the Destroyed event handler never panics
on malformed labels.

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