From d77facd09c0719ab2cacd5d0e923f41d083f16a7 Mon Sep 17 00:00:00 2001 From: Minit Date: Fri, 10 Apr 2026 13:12:55 +0530 Subject: [PATCH 1/7] fix(editor): clamp captions and hide keyboard segments past trim boundary Closes #1725 Related to #1689 Co-Authored-By: Claude Sonnet 4.6 --- apps/desktop/src/routes/editor/Timeline/CaptionsTrack.tsx | 8 ++++++-- apps/desktop/src/routes/editor/Timeline/KeyboardTrack.tsx | 6 +++++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/apps/desktop/src/routes/editor/Timeline/CaptionsTrack.tsx b/apps/desktop/src/routes/editor/Timeline/CaptionsTrack.tsx index c7f67ca76a..9743f62175 100644 --- a/apps/desktop/src/routes/editor/Timeline/CaptionsTrack.tsx +++ b/apps/desktop/src/routes/editor/Timeline/CaptionsTrack.tsx @@ -34,7 +34,11 @@ export function CaptionsTrack(props: { const minDuration = () => Math.max(MIN_SEGMENT_SECS, secsPerPixel() * MIN_SEGMENT_PIXELS); - const captionSegments = () => project.timeline?.captionSegments ?? []; + const captionSegments = createMemo(() => + (project.timeline?.captionSegments ?? []).filter( + (s) => s.start < totalDuration(), + ), + ); const selectedCaptionIndices = createMemo(() => { const selection = editorState.timeline.selection; if (!selection || selection.type !== "caption") return null; @@ -169,7 +173,7 @@ export function CaptionsTrack(props: { isSelected() ? "border-green-7" : "border-transparent", )} innerClass="ring-green-6" - segment={segment} + segment={{ start: segment.start, end: Math.min(segment.end, totalDuration()) }} onMouseDown={(e) => { e.stopPropagation(); if (editorState.timeline.interactMode === "split") { diff --git a/apps/desktop/src/routes/editor/Timeline/KeyboardTrack.tsx b/apps/desktop/src/routes/editor/Timeline/KeyboardTrack.tsx index 52c3d6feb2..b61b02ecc1 100644 --- a/apps/desktop/src/routes/editor/Timeline/KeyboardTrack.tsx +++ b/apps/desktop/src/routes/editor/Timeline/KeyboardTrack.tsx @@ -32,7 +32,11 @@ export function KeyboardTrack(props: { const minDuration = () => Math.max(MIN_SEGMENT_SECS, secsPerPixel() * MIN_SEGMENT_PIXELS); - const keyboardSegments = () => project.timeline?.keyboardSegments ?? []; + const keyboardSegments = createMemo(() => + (project.timeline?.keyboardSegments ?? []).filter( + (s) => s.start < totalDuration(), + ), + ); const selectedKeyboardIndices = createMemo(() => { const selection = editorState.timeline.selection; if (!selection || selection.type !== "keyboard") return null; From cd04decae42e86529e09b081679a4861f7a1e5d4 Mon Sep 17 00:00:00 2001 From: Minit Date: Fri, 10 Apr 2026 13:25:59 +0530 Subject: [PATCH 2/7] fix(editor): clamp keyboard segments at trim boundary Co-Authored-By: Claude Sonnet 4.6 --- apps/desktop/src/routes/editor/Timeline/KeyboardTrack.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/desktop/src/routes/editor/Timeline/KeyboardTrack.tsx b/apps/desktop/src/routes/editor/Timeline/KeyboardTrack.tsx index b61b02ecc1..3b1d44c5f9 100644 --- a/apps/desktop/src/routes/editor/Timeline/KeyboardTrack.tsx +++ b/apps/desktop/src/routes/editor/Timeline/KeyboardTrack.tsx @@ -165,7 +165,7 @@ export function KeyboardTrack(props: { isSelected() ? "border-sky-7" : "border-transparent", )} innerClass="ring-sky-6" - segment={segment} + segment={{ start: segment.start, end: Math.min(segment.end, totalDuration()) }} onMouseDown={(e) => { e.stopPropagation(); if (editorState.timeline.interactMode === "split") { From 2320479bb07049446b8ccdc98053ba3859c7e462 Mon Sep 17 00:00:00 2001 From: Minit Date: Sat, 11 Apr 2026 00:07:57 +0530 Subject: [PATCH 3/7] fix(editor): fix Biome formatting for segment prop objects Co-Authored-By: Claude Sonnet 4.6 --- apps/desktop/src/routes/editor/Timeline/CaptionsTrack.tsx | 5 ++++- apps/desktop/src/routes/editor/Timeline/KeyboardTrack.tsx | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/apps/desktop/src/routes/editor/Timeline/CaptionsTrack.tsx b/apps/desktop/src/routes/editor/Timeline/CaptionsTrack.tsx index 9743f62175..33a746f431 100644 --- a/apps/desktop/src/routes/editor/Timeline/CaptionsTrack.tsx +++ b/apps/desktop/src/routes/editor/Timeline/CaptionsTrack.tsx @@ -173,7 +173,10 @@ export function CaptionsTrack(props: { isSelected() ? "border-green-7" : "border-transparent", )} innerClass="ring-green-6" - segment={{ start: segment.start, end: Math.min(segment.end, totalDuration()) }} + segment={{ + start: segment.start, + end: Math.min(segment.end, totalDuration()), + }} onMouseDown={(e) => { e.stopPropagation(); if (editorState.timeline.interactMode === "split") { diff --git a/apps/desktop/src/routes/editor/Timeline/KeyboardTrack.tsx b/apps/desktop/src/routes/editor/Timeline/KeyboardTrack.tsx index 3b1d44c5f9..07a0a57d63 100644 --- a/apps/desktop/src/routes/editor/Timeline/KeyboardTrack.tsx +++ b/apps/desktop/src/routes/editor/Timeline/KeyboardTrack.tsx @@ -165,7 +165,10 @@ export function KeyboardTrack(props: { isSelected() ? "border-sky-7" : "border-transparent", )} innerClass="ring-sky-6" - segment={{ start: segment.start, end: Math.min(segment.end, totalDuration()) }} + segment={{ + start: segment.start, + end: Math.min(segment.end, totalDuration()), + }} onMouseDown={(e) => { e.stopPropagation(); if (editorState.timeline.interactMode === "split") { From a07fa128c12cab91d50d93de5a05fa54b2c2f340 Mon Sep 17 00:00:00 2001 From: Minit Date: Fri, 10 Apr 2026 01:08:39 +0530 Subject: [PATCH 4/7] fix(desktop): bypass TCC permission check for screen recording and accessibility in debug builds On macOS Sequoia, CGPreflightScreenCaptureAccess() always returns false for ad-hoc signed or unsigned binaries regardless of System Settings, making it impossible to test locally without a Developer ID certificate. Adding a debug_assertions early return for ScreenRecording and Accessibility allows contributors to run and test the app locally without being blocked by the permissions screen. Camera and microphone are unaffected as they prompt correctly without a signed binary. Fixes #1722 Co-Authored-By: Claude Sonnet 4.6 --- apps/desktop/src-tauri/src/permissions.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/apps/desktop/src-tauri/src/permissions.rs b/apps/desktop/src-tauri/src/permissions.rs index 17b9e7153e..345946d40c 100644 --- a/apps/desktop/src-tauri/src/permissions.rs +++ b/apps/desktop/src-tauri/src/permissions.rs @@ -156,6 +156,14 @@ pub(crate) fn sync_macos_dock_visibility(app: &tauri::AppHandle) { #[cfg(target_os = "macos")] fn macos_permission_status(permission: &OSPermission, initial_check: bool) -> OSPermissionStatus { + #[cfg(debug_assertions)] + if matches!( + permission, + OSPermission::ScreenRecording | OSPermission::Accessibility + ) { + return OSPermissionStatus::Granted; + } + match permission { OSPermission::ScreenRecording => { let granted = scap_screencapturekit::has_permission(); From f4bbe742ab13b6a7e81a67afa9db60cbbb762ad6 Mon Sep 17 00:00:00 2001 From: Minit Date: Sun, 12 Apr 2026 01:36:26 +0530 Subject: [PATCH 5/7] fix(editor): fix split-mode click position on clamped caption and keyboard segments segmentWidth used the original unclamped segment.end while SegmentRoot rendered the clamped visual width, causing split clicks to land at wrong positions for segments crossing the trim boundary. Co-Authored-By: Claude Sonnet 4.6 --- apps/desktop/src/routes/editor/Timeline/CaptionsTrack.tsx | 3 ++- apps/desktop/src/routes/editor/Timeline/KeyboardTrack.tsx | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/desktop/src/routes/editor/Timeline/CaptionsTrack.tsx b/apps/desktop/src/routes/editor/Timeline/CaptionsTrack.tsx index 33a746f431..9093e24e4f 100644 --- a/apps/desktop/src/routes/editor/Timeline/CaptionsTrack.tsx +++ b/apps/desktop/src/routes/editor/Timeline/CaptionsTrack.tsx @@ -161,7 +161,8 @@ export function CaptionsTrack(props: { return indices.has(i()); }); - const segmentWidth = () => segment.end - segment.start; + const segmentWidth = () => + Math.min(segment.end, totalDuration()) - segment.start; return ( segment.end - segment.start; + const segmentWidth = () => + Math.min(segment.end, totalDuration()) - segment.start; return ( Date: Fri, 17 Apr 2026 20:56:09 +0530 Subject: [PATCH 6/7] fix(clippy): collapse nested if into match guards for collapsible_match lint --- crates/audio/src/latency.rs | 14 +++---- .../rendering-skia/src/layers/background.rs | 40 +++++++++---------- 2 files changed, 25 insertions(+), 29 deletions(-) diff --git a/crates/audio/src/latency.rs b/crates/audio/src/latency.rs index 364c5d3faf..2a7804566e 100644 --- a/crates/audio/src/latency.rs +++ b/crates/audio/src/latency.rs @@ -685,15 +685,13 @@ mod macos { let mut latency_secs = total_frames as f64 / effective_rate; match transport_kind { - OutputTransportKind::Airplay => { - if latency_secs < AIRPLAY_MIN_LATENCY_SECS { - latency_secs = AIRPLAY_MIN_LATENCY_SECS; - } + OutputTransportKind::Airplay if latency_secs < AIRPLAY_MIN_LATENCY_SECS => { + latency_secs = AIRPLAY_MIN_LATENCY_SECS; } - OutputTransportKind::Wireless | OutputTransportKind::ContinuityWireless => { - if latency_secs < WIRELESS_MIN_LATENCY_SECS { - latency_secs = WIRELESS_MIN_LATENCY_SECS; - } + OutputTransportKind::Wireless | OutputTransportKind::ContinuityWireless + if latency_secs < WIRELESS_MIN_LATENCY_SECS => + { + latency_secs = WIRELESS_MIN_LATENCY_SECS; } _ => {} } diff --git a/crates/rendering-skia/src/layers/background.rs b/crates/rendering-skia/src/layers/background.rs index 90fd81b09b..71e187695a 100644 --- a/crates/rendering-skia/src/layers/background.rs +++ b/crates/rendering-skia/src/layers/background.rs @@ -277,31 +277,29 @@ impl RecordableLayer for BackgroundLayer { // Handle image loading if needed match &new_background { - Background::Image { path } | Background::Wallpaper { path } => { - if self.image_path.as_ref() != Some(path) || self.loaded_image.is_none() { - // For now, we'll do synchronous loading. In a real implementation, - // this should be async or cached at a higher level - match std::fs::read(path) { - Ok(image_data) => { - let data = skia_safe::Data::new_copy(&image_data); - if let Some(image) = Image::from_encoded(&data) { - self.loaded_image = Some(image); - self.image_path = Some(path.clone()); - } else { - tracing::error!("Failed to decode image: {:?}", path); - return Err(SkiaRenderingError::Other(anyhow::anyhow!( - "Failed to decode image" - ))); - } - } - Err(e) => { - tracing::error!("Failed to load image: {:?}, error: {}", path, e); + Background::Image { path } | Background::Wallpaper { path } + if self.image_path.as_ref() != Some(path) || self.loaded_image.is_none() => + { + match std::fs::read(path) { + Ok(image_data) => { + let data = skia_safe::Data::new_copy(&image_data); + if let Some(image) = Image::from_encoded(&data) { + self.loaded_image = Some(image); + self.image_path = Some(path.clone()); + } else { + tracing::error!("Failed to decode image: {:?}", path); return Err(SkiaRenderingError::Other(anyhow::anyhow!( - "Failed to load image: {}", - e + "Failed to decode image" ))); } } + Err(e) => { + tracing::error!("Failed to load image: {:?}, error: {}", path, e); + return Err(SkiaRenderingError::Other(anyhow::anyhow!( + "Failed to load image: {}", + e + ))); + } } } _ => {} From 9ed3e034a9b9c9dc38e2ff2fc85c0c03bcb05674 Mon Sep 17 00:00:00 2001 From: Minit Date: Fri, 17 Apr 2026 21:15:29 +0530 Subject: [PATCH 7/7] fix(clippy): replace sort_by with sort_by_key for unnecessary_sort_by lint --- crates/rendering/src/decoder/multi_position.rs | 2 +- crates/scap-targets/src/main.rs | 2 +- crates/scap-targets/src/platform/macos.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/rendering/src/decoder/multi_position.rs b/crates/rendering/src/decoder/multi_position.rs index 318c0d4d8f..98e7cd97eb 100644 --- a/crates/rendering/src/decoder/multi_position.rs +++ b/crates/rendering/src/decoder/multi_position.rs @@ -204,7 +204,7 @@ impl DecoderPoolManager { .iter() .map(|(&frame, &count)| (frame, count)) .collect(); - hotspots.sort_by(|a, b| b.1.cmp(&a.1)); + hotspots.sort_by_key(|b| std::cmp::Reverse(b.1)); let top_hotspots: Vec = hotspots .into_iter() diff --git a/crates/scap-targets/src/main.rs b/crates/scap-targets/src/main.rs index 8a836ddb53..d24f8f022d 100644 --- a/crates/scap-targets/src/main.rs +++ b/crates/scap-targets/src/main.rs @@ -128,7 +128,7 @@ fn main() { }) .collect::>(); - relevant_windows.sort_by(|a, b| b.1.cmp(&a.1)); + relevant_windows.sort_by_key(|b| std::cmp::Reverse(b.1)); // Print current topmost window info if let Some((topmost_window, level)) = relevant_windows.first() diff --git a/crates/scap-targets/src/platform/macos.rs b/crates/scap-targets/src/platform/macos.rs index 7436b01e59..3b7b139632 100644 --- a/crates/scap-targets/src/platform/macos.rs +++ b/crates/scap-targets/src/platform/macos.rs @@ -290,7 +290,7 @@ impl WindowImpl { }) .collect::>(); - windows_with_level.sort_by(|a, b| b.1.cmp(&a.1)); + windows_with_level.sort_by_key(|b| std::cmp::Reverse(b.1)); windows_with_level.first().map(|(window, _)| *window) }