Skip to content

Commit 563e238

Browse files
Merge pull request CapSoftware#1390 from CapSoftware/editor-perf
feat: Various features + performance bits
2 parents 9edf1ce + 2f75043 commit 563e238

File tree

34 files changed

+1370
-714
lines changed

34 files changed

+1370
-714
lines changed

AGENTS.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
- Rust: `rustfmt` + workspace clippy lints.
2121
- Naming: files kebab‑case (`user-menu.tsx`); components PascalCase; Rust modules snake_case, crates kebab‑case.
2222
- Runtime: Node 20, pnpm 10.x, Rust 1.88+, Docker for MySQL/MinIO.
23+
- **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.).
2324

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

4143
## Effect Usage
4244
- 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.
4345
- 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.
4446
- 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.
4547
- On the server, run effects through `EffectRuntime.runPromise` from `@/lib/server`, typically after `provideOptionalAuth`, so cookies and per-request context are attached automatically.
4648
- 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.
49+
50+
## Code Formatting
51+
- Always format code before completing work: run `pnpm format` for TypeScript/JavaScript and `cargo fmt` for Rust.
52+
- Run these commands regularly during development and always at the end of a coding session to ensure consistent formatting.

CLAUDE.md

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -360,7 +360,12 @@ Minimize `useEffect` usage: compute during render, handle logic in event handler
360360
- Windowing/permissions are handled in Rust; keep UI logic in Solid and avoid mixing IPC with rendering logic.
361361

362362
## Conventions
363-
- 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.
363+
- **CRITICAL: NO CODE COMMENTS**: Never add any form of comments to code. This includes:
364+
- Single-line comments: `//` (JavaScript/TypeScript/Rust), `#` (Python/Shell)
365+
- Multi-line comments: `/* */` (JavaScript/TypeScript), `/* */` (Rust)
366+
- Documentation comments: `///`, `//!` (Rust), `/** */` (JSDoc)
367+
- Any other comment syntax in any language
368+
- Code must be self-explanatory through naming, types, and structure. Use docs/READMEs for explanations when necessary.
364369
- Directory naming: lower-case-dashed
365370
- Components: PascalCase; hooks: camelCase starting with `use`
366371
- Strict TypeScript; avoid `any`; leverage shared types
@@ -414,3 +419,11 @@ Transcription/AI Enhancement → Database Storage
414419
- **Monorepo Guide**: Turborepo documentation
415420
- **Effect System**: Used in web-backend packages
416421
- **Media Processing**: FFmpeg documentation for Rust bindings
422+
423+
## Code Formatting
424+
425+
Always format code before completing work:
426+
- **TypeScript/JavaScript**: Run `pnpm format` to format all code with Biome
427+
- **Rust**: Run `cargo fmt` to format all Rust code with rustfmt
428+
429+
These commands should be run regularly during development and always at the end of a coding session to ensure consistent formatting across the codebase.

Cargo.lock

Lines changed: 5 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

apps/desktop/src-tauri/src/camera.rs

Lines changed: 98 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,9 @@ static TOOLBAR_HEIGHT: f32 = 56.0; // also defined in Typescript
2828
// Basically poor man's MSAA
2929
static GPU_SURFACE_SCALE: u32 = 4;
3030

31-
#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize, Type)]
32-
#[serde(rename_all = "lowercase")]
33-
pub enum CameraPreviewSize {
34-
#[default]
35-
Sm,
36-
Lg,
37-
}
31+
pub const MIN_CAMERA_SIZE: f32 = 150.0;
32+
pub const MAX_CAMERA_SIZE: f32 = 600.0;
33+
pub const DEFAULT_CAMERA_SIZE: f32 = 230.0;
3834

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

48-
#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize, Type)]
44+
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Type)]
4945
pub struct CameraPreviewState {
50-
size: CameraPreviewSize,
46+
size: f32,
5147
shape: CameraPreviewShape,
5248
mirrored: bool,
5349
}
5450

