Skip to content

Conversation

@oscartbeaumont
Copy link
Contributor

@oscartbeaumont oscartbeaumont commented Aug 22, 2025

Replaces #874

Blocking:

  • Recording countdown not hooked up
  • Target select overlay positions are broken on Windows with non-default DPI scaling
  • Instant Mode doesn't ask to sign in first
  • Overlay doesn't close until after the countdown
  • Window select overlay isn't showing on Windows
  • Select 'Display' mode -> Open settings -> Close settings: Overlay doesn't return
  • Microphone doesn't have level feedback

Summary by CodeRabbit

  • New Features

    • New recording setup window: Display/Window/Area target selection, camera & microphone pickers, system-audio toggle, mode/countdown controls, changelog/update check, and sign-in flow.
    • New display info and window-icon commands to surface per-display metadata and window icons.
  • Improvements

    • Better overlay sizing/positioning, always-on-top/content-protected overlays, Windows sizing fixes, refined drag/resize controls, live mic level meter, permission pills, and UI/layout tweaks.
  • Behavior

    • New-recording flow defaults to enabled in debug builds and disabled in release.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 22, 2025

Warning

Rate limit exceeded

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

⌛ How to resolve this issue?

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

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

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

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

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 6d7582a and 0980e85.

📒 Files selected for processing (2)
  • apps/desktop/src-tauri/src/target_select_overlay.rs (8 hunks)
  • packages/ui-solid/src/auto-imports.d.ts (4 hunks)

Walkthrough

Migrates display/window types from cap-displays to scap-targets, adds a Tauri display_information command and DisplayInformation type, changes new-recording-flow default to debug builds, refactors the new-recording UI into components, adjusts overlay/window sizing and target-selection flows, and renames several settings/metadata fields.

Changes

