WARNING: THIS SITE IS A MIRROR OF GITHUB.COM / IT CANNOT LOGIN OR REGISTER ACCOUNTS / THE CONTENTS ARE PROVIDED AS-IS / THIS SITE ASSUMES NO RESPONSIBILITY FOR ANY DISPLAYED CONTENT OR LINKS / IF YOU FOUND SOMETHING MAY NOT GOOD FOR EVERYONE, CONTACT ADMIN AT ilovescratch@foxmail.com
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
0a5fea6
Refactor project config save with custom debounce
richiemcilroy Nov 17, 2025
bd1fc73
fmt
richiemcilroy Nov 17, 2025
b61958c
Merge branch 'main' into editor-perf
richiemcilroy Nov 17, 2025
5d95f24
Handle excluded windows in macOS screen capture
richiemcilroy Nov 17, 2025
dbe8ae1
Refactor waveform rendering in ClipTrack
richiemcilroy Nov 17, 2025
cced653
Improve camera frame forwarding and logging
richiemcilroy Nov 17, 2025
37eb7c8
Update settings.local.json
richiemcilroy Nov 17, 2025
8b403a8
Update recording UI container styles
richiemcilroy Nov 18, 2025
184ad59
lost a days work (I F'D UP)
richiemcilroy Nov 18, 2025
fee3391
Reset camera and mic state on window close and recording end
richiemcilroy Nov 18, 2025
d2b8fd4
Update close button to use window close action
richiemcilroy Nov 18, 2025
6aca28e
Add initializing state to recording flow
richiemcilroy Nov 18, 2025
41fbc5c
Improve camera initialization and recording state handling
richiemcilroy Nov 18, 2025
44a5348
Refactor type casting for currentRecording data
richiemcilroy Nov 18, 2025
6dd3ff1
Hide native camera preview toggle on Windows
richiemcilroy Nov 18, 2025
ce63e02
Add resizing to camera window
richiemcilroy Nov 18, 2025
dd39425
Set Mellow as default for CursorAnimationStyle
richiemcilroy Nov 18, 2025
831fb76
Fix import order in experimental settings route
richiemcilroy Nov 18, 2025
891b122
Improve camera feed sender handling and logging
richiemcilroy Nov 18, 2025
2b504dd
Update camera.tsx
richiemcilroy Nov 18, 2025
d24e92c
Add code formatting guidelines to documentation
richiemcilroy Nov 18, 2025
e6e9320
Improve logging and error context in recording pipeline
richiemcilroy Nov 18, 2025
5de394f
Update apps/desktop/src-tauri/src/camera.rs
richiemcilroy Nov 18, 2025
0066eb1
Add camera overlay bounds update and revert logic
richiemcilroy Nov 18, 2025
dec0d8e
Merge branch 'editor-perf' of https://github.com/CapSoftware/Cap into…
richiemcilroy Nov 18, 2025
05f39bb
Improve countdown animation and fix recording logic
richiemcilroy Nov 18, 2025
fd30b14
Add checked_duration_since for timestamp types
richiemcilroy Nov 18, 2025
a0aeffa
Refactor duration calculation for readability
richiemcilroy Nov 18, 2025
51e2f6e
misc packages
richiemcilroy Nov 18, 2025
6db1d2d
Remove unused setCamera mutation in camera page
richiemcilroy Nov 18, 2025
ff78eba
Clarify and emphasize no code comments policy
richiemcilroy Nov 19, 2025
756d296
Improve camera window resizing and positioning logic
richiemcilroy Nov 19, 2025
0510be8
Add __CAP__ property to Window interface
richiemcilroy Nov 19, 2025
695bb66
Fix requestAnimationFrame cleanup in overlay
richiemcilroy Nov 19, 2025
ef920b2
gen'd files
richiemcilroy Nov 19, 2025
2f75043
clippy bits
richiemcilroy Nov 19, 2025
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
6 changes: 6 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
- Rust: `rustfmt` + workspace clippy lints.
- Naming: files kebab‑case (`user-menu.tsx`); components PascalCase; Rust modules snake_case, crates kebab‑case.
- Runtime: Node 20, pnpm 10.x, Rust 1.88+, Docker for MySQL/MinIO.
- **NO COMMENTS**: Never add comments to code (`//`, `/* */`, `///`, `//!`, `#`, etc.). Code must be self-explanatory through naming, types, and structure. This applies to all languages (TypeScript, Rust, JavaScript, etc.).

## Testing
- TS/JS: Vitest where present (e.g., desktop). Name tests `*.test.ts(x)` near sources.
Expand All @@ -37,10 +38,15 @@
- Database flow: always `db:generate` → `db:push` before relying on new schema.
- Keep secrets out of VCS; configure via `.env` from `pnpm env-setup`.
- macOS note: desktop permissions (screen/mic) apply to the terminal running `pnpm dev:desktop`.
- **CRITICAL: NO CODE COMMENTS**: Never add any form of comments (`//`, `/* */`, `///`, `//!`, `#`, etc.) to generated or edited code. Code must be self-explanatory.

## Effect Usage
- Next.js API routes in `apps/web/app/api/*` are built with `@effect/platform`'s `HttpApi` builder; copy the existing class/group/endpoint pattern instead of ad-hoc handlers.
- Acquire backend services (e.g., `Videos`, `S3Buckets`) inside `Effect.gen` blocks and wire them through `Layer.provide`/`HttpApiBuilder.group`, translating domain errors to `HttpApiError` variants.
- Convert the effectful API to a Next.js handler with `apiToHandler(ApiLive)` from `@/lib/server` and export the returned `handler`—avoid calling `runPromise` inside route files.
- On the server, run effects through `EffectRuntime.runPromise` from `@/lib/server`, typically after `provideOptionalAuth`, so cookies and per-request context are attached automatically.
- On the client, use `useEffectQuery`/`useEffectMutation` from `@/lib/EffectRuntime`; they already bind the managed runtime and tracing so you shouldn't call `EffectRuntime.run*` directly in components.

## Code Formatting
- Always format code before completing work: run `pnpm format` for TypeScript/JavaScript and `cargo fmt` for Rust.
- Run these commands regularly during development and always at the end of a coding session to ensure consistent formatting.
15 changes: 14 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -360,7 +360,12 @@ Minimize `useEffect` usage: compute during render, handle logic in event handler
- Windowing/permissions are handled in Rust; keep UI logic in Solid and avoid mixing IPC with rendering logic.

## Conventions
- No code comments: Never add inline, block, or docstring comments in any language. Code must be self-explanatory through naming, types, and structure. Use docs/READMEs for explanations when necessary.
- **CRITICAL: NO CODE COMMENTS**: Never add any form of comments to code. This includes:
- Single-line comments: `//` (JavaScript/TypeScript/Rust), `#` (Python/Shell)
- Multi-line comments: `/* */` (JavaScript/TypeScript), `/* */` (Rust)
- Documentation comments: `///`, `//!` (Rust), `/** */` (JSDoc)
- Any other comment syntax in any language
- Code must be self-explanatory through naming, types, and structure. Use docs/READMEs for explanations when necessary.
- Directory naming: lower-case-dashed
- Components: PascalCase; hooks: camelCase starting with `use`
- Strict TypeScript; avoid `any`; leverage shared types
Expand Down Expand Up @@ -414,3 +419,11 @@ Transcription/AI Enhancement → Database Storage
- **Monorepo Guide**: Turborepo documentation
- **Effect System**: Used in web-backend packages
- **Media Processing**: FFmpeg documentation for Rust bindings

## Code Formatting

Always format code before completing work:
- **TypeScript/JavaScript**: Run `pnpm format` to format all code with Biome
- **Rust**: Run `cargo fmt` to format all Rust code with rustfmt

These commands should be run regularly during development and always at the end of a coding session to ensure consistent formatting across the codebase.
10 changes: 5 additions & 5 deletions Cargo.lock

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

140 changes: 98 additions & 42 deletions apps/desktop/src-tauri/src/camera.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,9 @@ static TOOLBAR_HEIGHT: f32 = 56.0; // also defined in Typescript
// Basically poor man's MSAA
static GPU_SURFACE_SCALE: u32 = 4;

#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize, Type)]
#[serde(rename_all = "lowercase")]
pub enum CameraPreviewSize {
#[default]
Sm,
Lg,
}
pub const MIN_CAMERA_SIZE: f32 = 150.0;
pub const MAX_CAMERA_SIZE: f32 = 600.0;
pub const DEFAULT_CAMERA_SIZE: f32 = 230.0;

#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize, Type)]
#[serde(rename_all = "lowercase")]
Expand All @@ -45,13 +41,27 @@ pub enum CameraPreviewShape {
Full,
}

#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize, Type)]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Type)]
pub struct CameraPreviewState {
size: CameraPreviewSize,
size: f32,
shape: CameraPreviewShape,
mirrored: bool,
}

impl Default for CameraPreviewState {
fn default() -> Self {
Self {
size: DEFAULT_CAMERA_SIZE,
shape: CameraPreviewShape::default(),
mirrored: false,
}
}
}

fn clamp_size(size: f32) -> f32 {
size.clamp(MIN_CAMERA_SIZE, MAX_CAMERA_SIZE)
}

pub struct CameraPreviewManager {
store: Result<Arc<tauri_plugin_store::Store<tauri::Wry>>, String>,
preview: Option<InitializedCameraPreview>,
Expand All @@ -70,17 +80,22 @@ impl CameraPreviewManager {

/// Get the current state of the camera window.
pub fn get_state(&self) -> anyhow::Result<CameraPreviewState> {
Ok(self
let mut state: CameraPreviewState = self
.store
.as_ref()
.map_err(|err| anyhow!("{err}"))?
.get("state")
.and_then(|v| serde_json::from_value(v).ok().unwrap_or_default())
.unwrap_or_default())
.and_then(|v| serde_json::from_value(v).ok())
.unwrap_or_default();

state.size = clamp_size(state.size);
Ok(state)
}

/// Save the current state of the camera window.
pub fn set_state(&self, state: CameraPreviewState) -> anyhow::Result<()> {
pub fn set_state(&self, mut state: CameraPreviewState) -> anyhow::Result<()> {
state.size = clamp_size(state.size);

let store = self.store.as_ref().map_err(|err| anyhow!("{err}"))?;
store.set("state", serde_json::to_value(&state)?);
store.save()?;
Expand Down Expand Up @@ -171,8 +186,8 @@ impl InitializedCameraPreview {
1.0
};

let size =
resize_window(&window, default_state, aspect).context("Error resizing Tauri window")?;
let size = resize_window(&window, default_state, aspect, true)
.context("Error resizing Tauri window")?;

let (tx, rx) = oneshot::channel();
window
Expand Down Expand Up @@ -562,7 +577,7 @@ impl Renderer {

self.sync_ratio_uniform_and_resize_window_to_it(&window, &state, aspect_ratio);
self.update_state_uniforms(&state);
if let Ok((width, height)) = resize_window(&window, &state, aspect_ratio)
if let Ok((width, height)) = resize_window(&window, &state, aspect_ratio, false)
.map_err(|err| error!("Error resizing camera preview window: {err}"))
{
self.reconfigure_gpu_surface(width, height);
Expand Down Expand Up @@ -607,16 +622,17 @@ impl Renderer {

/// Update the uniforms which hold the camera preview state
fn update_state_uniforms(&self, state: &CameraPreviewState) {
let clamped_size = clamp_size(state.size);
let normalized_size =
(clamped_size - MIN_CAMERA_SIZE) / (MAX_CAMERA_SIZE - MIN_CAMERA_SIZE);

let state_uniforms = StateUniforms {
shape: match state.shape {
CameraPreviewShape::Round => 0.0,
CameraPreviewShape::Square => 1.0,
CameraPreviewShape::Full => 2.0,
},
size: match state.size {
CameraPreviewSize::Sm => 0.0,
CameraPreviewSize::Lg => 1.0,
},
size: normalized_size,
mirrored: if state.mirrored { 1.0 } else { 0.0 },
_padding: 0.0,
};
Expand Down Expand Up @@ -646,7 +662,7 @@ impl Renderer {
bytemuck::cast_slice(&[camera_uniforms]),
);

if let Ok((width, height)) = resize_window(window, state, aspect_ratio)
if let Ok((width, height)) = resize_window(window, state, aspect_ratio, false)
.map_err(|err| error!("Error resizing camera preview window: {err}"))
{
self.reconfigure_gpu_surface(width, height);
Expand All @@ -661,14 +677,11 @@ fn resize_window(
window: &WebviewWindow,
state: &CameraPreviewState,
aspect: f32,
should_move: bool,
) -> tauri::Result<(u32, u32)> {
trace!("CameraPreview/resize_window");

let base: f32 = if state.size == CameraPreviewSize::Sm {
230.0
} else {
400.0
};
let base = clamp_size(state.size);
let window_width = if state.shape == CameraPreviewShape::Full {
if aspect >= 1.0 { base * aspect } else { base }
} else {
Expand All @@ -680,25 +693,68 @@ fn resize_window(
base
} + TOOLBAR_HEIGHT;

let (monitor_size, monitor_offset, monitor_scale_factor): (
PhysicalSize<u32>,
LogicalPosition<u32>,
_,
) = if let Some(monitor) = window.current_monitor()? {
let size = monitor.position().to_logical(monitor.scale_factor());
(*monitor.size(), size, monitor.scale_factor())
} else {
(PhysicalSize::new(640, 360), LogicalPosition::new(0, 0), 1.0)
};
if should_move {
let (monitor_size, monitor_offset, monitor_scale_factor): (
PhysicalSize<u32>,
LogicalPosition<u32>,
_,
) = if let Some(monitor) = window.current_monitor()? {
let size = monitor.position().to_logical(monitor.scale_factor());
(*monitor.size(), size, monitor.scale_factor())
} else {
(PhysicalSize::new(640, 360), LogicalPosition::new(0, 0), 1.0)
};

let x = (monitor_size.width as f64 / monitor_scale_factor - window_width as f64 - 100.0)
as u32
+ monitor_offset.x;
let y = (monitor_size.height as f64 / monitor_scale_factor - window_height as f64 - 100.0)
as u32
+ monitor_offset.y;

window.set_position(LogicalPosition::new(x, y))?;
} else if let Some(monitor) = window.current_monitor()? {
// Ensure the window stays within the monitor bounds when resizing
let scale_factor = monitor.scale_factor();
let monitor_pos = monitor.position().to_logical::<f64>(scale_factor);
let monitor_size = monitor.size().to_logical::<f64>(scale_factor);

let current_pos = window
.outer_position()
.map(|p| p.to_logical::<f64>(scale_factor))
.unwrap_or(monitor_pos);

let mut new_x = current_pos.x;
let mut new_y = current_pos.y;
let new_width = window_width as f64;
let new_height = window_height as f64;

// Check right edge
if new_x + new_width > monitor_pos.x + monitor_size.width {
new_x = monitor_pos.x + monitor_size.width - new_width;
}

let x = (monitor_size.width as f64 / monitor_scale_factor - window_width as f64 - 100.0) as u32
+ monitor_offset.x;
let y = (monitor_size.height as f64 / monitor_scale_factor - window_height as f64 - 100.0)
as u32
+ monitor_offset.y;
// Check bottom edge
if new_y + new_height > monitor_pos.y + monitor_size.height {
new_y = monitor_pos.y + monitor_size.height - new_height;
}

// Check left edge
if new_x < monitor_pos.x {
new_x = monitor_pos.x;
}

// Check top edge
if new_y < monitor_pos.y {
new_y = monitor_pos.y;
}

if (new_x - current_pos.x).abs() > 1.0 || (new_y - current_pos.y).abs() > 1.0 {
window.set_position(LogicalPosition::new(new_x, new_y))?;
}
}

window.set_size(LogicalSize::new(window_width, window_height))?;
window.set_position(LogicalPosition::new(x, y))?;

Ok((window_width as u32, window_height as u32))
}
Expand Down
6 changes: 4 additions & 2 deletions apps/desktop/src-tauri/src/camera.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,8 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {

} else if (shape == 1.0) {
// Square shape with enhanced corner anti-aliasing
let corner_radius = select(0.1, 0.12, size == 1.0);
// Interpolate corner radius based on normalized size (0-1)
let corner_radius = mix(0.10, 0.14, size);
let abs_uv = abs(center_uv);
let corner_pos = abs_uv - (1.0 - corner_radius);
let corner_dist = length(max(corner_pos, vec2<f32>(0.0, 0.0)));
Expand All @@ -138,7 +139,8 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
} else if (shape == 2.0) {
// Full shape with aspect ratio-corrected rounded corners
let window_aspect = window_uniforms.window_width / window_uniforms.window_height;
let corner_radius = select(0.08, 0.1, size == 1.0); // radius based on size (8% for small, 10% for large)
// Interpolate corner radius based on normalized size (0-1)
let corner_radius = mix(0.08, 0.12, size);

let abs_uv = abs(center_uv);
let corner_pos = abs_uv - (1.0 - corner_radius);
Expand Down
9 changes: 3 additions & 6 deletions apps/desktop/src-tauri/src/deeplink_actions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,7 @@ use std::path::{Path, PathBuf};
use tauri::{AppHandle, Manager, Url};
use tracing::trace;

use crate::{
App, ArcLock, apply_camera_input, apply_mic_input, recording::StartRecordingInputs,
windows::ShowCapWindow,
};
use crate::{App, ArcLock, recording::StartRecordingInputs, windows::ShowCapWindow};

#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
Expand Down Expand Up @@ -119,8 +116,8 @@ impl DeepLinkAction {
} => {
let state = app.state::<ArcLock<App>>();

apply_camera_input(app.clone(), state.clone(), camera).await?;
apply_mic_input(state.clone(), mic_label).await?;
crate::set_camera_input(app.clone(), state.clone(), camera).await?;
crate::set_mic_input(state.clone(), mic_label).await?;

let capture_target: ScreenCaptureTarget = match capture_mode {
CaptureMode::Screen(name) => cap_recording::screen_capture::list_displays()
Expand Down
Loading
Loading