51+
impl Default for CameraPreviewState {
52+
fn default() -> Self {
53+
Self {
54+
size: DEFAULT_CAMERA_SIZE,
55+
shape: CameraPreviewShape::default(),
56+
mirrored: false,
57+
}
58+
}
59+
}
60+
61+
fn clamp_size(size: f32) -> f32 {
62+
size.clamp(MIN_CAMERA_SIZE, MAX_CAMERA_SIZE)
63+
}
64+
5565
pub struct CameraPreviewManager {
5666
store: Result<Arc<tauri_plugin_store::Store<tauri::Wry>>, String>,
5767
preview: Option<InitializedCameraPreview>,
@@ -70,17 +80,22 @@ impl CameraPreviewManager {
7080

7181
/// Get the current state of the camera window.
7282
pub fn get_state(&self) -> anyhow::Result<CameraPreviewState> {
73-
Ok(self
83+
let mut state: CameraPreviewState = self
7484
.store
7585
.as_ref()
7686
.map_err(|err| anyhow!("{err}"))?
7787
.get("state")
78-
.and_then(|v| serde_json::from_value(v).ok().unwrap_or_default())
79-
.unwrap_or_default())
88+
.and_then(|v| serde_json::from_value(v).ok())
89+
.unwrap_or_default();
90+
91+
state.size = clamp_size(state.size);
92+
Ok(state)
8093
}
8194

8295
/// Save the current state of the camera window.
83-
pub fn set_state(&self, state: CameraPreviewState) -> anyhow::Result<()> {
96+
pub fn set_state(&self, mut state: CameraPreviewState) -> anyhow::Result<()> {
97+
state.size = clamp_size(state.size);
98+
8499
let store = self.store.as_ref().map_err(|err| anyhow!("{err}"))?;
85100
store.set("state", serde_json::to_value(&state)?);
86101
store.save()?;
@@ -171,8 +186,8 @@ impl InitializedCameraPreview {
171186
1.0
172187
};
173188

174-
let size =
175-
resize_window(&window, default_state, aspect).context("Error resizing Tauri window")?;
189+
let size = resize_window(&window, default_state, aspect, true)
190+
.context("Error resizing Tauri window")?;
176191

177192
let (tx, rx) = oneshot::channel();
178193
window
@@ -562,7 +577,7 @@ impl Renderer {
562577

563578
self.sync_ratio_uniform_and_resize_window_to_it(&window, &state, aspect_ratio);
564579
self.update_state_uniforms(&state);
565-
if let Ok((width, height)) = resize_window(&window, &state, aspect_ratio)
580+
if let Ok((width, height)) = resize_window(&window, &state, aspect_ratio, false)
566581
.map_err(|err| error!("Error resizing camera preview window: {err}"))
567582
{
568583
self.reconfigure_gpu_surface(width, height);
@@ -607,16 +622,17 @@ impl Renderer {
607622

608623
/// Update the uniforms which hold the camera preview state
609624
fn update_state_uniforms(&self, state: &CameraPreviewState) {
625+
let clamped_size = clamp_size(state.size);
626+
let normalized_size =
627+
(clamped_size - MIN_CAMERA_SIZE) / (MAX_CAMERA_SIZE - MIN_CAMERA_SIZE);
628+
610629
let state_uniforms = StateUniforms {
611630
shape: match state.shape {
612631
CameraPreviewShape::Round => 0.0,
613632
CameraPreviewShape::Square => 1.0,
614633
CameraPreviewShape::Full => 2.0,
615634
},
616-
size: match state.size {
617-
CameraPreviewSize::Sm => 0.0,
618-
CameraPreviewSize::Lg => 1.0,
619-
},
635+
size: normalized_size,
620636
mirrored: if state.mirrored { 1.0 } else { 0.0 },
621637
_padding: 0.0,
622638
};
@@ -646,7 +662,7 @@ impl Renderer {
646662
bytemuck::cast_slice(&[camera_uniforms]),
647663
);
648664

649-
if let Ok((width, height)) = resize_window(window, state, aspect_ratio)
665+
if let Ok((width, height)) = resize_window(window, state, aspect_ratio, false)
650666
.map_err(|err| error!("Error resizing camera preview window: {err}"))
651667
{
652668
self.reconfigure_gpu_surface(width, height);
@@ -661,14 +677,11 @@ fn resize_window(
661677
window: &WebviewWindow,
662678
state: &CameraPreviewState,
663679
aspect: f32,
680+
should_move: bool,
664681
) -> tauri::Result<(u32, u32)> {
665682
trace!("CameraPreview/resize_window");
666683

667-
let base: f32 = if state.size == CameraPreviewSize::Sm {
668-
230.0
669-
} else {
670-
400.0
671-
};
684+
let base = clamp_size(state.size);
672685
let window_width = if state.shape == CameraPreviewShape::Full {
673686
if aspect >= 1.0 { base * aspect } else { base }
674687
} else {
@@ -680,25 +693,68 @@ fn resize_window(
680693
base
681694
} + TOOLBAR_HEIGHT;
682695

683-
let (monitor_size, monitor_offset, monitor_scale_factor): (
684-
PhysicalSize<u32>,
685-
LogicalPosition<u32>,
686-
_,
687-
) = if let Some(monitor) = window.current_monitor()? {
688-
let size = monitor.position().to_logical(monitor.scale_factor());
689-
(*monitor.size(), size, monitor.scale_factor())
690-
} else {
691-
(PhysicalSize::new(640, 360), LogicalPosition::new(0, 0), 1.0)
692-
};
696+
if should_move {
697+
let (monitor_size, monitor_offset, monitor_scale_factor): (
698+
PhysicalSize<u32>,
699+
LogicalPosition<u32>,
700+
_,
701+
) = if let Some(monitor) = window.current_monitor()? {
702+
let size = monitor.position().to_logical(monitor.scale_factor());
703+
(*monitor.size(), size, monitor.scale_factor())
704+
} else {
705+
(PhysicalSize::new(640, 360), LogicalPosition::new(0, 0), 1.0)
706+
};
707+
708+
let x = (monitor_size.width as f64 / monitor_scale_factor - window_width as f64 - 100.0)
709+
as u32
710+
+ monitor_offset.x;
711+
let y = (monitor_size.height as f64 / monitor_scale_factor - window_height as f64 - 100.0)
712+
as u32
713+
+ monitor_offset.y;
714+
715+
window.set_position(LogicalPosition::new(x, y))?;
716+
} else if let Some(monitor) = window.current_monitor()? {
717+
// Ensure the window stays within the monitor bounds when resizing
718+
let scale_factor = monitor.scale_factor();
719+
let monitor_pos = monitor.position().to_logical::<f64>(scale_factor);
720+
let monitor_size = monitor.size().to_logical::<f64>(scale_factor);
721+
722+
let current_pos = window
723+
.outer_position()
724+
.map(|p| p.to_logical::<f64>(scale_factor))
725+
.unwrap_or(monitor_pos);
726+
727+
let mut new_x = current_pos.x;
728+
let mut new_y = current_pos.y;
729+
let new_width = window_width as f64;
730+
let new_height = window_height as f64;
731+
732+
// Check right edge
733+
if new_x + new_width > monitor_pos.x + monitor_size.width {
734+
new_x = monitor_pos.x + monitor_size.width - new_width;
735+
}
693736

694-
let x = (monitor_size.width as f64 / monitor_scale_factor - window_width as f64 - 100.0) as u32
695-
+ monitor_offset.x;
696-
let y = (monitor_size.height as f64 / monitor_scale_factor - window_height as f64 - 100.0)
697-
as u32
698-
+ monitor_offset.y;
737+
// Check bottom edge
738+
if new_y + new_height > monitor_pos.y + monitor_size.height {
739+
new_y = monitor_pos.y + monitor_size.height - new_height;
740+
}
741+
742+
// Check left edge
743+
if new_x < monitor_pos.x {
744+
new_x = monitor_pos.x;
745+
}
746+
747+
// Check top edge
748+
if new_y < monitor_pos.y {
749+
new_y = monitor_pos.y;
750+
}
751+
752+
if (new_x - current_pos.x).abs() > 1.0 || (new_y - current_pos.y).abs() > 1.0 {
753+
window.set_position(LogicalPosition::new(new_x, new_y))?;
754+
}
755+
}
699756

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

703759
Ok((window_width as u32, window_height as u32))
704760
}

apps/desktop/src-tauri/src/camera.wgsl

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,8 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
124124

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

143145
let abs_uv = abs(center_uv);
144146
let corner_pos = abs_uv - (1.0 - corner_radius);

apps/desktop/src-tauri/src/deeplink_actions.rs

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,7 @@ use std::path::{Path, PathBuf};
66
use tauri::{AppHandle, Manager, Url};
77
use tracing::trace;
88

9-
use crate::{
10-
App, ArcLock, apply_camera_input, apply_mic_input, recording::StartRecordingInputs,
11-
windows::ShowCapWindow,
12-
};
9+
use crate::{App, ArcLock, recording::StartRecordingInputs, windows::ShowCapWindow};
1310

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

122-
apply_camera_input(app.clone(), state.clone(), camera).await?;
123-
apply_mic_input(state.clone(), mic_label).await?;
119+
crate::set_camera_input(app.clone(), state.clone(), camera).await?;
120+
crate::set_mic_input(state.clone(), mic_label).await?;
124121

125122
let capture_target: ScreenCaptureTarget = match capture_mode {
126123
CaptureMode::Screen(name) => cap_recording::screen_capture::list_displays()

0 commit comments

Comments
 (0)