diff --git a/.gitignore b/.gitignore index 6132d4d..6fabf5d 100644 --- a/.gitignore +++ b/.gitignore @@ -38,4 +38,5 @@ __pycache__/ # Local Package Builds /build -docs/marketing \ No newline at end of file +docs/marketing +.antigravitycli \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..2917431 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,22 @@ +# Changelog + +All notable changes to CleanMyLinux will be documented in this file. + +## [v1.0.0-beta.2] - 2026-05-24 + +### Fixed +- **Ghost Updates:** Resolved an issue where native DNF packages (like Brave Browser) would continuously reappear in the update list after successfully updating, caused by stale cache queries ([#3](https://github.com/Better-Linux/CleanMyLinux/issues/3)). +- **Flatpak Privilege Blindspot:** Fixed an issue where the updater could not detect or update user-installed flatpaks. Flatpak updates now seamlessly integrate with the system polkit for native permission handling ([#3](https://github.com/Better-Linux/CleanMyLinux/issues/3)). +- **System App Protection:** Fixed a bug where third-party apps were incorrectly flagged as protected system applications, which prevented users from uninstalling them ([#2](https://github.com/Better-Linux/CleanMyLinux/issues/2)). +- **KDE Dock Icon:** Fixed missing app icon in the KDE panel. ([#1](https://github.com/Better-Linux/CleanMyLinux/issues/1)) by @Joy-Majumder in [#4](https://github.com/Better-Linux/CleanMyLinux/pull/4). +- **Queue Manager UX:** Fixed a bug where a single failed application update would wipe the entire operations queue and force a false "Success" screen. Failed operations now correctly stay in the queue with visible error badges. +- **Queue Operation Clashing:** Fixed an issue where restarting an operation in the queue manager would overwrite previous operations. Every queued action now generates a mathematically unique ID. + +## [v1.0.0-beta.1] - 2026-05-22 + +### Added +- **Initial Beta Release:** First public preview of CleanMyLinux. +- **Unified App Manager:** Core architecture to scan, manage, update, and uninstall Native (RPM/APT/Pacman), Flatpak, and Snap packages. +- **System Optimizer:** Initial modules for cleaning system caches, logs, and unused dependencies. +- **Operations Queue:** Real-time background progress tracking for batch uninstalls and updates. +- **Polkit Integration:** Secure privilege escalation layer for system-level operations. diff --git a/package.json b/package.json index 21067dd..572c239 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "cleanmylinux", "private": true, - "version": "1.0.0-beta.1", + "version": "1.0.0-beta.2", "type": "module", "scripts": { "dev": "vite", diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 9627ec3..690ae31 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -2,22 +2,6 @@ # It is not intended for manual editing. version = 4 -[[package]] -name = "cleanmylinux" -version = "1.0.0-beta.1" -dependencies = [ - "base64 0.22.1", - "gtk", - "rayon", - "reqwest 0.12.28", - "serde", - "serde_json", - "tauri", - "tauri-build", - "tauri-plugin-opener", - "tauri-plugin-store", -] - [[package]] name = "adler2" version = "2.0.1" @@ -472,6 +456,22 @@ dependencies = [ "windows-link 0.2.1", ] +[[package]] +name = "cleanmylinux" +version = "1.0.0-beta.2" +dependencies = [ + "base64 0.22.1", + "gtk", + "rayon", + "reqwest 0.12.28", + "serde", + "serde_json", + "tauri", + "tauri-build", + "tauri-plugin-opener", + "tauri-plugin-store", +] + [[package]] name = "combine" version = "4.6.7" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 56dd9f4..5d43ae2 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cleanmylinux" -version = "1.0.0-beta.1" +version = "1.0.0-beta.2" description = "A premium, high performance system maintenance and optimization tool for Linux desktops." authors = ["Better Linux"] edition = "2021" diff --git a/src-tauri/src/system_info/commands/run_app_updates.rs b/src-tauri/src/system_info/commands/run_app_updates.rs index 3f5c227..9e7b197 100644 --- a/src-tauri/src/system_info/commands/run_app_updates.rs +++ b/src-tauri/src/system_info/commands/run_app_updates.rs @@ -37,7 +37,7 @@ pub async fn run_app_updates(app: tauri::AppHandle, updates: Vec) -> // STEP 1: Process Native Packages in a single unified TTY-style batch console invocation if !native_updates.is_empty() { - let op_id = "batch-update-native".to_string(); + let op_id = format!("batch-update-native-{}", std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_millis()); let first_app = &native_updates[0]; let base_title = "Updating System Packages".to_string(); let target_apps_clone = native_updates.clone(); @@ -58,6 +58,7 @@ pub async fn run_app_updates(app: tauri::AppHandle, updates: Vec) -> } else if let Ok(st2) = Command::new("dnf").arg("--version").output() { if st2.status.success() { base_args.push("upgrade".to_string()); + base_args.push("--refresh".to_string()); base_args.push("-y".to_string()); base_args.extend(pkg_ids); "dnf" @@ -76,6 +77,7 @@ pub async fn run_app_updates(app: tauri::AppHandle, updates: Vec) -> } else if let Ok(st2) = Command::new("dnf").arg("--version").output() { if st2.status.success() { base_args.push("upgrade".to_string()); + base_args.push("--refresh".to_string()); base_args.push("-y".to_string()); base_args.extend(pkg_ids); "dnf" @@ -134,7 +136,7 @@ pub async fn run_app_updates(app: tauri::AppHandle, updates: Vec) -> let app_clone = app.clone(); let app_emitter = app.clone(); join_handles.push(tauri::async_runtime::spawn(async move { - let op_id = "batch-update-flatpak".to_string(); + let op_id = format!("batch-update-flatpak-{}", std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_millis()); let base_title = "Updating Flatpak Apps".to_string(); let first_icon = flatpak_updates[0].icon.clone(); let target_apps_clone = flatpak_updates.clone(); @@ -145,7 +147,7 @@ pub async fn run_app_updates(app: tauri::AppHandle, updates: Vec) -> } let stream_res = tauri::async_runtime::spawn_blocking(move || { - let mut base_args = vec!["update".to_string(), "-y".to_string(), "--noninteractive".to_string()]; + let mut base_args = vec!["update".to_string(), "-y".to_string()]; base_args.extend(pkg_ids); let arg_refs: Vec<&str> = base_args.iter().map(|s| s.as_str()).collect(); @@ -157,7 +159,7 @@ pub async fn run_app_updates(app: tauri::AppHandle, updates: Vec) -> "update", "flatpak", &arg_refs, - true, + false, first_icon, Some(target_apps_clone), ) @@ -189,7 +191,7 @@ pub async fn run_app_updates(app: tauri::AppHandle, updates: Vec) -> let app_clone = app.clone(); let app_emitter = app.clone(); join_handles.push(tauri::async_runtime::spawn(async move { - let op_id = "batch-update-snap".to_string(); + let op_id = format!("batch-update-snap-{}", std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_millis()); let base_title = "Updating Snap Apps".to_string(); let first_icon = snap_updates[0].icon.clone(); let target_apps_clone = snap_updates.clone(); diff --git a/src-tauri/src/system_info/commands/uninstall_app.rs b/src-tauri/src/system_info/commands/uninstall_app.rs index 4aa3734..6801fd5 100644 --- a/src-tauri/src/system_info/commands/uninstall_app.rs +++ b/src-tauri/src/system_info/commands/uninstall_app.rs @@ -35,7 +35,7 @@ pub async fn uninstall_app( // STEP 1: Process Native Packages via single unified console removal if !native_apps.is_empty() { - let op_id = "batch-uninstall-native".to_string(); + let op_id = format!("batch-uninstall-native-{}", std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_millis()); let first_app = &native_apps[0]; let base_title = if native_apps.len() == 1 { first_app.name.clone() @@ -155,7 +155,7 @@ pub async fn uninstall_app( if !flatpak_apps.is_empty() { let app_clone = app_handle.clone(); join_handles.push(tauri::async_runtime::spawn(async move { - let op_id = "batch-uninstall-flatpak".to_string(); + let op_id = format!("batch-uninstall-flatpak-{}", std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_millis()); let first_app = &flatpak_apps[0]; let base_title = if flatpak_apps.len() == 1 { first_app.name.clone() @@ -256,7 +256,7 @@ pub async fn uninstall_app( if !snap_apps.is_empty() { let app_clone = app_handle.clone(); join_handles.push(tauri::async_runtime::spawn(async move { - let op_id = "batch-uninstall-snap".to_string(); + let op_id = format!("batch-uninstall-snap-{}", std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_millis()); let first_app = &snap_apps[0]; let base_title = if snap_apps.len() == 1 { first_app.name.clone() diff --git a/src-tauri/src/system_info/updates.rs b/src-tauri/src/system_info/updates.rs index b9a2f48..8d135a7 100644 --- a/src-tauri/src/system_info/updates.rs +++ b/src-tauri/src/system_info/updates.rs @@ -296,7 +296,7 @@ pub fn check_flatpak_updates(target_apps: &[String]) -> Vec { .filter_map(|line| { let parts: Vec<&str> = line.split('\t').collect(); - if parts.len() < 3 { + if parts.len() < 2 { return None; } @@ -309,9 +309,11 @@ pub fn check_flatpak_updates(target_apps: &[String]) -> Vec { } let name = parts[0].trim().to_string(); - let new_version = parts[2].trim().to_string(); - let current_version = get_flatpak_app_version(&app_id); + let mut new_version = parts.get(2).unwrap_or(&"").trim().to_string(); + if new_version.is_empty() { + new_version = current_version.clone(); + } Some(AppUpdate { name, @@ -431,7 +433,7 @@ pub fn check_native_updates(target_apps: &[String]) -> HashMap { } // Try Fedora/RHEL (dnf check-update) - if let Ok(output) = Command::new("dnf").args(["check-update"]).output() { + if let Ok(output) = Command::new("dnf").args(["check-update", "--refresh"]).output() { let stdout = String::from_utf8_lossy(&output.stdout); available.extend(check_dnf_updates(&stdout, target_apps)); } diff --git a/src/views/app_manager/store.ts b/src/views/app_manager/store.ts index 61ca9d3..5283313 100644 --- a/src/views/app_manager/store.ts +++ b/src/views/app_manager/store.ts @@ -166,8 +166,26 @@ export const useAppManagerStore = create((set, get) => ({ try { const targetUpdates = updates.filter((u) => selectedPackageIds.includes(u.package_id)); await invoke("run_app_updates", { updates: targetUpdates }); + + set((state) => { + const hasErrors = targetUpdates.some(t => { + const st = state.appStatuses.find(a => a.name === t.name); + return st?.status === "error"; + }); + + return { + // If there were errors, stay on the progress view so they can read the error badges + modalPhase: hasErrors ? "progress" : "done", + // Only remove apps that actually completed successfully + updates: state.updates.filter((u) => { + const st = state.appStatuses.find(a => a.name === u.name); + return st?.status !== "done"; + }), + selectedPackageIds: [], + }; + }); } catch { - // Preserve status + set({ modalPhase: "done" }); } finally { unlisten(); set((state) => {