Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
bcdbd05
feat(project): add smooth and fast cursor presets and retune motion d…
richiemcilroy Mar 27, 2026
488670b
perf(decoder): widen AVAssetReader frame cache fallback for parallel …
richiemcilroy Mar 27, 2026
69a608a
fix(rendering): preserve cursor trajectory and gate zoom on in-bounds UV
richiemcilroy Mar 27, 2026
00529b8
feat(rendering): simplify auto zoom framing and gentle cursor visibility
richiemcilroy Mar 27, 2026
fb9f50a
feat(rendering): follow raw cursor for zoom focus with zoom-aware lag…
richiemcilroy Mar 27, 2026
df25439
feat(editor): precompute cursor and zoom for playback and stabilize p…
richiemcilroy Mar 27, 2026
2e70210
feat(editor): add smooth and fast cursor options and lower motion blu…
richiemcilroy Mar 27, 2026
d54ac3a
fix(editor): improve hex color commit behavior on blur and Enter
richiemcilroy Mar 27, 2026
0a11a80
feat(editor): expose hasRecordedCursorData on transformed recording meta
richiemcilroy Mar 27, 2026
92d1905
feat(editor): add auto zoom generation prompt when zoom track is empty
richiemcilroy Mar 27, 2026
9b7e564
style(editor): refine timeline resize grip and loading skeleton
richiemcilroy Mar 27, 2026
4590cf8
fix(onboarding): scope permission flow to macOS and auto-advance else…
richiemcilroy Mar 27, 2026
f73e3f5
fix(main-window): show help entry point on all platforms
richiemcilroy Mar 27, 2026
9a310ba
fix(desktop): default auto zoom on clicks to off
richiemcilroy Mar 27, 2026
0dee9e8
chore(desktop): bump cap-desktop version to 0.4.81
richiemcilroy Mar 27, 2026
45a72d1
feat(desktop): enforce settings window minimum size and occlusion all…
richiemcilroy Mar 27, 2026
8cb17f4
locks etc
richiemcilroy Mar 27, 2026
b2f7d39
clippy
richiemcilroy Mar 27, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion apps/desktop/src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "cap-desktop"
version = "0.4.8"
version = "0.4.81"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Unusual version bump 0.4.80.4.81

Semver patch increments conventionally go 0.4.8 → 0.4.9. Jumping to 0.4.81 (skipping 72 patch levels) is valid semver but looks like either a typo (0.4.9 was intended) or a deliberate "sub-patch" scheme. If this is intentional (e.g., rapid hotfix numbering), a comment or convention note in the release process would prevent future confusion.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src-tauri/Cargo.toml
Line: 3

Comment:
**Unusual version bump `0.4.8``0.4.81`**

Semver patch increments conventionally go `0.4.8 → 0.4.9`. Jumping to `0.4.81` (skipping 72 patch levels) is valid semver but looks like either a typo (`0.4.9` was intended) or a deliberate "sub-patch" scheme. If this is intentional (e.g., rapid hotfix numbering), a comment or convention note in the release process would prevent future confusion.

How can I resolve this? If you propose a fix, please make it concise.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

description = "Beautiful screen recordings, owned by you."
authors = ["you"]
edition = "2024"
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/src-tauri/src/general_settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ impl Default for GeneralSettingsStore {
server_url: default_server_url(),
recording_countdown: Some(3),
enable_native_camera_preview: default_enable_native_camera_preview(),
auto_zoom_on_clicks: true,
auto_zoom_on_clicks: false,
capture_keyboard_events: true,
post_deletion_behaviour: PostDeletionBehaviour::DoNothing,
excluded_windows: default_excluded_windows(),
Expand Down
1 change: 1 addition & 0 deletions apps/desktop/src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3350,6 +3350,7 @@ pub async fn run(recording_logging_handle: LoggingHandle, logs_dir: PathBuf) {
.with_denylist(&[
CapWindowId::Onboarding.label().as_str(),
CapWindowId::Main.label().as_str(),
CapWindowId::Settings.label().as_str(),
"window-capture-occluder",
"target-select-overlay",
CapWindowId::CaptureArea.label().as_str(),
Expand Down
4 changes: 2 additions & 2 deletions apps/desktop/src-tauri/src/permissions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ pub enum OSPermission {

#[tauri::command(async)]
#[specta::specta]
pub fn open_permission_settings(app: tauri::AppHandle, _permission: OSPermission) {
pub fn open_permission_settings(_app: tauri::AppHandle, _permission: OSPermission) {
#[cfg(target_os = "macos")]
{
match _permission {
Expand Down Expand Up @@ -77,7 +77,7 @@ pub fn open_permission_settings(app: tauri::AppHandle, _permission: OSPermission

match process {
Ok(mut process) => {
let app = app.clone();
let app = _app.clone();
tokio::spawn(async move {
match tokio::task::spawn_blocking(move || process.wait()).await {
Ok(Err(err)) => {
Expand Down
23 changes: 20 additions & 3 deletions apps/desktop/src-tauri/src/windows.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ use std::{
time::Duration,
};
use tauri::{
AppHandle, LogicalPosition, Manager, Monitor, PhysicalPosition, PhysicalSize, WebviewUrl,
WebviewWindow, WebviewWindowBuilder, Wry,
AppHandle, LogicalPosition, LogicalSize, Manager, Monitor, PhysicalPosition, PhysicalSize,
WebviewUrl, WebviewWindow, WebviewWindowBuilder, Wry,
};
use tauri_specta::Event;
use tokio::sync::RwLock;
Expand Down Expand Up @@ -377,6 +377,19 @@ fn is_position_on_any_screen(pos_x: f64, pos_y: f64) -> bool {
false
}

fn ensure_settings_window_bounds(window: &WebviewWindow) {
const MIN_W: f64 = 800.0;
const MIN_H: f64 = 580.0;
let _ = window.set_min_size(Some(LogicalSize::new(MIN_W, MIN_H)));
if let (Ok(physical), Ok(scale)) = (window.inner_size(), window.scale_factor()) {
let width = physical.width as f64 / scale;
let height = physical.height as f64 / scale;
if width < MIN_W || height < MIN_H {
let _ = window.set_size(LogicalSize::new(width.max(MIN_W), height.max(MIN_H)));
}
}
}

#[derive(Clone, Deserialize, Type)]
pub enum CapWindowId {
Main,
Expand Down Expand Up @@ -955,6 +968,10 @@ impl ShowCapWindow {
window.unminimize().ok();
window.set_focus().ok();

if let Self::Settings { .. } = self {
ensure_settings_window_bounds(&window);
}

if let Self::Main { init_target_mode } = self {
let _ = RequestSetTargetMode {
target_mode: *init_target_mode,
Expand Down Expand Up @@ -1262,7 +1279,6 @@ impl ShowCapWindow {

#[cfg(windows)]
{
use tauri::LogicalSize;
if let Err(e) = window.set_size(LogicalSize::new(800.0, 580.0)) {
warn!("Failed to set Settings window size on Windows: {}", e);
}
Expand All @@ -1273,6 +1289,7 @@ impl ShowCapWindow {

window.show().ok();
window.set_focus().ok();
ensure_settings_window_bounds(&window);

window
}
Expand Down
8 changes: 1 addition & 7 deletions apps/desktop/src/routes/(window-chrome)/new-main/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
} from "@tauri-apps/api/webviewWindow";
import { getCurrentWindow, LogicalSize } from "@tauri-apps/api/window";
import * as dialog from "@tauri-apps/plugin-dialog";
import { type as ostype } from "@tauri-apps/plugin-os";
import * as shell from "@tauri-apps/plugin-shell";
import * as updater from "@tauri-apps/plugin-updater";
import { cx } from "cva";
Expand Down Expand Up @@ -1705,9 +1704,7 @@
class="flex flex-1 gap-1 items-center mx-2 min-w-0"
data-tauri-drag-region
>
<Show when={ostype() === "macos"}>
<MainWindowHelpButton />
</Show>
<MainWindowHelpButton />
<div class="flex-1 min-h-9 min-w-0" data-tauri-drag-region />
<div class="flex gap-1 items-center shrink-0" data-tauri-drag-region>
<Tooltip content={<span>Settings</span>}>
Expand Down Expand Up @@ -1773,9 +1770,6 @@
</button>
)}
</div>
<Show when={ostype() !== "macos"}>
<MainWindowHelpButton />
</Show>
</div>
</WindowChromeHeader>
<Show when={!activeMenu()}>
Expand All @@ -1783,7 +1777,7 @@
<div class="flex items-center space-x-1">
<a
class="*:w-[92px] *:h-auto text-[--text-primary]"
target="_blank"

Check failure on line 1780 in apps/desktop/src/routes/(window-chrome)/new-main/index.tsx

View workflow job for this annotation

GitHub Actions / Lint (Biome)

lint/security/noBlankTarget

Avoid using target="_blank" without rel="noopener" or rel="noreferrer".
href={
auth.data
? `${import.meta.env.VITE_SERVER_URL}/dashboard`
Expand Down
34 changes: 23 additions & 11 deletions apps/desktop/src/routes/(window-chrome)/onboarding.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,17 @@ export default function OnboardingPage() {
});

const isMacOS = createMemo(() => ostype() === "macos");
const permissionsOnly = createMemo(() => isRevisit() && permissionsNeeded());
const permissionsOnly = createMemo(
() => isMacOS() && isRevisit() && permissionsNeeded(),
);

createEffect(() => {
ready();
if (!isMacOS()) {
setPermsGranted(true);
setCorePermsGranted(true);
}
});

const totalSteps = createMemo(() => {
if (permissionsOnly()) return 1;
Expand Down Expand Up @@ -362,11 +372,11 @@ export default function OnboardingPage() {
setIsExiting(true);
await generalSettingsStore.set({ hasCompletedStartup: true });
setTimeout(() => {
setShowStartupOverlay(false);
setIsExiting(false);
if (!isMacOS()) {
goToStep(1);
}
setShowStartupOverlay(false);
setIsExiting(false);
}, 600);
};

Expand Down Expand Up @@ -403,7 +413,7 @@ export default function OnboardingPage() {
return "Continue";
};

const nextDisabled = () => step() === 0 && !permsGranted();
const nextDisabled = () => isMacOS() && step() === 0 && !permsGranted();

const handleSkipOnboarding = () => {
if (!corePermsGranted() || permissionsOnly()) return;
Expand Down Expand Up @@ -477,13 +487,15 @@ export default function OnboardingPage() {
<div class="flex flex-col flex-1 min-h-0 overflow-hidden relative">
<OnboardingAmbientBackdrop />
<div class="relative flex-1 min-h-0 z-10">
<StepPanel active={step() === 0} index={0} currentStep={step()}>
<PermissionsStep
active={step() === 0 && !showStartupOverlay()}
onPermissionsChanged={setPermsGranted}
onCorePermissionsChanged={setCorePermsGranted}
/>
</StepPanel>
<Show when={isMacOS()}>
<StepPanel active={step() === 0} index={0} currentStep={step()}>
<PermissionsStep
active={step() === 0 && !showStartupOverlay()}
onPermissionsChanged={setPermsGranted}
onCorePermissionsChanged={setCorePermsGranted}
/>
</StepPanel>
</Show>
<Show when={!permissionsOnly()}>
<StepPanel active={step() === 1} index={1} currentStep={step()}>
<ModesOverviewStep active={step() === 1} />
Expand Down
14 changes: 13 additions & 1 deletion apps/desktop/src/routes/editor/ConfigSidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@
friction: number;
};

const DEFAULT_CURSOR_MOTION_BLUR = 1.0;
const DEFAULT_CURSOR_MOTION_BLUR = 0.3;

const CURSOR_TYPE_OPTIONS = [
{
Expand All @@ -242,12 +242,24 @@
description: "Relaxed easing with a gentle follow and higher inertia.",
preset: { tension: 65, mass: 1.8, friction: 16 },
},
{
value: "smooth",
label: "Smooth",
description: "Ultra-smooth cinematic feel with high damping.",
preset: { tension: 80, mass: 2.5, friction: 28 },
},
{
value: "mellow",
label: "Mellow",
description: "Balanced smoothing for everyday tutorials and walkthroughs.",
preset: { tension: 120, mass: 1.1, friction: 18 },
},
{
value: "fast",
label: "Fast",
description: "Quick, responsive smoothing for fast-paced content.",
preset: { tension: 380, mass: 1.0, friction: 30 },
},
{
value: "custom",
label: "Custom",
Expand Down Expand Up @@ -748,9 +760,9 @@
icon={<IconLucideSparkles />}
value={
<Toggle
checked={(project.cursor as any).useSvg ?? true}

Check warning on line 763 in apps/desktop/src/routes/editor/ConfigSidebar.tsx

View workflow job for this annotation

GitHub Actions / Lint (Biome)

lint/suspicious/noExplicitAny

Unexpected any. Specify a different type.
onChange={(value) => {
setProject("cursor", "useSvg" as any, value);

Check warning on line 765 in apps/desktop/src/routes/editor/ConfigSidebar.tsx

View workflow job for this annotation

GitHub Actions / Lint (Biome)

lint/suspicious/noExplicitAny

Unexpected any. Specify a different type.
}}
/>
}
Expand Down Expand Up @@ -1224,7 +1236,7 @@
when={value().segments.length > 1}
fallback={
<SceneSegmentConfig
segment={value().segments[0].segment!}

Check warning on line 1239 in apps/desktop/src/routes/editor/ConfigSidebar.tsx

View workflow job for this annotation

GitHub Actions / Lint (Biome)

lint/style/noNonNullAssertion

Forbidden non-null assertion.
segmentIndex={value().segments[0].index}
/>
}
Expand Down Expand Up @@ -1290,7 +1302,7 @@
when={value().segments.length > 1}
fallback={
<ClipSegmentConfig
segment={value().segments[0].segment!}

Check warning on line 1305 in apps/desktop/src/routes/editor/ConfigSidebar.tsx

View workflow job for this annotation

GitHub Actions / Lint (Biome)

lint/style/noNonNullAssertion

Forbidden non-null assertion.
segmentIndex={value().segments[0].index}
/>
}
Expand Down Expand Up @@ -1368,8 +1380,8 @@
.filter((p) => p.path !== null)
.map(({ id, path }) => ({
id,
url: convertFileSrc(path!),

Check warning on line 1383 in apps/desktop/src/routes/editor/ConfigSidebar.tsx

View workflow job for this annotation

GitHub Actions / Lint (Biome)

lint/style/noNonNullAssertion

Forbidden non-null assertion.
rawPath: path!,

Check warning on line 1384 in apps/desktop/src/routes/editor/ConfigSidebar.tsx

View workflow job for this annotation

GitHub Actions / Lint (Biome)

lint/style/noNonNullAssertion

Forbidden non-null assertion.
}));
});

Expand Down Expand Up @@ -1768,13 +1780,13 @@
<For each={filteredWallpapers().slice(0, 21)}>
{(photo) => (
<KRadioGroup.Item
value={photo.url!}

Check warning on line 1783 in apps/desktop/src/routes/editor/ConfigSidebar.tsx

View workflow job for this annotation

GitHub Actions / Lint (Biome)

lint/style/noNonNullAssertion

Forbidden non-null assertion.
class="relative aspect-square group"
>
<KRadioGroup.ItemInput class="peer" />
<KRadioGroup.ItemControl class="overflow-hidden w-full h-full rounded-lg transition cursor-pointer ui-not-checked:ring-offset-1 ui-not-checked:ring-offset-gray-200 ui-not-checked:hover:ring-1 ui-not-checked:hover:ring-gray-400 ui-checked:ring-2 ui-checked:ring-gray-500 ui-checked:ring-offset-2 ui-checked:ring-offset-gray-200">
<img
src={photo.url!}

Check warning on line 1789 in apps/desktop/src/routes/editor/ConfigSidebar.tsx

View workflow job for this annotation

GitHub Actions / Lint (Biome)

lint/style/noNonNullAssertion

Forbidden non-null assertion.
loading="eager"
class="object-cover w-full h-full"
alt="Wallpaper option"
Expand All @@ -1789,13 +1801,13 @@
<For each={filteredWallpapers()}>
{(photo) => (
<KRadioGroup.Item
value={photo.url!}

Check warning on line 1804 in apps/desktop/src/routes/editor/ConfigSidebar.tsx

View workflow job for this annotation

GitHub Actions / Lint (Biome)

lint/style/noNonNullAssertion

Forbidden non-null assertion.
class="relative aspect-square group"
>
<KRadioGroup.ItemInput class="peer" />
<KRadioGroup.ItemControl class="overflow-hidden w-full h-full rounded-lg border cursor-pointer border-gray-5 ui-checked:border-blue-9 ui-checked:ring-2 ui-checked:ring-blue-9 peer-focus-visible:border-2 peer-focus-visible:border-blue-9">
<img
src={photo.url!}

Check warning on line 1810 in apps/desktop/src/routes/editor/ConfigSidebar.tsx

View workflow job for this annotation

GitHub Actions / Lint (Biome)

lint/style/noNonNullAssertion

Forbidden non-null assertion.
alt="Wallpaper option"
class="object-cover w-full h-full"
loading="lazy"
Expand Down Expand Up @@ -3588,7 +3600,7 @@

setProject(
produce((proj) => {
const clips = (proj.clips ??= []);

Check failure on line 3603 in apps/desktop/src/routes/editor/ConfigSidebar.tsx

View workflow job for this annotation

GitHub Actions / Lint (Biome)

lint/suspicious/noAssignInExpressions

The assignment should not be in an expression.
let clip = clips.find(
(clip) => clip.index === (props.segment.recordingSegment ?? 0),
);
Expand Down
28 changes: 20 additions & 8 deletions apps/desktop/src/routes/editor/Editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
createResource,
createSignal,
ErrorBoundary,
For,
Match,
on,
onCleanup,
Expand Down Expand Up @@ -54,9 +55,11 @@ import { Dialog, DialogContent, EditorButton, Input, Subfield } from "./ui";
const DEFAULT_TIMELINE_HEIGHT = 260;
const MIN_PLAYER_CONTENT_HEIGHT = 320;
const MIN_TIMELINE_HEIGHT = 240;
const RESIZE_HANDLE_HEIGHT = 8;
const RESIZE_HANDLE_HEIGHT = 16;
const MIN_PLAYER_HEIGHT = MIN_PLAYER_CONTENT_HEIGHT + RESIZE_HANDLE_HEIGHT;

const TIMELINE_RESIZE_GRIP_MARKS = [0, 1, 2] as const;

function getEditorErrorMessage(error: unknown) {
return error instanceof Error ? error.message : String(error);
}
Expand Down Expand Up @@ -462,18 +465,27 @@ function Inner() {
<div
role="separator"
aria-orientation="horizontal"
class="flex-none transition-colors hover:bg-gray-3/30"
class="flex-none shrink-0 border-t border-gray-4 dark:border-gray-5 bg-gray-2/95 dark:bg-gray-3/55 transition-colors hover:bg-gray-3/70 dark:hover:bg-gray-4/55"
style={{ height: `${RESIZE_HANDLE_HEIGHT}px` }}
>
<div
class="flex justify-center items-center h-full cursor-row-resize select-none group"
classList={{ "bg-gray-3/50": isResizingTimeline() }}
class="flex flex-col gap-0.5 justify-center items-center h-full w-full cursor-row-resize select-none group"
classList={{
"bg-gray-3/55 dark:bg-gray-4/50": isResizingTimeline(),
}}
onMouseDown={handleTimelineResizeStart}
aria-label="Resize timeline height"
>
<div
class="h-1 w-12 rounded-full bg-gray-4 transition-colors group-hover:bg-gray-6"
classList={{ "bg-gray-7": isResizingTimeline() }}
/>
<For each={TIMELINE_RESIZE_GRIP_MARKS}>
{() => (
<div
class="h-0.5 w-20 max-w-[85%] rounded-full bg-gray-6 dark:bg-gray-7 shadow-[0_1px_0_rgb(0_0_0_/0.06)] transition-colors group-hover:bg-gray-9 dark:group-hover:bg-gray-11"
classList={{
"bg-gray-9 dark:bg-gray-11": isResizingTimeline(),
}}
/>
)}
</For>
</div>
</div>
</div>
Expand Down
Loading
Loading