Cohort / File(s) Summary
Dependency & Manifests
apps/cli/Cargo.toml, apps/desktop/src-tauri/Cargo.toml, crates/*/Cargo.toml, crates/scap-targets/Cargo.toml
Replace cap-displays with scap-targets; rename package to scap-targets; adjust Windows dev-deps/formatting.
Rust imports & type shifts
apps/cli/src/record.rs, apps/desktop/src-tauri/src/{lib.rs,recording.rs,windows.rs,fake_window.rs,general_settings.rs,target_select_overlay.rs}, crates/*/src/**
Switch imports from cap_displaysscap_targets; update types and signatures (RecordingState::Pending now contains mode + target; LogicalBounds type paths changed; cursor/recording signatures updated).
Tauri backend: display API & defaults
apps/desktop/src-tauri/src/{target_select_overlay.rs,lib.rs,general_settings.rs,recording.rs}
Add display_information and get_window_icon commands and DisplayInformation shape; remove ScreenUnderCursor and screen from TargetUnderCursor; simplify close-overlay API; default_enable_new_recording_flow() now uses cfg!(debug_assertions); add InProgressRecording::mode().
Window sizing & overlay behavior
apps/desktop/src-tauri/src/windows.rs, apps/desktop/src/routes/target-select-overlay.tsx
Make TargetSelectOverlay always_on_top/content_protected; move sizing/position to platform-specific pre/post-build blocks (macOS/Windows corrective sizing and small delays); adjust overlay hide/show logic.
Frontend: new-main route & components
apps/desktop/src/routes/(window-chrome)/new-main/index.tsx, apps/desktop/src/routes/(window-chrome)/new-main/* (CameraSelect.tsx, MicrophoneSelect.tsx, SystemAudio.tsx, TargetTypeButton.tsx, TargetSelectInfoPill.tsx, InfoPill.tsx, ChangeLogButton.tsx, useRequestPermission.ts)
Extract inline UI into separate components; add permission-request helper; add changelog/update check flow; implement menu-driven device pickers, recording-options wiring, and window-sizing safeguards.
Frontend queries & overlay UI
apps/desktop/src/routes/target-select-overlay.tsx, apps/desktop/src/routes/(window-chrome)/new-main/*
Add queries for displayInformation and getWindowIcon; refactor area drag/resize (Occluders, ResizeHandles, throttled drag); recording controls wired to mode/countdown and auth state.
Frontend typings & commands
apps/desktop/src/utils/tauri.ts, packages/ui-solid/src/auto-imports.d.ts
Add displayInformation command and DisplayInformation type; replace RecordingMetaWithType/RecordingType with RecordingMetaWithMode/RecordingMode; remove ScreenUnderCursor; add icon auto-import declarations.
Settings & UI renames
apps/desktop/src/routes/**, apps/desktop/src-tauri/src/general_settings.rs
Rename customCursorCapture2custom_cursor_capture2; switch metadata from typemode across UI and tauri types; remove callback.template.ts.
Auth/session & safety guards
apps/desktop/src/utils/auth.ts, apps/desktop/src-tauri/src/recording.rs
Add null/abort guards to local server session and sign-in flow; update start_recording to set pending with (mode, target) and reflow start sequence.
Windows internals & icon handling
crates/scap-targets/src/platform/win.rs
Remove multiple monitor-name heuristics; add ignored-exe filtering; simplify and optimize app icon extraction/conversion pipeline.
Small hygiene & misc
crates/recording/src/feeds/microphone.rs, apps/desktop/src/routes/window-capture-occluder.tsx, apps/desktop/src-tauri/src/hotkeys.rs, deleted apps/desktop/src/routes/(window-chrome)/callback.template.ts
Add #[allow(dead_code)] to field, remove unused imports, make a binding immutable, and delete unused HTML template file.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Frontend as UI
  participant Tauri as Backend
  participant Targets as scap_targets

  UI->>Tauri: commands.display_information(displayId)
  Tauri->>Targets: scap_targets::Display::from_id(displayId)
  Targets-->>Tauri: Display { name?, physical_size?, refresh_rate? }
  Tauri-->>UI: DisplayInformation { name?, physical_size?, refresh_rate }
  note right of UI: overlay label and size/refresh_rate\nrendered using DisplayInformation
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

codex

Suggested reviewers

  • Brendonovich

Poem

I hopped through crates and changed their routes,
Swapped displays and tuned the overlay shouts.
Permissions, icons, a draggable beat—
Debug-only hops make the new flow neat.
Thump-thump—builds bloom; a rabbit's small feat. 🐇✨

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch new-recording-flow3

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

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

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

♻️ Duplicate comments (14)
crates/scap-targets/src/platform/win.rs (1)

1045-1053: Duplicated window filtering logic should be consolidated.

The window filtering logic is duplicated between is_window_valid_for_enumeration and is_window_valid_for_topmost_selection. Consider extracting the common process filtering into a shared helper.

As noted in lines 1076-1088, this duplicated process filtering logic should be extracted into a helper function.

packages/ui-solid/src/auto-imports.d.ts (2)

86-86: Use correct hyphenated Lucide slug for Volume 2.

As noted in the past review comment and confirmed by web search, the canonical slug for the Lucide icon "Volume 2" is volume-2 (with a hyphen), not volume2.


88-88: Correct Material Symbols icon slug to include hyphen.

As noted in the past review comment, the canonical Material Symbols slug is screenshot-frame-2-rounded (with hyphen between "frame" and "2").

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

135-139: Fix: incorrect Solid Show usage causes runtime error.

Inside the inner Show, the callback parameter is the raw value (not an accessor), but it's invoked as size(). This will throw a runtime error.


175-182: Fix: incorrect Solid Show usage for window icon.

Inside Show, the parameter is the value, not an accessor. Calling icon() will throw.


247-261: Memory leak: createRoot around window resize listener.

The inner createRoot is unnecessary and the dispose() call in onCleanup creates a circular/ineffective cleanup path.


729-730: Avoid raw string events; use generated specta events.

Per desktop guidelines, prefer typed events from events or directly trigger sign-in.

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

524-528: Unify refresh-rate types (string vs number) via Rust struct change + regenerate TS bindings.

DisplayInformation.refresh_rate is a string, while CaptureDisplay.refresh_rate and CaptureWindow.refresh_rate are numbers. This inconsistency will force ad‑hoc parsing or branching in the UI.

Given this file is auto-generated, don’t edit it directly. Instead:

  • Change the Rust DisplayInformation to return both a numeric Hz and a label, then regenerate tauri.ts.
  • Keep UI formatting concerns in the frontend, numeric logic on Hz.

Proposed Rust shape (in apps/desktop/src-tauri/src/target_select_overlay.rs or wherever DisplayInformation is defined):

#[derive(Serialize, specta::Type)]
pub struct DisplayInformation {
    pub name: Option<String>,
    pub physical_size: Option<PhysicalSize>,
    pub refresh_rate_hz: u32,        // numeric
    pub refresh_rate_label: String,  // e.g. "144 Hz"
}

After regenerating, the TS type will match:

export type DisplayInformation = {
  name: string | null;
  physical_size: PhysicalSize | null;
  refresh_rate_hz: number;
  refresh_rate_label: string;
};

This aligns all refresh-rate shapes, removes parsing, and keeps the generated file untouched.

Also applies to: 471-475

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

158-161: Close overlays at countdown start to match UX (“overlay shouldn't linger during countdown”).

Subscribe to recordingEvent and close overlays when Countdown fires.

+ import { events } from "~/utils/tauri";
  createEffect(() => {
    if (rawOptions.targetMode) commands.openTargetSelectOverlays();
    else commands.closeTargetSelectOverlays();
  });
+
+ // Close overlays immediately when the countdown begins
+ events.recordingEvent.listen(({ payload }) => {
+   if (payload.variant === "Countdown") {
+     commands.closeTargetSelectOverlays();
+   }
+ });

296-296: UI direction forced to RTL; likely a typo.

dir={ostype() === "windows" ? "rtl" : "rtl"} sets RTL everywhere.

- dir={ostype() === "windows" ? "rtl" : "rtl"}
+ dir={ostype() === "windows" ? "rtl" : "ltr"}

369-369: Typo in user-facing text: “Singning” → “Signing”.

- <span>Singning In...</span>
+ <span>Signing In...</span>

4-4: Use Solid Query’s Solid API: replace useQuery with createQuery.

@tanstack/solid-query for Solid exposes createQuery, not useQuery. Current code won’t type-check and will fail at runtime.

Apply:

- import { createMutation, useQuery } from "@tanstack/solid-query";
+ import { createMutation, createQuery } from "@tanstack/solid-query";
- const screens = useQuery(() => listScreens);
- const windows = useQuery(() => listWindows);
- const cameras = useQuery(() => listVideoDevices);
- const mics = useQuery(() => listAudioDevices);
+ const screens = createQuery(() => listScreens);
+ const windows = createQuery(() => listWindows);
+ const cameras = createQuery(() => listVideoDevices);
+ const mics = createQuery(() => listAudioDevices);

Also applies to: 163-167


168-178: Solid Query objects don’t expose .promise; use reactive effects on .data.

Replace the .promise chains with createEffect blocks.

- cameras.promise.then((cameras) => {
-   if (rawOptions.cameraID && findCamera(cameras, rawOptions.cameraID)) {
-     setOptions("cameraLabel", null);
-   }
- });
+ createEffect(() => {
+   const cs = cameras.data;
+   if (!cs) return;
+   if (rawOptions.cameraID && findCamera(cs, rawOptions.cameraID)) {
+     setOptions("cameraLabel", null);
+   }
+ });
- mics.promise.then((mics) => {
-   if (rawOptions.micName && !mics.includes(rawOptions.micName)) {
-     setOptions("micName", null);
-   }
- });
+ createEffect(() => {
+   const names = mics.data;
+   if (!names) return;
+   if (rawOptions.micName && !names.includes(rawOptions.micName)) {
+     setOptions("micName", null);
+   }
+ });

4-4: Fix remaining useQuery and .promise usages

The automated search uncovered leftover React-style Solid Query patterns that must be replaced with Solid Query’s createQuery and appropriate signal handling:

• In apps/desktop/src/utils/queries.ts at line 188:

return useQuery(() => ({}))

• In apps/desktop/src/routes/(window-chrome)/new-main/index.tsx at lines 163–166:

const screens  = useQuery(() => listScreens);
const windows  = useQuery(() => listWindows);
const cameras  = useQuery(() => listVideoDevices);
const mics     = useQuery(() => listAudioDevices);

• And the corresponding .promise accesses at lines 168 and 174:

cameras.promise.then((cameras) => {  })
mics.promise   .then((mics)    => {  })

Please refactor these to use createQuery (e.g. const screens = createQuery(() => listScreens())) and consume the returned data via Solid’s reactive signals or suspense, removing all .promise calls.

🧹 Nitpick comments (6)
crates/scap-targets/src/platform/win.rs (3)

54-60: Static array is safer as a constant rather than a static reference.

The current static IGNORED_EXES: &'static [&str] pattern can be simplified and made safer. Consider using a const array instead of a static reference to an array.

-static IGNORED_EXES: &'static [&str] = &[
+const IGNORED_EXES: &[&str] = &[
     // As it's a system webview it isn't owned by the Cap process.
     "webview2",
     "msedgewebview2",
     // Just make sure, lol
     "cap",
 ];

1076-1088: Complex inline condition could benefit from extraction.

The window filtering logic has become complex with multiple webview process checks. Consider extracting to a helper function for better readability and reusability.

-        // Also skip WebView2 and Cap-related processes
-        if let Ok(exe_path) = pid_to_exe_path(process_id) {
-            if let Some(exe_name) = exe_path.file_name().and_then(|n| n.to_str()) {
-                let exe_name_lower = exe_name.to_lowercase();
-                if exe_name_lower.contains("webview2")
-                    || exe_name_lower.contains("msedgewebview2")
-                    || exe_name_lower.contains("cap")
-                {
-                    return false;
-                }
-            }
-        }
+        // Also skip WebView2 and Cap-related processes
+        if is_ignored_process(process_id) {
+            return false;
+        }

And add a helper function:

fn is_ignored_process(process_id: u32) -> bool {
    if let Ok(exe_path) = unsafe { pid_to_exe_path(process_id) } {
        if let Some(exe_name) = exe_path.file_name().and_then(|n| n.to_str()) {
            let exe_name_lower = exe_name.to_lowercase();
            // Check against static list
            if IGNORED_EXES.contains(&&*exe_name_lower) {
                return true;
            }
            // Additional pattern-based checks
            if exe_name_lower.contains("webview2") || exe_name_lower.contains("cap") {
                return true;
            }
        }
    }
    false
}

527-528: Document rationale for GOOD_SIZE_THRESHOLD and make it configurable

The constant GOOD_SIZE_THRESHOLD is hard-coded to 64px without explaining why that size was chosen or allowing callers to opt into a different cutoff. To improve clarity and flexibility:

• Add a doc-comment on the constant describing the trade-off (e.g. “64px yields acceptable icon fidelity on Windows while avoiding unnecessary scaling”).
• Expose the threshold as a parameter or configuration option on the icon-loading API so downstream code can override it if needed.
• Double-check that each HDC and bitmap handle allocated in the search loop is properly dropped on both success and early exit—Rust’s RAII will normally free these, but an inline comment can help future maintainers confirm no leaks.

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

73-87: Name the default-exported component (PascalCase per guidelines).

Components should be PascalCase. Name the default export for clearer stack traces and consistency.

-export default function () {
+export default function NewMain() {

119-156: Minor: primaryMonitor() result is unused.

If not needed, remove to avoid confusion; otherwise use it to place/center the window.


274-291: Use typed events via tauri_specta for “start-sign-in”

It looks like we’re emitting and listening for "start-sign-in" as a plain string in two places:

  • apps/desktop/src/routes/target-select-overlay.tsx: onClick → emit("start-sign-in")
  • apps/desktop/src/routes/(window-chrome)/new-main/index.tsx: listen("start-sign-in", …)

No matching generated event helper exists in the frontend bindings, so the suggestion still stands:
• Define a typed event in your Rust side (e.g. in src-tauri/src/events.rs) using tauri_specta’s #[specta] attribute.
• Re-run the binding generator to produce a TS helper (e.g. events.startSignIn) and replace both the string literal emit("start-sign-in") and listen("start-sign-in",…) calls with the generated, type-safe helpers.

This will keep the client and backend in sync and eliminate string-literal drift as your IPC surface grows.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 1f388da and 6d7582a.

📒 Files selected for processing (8)
  • apps/desktop/src-tauri/src/lib.rs (10 hunks)
  • apps/desktop/src-tauri/src/recording.rs (3 hunks)
  • apps/desktop/src-tauri/src/target_select_overlay.rs (8 hunks)
  • apps/desktop/src/routes/(window-chrome)/new-main/index.tsx (1 hunks)
  • apps/desktop/src/routes/target-select-overlay.tsx (6 hunks)
  • apps/desktop/src/utils/tauri.ts (7 hunks)
  • crates/scap-targets/src/platform/win.rs (9 hunks)
  • packages/ui-solid/src/auto-imports.d.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
apps/desktop/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

apps/desktop/**/*.{ts,tsx}: In the desktop app, rely on unplugin-icons auto-imports; do not manually import icon modules
Use generated tauri_specta commands/events (commands, events) in the desktop frontend; listen to generated events directly
Use @tanstack/solid-query for server state in the desktop app

Files:

  • apps/desktop/src/routes/(window-chrome)/new-main/index.tsx
  • apps/desktop/src/routes/target-select-overlay.tsx
  • apps/desktop/src/utils/tauri.ts
{apps/desktop,packages/ui-solid}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Component naming (Solid): components in PascalCase; hooks/utilities in camelCase starting with 'use' where applicable

Files:

  • apps/desktop/src/routes/(window-chrome)/new-main/index.tsx
  • apps/desktop/src/routes/target-select-overlay.tsx
  • packages/ui-solid/src/auto-imports.d.ts
  • apps/desktop/src/utils/tauri.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use strict TypeScript and avoid any; prefer shared types from packages

Files:

  • apps/desktop/src/routes/(window-chrome)/new-main/index.tsx
  • apps/desktop/src/routes/target-select-overlay.tsx
  • packages/ui-solid/src/auto-imports.d.ts
  • apps/desktop/src/utils/tauri.ts
apps/desktop/src-tauri/**/*.rs

📄 CodeRabbit inference engine (CLAUDE.md)

Use tauri_specta on the Rust side for IPC (typed commands/events) and emit events via generated types

Files:

  • apps/desktop/src-tauri/src/target_select_overlay.rs
  • apps/desktop/src-tauri/src/recording.rs
  • apps/desktop/src-tauri/src/lib.rs
**/tauri.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Never edit auto-generated IPC bindings file: tauri.ts

Files:

  • apps/desktop/src/utils/tauri.ts
🧠 Learnings (4)
📚 Learning: 2025-08-25T10:58:06.134Z
Learnt from: CR
PR: CapSoftware/Cap#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-25T10:58:06.134Z
Learning: Applies to apps/desktop/**/*.{ts,tsx} : Use tanstack/solid-query for server state in the desktop app

Applied to files:

  • apps/desktop/src/routes/(window-chrome)/new-main/index.tsx
  • apps/desktop/src/routes/target-select-overlay.tsx
📚 Learning: 2025-08-25T10:58:06.134Z
Learnt from: CR
PR: CapSoftware/Cap#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-25T10:58:06.134Z
Learning: Applies to apps/web/**/*.{ts,tsx} : Use TanStack Query v5 for client-side server state and data fetching in the web app

Applied to files:

  • apps/desktop/src/routes/(window-chrome)/new-main/index.tsx
📚 Learning: 2025-08-25T10:58:06.134Z
Learnt from: CR
PR: CapSoftware/Cap#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-25T10:58:06.134Z
Learning: Applies to **/queries.ts : Never edit auto-generated query bindings file: queries.ts

Applied to files:

  • apps/desktop/src/routes/(window-chrome)/new-main/index.tsx
📚 Learning: 2025-08-25T10:58:06.134Z
Learnt from: CR
PR: CapSoftware/Cap#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-25T10:58:06.134Z
Learning: Applies to apps/desktop/**/*.{ts,tsx} : In the desktop app, rely on unplugin-icons auto-imports; do not manually import icon modules

Applied to files:

  • packages/ui-solid/src/auto-imports.d.ts
🧬 Code graph analysis (5)
apps/desktop/src/routes/(window-chrome)/new-main/index.tsx (9)
apps/desktop/src/utils/tauri.ts (7)
  • CameraInfo (427-431)
  • DeviceOrModelID (522-522)
  • LogicalSize (606-606)
  • commands (5-311)
  • listAudioDevices (42-44)
  • ScreenCaptureTarget (695-698)
  • setMicInput (6-8)
apps/desktop/src/store.ts (1)
  • generalSettingsStore (61-62)
apps/desktop/src/routes/(window-chrome)/OptionsContext.tsx (1)
  • useRecordingOptions (9-16)
apps/desktop/src/utils/queries.ts (4)
  • listScreens (41-46)
  • listWindows (24-39)
  • listVideoDevices (53-58)
  • createCameraMutation (152-185)
apps/desktop/src/utils/auth.ts (1)
  • createSignInMutation (13-32)
apps/desktop/src/routes/(window-chrome)/Context.tsx (1)
  • WindowChromeHeader (29-41)
apps/desktop/src/routes/(window-chrome)/new-main/CameraSelect.tsx (1)
  • CameraSelect (11-83)
apps/desktop/src/routes/(window-chrome)/new-main/MicrophoneSelect.tsx (1)
  • MicrophoneSelect (12-120)
apps/desktop/src/routes/(window-chrome)/new-main/SystemAudio.tsx (1)
  • SystemAudio (5-29)
apps/desktop/src/routes/target-select-overlay.tsx (3)
apps/desktop/src/utils/tauri.ts (3)
  • commands (5-311)
  • displayInformation (305-307)
  • ScreenCaptureTarget (695-698)
apps/desktop/src/store.ts (2)
  • authStore (59-59)
  • generalSettingsStore (61-62)
apps/desktop/src/utils/queries.ts (1)
  • createOptionsQuery (87-118)
apps/desktop/src-tauri/src/target_select_overlay.rs (3)
apps/desktop/src/utils/tauri.ts (5)
  • DisplayId (523-523)
  • WindowId (791-791)
  • DisplayInformation (524-528)
  • PhysicalSize (645-645)
  • WindowUnderCursor (792-796)
crates/scap-targets/src/platform/win.rs (8)
  • list (91-114)
  • list (277-307)
  • display (925-932)
  • get_containing_cursor (146-159)
  • get_topmost_at_cursor (313-356)
  • owner_name (437-477)
  • from_id (124-127)
  • std (967-967)
crates/scap-targets/src/lib.rs (9)
  • list (16-18)
  • list (103-105)
  • display (146-148)
  • get_containing_cursor (36-38)
  • get_topmost_at_cursor (114-116)
  • owner_name (134-136)
  • display_relative_logical_bounds (154-198)
  • from_id (32-34)
  • from_id (122-124)
apps/desktop/src-tauri/src/recording.rs (2)
apps/desktop/src/utils/tauri.ts (2)
  • RecordingMode (682-682)
  • ShowCapWindow (718-730)
apps/desktop/src-tauri/src/windows.rs (8)
  • id (664-690)
  • app (195-195)
  • app (315-315)
  • app (397-397)
  • app (670-670)
  • app (859-859)
  • label (112-114)
  • from_str (52-84)
apps/desktop/src-tauri/src/lib.rs (7)
apps/desktop/src/utils/tauri.ts (10)
  • RecordingMode (682-682)
  • DisplayId (523-523)
  • WindowId (791-791)
  • LogicalBounds (604-604)
  • ScreenCaptureTarget (695-698)
  • JsonValue (601-601)
  • CurrentRecordingTarget (495-498)
  • CurrentRecording (490-493)
  • RecordingMetaWithMode (674-681)
  • RecordingMeta (669-673)
apps/desktop/src-tauri/src/target_select_overlay.rs (3)
  • scap_targets (49-52)
  • display_information (138-149)
  • get_window_icon (125-134)
apps/desktop/src-tauri/src/recording.rs (3)
  • mode (125-130)
  • capture_target (58-63)
  • id (147-152)
crates/scap-targets/src/platform/win.rs (3)
  • id (125-125)
  • id (418-420)
  • from_id (124-127)
crates/scap-targets/src/platform/macos.rs (3)
  • id (44-44)
  • id (288-290)
  • from_id (43-46)
apps/desktop/src-tauri/src/windows.rs (7)
  • id (664-690)
  • app (195-195)
  • app (315-315)
  • app (397-397)
  • app (670-670)
  • app (859-859)
  • label (112-114)
crates/scap-targets/src/lib.rs (4)
  • id (28-30)
  • id (118-120)
  • from_id (32-34)
  • from_id (122-124)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Build Desktop (aarch64-apple-darwin, macos-latest)
  • GitHub Check: Build Desktop (x86_64-pc-windows-msvc, windows-latest)
  • GitHub Check: Analyze (rust)
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (25)
crates/scap-targets/src/platform/win.rs (1)

1002-1009: Ensure IGNORED_EXES comparison is case-insensitive on Windows.

The code converts the exe_name to lowercase for comparison against IGNORED_EXES, which is correct for Windows (case-insensitive filesystem). However, consider documenting this platform-specific behavior.

The case-insensitive comparison using to_lowercase() is appropriate for Windows filesystem semantics.

packages/ui-solid/src/auto-imports.d.ts (1)

14-14: LGTM: New Cap icon declarations added correctly.

The three new icon declarations (IconCapCaretDown, IconCapGear, IconCapX) follow the existing pattern and use correct hyphenated slugs.

Also applies to: 29-29, 60-60

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

124-130: LGTM: Clean implementation of mode getter.

The mode() method provides a clear way to determine the recording mode from the enum variant.


310-313: LGTM: Correct parameter order for set_pending_recording.

The change to pass both mode and capture_target aligns with the updated state management API.


315-324: LGTM: Proper cleanup of target select overlays.

The code correctly closes all TargetSelectOverlay windows before starting the countdown, ensuring a clean recording flow.


329-334: LGTM: Main window behavior correctly positioned after showing InProgressRecording.

The main window start behavior is now triggered at the right time in the recording flow.

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

11-14: LGTM: Clean migration to scap_targets.

The imports correctly use the new scap_targets module, aligning with the PR's migration from cap_displays.


36-41: LGTM: Well-structured DisplayInformation type.

The new DisplayInformation struct properly encapsulates display metadata with appropriate optional fields.


125-134: LGTM: Well-implemented window icon retrieval.

The get_window_icon command properly validates the window ID, retrieves the icon, and returns it as a base64-encoded data URL.


137-149: LGTM: Clean display information retrieval.

The display_information command properly validates the display ID and returns comprehensive display metadata.


196-196: Reevaluate the polling interval for focus updates

I only found a single instance of this polling delay:

  • apps/desktop/src-tauri/src/target_select_overlay.rs line 196:
    tokio::time::sleep(std::time::Duration::from_millis(400)).await;

No other timing constants or “focus poll”–style logic appeared elsewhere in the codebase. Since this interval was bumped from 300 ms to 400 ms, please verify that the extra 100 ms of latency still meets your UI responsiveness requirements for focus management. You may want to:

  • Exercise typical focus transitions in the desktop app and gauge perceived delay.
  • Consider making the interval configurable or introducing a more event-driven notification if 400 ms proves too coarse.
