Skip to content

Filter un-cleanable items at module boundary, not just ScanCoordinator (1.5.2)#25

Merged
iliyami merged 3 commits into
mainfrom
fix/filter-at-module-level
Jun 1, 2026
Merged

Filter un-cleanable items at module boundary, not just ScanCoordinator (1.5.2)#25
iliyami merged 3 commits into
mainfrom
fix/filter-at-module-level

Conversation

@iliyami
Copy link
Copy Markdown
Owner

@iliyami iliyami commented Jun 1, 2026

Why this is needed (the 1.5.1 fix was incomplete)

Real-world test on 1.5.2 produced the exact same 10 errors 1.5.1 was supposed to eliminate. Root cause: the 1.5.1 filter sat in ScanCoordinator.scanModules, which is only the Smart Scan path. The per-module Clean flows — the ones users actually hit when they open System Junk, Mail Attachments, Trash Bins, Malware, Privacy, Large & Old Files, or Duplicates — all bypass the coordinator and call module.scan() directly from their views/view models. Those direct callers got unfiltered results.

SystemJunkViewModel.swift:57   async let scanTask = module.scan()
MailAttachmentsView.swift:53   async let scanTask = module.scan()
MalwareView.swift:53           async let scanTask = module.scan()
LargeOldFilesView.swift:52     async let scanTask = module.scan()
TrashBinsView.swift:54         async let scanTask = module.scan()
PrivacyView.swift:53           async let scanTask = module.scan()
DuplicatesView.swift:167       let scanResults = await module.scan()

System Junk is the primary feature, and its viewmodel was the one the user's repro hit.

The fix

Push the filter into the module, so the contract travels with the result:

A ScanModule.scan() only returns items the current process could trash.

Mechanically: Array<ScanResult>.filteringUncleanable() is added in MacCleanKit/CleanFilter.swift, and every producing module's scan() calls it before returning. ScanCoordinator's inline filter is left in place as belt-and-suspenders — modules now do the work, but a forgetting-prone future module still gets caught for Smart Scan.

Files touched

  • Sources/MacCleanKit/CleanFilter.swift — new Array<ScanResult>.filteringUncleanable() extension.
  • 7 producing modules — append .filteringUncleanable() to their scan() return:
    • SystemJunkModule, MailAttachmentsModule, TrashBinsModule, MalwareModule, PrivacyModule, DuplicatesModule, LargeOldFilesModule
  • 6 non-producing modules untouched (Optimization, Maintenance, Updater, Uninstaller, SpaceLens, Shredder all return [] from scan() and use sidecar managers).
  • Tests/MacCleanKitTests/CleanFilterTests.swift — one new test asserting the array extension preserves category and autoSelect while dropping un-cleanable items.
  • VERSION + MCConstants.appVersion → 1.5.2.

Test plan

  • swift test — 388 tests, 4 skipped, 0 failures
  • swift build — clean
  • Version sync OK
  • Local install of the 1.5.2 DMG: System Junk clean shows 0 errors (or only real ones, not the prior 10 fixtures)
  • CI green, Release green, brew cask synced after merge

iliyami added 3 commits June 1, 2026 09:40
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.
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.
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.
@iliyami iliyami self-assigned this Jun 1, 2026
@iliyami iliyami merged commit 5e7aebc into main Jun 1, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant