From ce299f5cf310ee201e21c64601937086534764d8 Mon Sep 17 00:00:00 2001 From: Eduardo Date: Mon, 21 Sep 2026 11:48:26 -0300 Subject: [PATCH 1/3] fix(ide): resolve Win32 error 193 on Windows and support WSL UNC paths --- .../codelaunch-core/src/ide/terminal/mod.rs | 46 +++++++-- crates/codelaunch-core/src/ide/vscode/mod.rs | 94 ++++++++++++++++--- 2 files changed, 118 insertions(+), 22 deletions(-) diff --git a/crates/codelaunch-core/src/ide/terminal/mod.rs b/crates/codelaunch-core/src/ide/terminal/mod.rs index c46010c..cab4664 100644 --- a/crates/codelaunch-core/src/ide/terminal/mod.rs +++ b/crates/codelaunch-core/src/ide/terminal/mod.rs @@ -319,13 +319,13 @@ fn collect_grouped_terminals( } fn resolve_folder_path(folder: &Folder) -> String { - let path = if folder.path.is_absolute() { - folder.path.clone() - } else { - std::env::current_dir() - .unwrap_or_default() - .join(&folder.path) - }; + let folder_str = folder.path.to_string_lossy(); + if folder.path.is_absolute() || folder_str.starts_with(r"\\") || folder_str.starts_with("//") { + return folder_str.into_owned(); + } + let path = std::env::current_dir() + .unwrap_or_default() + .join(&folder.path); path.to_string_lossy().into_owned() } @@ -678,7 +678,11 @@ fn generate_windows_terminal_script(term: &Terminal, folder: &Folder) -> String let mut script = String::new(); script.push_str("@echo off\n"); script.push_str(&format!("title {}\n", term.label)); - script.push_str(&format!("cd /d \"{}\"\n", folder_path)); + if folder_path.starts_with(r"\\") { + script.push_str(&format!("pushd \"{}\"\n", folder_path)); + } else { + script.push_str(&format!("cd /d \"{}\"\n", folder_path)); + } match (&term.command, term.keep_alive) { (Some(cmd), true) => { @@ -708,7 +712,11 @@ fn generate_windows_master_script( if grouped_scripts.is_empty() { if let Some(folder) = project.folders.first() { let path = resolve_folder_path(folder); - script.push_str(&format!("start \"\" cmd /k \"cd /d \"{}\"\"\n", path)); + if path.starts_with(r"\\") { + script.push_str(&format!("start \"\" cmd /k \"pushd \"{}\"\"\n", path)); + } else { + script.push_str(&format!("start \"\" cmd /k \"cd /d \"{}\"\"\n", path)); + } } } else { script.push_str("where wt.exe >nul 2>&1\n"); @@ -940,6 +948,26 @@ mod tests { assert!(term2_content.contains("exit")); } + #[test] + fn render_for_windows_uses_pushd_for_unc_paths() { + let tmp = tempfile::tempdir().unwrap(); + let mut project = Project::new("WSL Project", IdeKind::Terminal); + project.terminals_enabled = true; + let folder = Folder::new("backend", r"\\wsl.localhost\Ubuntu\home\tiyo\TEAEdu-back"); + let folder_id = folder.id; + project.folders.push(folder); + let mut group = TerminalGroup::new("Main", 0); + let mut term = Terminal::new("Server", folder_id, 0); + term.command = Some("npm run dev".into()); + term.keep_alive = true; + group.terminals.push(term); + project.terminal_groups.push(group); + + let rendered = render_for_os(&project, tmp.path(), TargetOs::Windows).unwrap(); + let term_content = fs::read_to_string(&rendered.generated_files[0]).unwrap(); + assert!(term_content.contains(r#"pushd "\\wsl.localhost\Ubuntu\home\tiyo\TEAEdu-back""#)); + } + #[test] fn preview_contains_all_supported_os() { let project = create_test_project(); diff --git a/crates/codelaunch-core/src/ide/vscode/mod.rs b/crates/codelaunch-core/src/ide/vscode/mod.rs index 5dded66..70a14fb 100644 --- a/crates/codelaunch-core/src/ide/vscode/mod.rs +++ b/crates/codelaunch-core/src/ide/vscode/mod.rs @@ -67,6 +67,20 @@ impl IdeAdapter for VsCodeAdapter { fn launch_command(&self, workspace: &RenderedWorkspace) -> Command { let binary = resolve_cli_path(&self.cli_binary); + #[cfg(target_os = "windows")] + { + let binary_str = binary.to_string_lossy().to_lowercase(); + if !binary_str.ends_with(".exe") { + let mut cmd = Command::new("cmd"); + use std::os::windows::process::CommandExt; + cmd.creation_flags(0x08000000); // CREATE_NO_WINDOW + cmd.arg("/c") + .arg(&binary) + .arg("--new-window") + .arg(&workspace.entry_path); + return cmd; + } + } let mut cmd = Command::new(binary); cmd.arg("--new-window").arg(&workspace.entry_path); cmd @@ -85,9 +99,27 @@ pub fn resolve_cli_path(binary: &str) -> PathBuf { // 1. Search in PATH if let Some(path_var) = std::env::var_os("PATH") { for dir in std::env::split_paths(&path_var) { - let candidate = dir.join(binary); - if candidate.is_file() { - return candidate; + #[cfg(target_os = "windows")] + { + // On Windows, do not match extensionless scripts (e.g. `bin/code` shell script). + // Search for executable extensions: .exe, .cmd, .bat + for ext in &[".exe", ".cmd", ".bat"] { + let candidate = if binary.to_lowercase().ends_with(ext) { + dir.join(binary) + } else { + dir.join(format!("{binary}{ext}")) + }; + if candidate.is_file() { + return candidate; + } + } + } + #[cfg(not(target_os = "windows"))] + { + let candidate = dir.join(binary); + if candidate.is_file() { + return candidate; + } } } } @@ -104,8 +136,12 @@ pub fn resolve_cli_path(binary: &str) -> PathBuf { #[cfg(target_os = "macos")] "/Applications/Visual Studio Code - Insiders.app/Contents/Resources/app/bin/code", #[cfg(target_os = "windows")] + "C:\\Program Files\\Microsoft VS Code\\Code.exe", + #[cfg(target_os = "windows")] "C:\\Program Files\\Microsoft VS Code\\bin\\code.cmd", #[cfg(target_os = "windows")] + "C:\\Program Files (x86)\\Microsoft VS Code\\Code.exe", + #[cfg(target_os = "windows")] "C:\\Program Files (x86)\\Microsoft VS Code\\bin\\code.cmd", #[cfg(target_os = "linux")] "/usr/bin/code", @@ -124,6 +160,8 @@ pub fn resolve_cli_path(binary: &str) -> PathBuf { #[cfg(target_os = "macos")] "/Applications/Cursor.app/Contents/MacOS/Cursor", #[cfg(target_os = "windows")] + "C:\\Program Files\\Cursor\\Cursor.exe", + #[cfg(target_os = "windows")] "C:\\Program Files\\Cursor\\bin\\cursor.cmd", #[cfg(target_os = "windows")] "C:\\Program Files\\Cursor\\resources\\app\\bin\\cursor.cmd", @@ -144,6 +182,8 @@ pub fn resolve_cli_path(binary: &str) -> PathBuf { #[cfg(target_os = "macos")] "/Applications/VSCodium - Insiders.app/Contents/Resources/app/bin/codium", #[cfg(target_os = "windows")] + "C:\\Program Files\\VSCodium\\VSCodium.exe", + #[cfg(target_os = "windows")] "C:\\Program Files\\VSCodium\\bin\\codium.cmd", #[cfg(target_os = "linux")] "/usr/bin/codium", @@ -162,6 +202,8 @@ pub fn resolve_cli_path(binary: &str) -> PathBuf { #[cfg(target_os = "macos")] "/Applications/Windsurf.app/Contents/MacOS/Windsurf", #[cfg(target_os = "windows")] + "C:\\Program Files\\Windsurf\\Windsurf.exe", + #[cfg(target_os = "windows")] "C:\\Program Files\\Windsurf\\bin\\windsurf.cmd", #[cfg(target_os = "linux")] "/usr/bin/windsurf", @@ -221,13 +263,23 @@ pub fn resolve_cli_path(binary: &str) -> PathBuf { { let local_data = dirs.data_local_dir(); let user_candidates: Vec = match binary { - "code" => vec![local_data.join("Programs/Microsoft VS Code/bin/code.cmd")], + "code" => vec![ + local_data.join("Programs/Microsoft VS Code/Code.exe"), + local_data.join("Programs/Microsoft VS Code/bin/code.cmd"), + ], "cursor" => vec![ + local_data.join("Programs/cursor/Cursor.exe"), local_data.join("Programs/cursor/bin/cursor.cmd"), local_data.join("Programs/cursor/resources/app/bin/cursor.cmd"), ], - "codium" => vec![local_data.join("Programs/VSCodium/bin/codium.cmd")], - "windsurf" => vec![local_data.join("Programs/windsurf/bin/windsurf.cmd")], + "codium" => vec![ + local_data.join("Programs/VSCodium/VSCodium.exe"), + local_data.join("Programs/VSCodium/bin/codium.cmd"), + ], + "windsurf" => vec![ + local_data.join("Programs/windsurf/Windsurf.exe"), + local_data.join("Programs/windsurf/bin/windsurf.cmd"), + ], _ => vec![], }; for candidate in user_candidates { @@ -284,13 +336,13 @@ pub fn build_workspace_document(project: &Project) -> Result String { // The generated .code-workspace lives in CodeLaunch's own directory, not next to // the user's repositories, so folder paths must be absolute rather than relative. - let path: PathBuf = if folder.path.is_absolute() { - folder.path.clone() - } else { - std::env::current_dir() - .unwrap_or_default() - .join(&folder.path) - }; + let folder_str = folder.path.to_string_lossy(); + if folder.path.is_absolute() || folder_str.starts_with(r"\\") || folder_str.starts_with("//") { + return folder_str.into_owned(); + } + let path = std::env::current_dir() + .unwrap_or_default() + .join(&folder.path); path.to_string_lossy().into_owned() } @@ -386,4 +438,20 @@ mod tests { .ends_with("renamed-project.code-workspace")); assert!(!rendered1.entry_path.exists()); } + + #[test] + fn launch_command_builds_expected_arguments() { + let adapter = VsCodeAdapter::new(IdeKind::VsCode, "/dummy/code"); + let workspace = RenderedWorkspace { + entry_path: PathBuf::from("/path/to/project.code-workspace"), + generated_files: vec![], + }; + let cmd = adapter.launch_command(&workspace); + let args: Vec = cmd + .get_args() + .map(|a| a.to_string_lossy().into_owned()) + .collect(); + assert!(args.contains(&"--new-window".to_string())); + assert!(args.contains(&"/path/to/project.code-workspace".to_string())); + } } From c85686912fed91fb3515737fcc0aecc8e32be837 Mon Sep 17 00:00:00 2001 From: Eduardo Date: Mon, 21 Sep 2026 13:16:32 -0300 Subject: [PATCH 2/3] fix(release): downgrade tauri-action to v1 for compatibility --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b35210b..57a6282 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -68,7 +68,7 @@ jobs: run: npm ci - name: Build and Publish Tauri Release - uses: tauri-apps/tauri-action@v2 + uses: tauri-apps/tauri-action@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # To enable code signing & notarization, configure the secrets in GitHub and uncomment: From ac7a7bcc2a716ca3191ae5c36987a64f330dfce8 Mon Sep 17 00:00:00 2001 From: Eduardo Date: Mon, 21 Sep 2026 14:04:38 -0300 Subject: [PATCH 3/3] fix(ui): fix radix data-state selectors for switch and form components --- src/components/ui/checkbox.tsx | 2 +- src/components/ui/dialog.tsx | 4 ++-- src/components/ui/dropdown-menu.tsx | 6 +++--- src/components/ui/select.tsx | 2 +- src/components/ui/switch.tsx | 6 ++---- src/components/ui/tooltip.tsx | 2 +- 6 files changed, 10 insertions(+), 12 deletions(-) diff --git a/src/components/ui/checkbox.tsx b/src/components/ui/checkbox.tsx index 3f19b8d..4b3e352 100644 --- a/src/components/ui/checkbox.tsx +++ b/src/components/ui/checkbox.tsx @@ -11,7 +11,7 @@ function Checkbox({ @@ -223,7 +223,7 @@ function DropdownMenuSubTrigger({ data-slot="dropdown-menu-sub-trigger" data-inset={inset} className={cn( - "flex cursor-default items-center gap-1.5 rounded-md px-1.5 py-1 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-inset:pl-7 data-open:bg-accent data-open:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", + "flex cursor-default items-center gap-1.5 rounded-md px-1.5 py-1 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-inset:pl-7 data-[state=open]:bg-accent data-[state=open]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", className )} {...props} @@ -241,7 +241,7 @@ function DropdownMenuSubContent({ return ( ) diff --git a/src/components/ui/select.tsx b/src/components/ui/select.tsx index 6066bcd..e472f2c 100644 --- a/src/components/ui/select.tsx +++ b/src/components/ui/select.tsx @@ -66,7 +66,7 @@ function SelectContent({ ) diff --git a/src/components/ui/tooltip.tsx b/src/components/ui/tooltip.tsx index 443b7b7..0d5fb70 100644 --- a/src/components/ui/tooltip.tsx +++ b/src/components/ui/tooltip.tsx @@ -39,7 +39,7 @@ function TooltipContent({ data-slot="tooltip-content" sideOffset={sideOffset} className={cn( - "z-50 inline-flex w-fit max-w-xs origin-(--radix-tooltip-content-transform-origin) items-center gap-1.5 rounded-md bg-foreground px-3 py-1.5 text-xs text-background has-data-[slot=kbd]:pr-1.5 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 **:data-[slot=kbd]:relative **:data-[slot=kbd]:isolate **:data-[slot=kbd]:z-50 **:data-[slot=kbd]:rounded-sm data-[state=delayed-open]:animate-in data-[state=delayed-open]:fade-in-0 data-[state=delayed-open]:zoom-in-95 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95", + "z-50 inline-flex w-fit max-w-xs origin-(--radix-tooltip-content-transform-origin) items-center gap-1.5 rounded-md bg-foreground px-3 py-1.5 text-xs text-background has-data-[slot=kbd]:pr-1.5 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 **:data-[slot=kbd]:relative **:data-[slot=kbd]:isolate **:data-[slot=kbd]:z-50 **:data-[slot=kbd]:rounded-sm data-[state=delayed-open]:animate-in data-[state=delayed-open]:fade-in-0 data-[state=delayed-open]:zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95", className )} {...props}