apps/desktop/src/routes/target-select-overlay.tsx (4)

49-59: LGTM: Well-structured window icon query.

The query is properly configured with appropriate caching (5 minutes) and conditional enabling based on window ID availability.


61-75: LGTM: Robust display information query with error handling.

The query includes proper error handling and conditional enabling based on both display ID and target mode.


263-298: LGTM: Clean drag handler implementation with animation frame throttling.

The use of requestAnimationFrame for throttling mouse move events is a good performance optimization.


666-668: LGTM: Proper text capitalization utility.

Simple and effective implementation for capitalizing mode names.

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

305-310: New commands look correct and match Rust exports.

displayInformation and getWindowIcon bindings look consistent with the backend; good addition for the overlay flows.


492-493: Rename propagated: CurrentRecording now uses mode (not type).

Frontend type matches the backend change; this should prevent drift and confusion.


674-682: RecordingMetaWithMode type and list/get APIs are coherent.

  • getRecordingMeta → RecordingMetaWithMode
  • listRecordings → [string, RecordingMetaWithMode][]

These look consistent with the Rust side and avoid union narrowing in the UI.

Also applies to: 145-147, 154-156


562-563: Field rename ‘custom_cursor_capture2’ verified across backend and UI

All occurrences of the legacy custom_cursor_capture key have been updated to custom_cursor_capture2. Grep results confirm that both data definitions and UI reads/writes use the new field:

  • apps/desktop/src-tauri/src/general_settings.rs (serde rename and default)
  • apps/desktop/src/utils/tauri.ts (type definition)
  • apps/desktop/src/routes/(window-chrome)/settings/experimental.tsx (initial state, value binding, handleChange)
  • apps/desktop/src/routes/(window-chrome)/settings/general.tsx (initial state)
  • apps/desktop/src/routes/editor/ConfigSidebar.tsx (conditional disable)

No references to the old custom_cursor_capture remain.

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

1464-1479: Wrapper type for mode is clean and future‑proof.

RecordingMetaWithMode.new derives RecordingMode from RecordingMetaInner. Flatting inner is convenient for the UI. LGTM.


1964-1966: Exporting display_information and get_window_icon commands is aligned with the new overlay flow.

The generated bindings pick these up; good separation of concerns.


2235-2245: Nice UX: restore relevant windows on Settings/Upgrade/ModeSelect close.

Showing TargetSelectOverlay and Main again avoids “lost window” states reported in the PR.

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

253-259: Mutations for mic/camera look good; error handling restores UI state.

The mic mutation updates the option after the backend call. The camera mutation (from utils/queries) already handles rollback on failure. Solid implementation.

Also applies to: 260-269


382-434: Options reconciliation logic reads well and handles fallbacks (window→display).

The derived options and window→display fallback guard against empty sources. This should mitigate “window select overlay isn’t showing” edge-cases during data churn.

Also applies to: 185-236, 239-251


1-71: General: imports follow the Auto-Icons guideline and strict TS—nice.

Icons are auto-imported, no any usage, and component naming elsewhere adheres to conventions.

Comment on lines +462 to 484
let (mode, capture_target) = match &state.recording_state {
RecordingState::None => return Ok(JsonValue::new(&None)),
RecordingState::Pending { mode, target } => (*mode, target),
RecordingState::Active(inner) => (inner.mode(), inner.capture_target()),
};

let target = match capture_target {
ScreenCaptureTarget::Display { id } => CurrentRecordingTarget::Screen { id: id.clone() },
ScreenCaptureTarget::Window { id } => CurrentRecordingTarget::Window {
id: id.clone(),
bounds: scap_targets::Window::from_id(&id)
.ok_or(())?
.display_relative_logical_bounds()
.ok_or(())?,
},
ScreenCaptureTarget::Area { screen, bounds } => CurrentRecordingTarget::Area {
screen: screen.clone(),
bounds: bounds.clone(),
},
};

Ok(JsonValue::new(&Some(CurrentRecording { target, mode })))
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Avoid erroring when a window target disappears; return None instead.

get_current_recording currently returns Err(()) if the WindowId no longer resolves (e.g., target window closed) or bounds aren’t available. This will reject the frontend invoke and can break polling/UI. Prefer degrading to Ok(JsonValue::new(&None)).

Suggested change:

