Skip to content

Conversation

@richiemcilroy
Copy link
Member

@richiemcilroy richiemcilroy commented Nov 8, 2025

Summary by CodeRabbit

  • New Features

    • Cursor animation presets (slow, mellow, custom) with tunable physics, corner rounding options (squircle/rounded) for background and camera, and enhanced motion-blur and zoom smoothing for richer visuals.
    • Project configuration serialization to persist editor corner styles when saving.
  • Bug Fixes

    • Updated cursor hotspot positions for macOS and Windows for more accurate pointer alignment.
  • Chores

    • Version bumped to 0.3.83.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 8, 2025

Caution

Review failed

The pull request is closed.

Walkthrough

Adds cursor physics and presets, motion-blur and zoom analysis, corner-rounding support, cursor hotspot updates, spring-mass-damper and interpolation changes, shader and uniform updates, editor serialization for corner styles, and a desktop package version bump to 0.3.83.

Changes

Cohort / File(s) Summary
Version & Constant
apps/desktop/src-tauri/Cargo.toml, apps/desktop/src-tauri/src/recording.rs
Bumped package version 0.3.820.3.83; extracted AUTO_ZOOM_AMOUNT = 1.5 replacing a hard-coded zoom amount.
Editor UI & Context
apps/desktop/src/routes/editor/ConfigSidebar.tsx, apps/desktop/src/routes/editor/Player.tsx, apps/desktop/src/routes/editor/context.ts, apps/desktop/src/routes/editor/PresetsDropdown.tsx
Added cursor animation presets, tension/mass/friction controls, corner-style selector; introduced EditorProjectConfiguration, normalizeProject/serializeProjectConfiguration, and wired serialization on init/save and preset application.
Tauri Types
apps/desktop/src/utils/tauri.ts
Added CornerStyle (`"squircle"
Project Configuration
crates/project/src/configuration.rs
Added public CornerStyle enum and rounding_type on Background/Camera; updated CursorAnimationStyle (Slow, Mellow, Custom), added CursorSmoothingPreset and preset() mapping; adjusted defaults.
Cursor Hotspots
crates/cursor-info/cursors.html, crates/cursor-info/src/macos.rs, crates/cursor-info/src/windows.rs
Updated hotspot coordinates for multiple macOS and Windows cursor variants (arrow, ibeam, hand/pointing_hand, tahoe variants).
Cursor Interpolation
crates/rendering/src/cursor_interpolation.rs
New spring-mass-damper interpolation pipeline with motion profiles (Default/Snappy/Drag), densification for gaps, updated smoothing call signatures, added velocity to InterpolatedCursorPosition, and unit tests.
Spring Simulation
crates/rendering/src/spring_mass_damper.rs
Switched to variable-step integration (run(&mut, dt_ms: f32)), introduced SIMULATION_TICK_MS, and added set_config(...) to update simulation parameters at runtime.
Motion Blur & Project Uniforms
crates/rendering/src/lib.rs, crates/rendering/src/composite_frame.rs
Added motion blur subsystem (MotionBounds, MotionBlurDescriptor/Mode, analysis), expanded ProjectUniforms with frame_rate, prev_cursor, display_parent_motion_px, motion_blur_amount; updated CompositeVideoFrameUniforms fields for motion blur and rounding_type.
Cursor Layer
crates/rendering/src/layers/cursor.rs
Replaced velocity-based blur with motion-vector/strength pipeline; renamed uniform field velocity_blur_opacitymotion_vector_strength and added combining/clamping helpers.
Shaders
crates/rendering/src/shaders/composite-video-frame.wgsl, crates/rendering/src/shaders/cursor.wgsl
Composite shader: replaced velocity with motion_blur_vector, motion_blur_zoom_center, motion_blur_params, and rounding_type; added superellipse/rounded corner helpers and dual-mode blur (directional vs zoom). Cursor shader: renamed velocity_blur_opacitymotion_vector_strength and switched to motion-vector-driven weighted blur sampling.

Sequence Diagram(s)

sequenceDiagram
    participant Editor as Editor UI
    participant Context as Editor Context
    participant Renderer as Rendering
    participant Interp as Cursor Interpolator
    participant Spring as Spring Simulation
    participant Shader as GPU Shader

    Editor->>Context: change cursor preset / corner style
    Context->>Context: applyCursorStylePreset / setCursorPhysics
    Editor->>Context: save project
    Context->>Renderer: serializeProjectConfiguration(project)

    Renderer->>Interp: provide cursor events & smoothing config
    Interp->>Interp: densify_cursor_moves()
    Interp->>Spring: run(dt_ms) per tick
    Spring-->>Interp: position + velocity
    Interp->>Renderer: fill uniforms (motion_blur_vector, prev_cursor, rounding_type)
    Renderer->>Shader: upload uniforms (motion_blur_params, zoom_center, rounding_type)
    Shader->>Shader: select blur mode (directional or zoom) and apply rounding
    Shader-->>Renderer: rendered frame
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

  • Areas needing extra attention:
    • Cursor interpolation densification thresholds and unit tests.
    • Variable-step spring integration stability and set_config behavior.
    • Motion blur descriptor math and correct mapping into composite uniforms.
    • Shader dual-mode blur correctness and rounding_type propagation.
    • Editor <-> serialized config round-trip for corner rounding fields.

Possibly related PRs

Suggested reviewers

  • Brendonovich

Poem

🐰 Whiskers twitch, a springy tune,

Motion blurs beneath the moon,
Corners squircle, presets hum,
Hotspots true for clicks to come,
A rabbit cheers — hop, code, zoom!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 9.64% 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 reflects the main changes: comprehensive updates to cursor movement physics (spring-mass-damper system), motion blur implementation (new motion blur modes, zoom blur), and UI styling enhancements (corner rounding types, cursor presets).

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9f82360 and 2f53c14.

📒 Files selected for processing (2)
  • apps/desktop/src/routes/editor/PresetsDropdown.tsx (2 hunks)
  • apps/desktop/src/routes/editor/context.ts (2 hunks)

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.

@richiemcilroy richiemcilroy changed the title feat: Add cursor movement style presets and motion blur Studio Mode movement, motion blur and styling overhaul Nov 9, 2025
@richiemcilroy richiemcilroy marked this pull request as ready for review November 9, 2025 17:42
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

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

⚠️ Outside diff range comments (1)
crates/rendering/src/cursor_interpolation.rs (1)

153-177: Interpolate raw cursor positions

In the non-smoothed branch we now return c.x/c.y (the earlier sample) for every time between chunk[0] and chunk[1]. That turns the raw cursor into a step function until the next event arrives, which is a regression from the previous continuous motion. Please interpolate between the two samples using the elapsed time before constructing InterpolatedCursorPosition. (forums.autodesk.com)

-                Some((
-                    XY::new(c.x as f32, c.y as f32),
-                    c.cursor_id.clone(),
-                    velocity,
-                ))
+                let alpha = ((time_ms - c.time_ms) as f32 / delta_ms).clamp(0.0, 1.0);
+                let pos = XY::new(
+                    (c.x + (next.x - c.x) * alpha) as f32,
+                    (c.y + (next.y - c.y) * alpha) as f32,
+                );
+                Some((pos, c.cursor_id.clone(), velocity))
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3cfb332 and cbc7937.

⛔ Files ignored due to path filters (11)
  • Cargo.lock is excluded by !**/*.lock
  • crates/cursor-info/assets/mac/arrow.svg is excluded by !**/*.svg
  • crates/cursor-info/assets/mac/ibeam.svg is excluded by !**/*.svg
  • crates/cursor-info/assets/mac/pointing_hand.svg is excluded by !**/*.svg
  • crates/cursor-info/assets/mac/tahoe/default.svg is excluded by !**/*.svg
  • crates/cursor-info/assets/mac/tahoe/pointer.svg is excluded by !**/*.svg
  • crates/cursor-info/assets/mac/tahoe/tahoe.svg is excluded by !**/*.svg
  • crates/cursor-info/assets/mac/tahoe/text.svg is excluded by !**/*.svg
  • crates/cursor-info/assets/windows/arrow.svg is excluded by !**/*.svg
  • crates/cursor-info/assets/windows/hand.svg is excluded by !**/*.svg
  • crates/cursor-info/assets/windows/ibeam.svg is excluded by !**/*.svg
📒 Files selected for processing (19)
  • apps/desktop/src-tauri/Cargo.toml (1 hunks)
  • apps/desktop/src-tauri/src/camera.wgsl (2 hunks)
  • apps/desktop/src-tauri/src/recording.rs (2 hunks)
  • apps/desktop/src/routes/camera.tsx (2 hunks)
  • apps/desktop/src/routes/editor/ConfigSidebar.tsx (12 hunks)
  • apps/desktop/src/routes/editor/Player.tsx (2 hunks)
  • apps/desktop/src/routes/editor/context.ts (2 hunks)
  • apps/desktop/src/utils/tauri.ts (2 hunks)
  • crates/cursor-info/cursors.html (8 hunks)
  • crates/cursor-info/src/macos.rs (6 hunks)
  • crates/cursor-info/src/windows.rs (2 hunks)
  • crates/project/src/configuration.rs (7 hunks)
  • crates/rendering/src/composite_frame.rs (2 hunks)
  • crates/rendering/src/cursor_interpolation.rs (8 hunks)
  • crates/rendering/src/layers/cursor.rs (5 hunks)
  • crates/rendering/src/lib.rs (13 hunks)
  • crates/rendering/src/shaders/composite-video-frame.wgsl (7 hunks)
  • crates/rendering/src/shaders/cursor.wgsl (2 hunks)
  • crates/rendering/src/spring_mass_damper.rs (4 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

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

Files:

  • apps/desktop/src-tauri/src/recording.rs
  • crates/rendering/src/layers/cursor.rs
  • crates/cursor-info/src/macos.rs
  • crates/rendering/src/spring_mass_damper.rs
  • crates/rendering/src/cursor_interpolation.rs
  • crates/rendering/src/composite_frame.rs
  • crates/project/src/configuration.rs
  • crates/rendering/src/lib.rs
  • crates/cursor-info/src/windows.rs
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

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

Files:

  • apps/desktop/src/routes/camera.tsx
  • apps/desktop/src/routes/editor/Player.tsx
  • apps/desktop/src/utils/tauri.ts
  • apps/desktop/src/routes/editor/ConfigSidebar.tsx
  • apps/desktop/src/routes/editor/context.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

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

Files:

  • apps/desktop/src/routes/camera.tsx
  • apps/desktop/src/routes/editor/Player.tsx
  • apps/desktop/src/utils/tauri.ts
  • apps/desktop/src/routes/editor/ConfigSidebar.tsx
  • apps/desktop/src/routes/editor/context.ts
crates/*/src/**/*

📄 CodeRabbit inference engine (AGENTS.md)

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

Files:

  • crates/rendering/src/layers/cursor.rs
  • crates/cursor-info/src/macos.rs
  • crates/rendering/src/spring_mass_damper.rs
  • crates/rendering/src/shaders/cursor.wgsl
  • crates/rendering/src/cursor_interpolation.rs
  • crates/rendering/src/composite_frame.rs
  • crates/project/src/configuration.rs
  • crates/rendering/src/shaders/composite-video-frame.wgsl
  • crates/rendering/src/lib.rs
  • crates/cursor-info/src/windows.rs
**/tauri.ts

📄 CodeRabbit inference engine (AGENTS.md)

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

Files:

  • apps/desktop/src/utils/tauri.ts
🧠 Learnings (2)
📚 Learning: 2025-09-22T14:19:56.010Z
Learnt from: CR
Repo: CapSoftware/Cap PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-22T14:19:56.010Z
Learning: Applies to **/tauri.ts : Do not edit auto-generated files named `tauri.ts`.

Applied to files:

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

Applied to files:

  • apps/desktop/src-tauri/Cargo.toml
🧬 Code graph analysis (8)
crates/rendering/src/layers/cursor.rs (2)
crates/rendering/src/composite_frame.rs (1)
  • new (93-105)
crates/rendering/src/lib.rs (7)
  • new (70-140)
  • new (323-366)
  • new (406-408)
  • new (992-1436)
  • new (1452-1457)
  • new (1504-1514)
  • new (1657-1690)
apps/desktop/src/routes/editor/Player.tsx (1)
apps/desktop/src/routes/editor/context.ts (1)
  • serializeProjectConfiguration (99-118)
crates/rendering/src/cursor_interpolation.rs (3)
crates/rendering/src/lib.rs (8)
  • new (70-140)
  • new (323-366)
  • new (406-408)
  • new (992-1436)
  • new (1452-1457)
  • new (1504-1514)
  • new (1657-1690)
  • cursor_move (1908-1916)
crates/rendering/src/spring_mass_damper.rs (1)
  • new (22-31)
crates/project/src/cursor.rs (1)
  • moves (209-213)
crates/rendering/src/composite_frame.rs (2)
crates/project/src/configuration.rs (8)
  • default (54-58)
  • default (240-247)
  • default (251-264)
  • default (329-346)
  • default (350-356)
  • default (382-390)
  • default (464-488)
  • default (629-644)
crates/rendering/src/lib.rs (1)
  • default (636-644)
crates/project/src/configuration.rs (2)
apps/desktop/src/utils/tauri.ts (3)
  • CornerStyle (384-384)
  • CursorAnimationStyle (389-389)
  • CursorType (392-392)
crates/rendering/src/composite_frame.rs (1)
  • default (40-66)
apps/desktop/src/routes/editor/ConfigSidebar.tsx (4)
apps/desktop/src/routes/editor/context.ts (1)
  • CornerRoundingType (64-64)
apps/desktop/src/utils/tauri.ts (1)
  • CursorAnimationStyle (389-389)
crates/project/src/configuration.rs (3)
  • preset (419-433)
  • div (139-144)
  • div (150-155)
apps/desktop/src/routes/editor/ui.tsx (6)
  • Field (25-47)
  • Slider (65-147)
  • MenuItem (264-279)
  • PopperContent (287-293)
  • topSlideAnimateClasses (432-433)
  • MenuItemList (295-309)
apps/desktop/src/routes/editor/context.ts (1)
apps/desktop/src/utils/tauri.ts (3)
  • ProjectConfiguration (447-447)
  • SerializedEditorInstance (470-470)
  • commands (7-292)
crates/rendering/src/lib.rs (4)
apps/desktop/src/utils/tauri.ts (8)
  • AspectRatio (350-350)
  • CameraShape (370-370)
  • CameraXPosition (371-371)
  • CameraYPosition (372-372)
  • ClipOffsets (382-382)
  • CornerStyle (384-384)
  • Crop (385-385)
  • XY (499-499)
crates/project/src/configuration.rs (9)
  • new (69-71)
  • default (54-58)
  • default (240-247)
  • default (251-264)
  • default (329-346)
  • default (350-356)
  • default (382-390)
  • default (464-488)
  • default (629-644)
crates/rendering/src/composite_frame.rs (2)
  • new (93-105)
  • default (40-66)
crates/rendering/src/cursor_interpolation.rs (1)
  • interpolate_cursor (111-179)
🪛 GitHub Check: Clippy (aarch64-apple-darwin, macos-latest)
crates/rendering/src/lib.rs

[warning] 430-430: methods with the following characteristics: (to_* and self type is Copy) usually take self by value
warning: methods with the following characteristics: (to_* and self type is Copy) usually take self by value
--> crates/rendering/src/lib.rs:430:14
|
430 | fn to_uv(&self, point: XY) -> XY {
| ^^^^^
|
= help: consider choosing a less ambiguous name
= help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.91.0/index.html#wrong_self_convention
= note: #[warn(clippy::wrong_self_convention)] on by default

🔇 Additional comments (5)
apps/desktop/src-tauri/Cargo.toml (1)

3-3: Version bump is appropriate.

The patch version increment from 0.3.82 to 0.3.83 aligns with the feature and styling updates (motion blur, physics-based cursor motion, corner rounding styles) described in the PR. No dependencies have changed, and the manifest structure remains intact.

crates/cursor-info/src/windows.rs (1)

73-73: LGTM! Hotspot coordinates updated correctly.

The hotspot coordinate updates for Arrow, IBeam, and Hand cursors are within valid ranges and maintain consistent precision. These changes align with the corresponding updates in the HTML testing file.

Also applies to: 77-77, 117-117

crates/cursor-info/src/macos.rs (1)

74-74: LGTM! Hotspot coordinates updated correctly.

The hotspot coordinate updates for both standard and Tahoe cursor variants (Arrow, IBeam, PointingHand) are within valid ranges and maintain consistent precision. These changes align with the corresponding updates in the HTML testing file.

Also applies to: 99-99, 111-111, 144-144, 170-170, 182-182

crates/cursor-info/cursors.html (1)

166-166: LGTM! Hotspot data synchronized correctly.

The hotspot coordinate updates in this testing utility are fully consistent with the corresponding changes in windows.rs and macos.rs. All values are within valid ranges and maintain appropriate precision. The HTML visualization tool provides a good way to validate hotspot positioning during development.

Also applies to: 202-202, 220-220, 268-268, 311-311, 329-329, 391-391, 397-397, 433-433

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

49-72: Verify callers switched to millisecond input before merging.

run used to treat dt as seconds; now it expects milliseconds and converts back with / 1000.0. If any existing caller is still passing a seconds-based delta (which was the previous contract), the simulation will run 1000× slower. Please confirm every call site is now supplying milliseconds (or consider keeping the API in seconds and doing the conversion internally).

Comment on lines +69 to +71
if (motion_len < 1e-4 || blur_strength < 0.001) {
return textureSample(t_cursor, s_cursor, input.uv) * opacity;
}
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

Restore shadow blend on the no-blur path

The new early-exit returns textureSample(...) * opacity directly, so whenever motion is below the blur threshold the shader skips mix(shadow_color, …) entirely. That removes the cursor shadow for the common “not moving” case. Please return the same shadow-composited color here. (community.adobe.com)

-    if (motion_len < 1e-4 || blur_strength < 0.001) {
-        return textureSample(t_cursor, s_cursor, input.uv) * opacity;
-    }
+    if (motion_len < 1e-4 || blur_strength < 0.001) {
+        let sample = textureSample(t_cursor, s_cursor, input.uv) * opacity;
+        return mix(shadow_color, sample, sample.a);
+    }
📝 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
if (motion_len < 1e-4 || blur_strength < 0.001) {
return textureSample(t_cursor, s_cursor, input.uv) * opacity;
}
if (motion_len < 1e-4 || blur_strength < 0.001) {
let sample = textureSample(t_cursor, s_cursor, input.uv) * opacity;
return mix(shadow_color, sample, sample.a);
}
🤖 Prompt for AI Agents
In crates/rendering/src/shaders/cursor.wgsl around lines 69–71, the early return
bypasses the shadow compositing and returns textureSample(...) * opacity
directly; change the return to the same shadow-composited color used on the
normal path so the cursor shadow remains when not blurring. Replace the direct
return with computing the sampled color = textureSample(t_cursor, s_cursor,
input.uv) * opacity (or whatever local name is used) and then return
mix(shadow_color, sampled_color, shadow_alpha) (or the existing shadow blend
expression) so the branch yields an identical shadow-blended result as the
non-early-exit path.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
crates/rendering/src/shaders/cursor.wgsl (1)

68-71: Avoid redundant texture sample in early exit.

Line 70 resamples the texture, but base_sample was already computed at line 59. Reusing it would save a texture fetch.

Apply this diff:

     let motion_len = length(motion_vec);
     if (motion_len < 1e-4 || blur_strength < 0.001) {
-        return textureSample(t_cursor, s_cursor, input.uv) * opacity;
+        return base_sample * opacity;
     }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cbc7937 and 5ccd196.

📒 Files selected for processing (4)
  • apps/desktop/src/routes/editor/context.ts (2 hunks)
  • crates/project/src/configuration.rs (7 hunks)
  • crates/rendering/src/shaders/composite-video-frame.wgsl (7 hunks)
  • crates/rendering/src/shaders/cursor.wgsl (2 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
crates/*/src/**/*

📄 CodeRabbit inference engine (AGENTS.md)

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

Files:

  • crates/rendering/src/shaders/cursor.wgsl
  • crates/project/src/configuration.rs
  • crates/rendering/src/shaders/composite-video-frame.wgsl
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

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

Files:

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

📄 CodeRabbit inference engine (AGENTS.md)

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

Files:

  • apps/desktop/src/routes/editor/context.ts
**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

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

Files:

  • crates/project/src/configuration.rs
🧬 Code graph analysis (2)
apps/desktop/src/routes/editor/context.ts (1)
apps/desktop/src/utils/tauri.ts (2)
  • ProjectConfiguration (447-447)
  • SerializedEditorInstance (470-470)
crates/project/src/configuration.rs (2)
apps/desktop/src/utils/tauri.ts (3)
  • CornerStyle (384-384)
  • CursorAnimationStyle (389-389)
  • CursorType (392-392)
crates/rendering/src/composite_frame.rs (1)
  • default (40-66)
🪛 GitHub Check: Typecheck
apps/desktop/src/routes/editor/context.ts

[failure] 111-111:
Object literal may only specify known properties, but 'rounding_type' does not exist in type 'BackgroundConfiguration'. Did you mean to write 'roundingType'?

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Clippy (aarch64-apple-darwin, macos-latest)
  • GitHub Check: Analyze (rust)
🔇 Additional comments (11)
crates/rendering/src/shaders/cursor.wgsl (5)

59-61: LGTM! Proper weighted accumulation initialization.

Starting with the base sample and weight 1.0 correctly includes the center tap in the weighted average.


64-66: LGTM! Clear uniform unpacking.

Extracting motion parameters from the packed vec4 is straightforward and well-named.


73-74: LGTM! Direction normalization is correct.

Dividing by motion_len is safe here since the early exit already ensures motion_len >= 1e-4.


76-89: LGTM! Weighted blur sampling is correctly implemented.

The loop properly starts at i=1 since the base sample is already included, and the weighted accumulation with smoothstep easing produces a smooth blur trail.


91-93: LGTM! Proper weighted normalization and opacity.

Dividing by weight_sum correctly normalizes the weighted accumulation, and applying opacity at the end is appropriate.

crates/rendering/src/shaders/composite-video-frame.wgsl (6)

48-61: LGTM! Superellipse math is correct.

The superellipse formula correctly computes (|x|^p + |y|^p)^(1/p) with power=4 for squircle rounding, and the conditional in rounded_corner_norm properly switches between circular and squircle styles.


63-68: LGTM! SDF correctly integrates custom rounding norm.

Replacing length(outside) with rounded_corner_norm(outside, rounding_type) properly extends the SDF to support both circular and squircle corner styles.


143-149: LGTM! Clean blur parameter extraction and early exit.

The early exit correctly preserves shadow blending when blur is disabled or negligible.


154-180: LGTM! Directional blur with jitter is well-implemented.

The mode correctly samples along the motion vector with smoothstep easing, adaptive sample counts, and jittering to reduce banding. The bounds checking prevents invalid samples.


181-207: LGTM! Radial zoom blur is correctly implemented.

The mode properly creates a zoom blur effect by sampling radially toward the zoom center with jittering, adaptive sample counts, and bounds checking.


272-280: LGTM! Rounded corners now respect rounding_type.

The corner clipping correctly uses rounded_corner_norm to support both circular and squircle corner styles.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
crates/rendering/src/layers/cursor.rs (2)

19-24: Consider adding doc comments to motion constants.

The new motion-related constants would benefit from brief doc comments explaining their purpose and units (e.g., CURSOR_VECTOR_CAP is in pixels, CURSOR_MIN_MOTION_NORMALIZED is a ratio). This improves maintainability for future developers tuning the motion system.


214-253: Motion computation logic is correct and well-guarded.

The motion computation properly handles edge cases:

  • FPS and screen diagonal guarded against division by zero
  • Cursor ID filtering prevents motion blending between different cursor types
  • Strength gating avoids unnecessary computation

Consider extracting this block into a helper method like compute_cursor_motion_vector() to improve modularity and testability, though the current inline approach is acceptable.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5ccd196 and b583b33.

📒 Files selected for processing (1)
  • crates/rendering/src/layers/cursor.rs (5 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

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

Files:

  • crates/rendering/src/layers/cursor.rs
crates/*/src/**/*

📄 CodeRabbit inference engine (AGENTS.md)

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

Files:

  • crates/rendering/src/layers/cursor.rs
🧬 Code graph analysis (1)
crates/rendering/src/layers/cursor.rs (1)
crates/rendering/src/composite_frame.rs (1)
  • new (93-105)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (rust)
🔇 Additional comments (2)
crates/rendering/src/layers/cursor.rs (2)

460-467: LGTM: Standard vector clamping with proper guards.

The implementation correctly clamps the motion vector magnitude to CURSOR_VECTOR_CAP while protecting against division by zero. The logic is sound.


413-418: Field rename accurately reflects the new motion system.

The rename from velocity_blur_opacity to motion_vector_strength with the new structure [x, y, strength, opacity] clearly communicates the motion vector (x, y), strength multiplier, and opacity. The uniform assignment (lines 413-418) correctly maps the computed motion values.

Also applies to: 475-475

Comment on lines +442 to +458
fn combine_cursor_motion(parent: XY<f32>, child: XY<f32>) -> XY<f32> {
fn combine_axis(parent: f32, child: f32) -> f32 {
if parent.abs() > CURSOR_MIN_MOTION_PX
&& child.abs() > CURSOR_MIN_MOTION_PX
&& parent.signum() != child.signum()
{
0.0
} else {
parent + child
}
}

XY::new(
combine_axis(parent.x, child.x),
combine_axis(parent.y, child.y),
)
}
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 | 🟠 Major

Document the directional conflict logic.

The function zeros out motion when parent and child movement oppose each other (line 448), which is clever behavior to avoid conflicting motion blur artifacts. However, this non-obvious logic deserves a comment explaining the rationale.

Apply this diff to add clarifying documentation:

+/// Combines parent (display) and child (cursor) motion vectors.
+/// Returns zero for an axis when parent and child move in opposite directions
+/// to avoid conflicting motion blur artifacts.
 fn combine_cursor_motion(parent: XY<f32>, child: XY<f32>) -> XY<f32> {
     fn combine_axis(parent: f32, child: f32) -> f32 {
+        // Zero out motion when movements conflict (opposite directions)
         if parent.abs() > CURSOR_MIN_MOTION_PX
             && child.abs() > CURSOR_MIN_MOTION_PX
             && parent.signum() != child.signum()
         {
             0.0
         } else {
             parent + child
         }
     }
📝 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
fn combine_cursor_motion(parent: XY<f32>, child: XY<f32>) -> XY<f32> {
fn combine_axis(parent: f32, child: f32) -> f32 {
if parent.abs() > CURSOR_MIN_MOTION_PX
&& child.abs() > CURSOR_MIN_MOTION_PX
&& parent.signum() != child.signum()
{
0.0
} else {
parent + child
}
}
XY::new(
combine_axis(parent.x, child.x),
combine_axis(parent.y, child.y),
)
}
/// Combines parent (display) and child (cursor) motion vectors.
/// Returns zero for an axis when parent and child move in opposite directions
/// to avoid conflicting motion blur artifacts.
fn combine_cursor_motion(parent: XY<f32>, child: XY<f32>) -> XY<f32> {
fn combine_axis(parent: f32, child: f32) -> f32 {
// Zero out motion when movements conflict (opposite directions)
if parent.abs() > CURSOR_MIN_MOTION_PX
&& child.abs() > CURSOR_MIN_MOTION_PX
&& parent.signum() != child.signum()
{
0.0
} else {
parent + child
}
}
XY::new(
combine_axis(parent.x, child.x),
combine_axis(parent.y, child.y),
)
}
🤖 Prompt for AI Agents
In crates/rendering/src/layers/cursor.rs around lines 442 to 458, the
combine_cursor_motion function contains non-obvious logic that zeros out axis
motion when parent and child movements exceed CURSOR_MIN_MOTION_PX and have
opposite signs; add a concise comment above the combine_axis function explaining
that this intentionally cancels opposing axis motion to avoid conflicting
motion-blur artifacts (i.e., when both inputs represent significant movement in
opposite directions), mention the CURSOR_MIN_MOTION_PX threshold and the sign
check, and note that otherwise motions are summed — place the comment directly
above combine_axis so future readers understand the rationale.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

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

⚠️ Outside diff range comments (1)
crates/project/src/configuration.rs (1)

718-724: Remove these unused constants.

These constants are not referenced anywhere in the codebase. They belonged to the old velocity-based cursor smoothing approach and should be removed now that the PR replaces it with spring-mass profiles.

Remove lines 718-724 from crates/project/src/configuration.rs:

  • SLOW_SMOOTHING_SAMPLES
  • REGULAR_SMOOTHING_SAMPLES
  • FAST_SMOOTHING_SAMPLES
  • SLOW_VELOCITY_THRESHOLD
  • REGULAR_VELOCITY_THRESHOLD
  • FAST_VELOCITY_THRESHOLD
🧹 Nitpick comments (1)
crates/project/src/configuration.rs (1)

463-488: Approve with optional refactor suggestion.

The implementation correctly applies presets to override default values. Note that the initial defaults (tension: 65.0, mass: 1.8, friction: 16.0) match the Slow preset exactly, making the override redundant for the default case. However, this pattern enhances maintainability and provides clear fallback values.

If you prefer a more concise approach, consider:

 impl Default for CursorConfiguration {
     fn default() -> Self {
-        let animation_style = CursorAnimationStyle::default();
-        let mut config = Self {
+        let animation_style = CursorAnimationStyle::default();
+        let preset = animation_style.preset().unwrap_or(CursorSmoothingPreset {
+            tension: 65.0,
+            mass: 1.8,
+            friction: 16.0,
+        });
+        
+        Self {
             hide: false,
             hide_when_idle: false,
             hide_when_idle_delay: Self::default_hide_when_idle_delay(),
             size: 100,
             r#type: CursorType::default(),
             animation_style,
-            tension: 65.0,
-            mass: 1.8,
-            friction: 16.0,
+            tension: preset.tension,
+            mass: preset.mass,
+            friction: preset.friction,
             raw: false,
             motion_blur: 0.5,
             use_svg: true,
-        };
-
-        if let Some(preset) = animation_style.preset() {
-            config.tension = preset.tension;
-            config.mass = preset.mass;
-            config.friction = preset.friction;
         }
-
-        config
     }
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b583b33 and 8276170.

📒 Files selected for processing (2)
  • crates/project/src/configuration.rs (8 hunks)
  • crates/rendering/src/shaders/composite-video-frame.wgsl (7 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • crates/rendering/src/shaders/composite-video-frame.wgsl
🧰 Additional context used
📓 Path-based instructions (2)
**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

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

Files:

  • crates/project/src/configuration.rs
crates/*/src/**/*

📄 CodeRabbit inference engine (AGENTS.md)

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

Files:

  • crates/project/src/configuration.rs
🧬 Code graph analysis (1)
crates/project/src/configuration.rs (2)
apps/desktop/src/utils/tauri.ts (3)
  • CornerStyle (384-384)
  • CursorAnimationStyle (389-389)
  • CursorType (392-392)
crates/rendering/src/composite_frame.rs (1)
  • default (40-66)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (rust)
🔇 Additional comments (7)
crates/project/src/configuration.rs (7)

183-189: LGTM! Clean enum definition.

The CornerStyle enum is well-defined with appropriate derives and matches the TypeScript interface. The serde aliases and default implementation are correct.


227-228: LGTM! Proper backward-compatible field addition.

The rounding_type field is correctly annotated with #[serde(default)] to ensure backward compatibility with existing serialized configurations.


306-307: LGTM! Proper backward-compatible field addition.

The rounding_type field is correctly added with #[serde(default)] for backward compatibility.

Also applies to: 344-344


401-409: LGTM! Excellent backward compatibility approach.

The serde aliases ensure that existing configurations using "regular", "quick", "rapid", or "fast" will seamlessly map to the new Mellow variant, preventing breaking changes.


411-416: LGTM! Clean preset structure.

The CursorSmoothingPreset struct is well-defined with appropriate derives for returning preset parameters.


418-434: LGTM! Clean preset mapping.

The preset() method provides sensible spring-mass-damper parameters for each animation style, with Custom correctly returning None to allow user-specified values.


324-324: Confirm whether the camera rounding increase from 30.0 to 100.0 was UX-tested and document the design decision.

The change is confirmed to be intentional (commit: 3d90fcc9 Increase default camera rounding value), and it does affect all new Camera instances via the serde default. However, the codebase contains no tests verifying this rounding behavior, no design documentation, and no comments explaining the rationale. Please confirm this change was UX-tested and update the code with a comment explaining why this 3.3x increase was necessary.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
apps/desktop/src/routes/editor/ConfigSidebar.tsx (1)

2243-2307: Consider making the label prop required.

The label prop is marked optional (label?: string), but it's always provided at both call sites (lines 1814, 2168). Making it required would better reflect actual usage and prevent accidentally omitting it.

Apply this diff if you'd like to make it required:

 function CornerStyleSelect(props: {
-  label?: string;
+  label: string;
   value: CornerRoundingType;
   onChange: (value: CornerRoundingType) => void;
 }) {
   return (
     <div class="flex flex-col gap-1.5">
-      <Show when={props.label}>
-        {(label) => (
-          <span class="text-[0.65rem] uppercase tracking-wide text-gray-11">
-            {label()}
-          </span>
-        )}
-      </Show>
+      <span class="text-[0.65rem] uppercase tracking-wide text-gray-11">
+        {props.label}
+      </span>
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8276170 and 70dbbd4.

📒 Files selected for processing (1)
  • apps/desktop/src/routes/editor/ConfigSidebar.tsx (12 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

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

Files:

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

📄 CodeRabbit inference engine (AGENTS.md)

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

Files:

  • apps/desktop/src/routes/editor/ConfigSidebar.tsx
🧬 Code graph analysis (1)
apps/desktop/src/routes/editor/ConfigSidebar.tsx (4)
apps/desktop/src/routes/editor/context.ts (1)
  • CornerRoundingType (64-64)
apps/desktop/src/utils/tauri.ts (1)
  • CursorAnimationStyle (389-389)
crates/project/src/configuration.rs (3)
  • preset (419-433)
  • div (139-144)
  • div (150-155)
apps/desktop/src/routes/editor/ui.tsx (5)
  • Field (25-47)
  • MenuItem (264-279)
  • PopperContent (287-293)
  • topSlideAnimateClasses (432-433)
  • MenuItemList (295-309)
⏰ 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). (3)
  • GitHub Check: Clippy (aarch64-apple-darwin, macos-latest)
  • GitHub Check: Analyze (rust)
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (1)
apps/desktop/src/routes/editor/ConfigSidebar.tsx (1)

605-605: Type assertions are unnecessary—cursor properties are fully defined in CursorConfiguration type.

The CursorConfiguration type (defined in apps/desktop/src/utils/tauri.ts:390) includes all three properties: hideWhenIdleDelay?: number, useSvg?: boolean, and motionBlur?: number. The type is complete. The as any assertions on string keys are a Solid.js createStore typing workaround, not evidence of missing type definitions. No fixes required.

Likely an incorrect or invalid review comment.

Comment on lines +1822 to 1831
<Field name="Motion Blur" icon={<IconLucideWind class="size-4" />}>
<Slider
value={[project.background.rounding]}
onChange={(v) => setProject("background", "rounding", v[0])}
value={[project.cursor.motionBlur ?? DEFAULT_CURSOR_MOTION_BLUR]}
onChange={(v) => setProject("cursor", "motionBlur" as any, v[0])}
minValue={0}
maxValue={100}
step={0.1}
formatTooltip="%"
maxValue={1}
step={0.01}
formatTooltip={(value) => `${Math.round(value * 100)}%`}
/>
</Field>
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

🧩 Analysis chain

Verify Motion Blur field placement and type definition.

Two concerns:

  1. Type safety: "motionBlur" as any on line 1825 suggests the property isn't properly typed in the cursor configuration. This bypasses type checking.

  2. UI placement: The Motion Blur control is in the Background tab (BackgroundConfig) but modifies project.cursor.motionBlur. Users looking for cursor-related settings would naturally check the Cursor tab, not Background. Consider moving this field to the Cursor tab (around line 708) or document why it belongs here.

Verify that the cursor type definitions include motionBlur to remove the type assertion:


🏁 Script executed:

#!/bin/bash
# Search for cursor type definitions and motionBlur property
rg -nP --type=ts 'interface.*Cursor|type.*Cursor.*=' -A 20 | rg -C 3 'motionBlur'

Length of output: 742



---

🏁 Script executed:

```shell
#!/bin/bash
# Search for other motionBlur references in ConfigSidebar.tsx
rg -n 'motionBlur' apps/desktop/src/routes/editor/ConfigSidebar.tsx

Length of output: 151


🏁 Script executed:

#!/bin/bash
# Check setProject function signature and usage context
rg -nP --type=ts '\bsetProject\s*\(' apps/desktop/src/routes/editor/ConfigSidebar.tsx -B 2 -A 2 | head -50

Length of output: 1587



---

🏁 Script executed:

```shell
#!/bin/bash
# Find setProject function definition and its type signature
rg -nP --type=ts 'const setProject|function setProject' apps/desktop/src/routes/editor/ConfigSidebar.tsx -A 5

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Alternative search for setProject in imports or context setup
rg -n 'setProject' apps/desktop/src/routes/editor/ConfigSidebar.tsx | head -5

Length of output: 235



---

🏁 Script executed:

```shell
#!/bin/bash
# Find setProject import
rg -n 'import.*setProject|setProject.*from' apps/desktop/src/routes/editor/ConfigSidebar.tsx -B 2 -A 2

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Check top 50 lines for imports
head -100 apps/desktop/src/routes/editor/ConfigSidebar.tsx | rg -n 'import|setProject'

Length of output: 1718



---

🏁 Script executed:

```shell
#!/bin/bash
# Find where setProject is destructured/defined
sed -n '280,310p' apps/desktop/src/routes/editor/ConfigSidebar.tsx

Length of output: 621


🏁 Script executed:

#!/bin/bash
# Check useEditorContext source to understand setProject
rg -n 'setProject' apps/desktop/src/routes/editor/context.ts -B 3 -A 1 | head -30

Length of output: 736



---

🏁 Script executed:

```shell
#!/bin/bash
# Find EditorProjectConfiguration type definition
rg -n 'type EditorProjectConfiguration|interface EditorProjectConfiguration' apps/desktop/src/routes/editor/context.ts -A 30

Length of output: 840


🏁 Script executed:

#!/bin/bash
# Alternative: search in all files for the type
rg -n 'type EditorProjectConfiguration|interface EditorProjectConfiguration' --type=ts -A 30 | head -80

Length of output: 2142



---

🏁 Script executed:

```shell
#!/bin/bash
# Find ProjectConfiguration type
rg -n 'type ProjectConfiguration|interface ProjectConfiguration' --type=ts -A 20 | head -80

Length of output: 4365


Remove unnecessary type assertion on motionBlur property.

Verification found that motionBlur is properly defined in CursorConfiguration as an optional number. The "as any" assertion on line 1825 is unnecessary and inconsistent—other cursor properties like tension, mass, and friction don't require it.

Changes needed:

  1. Remove as any from line 1825; change "motionBlur" as any to "motionBlur"
  2. Reconsider placement: Motion Blur controls project.cursor.motionBlur but lives in the Background tab. Users naturally look for cursor settings in the Cursor tab. Either move this field to the Cursor tab (around line 708) or document why it belongs in Background.
🤖 Prompt for AI Agents
In apps/desktop/src/routes/editor/ConfigSidebar.tsx around lines 1822 to 1831,
remove the unnecessary type assertion on the motionBlur property by changing the
setProject call to use "motionBlur" (no "as any"); then either move this Field
out of the Background tab and place it with the other cursor controls near the
Cursor tab (around line ~708) so users find cursor settings together, or leave
it in Background but add a brief comment/documentation in the file explaining
why motionBlur is scoped there instead of with the Cursor settings.

@richiemcilroy richiemcilroy merged commit 25f0bfb into main Nov 10, 2025
15 of 16 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants