diff --git a/apps/desktop/app.config.ts b/apps/desktop/app.config.ts index b149bc9135..cfa423e390 100644 --- a/apps/desktop/app.config.ts +++ b/apps/desktop/app.config.ts @@ -7,6 +7,28 @@ import tsconfigPaths from "vite-tsconfig-paths"; const enableSolidDevtools = !!process.env.VITE_SOLID_DEVTOOLS; +// Bundled fonts load from local assets near-instantly, so `block` is safe and +// avoids the fallback-font flash (FOUT) that `swap` causes on every window +// open. Desktop-only: web keeps `swap` for slow networks. +// No `enforce`: must run after vite:css has inlined the virtual module's +// @import statements (a `pre` transform only sees the un-inlined imports). +const fontDisplayBlock = { + name: "cap:font-display-block", + transform(code: string, id: string) { + // unplugin-fonts inlines the @fontsource CSS into its virtual + // "unfonts.css" module, so match that as well as direct imports. + const [file] = id.split("?"); + const isFontCss = + file.endsWith("unfonts.css") || + (file.includes("@fontsource") && file.endsWith(".css")); + if (!isFontCss) return; + return { + code: code.replace(/font-display:\s*swap;/g, "font-display: block;"), + map: null, + }; + }, +}; + export default defineConfig({ ssr: false, server: { preset: "static" }, @@ -31,6 +53,7 @@ export default defineConfig({ assetsInclude: ["**/*.riv"], plugins: [ ...(enableSolidDevtools ? [devtools({ autoname: true })] : []), + fontDisplayBlock, wasm(), topLevelAwait(), capUIPlugin, diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 288583b016..3eb2586b91 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -47,14 +47,14 @@ "@tauri-apps/plugin-deep-link": "^2.4.1", "@tauri-apps/plugin-dialog": "2.4.2", "@tauri-apps/plugin-fs": "2.4.2", - "@tauri-apps/plugin-http": "^2.5.1", + "@tauri-apps/plugin-http": "2.5.2", "@tauri-apps/plugin-notification": "^2.3.0", "@tauri-apps/plugin-opener": "^2.5.0", "@tauri-apps/plugin-os": "^2.3.0", "@tauri-apps/plugin-process": "2.3.0", "@tauri-apps/plugin-shell": "^2.3.0", "@tauri-apps/plugin-store": "^2.4.0", - "@tauri-apps/plugin-updater": "^2.9.0", + "@tauri-apps/plugin-updater": "2.9.0", "@ts-rest/core": "^3.52.1", "@types/react-tooltip": "^4.2.4", "cva": "npm:class-variance-authority@^0.7.0", diff --git a/apps/desktop/src-tauri/Cargo.toml b/apps/desktop/src-tauri/Cargo.toml index 7247700185..6700db3edc 100644 --- a/apps/desktop/src-tauri/Cargo.toml +++ b/apps/desktop/src-tauri/Cargo.toml @@ -29,7 +29,7 @@ tauri-specta = { version = "=2.0.0-rc.20", features = ["derive", "typescript"] } tauri-plugin-dialog = "2.2.0" tauri-plugin-fs = "2.2.0" tauri-plugin-global-shortcut = "2.2.0" -tauri-plugin-http = "2.2.0" +tauri-plugin-http = "=2.5.2" tauri-plugin-notification = "2.2.0" tauri-plugin-os = "2.2.0" tauri-plugin-process = "2.2.0" diff --git a/apps/desktop/src-tauri/examples/desktop-display-transport-benchmark.rs b/apps/desktop/src-tauri/examples/desktop-display-transport-benchmark.rs index ea8d60c690..1671af44e8 100644 --- a/apps/desktop/src-tauri/examples/desktop-display-transport-benchmark.rs +++ b/apps/desktop/src-tauri/examples/desktop-display-transport-benchmark.rs @@ -58,6 +58,9 @@ impl Summary { PlaybackTelemetryEvent::RendererSendFailed { .. } => { self.send_failures += 1; } + PlaybackTelemetryEvent::AudioSegmentsResolved { .. } + | PlaybackTelemetryEvent::AudioPipelineReady { .. } + | PlaybackTelemetryEvent::ClockStarted { .. } => {} } } @@ -307,6 +310,7 @@ async fn main() { }; let (_project_tx, project_rx) = watch::channel(project); + let audio_output = Arc::new(cap_editor::AudioOutput::new()); let playback = Playback { renderer: renderer.clone(), render_constants, @@ -314,6 +318,7 @@ async fn main() { project: project_rx, segment_medias, music: cap_editor::MusicTracks::new(), + audio_output, telemetry: Some(telemetry), }; diff --git a/apps/desktop/src-tauri/src/lib.rs b/apps/desktop/src-tauri/src/lib.rs index 10610701f5..61b3006196 100644 --- a/apps/desktop/src-tauri/src/lib.rs +++ b/apps/desktop/src-tauri/src/lib.rs @@ -139,6 +139,65 @@ use tauri::menu::{ type FinalizingRecordingsMap = std::collections::HashMap, watch::Receiver)>; +const EDITOR_PREVIEW_FPS: u32 = 60; +const EDITOR_OUTPUT_SIZE: XY = XY::new(1920, 1080); +const DEFAULT_EDITOR_PREVIEW_SCALE_NUMERATOR: u32 = 65; +const DEFAULT_EDITOR_PREVIEW_SCALE_DENOMINATOR: u32 = 100; + +fn default_editor_preview_resolution() -> XY { + scaled_editor_preview_resolution( + EDITOR_OUTPUT_SIZE, + DEFAULT_EDITOR_PREVIEW_SCALE_NUMERATOR, + DEFAULT_EDITOR_PREVIEW_SCALE_DENOMINATOR, + ) +} + +fn scaled_editor_preview_resolution( + output_size: XY, + numerator: u32, + denominator: u32, +) -> XY { + XY::new( + scaled_editor_preview_dimension(output_size.x, numerator, denominator, 4, 4), + scaled_editor_preview_dimension(output_size.y, numerator, denominator, 2, 2), + ) +} + +fn scaled_editor_preview_dimension( + value: u32, + numerator: u32, + denominator: u32, + minimum: u32, + alignment: u32, +) -> u32 { + let denominator = denominator.max(1); + let alignment = alignment.max(1); + let scaled = ((u64::from(value) * u64::from(numerator)) + (u64::from(denominator) / 2)) + / u64::from(denominator); + let rounded = u32::try_from(scaled).unwrap_or(u32::MAX).max(minimum); + let aligned = u64::from(rounded).div_ceil(u64::from(alignment)) * u64::from(alignment); + + u32::try_from(aligned).unwrap_or(u32::MAX) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn default_editor_preview_resolution_matches_frontend_defaults() { + assert_eq!(default_editor_preview_resolution(), XY::new(1248, 702)); + } + + #[test] + fn scaled_editor_preview_resolution_rounds_like_frontend() { + assert_eq!( + scaled_editor_preview_resolution(XY::new(1919, 1079), 65, 100), + XY::new(1248, 702) + ); + } +} + #[derive(Default)] pub struct FinalizingRecordings { recordings: std::sync::Mutex, @@ -2850,10 +2909,6 @@ async fn create_editor_instance(window: Window) -> Result Result out.push(audio::get_waveform(&audio)), + Ok(None) => out.push(Vec::new()), + Err(error) => { + warn!(%error, "Mic audio failed to load; returning empty waveform"); + out.push(Vec::new()); + } } } @@ -4123,10 +4183,13 @@ async fn get_system_audio_waveforms( let mut out = Vec::new(); for segment in editor_instance.segment_medias.iter() { - if let Some(audio) = &segment.system_audio { - out.push(audio::get_waveform(audio)); - } else { - out.push(Vec::new()); + match segment.system_audio.get().await { + Ok(Some(audio)) => out.push(audio::get_waveform(&audio)), + Ok(None) => out.push(Vec::new()), + Err(error) => { + warn!(%error, "System audio failed to load; returning empty waveform"); + out.push(Vec::new()); + } } } @@ -6135,6 +6198,10 @@ async fn create_editor_instance_impl( } }); + instance + .preview_tx + .send_modify(|v| *v = Some((0, EDITOR_PREVIEW_FPS, default_editor_preview_resolution()))); + Ok((instance, event_id)) } diff --git a/apps/desktop/src-tauri/src/platform/macos/mod.rs b/apps/desktop/src-tauri/src/platform/macos/mod.rs index 6fbaeb5c3a..e50f93de05 100644 --- a/apps/desktop/src-tauri/src/platform/macos/mod.rs +++ b/apps/desktop/src-tauri/src/platform/macos/mod.rs @@ -213,13 +213,10 @@ pub fn apply_liquid_glass_background( // single inheritance path. // // macOS 26.3 shipped an NSGlassEffectView that responds to neither selector, - // so the pin silently fails. We MUST NOT proceed to disable occlusion - // detection in that state: an occlusion-suppressed window whose private glass - // view is not pinned active leaves WindowServer unable to ever quiesce the - // surface, which wedges the compositor when the window is hidden/closed and - // takes down the whole login session. When we can't pin the material, abandon - // the private SPI entirely and let the caller fall back to NSVisualEffectView - // vibrancy (Ok(false)). + // so the pin silently fails. In that state the material can't be relied on + // (it dims whenever another window becomes key), so abandon the private SPI + // entirely and let the caller fall back to NSVisualEffectView vibrancy + // (Ok(false)). if !force_glass_view_always_active(glass_view) { // Never entered the view hierarchy; balance the alloc and bail. The // content-layer squircle clip applied above is kept (plain Core Animation, @@ -236,19 +233,19 @@ pub fn apply_liquid_glass_background( let _: () = msg_send![ns_window, setOpaque: false]; let _: () = msg_send![ns_window, setBackgroundColor: clear_color]; - // Stop AppKit from marking the window "occluded" when another app becomes - // frontmost. The OS treats a window-without-key as occluded for power-saving - // purposes and freezes the contentView's rendering loop — which means the - // NSGlassEffectView's backdrop sampling pauses on the last frame, so the - // glass shows whatever happened to be behind us at the moment we lost focus - // (Safari, etc.) instead of reflecting the live backdrop change. There's no - // public API for this; probe the private setters used across NSWindow and - // NSPanel SPI variants, plus the same SPI on the embedded WKWebView since - // its pause is what actually freezes the contentView's render loop. Only - // reached once the glass view pinned active (see above); reversed by - // teardown_liquid_glass_ns before the window is hidden or the process exits. - disable_window_occlusion_detection(ns_window); - disable_webview_occlusion_detection(content_view); + // Deliberately DO NOT disable window/WKWebView occlusion detection here. + // Earlier builds called the private `_setWindowOcclusionDetectionEnabled:` / + // `_setWebViewWindowOcclusionDetectionEnabled:` SPI so the glass backdrop kept + // sampling while Cap was unfocused, but an occlusion-suppressed surface is one + // WindowServer can never quiesce — on macOS 26 that wedged the compositor when + // the window was later hidden or the app quit (often after sleep/lid-close), + // watchdog-stalling WindowServer and soft-restarting the whole login session. + // Gating it on the always-active pin was not enough: the main window's + // close-to-tray path hides the window without any teardown, so any future + // macOS build where the pin succeeds would silently re-arm the wedge. The + // only field-verified-safe configuration is to leave occlusion detection + // alone entirely; the trade-off is merely cosmetic (backdrop sampling may + // pause on the last frame while the app is deactivated). let _: () = msg_send![glass_view, setAutoresizingMask: 18usize]; let _: () = msg_send![ @@ -268,144 +265,6 @@ pub fn apply_liquid_glass_background( } } -unsafe fn disable_window_occlusion_detection(ns_window: cocoa::base::id) { - use objc::{msg_send, sel, sel_impl}; - - // Apple uses different naming for this private SPI between NSWindow and - // various AppKit subclasses (and the name has shifted across OS versions). - // Try each variant directly; respondsToSelector keeps the call safe. - unsafe { - let responds: bool = msg_send![ - ns_window, - respondsToSelector: sel!(_setWindowOcclusionDetectionEnabled:) - ]; - if responds { - let _: () = msg_send![ns_window, _setWindowOcclusionDetectionEnabled: false]; - tracing::info!( - target: "cap_desktop_lib::liquid_glass", - "Disabled window occlusion detection via _setWindowOcclusionDetectionEnabled:" - ); - return; - } - - let responds: bool = msg_send![ - ns_window, - respondsToSelector: sel!(setWindowOcclusionDetectionEnabled:) - ]; - if responds { - let _: () = msg_send![ns_window, setWindowOcclusionDetectionEnabled: false]; - tracing::info!( - target: "cap_desktop_lib::liquid_glass", - "Disabled window occlusion detection via setWindowOcclusionDetectionEnabled:" - ); - return; - } - - let responds: bool = msg_send![ - ns_window, - respondsToSelector: sel!(_setOcclusionDetectionEnabled:) - ]; - if responds { - let _: () = msg_send![ns_window, _setOcclusionDetectionEnabled: false]; - tracing::info!( - target: "cap_desktop_lib::liquid_glass", - "Disabled window occlusion detection via _setOcclusionDetectionEnabled:" - ); - return; - } - - let responds: bool = msg_send![ - ns_window, - respondsToSelector: sel!(setOcclusionDetectionEnabled:) - ]; - if responds { - let _: () = msg_send![ns_window, setOcclusionDetectionEnabled: false]; - tracing::info!( - target: "cap_desktop_lib::liquid_glass", - "Disabled window occlusion detection via setOcclusionDetectionEnabled:" - ); - return; - } - - tracing::warn!( - target: "cap_desktop_lib::liquid_glass", - "NSWindow does not respond to any known occlusion-detection selector; \ - glass backdrop will freeze when app deactivates" - ); - } -} - -unsafe fn disable_webview_occlusion_detection(content_view: cocoa::base::id) { - use cocoa::base::id; - use objc::{msg_send, runtime::Class, sel, sel_impl}; - - unsafe { - let Some(wkwebview_class) = Class::get("WKWebView") else { - tracing::warn!( - target: "cap_desktop_lib::liquid_glass", - "WKWebView class not found; skipping WebView occlusion fix" - ); - return; - }; - let wkwebview_class = wkwebview_class as *const Class; - - let subviews: id = msg_send![content_view, subviews]; - if subviews == cocoa::base::nil { - return; - } - - let count: usize = msg_send![subviews, count]; - for index in 0..count { - let subview: id = msg_send![subviews, objectAtIndex: index]; - if subview == cocoa::base::nil { - continue; - } - - let is_webview: bool = msg_send![subview, isKindOfClass: wkwebview_class]; - if !is_webview { - continue; - } - - let responds: bool = msg_send![ - subview, - respondsToSelector: sel!(_setWebViewWindowOcclusionDetectionEnabled:) - ]; - if responds { - let _: () = msg_send![subview, _setWebViewWindowOcclusionDetectionEnabled: false]; - tracing::info!( - target: "cap_desktop_lib::liquid_glass", - "Disabled WKWebView occlusion via _setWebViewWindowOcclusionDetectionEnabled:" - ); - return; - } - - let responds: bool = msg_send![ - subview, - respondsToSelector: sel!(_setWindowOcclusionDetectionEnabled:) - ]; - if responds { - let _: () = msg_send![subview, _setWindowOcclusionDetectionEnabled: false]; - tracing::info!( - target: "cap_desktop_lib::liquid_glass", - "Disabled WKWebView occlusion via _setWindowOcclusionDetectionEnabled:" - ); - return; - } - - tracing::warn!( - target: "cap_desktop_lib::liquid_glass", - "WKWebView does not respond to any known occlusion-detection selector" - ); - return; - } - - tracing::warn!( - target: "cap_desktop_lib::liquid_glass", - "No WKWebView found in content view subviews" - ); - } -} - /// Pin the glass material to its always-active representation. Returns whether the /// view actually responded to a known pinning selector. A `false` return means this /// macOS build's NSGlassEffectView SPI differs from what we understand (notably 26.3, @@ -467,8 +326,9 @@ unsafe fn force_glass_view_always_active(glass_view: cocoa::base::id) -> bool { unsafe fn enable_window_occlusion_detection(ns_window: cocoa::base::id) { use objc::{msg_send, sel, sel_impl}; - // Mirror disable_window_occlusion_detection: re-enable via whichever private SPI - // variant this AppKit build responds to, so the OS can quiesce the surface again. + // Defense-in-depth heal: current builds never disable occlusion detection, but + // re-assert the OS default via whichever private SPI variant this AppKit build + // responds to, so the OS can always quiesce the surface. unsafe { let responds: bool = msg_send![ ns_window, diff --git a/apps/desktop/src-tauri/src/windows.rs b/apps/desktop/src-tauri/src/windows.rs index 24e8a861ac..b365a95ca3 100644 --- a/apps/desktop/src-tauri/src/windows.rs +++ b/apps/desktop/src-tauri/src/windows.rs @@ -1785,13 +1785,25 @@ impl ShowCapWindow { Self::Editor { .. } => { hide_recording_windows(app, false); - let window = self + let window = match self .window_builder(app, "/editor") .maximizable(true) .inner_size(1275.0, 800.0) .min_inner_size(1275.0, 800.0) .focused(true) - .build()?; + .build() + { + Ok(window) => window, + Err(error) => { + // Don't leave the prewarmed instance (decoders, frame + // websocket) orphaned if the window failed to appear. + let window_label = self.id(app).label(); + PendingEditorInstances::get(app) + .cancel_prewarm(&window_label) + .await; + return Err(error); + } + }; lock_window_text_scale(&window); let (pos_x, pos_y) = cursor_monitor.center_position(1275.0, 800.0); @@ -1808,6 +1820,22 @@ impl ShowCapWindow { } } + // Show immediately: the native background color is already + // themed, so the window can appear before the webview loads and + // the editor skeleton takes over. When window transparency is + // enabled we keep the old behaviour (the frontend reveals the + // window after applying the HudWindow effects) to avoid an + // opaque-to-transparent pop. + let transparency_enabled = GeneralSettingsStore::get(app) + .ok() + .flatten() + .map(|s| s.window_transparency) + .unwrap_or(false); + if !transparency_enabled { + window.show().ok(); + window.set_focus().ok(); + } + window } Self::ScreenshotEditor { path } => { @@ -2672,9 +2700,12 @@ impl ShowCapWindow { ) -> WebviewWindowBuilder<'a, Wry, AppHandle> { let id = self.id(app); - let theme = GeneralSettingsStore::get(app) - .ok() - .flatten() + let settings = GeneralSettingsStore::get(app).ok().flatten(); + let window_transparency_enabled = settings + .as_ref() + .map(|s| s.window_transparency) + .unwrap_or(false); + let theme = settings .map(|s| match s.theme { AppTheme::System => None, AppTheme::Light => Some(tauri::Theme::Light), @@ -2702,6 +2733,20 @@ impl ShowCapWindow { r#"(function(){{var s=document.createElement('style');s.textContent='html,body{{background-color:{bg_color}}}';document.documentElement.appendChild(s);}})();"# ); builder = builder.initialization_script(&init_script); + + // Native backing color so the window is themed before the webview's + // first paint, allowing windows to be shown immediately without a + // white/black flash. Skipped when the user has window transparency + // enabled: an opaque native background would sit behind the + // translucent webview content and defeat the effect. + if !window_transparency_enabled { + let native_bg = if is_dark { + tauri::window::Color(0x14, 0x14, 0x14, 0xff) + } else { + tauri::window::Color(0xff, 0xff, 0xff, 0xff) + }; + builder = builder.background_color(native_bg); + } } if let Some(min) = id.min_size() { diff --git a/apps/desktop/src/app.tsx b/apps/desktop/src/app.tsx index 022501fa3d..d55ade5dd3 100644 --- a/apps/desktop/src/app.tsx +++ b/apps/desktop/src/app.tsx @@ -126,6 +126,7 @@ function Inner() { onMount(() => { initAnonymousUser(); + prewarmFontCaches(); }); return ( @@ -253,6 +254,29 @@ function Inner() { ); } +// WebKit resolves the emoji fallback chain lazily on first glyph paint, which +// can jank the first list/text render containing emoji (e.g. recording +// titles). Drawing once to an offscreen canvas at idle warms the per-process +// font caches instead. +function prewarmFontCaches() { + const warm = () => { + try { + const canvas = document.createElement("canvas"); + canvas.width = 32; + canvas.height = 32; + const ctx = canvas.getContext("2d"); + if (!ctx) return; + ctx.font = "16px 'Geist Sans'"; + ctx.fillText("Ag", 0, 24); + ctx.font = "16px system-ui"; + ctx.fillText("😀", 0, 24); + } catch {} + }; + + if ("requestIdleCallback" in window) requestIdleCallback(warm); + else setTimeout(warm, 250); +} + function createThemeListener(currentWindow: WebviewWindow) { const [appTheme, setAppTheme] = createSignal(); let disposed = false; diff --git a/apps/desktop/src/components/ModeSelect.tsx b/apps/desktop/src/components/ModeSelect.tsx index 9af75e1606..34ab6e51fb 100644 --- a/apps/desktop/src/components/ModeSelect.tsx +++ b/apps/desktop/src/components/ModeSelect.tsx @@ -18,7 +18,7 @@ const ModeOption = (props: ModeOptionProps) => { data-tauri-drag-region="false" onClick={() => props.onSelect(props.mode)} class={cx( - "relative flex flex-col items-center rounded-xl border-2 transition-all duration-200 cursor-pointer overflow-hidden group", + "relative flex flex-col items-center rounded-xl border-2 transition-all duration-200 overflow-hidden group", props.isSelected ? "border-blue-9 bg-blue-3 dark:bg-blue-3/30 shadow-lg shadow-blue-9/10" : "border-gray-4 dark:border-gray-5 bg-gray-2 dark:bg-gray-3 hover:border-gray-6 dark:hover:border-gray-6 hover:bg-gray-3 dark:hover:bg-gray-4", @@ -96,7 +96,7 @@ const ModeSelect = (props: { onClose?: () => void; standalone?: boolean }) => {
props.onClose?.()} - class="absolute -top-2.5 -right-2.5 p-2 rounded-full border duration-200 bg-gray-2 border-gray-3 hover:bg-gray-3 transition-colors cursor-pointer" + class="absolute -top-2.5 -right-2.5 p-2 rounded-full border duration-200 bg-gray-2 border-gray-3 hover:bg-gray-3 transition-colors" >
diff --git a/apps/desktop/src/components/Toggle.tsx b/apps/desktop/src/components/Toggle.tsx index 74928408b4..43e8d2250a 100644 --- a/apps/desktop/src/components/Toggle.tsx +++ b/apps/desktop/src/components/Toggle.tsx @@ -41,7 +41,7 @@ export function Toggle( return ( - + diff --git a/apps/desktop/src/routes/(window-chrome).tsx b/apps/desktop/src/routes/(window-chrome).tsx index e99e68d2b5..42ee74d047 100644 --- a/apps/desktop/src/routes/(window-chrome).tsx +++ b/apps/desktop/src/routes/(window-chrome).tsx @@ -143,7 +143,7 @@ function Inner(props: ParentProps) { return (
{props.children}
diff --git a/apps/desktop/src/routes/(window-chrome)/onboarding.tsx b/apps/desktop/src/routes/(window-chrome)/onboarding.tsx index 68bc3b5780..6a84061a35 100644 --- a/apps/desktop/src/routes/(window-chrome)/onboarding.tsx +++ b/apps/desktop/src/routes/(window-chrome)/onboarding.tsx @@ -889,7 +889,7 @@ function ToggleStep(props: { active: boolean }) { {(mode, index) => (
(
-
+
-
+
}> {props.children} diff --git a/apps/desktop/src/routes/(window-chrome)/settings/automations.tsx b/apps/desktop/src/routes/(window-chrome)/settings/automations.tsx index 2c47899ea2..ff1a855574 100644 --- a/apps/desktop/src/routes/(window-chrome)/settings/automations.tsx +++ b/apps/desktop/src/routes/(window-chrome)/settings/automations.tsx @@ -391,7 +391,7 @@ function SelectInput(props: { type="button" onClick={() => void openMenu()} class={cx( - "flex gap-2 justify-between items-center w-full px-2.5 h-8 text-[13px] rounded-lg border transition-colors cursor-pointer bg-gray-1 border-gray-3 text-gray-12 outline-none hover:bg-gray-2 hover:border-gray-5 focus-visible:border-gray-6", + "flex gap-2 justify-between items-center w-full px-2.5 h-8 text-[13px] rounded-lg border transition-colors bg-gray-1 border-gray-3 text-gray-12 outline-none hover:bg-gray-2 hover:border-gray-5 focus-visible:border-gray-6", props.class, )} > diff --git a/apps/desktop/src/routes/(window-chrome)/settings/changelog.tsx b/apps/desktop/src/routes/(window-chrome)/settings/changelog.tsx index 771c7514c2..27e093a7ad 100644 --- a/apps/desktop/src/routes/(window-chrome)/settings/changelog.tsx +++ b/apps/desktop/src/routes/(window-chrome)/settings/changelog.tsx @@ -1,6 +1,5 @@ import { createQuery } from "@tanstack/solid-query"; -import { cx } from "cva"; -import { ErrorBoundary, For, onMount, Show, Suspense } from "solid-js"; +import { ErrorBoundary, For, Show } from "solid-js"; import { SolidMarkdown } from "solid-markdown"; import { AbsoluteInsetLoader } from "~/components/Loader"; @@ -8,97 +7,80 @@ import { apiClient } from "~/utils/web-api"; import { SettingsPageContent } from "./Setting"; export default function Page() { - console.log("[Changelog] Component mounted"); - const changelog = createQuery(() => { - console.log("[Changelog] Creating query"); return { queryKey: ["changelog"], queryFn: async () => { - console.log("[Changelog] Executing query function"); - try { - const response = await apiClient.desktop.getChangelogPosts({ - query: { origin: window.location.origin }, - }); - - console.log("[Changelog] Response", response); + const response = await apiClient.desktop.getChangelogPosts({ + query: { origin: window.location.origin }, + }); - if (response.status !== 200) { - console.error("[Changelog] Error status:", response.status); - throw new Error("Failed to fetch changelog"); - } - return response.body; - } catch (error) { - console.error("[Changelog] Error in query:", error); - throw error; + if (response.status !== 200) { + throw new Error("Failed to fetch changelog"); } + return response.body; }, }; }); - onMount(() => { - console.log("[Changelog] Query state:", { - isLoading: changelog.isLoading, - isError: changelog.isError, - error: changelog.error, - data: changelog.data, - }); - }); - - const fadeIn = changelog.isLoading; - return ( ); diff --git a/apps/desktop/src/routes/(window-chrome)/settings/general.tsx b/apps/desktop/src/routes/(window-chrome)/settings/general.tsx index 7af10cc3cb..a491c98c9a 100644 --- a/apps/desktop/src/routes/(window-chrome)/settings/general.tsx +++ b/apps/desktop/src/routes/(window-chrome)/settings/general.tsx @@ -1135,7 +1135,7 @@ function DefaultProjectNameCard(props: {
setIsCommercialAnnual((v) => !v)} - class="px-3 py-2 text-center rounded-full border border-transparent transition-all duration-200 cursor-pointer w-fit bg-gray-5 hover:border-gray-400" + class="px-3 py-2 text-center rounded-full border border-transparent transition-all duration-200 w-fit bg-gray-5 hover:border-gray-400" >

Switch to {isCommercialAnnual() ? "lifetime" : "yearly"}:{" "} @@ -305,7 +307,7 @@ function CommercialLicensePurchase() { licenseKey: value.licenseKey, }, }); - await queryClient.refetchQueries({ queryKey: ["bruh"] }); + await queryClient.refetchQueries({ queryKey: ["licenseQuery"] }); }} /> diff --git a/apps/desktop/src/routes/(window-chrome)/settings/recordings.tsx b/apps/desktop/src/routes/(window-chrome)/settings/recordings.tsx index 84b2724b1c..461b804557 100644 --- a/apps/desktop/src/routes/(window-chrome)/settings/recordings.tsx +++ b/apps/desktop/src/routes/(window-chrome)/settings/recordings.tsx @@ -233,7 +233,7 @@ export default function Recordings() { "flex gap-1.5 items-center transition-colors duration-200 p-2 px-3 border rounded-full", activeTab() === tab.id ? "bg-gray-5 cursor-default border-gray-5" - : "bg-transparent cursor-pointer hover:bg-gray-3 border-gray-5", + : "bg-transparent hover:bg-gray-3 border-gray-5", )} onClick={() => setActiveTab(tab.id)} > @@ -346,9 +346,7 @@ function RecordingItem(props: { }} class={cx( "flex flex-row justify-between p-3 not-last:border-b not-last:border-gray-3 items-center w-full transition-colors duration-200", - studioCompleteCheck() - ? "cursor-pointer hover:bg-gray-3" - : "cursor-default", + studioCompleteCheck() ? "hover:bg-gray-3" : "cursor-default", )} >

diff --git a/apps/desktop/src/routes/(window-chrome)/settings/screenshots.tsx b/apps/desktop/src/routes/(window-chrome)/settings/screenshots.tsx index 68e91d52dc..98363de385 100644 --- a/apps/desktop/src/routes/(window-chrome)/settings/screenshots.tsx +++ b/apps/desktop/src/routes/(window-chrome)/settings/screenshots.tsx @@ -284,7 +284,7 @@ function ScreenshotItem(props: { return (
  • setIsCommercialAnnual((v) => !v)} - class="px-3 py-2 text-center rounded-full border border-transparent transition-all duration-200 cursor-pointer bg-gray-5 hover:border-gray-400" + class="px-3 py-2 text-center rounded-full border border-transparent transition-all duration-200 bg-gray-5 hover:border-gray-400" >

    Switch to {isCommercialAnnual() ? "lifetime" : "yearly"} @@ -429,7 +429,7 @@ export default function Page() {

    setOpenLicenseDialog(true)} - class="mb-2 text-sm transition-colors cursor-pointer text-gray-11 hover:text-gray-12" + class="mb-2 text-sm transition-colors text-gray-11 hover:text-gray-12" > Already have a license key?

    @@ -487,7 +487,7 @@ export default function Page() {
    setIsProAnnual((v) => !v)} - class="px-3 py-2 text-center bg-blue-500 rounded-full border border-transparent transition-all duration-200 cursor-pointer hover:border-blue-400" + class="px-3 py-2 text-center bg-blue-500 rounded-full border border-transparent transition-all duration-200 hover:border-blue-400" >

    Switch to {isProAnnual() ? "monthly" : "yearly"}:{" "} @@ -563,7 +563,7 @@ const ActivateLicenseDialog = ({ open, onOpenChange }: Props) => { licenseKey: value.licenseKey, }, }); - await queryClient.refetchQueries({ queryKey: ["bruh"] }); + await queryClient.refetchQueries({ queryKey: ["licenseQuery"] }); }, })); return ( diff --git a/apps/desktop/src/routes/editor/CaptionOverlay.tsx b/apps/desktop/src/routes/editor/CaptionOverlay.tsx index 2bc0aa98de..17516a1a91 100644 --- a/apps/desktop/src/routes/editor/CaptionOverlay.tsx +++ b/apps/desktop/src/routes/editor/CaptionOverlay.tsx @@ -298,7 +298,7 @@ export function CaptionOverlay(props: CaptionOverlayProps) { classList={{ "border-blue-9 bg-blue-9/10 cursor-move": selectedCaptionIndex() === caption().index, - "border-transparent hover:border-blue-6 hover:bg-blue-9/5 cursor-pointer": + "border-transparent hover:border-blue-6 hover:bg-blue-9/5": selectedCaptionIndex() !== caption().index, }} style={{ diff --git a/apps/desktop/src/routes/editor/ConfigSidebar.tsx b/apps/desktop/src/routes/editor/ConfigSidebar.tsx index bbcc4ad534..7bfea32ec5 100644 --- a/apps/desktop/src/routes/editor/ConfigSidebar.tsx +++ b/apps/desktop/src/routes/editor/ConfigSidebar.tsx @@ -738,7 +738,7 @@ export function ConfigSidebar() { class="rounded-lg border border-gray-3 transition-colors data-checked:border-blue-8 data-checked:bg-blue-3/40" > - +

    @@ -822,7 +822,7 @@ export function ConfigSidebar() { class="rounded-lg border border-gray-3 transition-colors data-checked:border-blue-8 data-checked:bg-blue-3/40" > - +
    @@ -2157,7 +2157,7 @@ function BackgroundConfig(props: { ensureBackgroundPresentation(); }} class={cx( - "overflow-hidden relative w-full h-48 rounded-lg border transition cursor-pointer group", + "overflow-hidden relative w-full h-48 rounded-lg border transition group", project.background.source.type === "wallpaper" && project.background.source.path === photo().rawPath ? "border-blue-9 ring-2 ring-blue-9" @@ -2271,7 +2271,7 @@ function BackgroundConfig(props: { class="relative aspect-square group" > - + - + Wallpaper option