-        ScreenCaptureTarget::Window { id } => CurrentRecordingTarget::Window {
-            id: id.clone(),
-            bounds: scap_targets::Window::from_id(&id)
-                .ok_or(())?
-                .display_relative_logical_bounds()
-                .ok_or(())?,
-        },
+        ScreenCaptureTarget::Window { id } => {
+            if let Some(bounds) = scap_targets::Window::from_id(&id)
+                .and_then(|w| w.display_relative_logical_bounds())
+            {
+                CurrentRecordingTarget::Window { id: id.clone(), bounds }
+            } else {
+                // Target no longer valid; surface "no current recording" to the UI rather than erroring out.
+                return Ok(JsonValue::new(&None));
+            }
+        },

This keeps the command shape stable and avoids spurious frontend errors during transient target churn.

📝 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
let (mode, capture_target) = match &state.recording_state {
RecordingState::None => return Ok(JsonValue::new(&None)),
RecordingState::Pending { mode, target } => (*mode, target),
RecordingState::Active(inner) => (inner.mode(), inner.capture_target()),
};
let target = match capture_target {
ScreenCaptureTarget::Display { id } => CurrentRecordingTarget::Screen { id: id.clone() },
ScreenCaptureTarget::Window { id } => CurrentRecordingTarget::Window {
id: id.clone(),
bounds: scap_targets::Window::from_id(&id)
.ok_or(())?
.display_relative_logical_bounds()
.ok_or(())?,
},
ScreenCaptureTarget::Area { screen, bounds } => CurrentRecordingTarget::Area {
screen: screen.clone(),
bounds: bounds.clone(),
},
};
Ok(JsonValue::new(&Some(CurrentRecording { target, mode })))
}
let (mode, capture_target) = match &state.recording_state {
RecordingState::None => return Ok(JsonValue::new(&None)),
RecordingState::Pending { mode, target } => (*mode, target),
RecordingState::Active(inner) => (inner.mode(), inner.capture_target()),
};
let target = match capture_target {
ScreenCaptureTarget::Display { id } => CurrentRecordingTarget::Screen { id: id.clone() },
ScreenCaptureTarget::Window { id } => {
if let Some(bounds) = scap_targets::Window::from_id(&id)
.and_then(|w| w.display_relative_logical_bounds())
{
CurrentRecordingTarget::Window { id: id.clone(), bounds }
} else {
// Target no longer valid; surface "no current recording" to the UI rather than erroring out.
return Ok(JsonValue::new(&None));
}
},
ScreenCaptureTarget::Area { screen, bounds } => CurrentRecordingTarget::Area {
screen: screen.clone(),
bounds: bounds.clone(),
},
};
Ok(JsonValue::new(&Some(CurrentRecording { target, mode })))
}

Comment on lines +142 to +148
// Listen for resize events
const unlistenResize = currentWindow.onResized(() => {
const size = getWindowSize();

currentWindow.setSize(new LogicalSize(size.width, size.height));
});

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

Potential resize event loop: onResized calls setSize unconditionally.

Setting size inside onResized will re-trigger the event on some platforms, causing a loop/flicker. Guard the handler so it only sets size when it differs.

- const unlistenResize = currentWindow.onResized(() => {
-   const size = getWindowSize();
-   currentWindow.setSize(new LogicalSize(size.width, size.height));
- });
+ const unlistenResize = currentWindow.onResized(async ({ payload }) => {
+   const desired = getWindowSize();
+   // payload is PhysicalSize; convert to Logical via scale factor if needed
+   const scale = await currentWindow.scaleFactor();
+   const w = Math.round(payload.width / scale);
+   const h = Math.round(payload.height / scale);
+   if (w !== desired.width || h !== desired.height) {
+     currentWindow.setSize(new LogicalSize(desired.width, desired.height));
+   }
+ });

Optionally, drop the onResized clamp altogether and enforce only on mount/focus if you don’t need hard clamping.

Also applies to: 131-139

🤖 Prompt for AI Agents
In apps/desktop/src/routes/(window-chrome)/new-main/index.tsx around lines
142-148 (and similarly lines 131-139), the onResized handler always calls
currentWindow.setSize which can re-trigger onResized and cause an event
loop/flicker; change the handler to compute the desired/clamped size from
getWindowSize, compare the desired width and height to the current window size,
and only call currentWindow.setSize(new LogicalSize(...)) if either dimension
differs; alternatively remove the onResized clamp and enforce sizing only on
mount/focus if hard clamping isn’t required.

@Brendonovich Brendonovich merged commit 096579e into main Aug 25, 2025
15 checks passed
@oscartbeaumont oscartbeaumont deleted the new-recording-flow3 branch August 25, 2025 19:58
@coderabbitai coderabbitai bot mentioned this pull request Nov 22, 2025
@coderabbitai coderabbitai bot mentioned this pull request Dec 2, 2025
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.

3 participants