Tags: iliyami/MacSai
Tags
Fix menu-bar launch crash on macOS 26 — hop to main actor via async o… …pen (1.8.4) (#60) NSWorkspace.openApplication's completion handler is delivered on LaunchServices' background queue (com.apple.launchservices.open-queue). Because MenuBarLauncher is @mainactor, the trailing closure was inferred main-actor-isolated; on the macOS 26 runtime its main-actor executor assertion traps (SIGTRAP) the instant it fires off-main — crashing the app when the menu-bar widget launches the helper. Older runtimes tolerated the violation, so it only surfaced on macOS 26. Fixes #58. Switch to the async openApplication overload inside a @mainactor Task: the continuation resumes on the main actor, so the lastError write is safe and no main-actor closure is ever handed to LaunchServices. Adds a regression test (openHelper against a non-launchable URL) that, on macOS 26, aborts the test process if this path ever touches @mainactor state off-main again. Test count 484 -> 485.
Mac Clean 1.8.2 — community feedback #21: file-list UX, uninstaller, … …scan persistence, settings & sidebar folding Lands the stacked work from #44–#48 in one release (1.8.2). Closes #21. - File lists: collapsible categories, whole-row select, Reveal in Finder (#44) - Uninstaller: confirm dialog, real app icons, removable non-critical Apple apps (#45) - Scans preserved across navigation; views kept alive (no switch lag, scans continue) (#46, #48) - Settings: dynamic installed-language discovery + search, English always kept; sidebar group folding; removed dead sidebar toggle (#47, #48) - Updater: real app icons; Smart Scan real cleanup; SpaceLens off-main + zoom-out (earlier in stack)
Fix Trash Bins: permanently delete instead of re-trashing (no-op) (1.… …6.1) (#30) Emptying the Trash reported success but never deleted anything — a rescan re-found every item and ~/.Trash stayed full. Root cause: CleanActions.executeUserClean routed every non- universalBinaries category, including .trashBins, through the engine's .trash mode (FileManager.trashItem). Trash Bins items already live in ~/.Trash, and trashItem on an already-trashed file is a silent no-op: it returns success (so removedCount incremented and the UI showed "cleaned N items, freed X bytes") while leaving the file exactly where it was. Fix: partition selected items by category and send .trashBins through .permanent mode (FileManager.removeItem), which actually empties the Trash. Ancestor/descendant dedup is applied to the permanent path too. Because emptying the Trash is now irreversible, the Trash Bins Clean button shows an "Empty the Trash? … cannot be undone" confirmation on every empty (ModuleContainerView.confirmEmptyTrash), regardless of selection size. Other modules still move to Trash (recoverable) and keep the existing large-selection confirm. Adds a red→green regression test that places a real file in ~/.Trash and asserts executeUserClean removes it.
README: add Mole, surface recently-shipped features in comparison + s… …afety model (#26) * README: add Mole to comparison, surface recently-shipped features Comparison table: - Add Mole column (tw93/Mole — 54.2k stars, CLI-first MIT cleaner) - New rows for what the codebase actually does now and what differentiates Mac Clean from the rest: * Native GUI app (CLI-only callout for Mole) * Universal Binary thinning (lipo-based fat-binary slimming) * In-app activity log viewer (post-clean sheet with copy-all) - Test count badge 275 → 388 Features section: - System Junk row calls out Universal Binary thinning with a one- sentence mechanic (lipo + native-arch detection) - Maintenance row mentions severity tagging, 'Run All' confirmation, mid-flight cancellation Safety Model: - Adds firmlink canonicalization (var↔private/var etc.) - Adds pre-scan cleanability filter (drops items access(W_OK) denies so the UI never offers root-owned or UF_DATAVAULT items) - Replaces stale '10,000 file cap' line with the real story — chunked cleanup, 50k confirmation, recursive byte accounting - Adds in-app log viewer + 30-day prune callout - Adds kernel-enforced XPC code-signing requirement Acknowledgments: - Fix dead links: Mole (nicehash/Mole → tw93/Mole) and Tencent Lemon Cleaner (nicehash/Lemon → Tencent/lemon-cleaner). * README: document the menu bar widget with screenshot Adds assets/menu_bar.png and rewrites the "Menu Bar Monitor" section into "Menu Bar Widget" — the shipped glassmorphism widget: 2×2 stat ring grid (CPU/Memory/Disk/Battery), network/uptime/swap, actionable Recommendations, Protection status, Connected devices, and throttled health alerts. Bumps the test badge 388 → 403 to match main. Merged main into this branch first so it carries the merged menu-bar and scan-correctness work. Verified the full local gate (check-version-sync + swift build + swift test, 403 passing) before pushing.
Fix three scan-correctness bugs: Trash FDA, duplicate rows, inflated … …estimate (#29) Root cause shared by two of these: TargetedScanner swallowed permission denials and never de-duplicated overlapping targets. 1. "Trash is empty" on a full Trash. ~/.Trash is TCC-protected; without Full Disk Access the recursive enumerator silently yields nothing and fileExists() still returns true, so the scan looked empty. The scanner now probes each target root with open(O_RDONLY|O_DIRECTORY) and reports EPERM/EACCES via the new ScanOutcome.permissionDenied. TrashBinsView surfaces a "Full Disk Access needed" empty-state (Open Settings / Rescan) instead of a false "Trash is empty." 2. Large & Old Files listed the same file twice. That module scans ~ recursively AND ~/Downloads recursively, so Downloads files were enumerated by both targets. Scan results are now de-duplicated by URL. 3. Cleanup freed less than the estimate (~2 GB estimated, 934 MB freed). Same duplicates: the pre-clean estimate summed each duplicate FileItem, while Clean trashes each URL once (the second copy hits a benign "already gone" skip). selectedSize() now counts each URL once, so the "X will be freed" preview matches reality even when a file appears in multiple categories. Bump 1.5.2 -> 1.5.3.
Filter un-cleanable items at module boundary, not just ScanCoordinato… …r (1.5.2) (#25) * Add Array<ScanResult>.filteringUncleanable() — the contract carrier Modules will call this on their results before returning so every consumer (ScanCoordinator AND per-module ViewModels/Views that call `module.scan()` directly) sees a filtered set. The single 1.5.1 wire in ScanCoordinator was a partial fix — it only covered Smart Scan; direct module.scan() calls from SystemJunkViewModel, MailAttachmentsView, TrashBinsView, MalwareView, PrivacyView, LargeOldFilesView, and DuplicatesView still leaked uncleanable items to the UI. Adds one Array-extension test asserting category + autoSelect are preserved across the filter. * Apply .filteringUncleanable() inside every producing module's scan() Each ScanModule now returns only items the current process could trash. Callers don't need to know about the filter — the contract travels with the result. This is what 1.5.1 was supposed to be: the fix has to live at the module boundary, not at the ScanCoordinator, because seven views/ViewModels call `module.scan()` directly: - SystemJunkViewModel — the path the user reported on - MailAttachmentsView - TrashBinsView - MalwareView - PrivacyView - LargeOldFilesView - DuplicatesView ScanCoordinator (Smart Scan) was the only path that went through the 1.5.1 inline filter, so the per-module Clean flow leaked the exact same 10 un-cleanable items the filter was designed to drop: /Library/Caches/com.apple.*, /private/var/log/com.apple.xpc.launchd/*, /Library/Logs/PaloAltoNetworks/*, and the data-vaulted ~/Library/Caches/com.apple.* set. The three modules that don't produce file results (Optimization, Maintenance, Updater, Uninstaller, SpaceLens, Shredder all return [] from scan()) are untouched. * Bump 1.5.1 → 1.5.2: filter at module boundary, not just coordinator Patch bump. Same UX outcome as 1.5.1 intended — un-cleanable items disappear from the post-clean error count — but the fix is applied inside each producing module's scan() instead of solely at ScanCoordinator, so it covers every per-module Clean flow.
PreviousNext