Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,5 @@ __pycache__/
# Local Package Builds
/build

docs/marketing
docs/marketing
.antigravitycli
22 changes: 22 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
32 changes: 16 additions & 16 deletions src-tauri/Cargo.lock

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

2 changes: 1 addition & 1 deletion src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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"
Expand Down
12 changes: 7 additions & 5 deletions src-tauri/src/system_info/commands/run_app_updates.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ pub async fn run_app_updates(app: tauri::AppHandle, updates: Vec<AppUpdate>) ->

// 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();
Expand All @@ -58,6 +58,7 @@ pub async fn run_app_updates(app: tauri::AppHandle, updates: Vec<AppUpdate>) ->
} 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"
Expand All @@ -76,6 +77,7 @@ pub async fn run_app_updates(app: tauri::AppHandle, updates: Vec<AppUpdate>) ->
} 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"
Expand Down Expand Up @@ -134,7 +136,7 @@ pub async fn run_app_updates(app: tauri::AppHandle, updates: Vec<AppUpdate>) ->
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();
Expand All @@ -145,7 +147,7 @@ pub async fn run_app_updates(app: tauri::AppHandle, updates: Vec<AppUpdate>) ->
}

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();

Expand All @@ -157,7 +159,7 @@ pub async fn run_app_updates(app: tauri::AppHandle, updates: Vec<AppUpdate>) ->
"update",
"flatpak",
&arg_refs,
true,
false,
first_icon,
Some(target_apps_clone),
)
Expand Down Expand Up @@ -189,7 +191,7 @@ pub async fn run_app_updates(app: tauri::AppHandle, updates: Vec<AppUpdate>) ->
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();
Expand Down
6 changes: 3 additions & 3 deletions src-tauri/src/system_info/commands/uninstall_app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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()
Expand Down
10 changes: 6 additions & 4 deletions src-tauri/src/system_info/updates.rs
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,7 @@ pub fn check_flatpak_updates(target_apps: &[String]) -> Vec<AppUpdate> {
.filter_map(|line| {
let parts: Vec<&str> = line.split('\t').collect();

if parts.len() < 3 {
if parts.len() < 2 {
return None;
}

Expand All @@ -309,9 +309,11 @@ pub fn check_flatpak_updates(target_apps: &[String]) -> Vec<AppUpdate> {
}

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,
Expand Down Expand Up @@ -431,7 +433,7 @@ pub fn check_native_updates(target_apps: &[String]) -> HashMap<String, String> {
}

// 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));
}
Expand Down
20 changes: 19 additions & 1 deletion src/views/app_manager/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,8 +166,26 @@ export const useAppManagerStore = create<AppManagerStore>((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) => {
Expand Down
Loading