Skip to content

Add a Cleanup tab for brew cleanup - #292

Open
tongliuTL wants to merge 4 commits into
Homebrew:mainfrom
tongliuTL:cleanup-tab
Open

tongliuTL wants to merge 4 commits into
Homebrew:mainfrom
tongliuTL:cleanup-tab

Conversation

@tongliuTL

@tongliuTL tongliuTL commented Oct 23, 2026 •

Copy link
Copy Markdown

What and why

Adds a Cleanup tab for brew cleanup, the main maintenance task missing from BrewUI. Old versions, stale downloads and unneeded dependencies build up silently, and Cakebrew users switching to BrewUI expect a place to reclaim that space without Terminal.

  • On each visit the tab runs brew cleanup --dry-run through the command centre, so the preview appears in the console like Doctor's read. Installs and upgrades elsewhere change what is stale, so the preview is not cached across visits.
  • It lists unneeded dependencies (the autoremove block) and every path Homebrew would remove, using Homebrew's own sizes and its "would free approximately" total rather than recomputing them. Lines the parser does not recognise are left to the console.
  • Clean Up asks for confirmation first, naming any formulae that will be uninstalled. It then runs brew cleanup with the same flags as the preview, so what runs is what was reviewed, and previews again afterwards. It is a mutating operation, so the installed inventory reconciles afterwards.
  • Remove all cached downloads adds --prune=all to both the preview and the run.
  • brew cleanup already runs autoremove unless HOMEBREW_NO_AUTOREMOVE is set, so there is no separate brew autoremove action.
  • Cleanup sits after Doctor in the sidebar: ⌘1–⌘5 are unchanged and Configuration moves to ⌘7.

Out of scope: brew bundle cleanup. It uninstalls everything a chosen Brewfile does not list, so it needs a file picker and deserves its own review.

Validation

macOS 27.0.1, Apple M5 Pro, Xcode 27.1, Swift 6.4.

  • scripts/test: all pass, including the new parser, repository, view model, command and AXID tests.
  • mint run swiftformat --lint ., mint run swiftlint lint --strict, BrewUILint over the production tree and scripts/localize verify: all pass.
  • scripts/test-ui -only-testing:BrewUITests/CleanupUITests -only-testing:BrewUITests/NavigationUITests: 5 tests pass.
  • Manual: ran the app against real Homebrew 7.0.10. The tab listed the four paths and the 67.5MB total that brew cleanup --dry-run prints in Terminal, and the dry run streamed in the console. Clean Up opened the confirmation, and Cancel left everything in place: a later dry run still reported 67.5MB.
  • scripts/localize sync stops on Xcode 27.1, which names package build folders <Target>-t.build, but the script looks for <Target>.build. Its app-catalog pass also reorders existing translations. I synced the new catalog with xcstringstool directly and added the two app-catalog keys by hand. The app catalog matches what the tool produces, ignoring order.
  • Not run: the full UI suite, and running Clean Up for real against this machine's Homebrew.
  • I followed the conventions and workflow, checked for duplicate PRs and kept this change focused.
  • I added regression coverage for bug fixes or explained why automated coverage is impractical, and reported the relevant validation above.

Screenshots

Before After
1-before-installed 2-after-cleanup-tab

Confirmation before running brew cleanup:

3-after-confirmation

Both builds ran against real Homebrew 7.0.10 on the same Mac, in light appearance.

AI assistance

  • AI was used to generate or assist with generating this PR.

Claude Code (Claude Opus 5.5) wrote the implementation, tests and this description, and ran the checks listed above.

🤖 Generated with Claude Code

tongliuTL and others added 2 commits October 23, 2026 11:05
Preview `brew cleanup --dry-run` through the command centre so the
Cleanup tab lists unneeded dependencies, old versions, stale downloads
and logs with Homebrew's own sizes and total. Clean Up runs
`brew cleanup` with the same flags after a confirmation, and an option
adds `--prune=all` to both.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Drop the unused raw output from CleanupReport, share the card chrome
and error text within the view, derive the row id from its path and
give the cleanup fixture its own scenario arm.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@coderabbitai

coderabbitai Bot commented Oct 23, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

📝 Summary

Summary by CodeRabbit

  • New Features
    • Added a Cleanup tab with a preview of old versions, stale downloads and unneeded dependencies, including Homebrew’s estimated space savings.
    • Review eligible items and confirm before running cleanup. You can optionally choose to remove all cached downloads.
    • Refresh the preview to check for newly eligible items. Cleanup uses the options shown in the preview.
    • Cleanup is now available from the sidebar and keyboard navigation.
  • Bug Fixes
    • Cleanup command output now appears in the console, including output the preview does not recognise.

The pull request adds a Cleanup tab with a dry-run preview of removable formulae and files, an optional --prune=all setting, and a confirmed cleanup action. It also adds app navigation, repository support, accessibility identifiers and automated tests.

Changes: Cleanup feature

Layer / File(s) Summary
Cleanup commands and report pipeline
Sources/BrewCore/Models/CleanupReport*, Sources/BrewCore/Operations/*, Sources/BrewRepositoryInterfaces/Protocols/CleanupRepository.swift, Sources/BrewRepositories/*, related interfaces, fakes and tests, ARCHITECTURE.md
Adds cleanup preview and execution commands, report types, and a repository that parses recognised Homebrew dry-run output. Tests cover parsing, command arguments, repository state and refresh behaviour.
Cleanup screen and actions
Sources/BrewFeatureCleanup/*, Tests/BrewFeatureCleanupTests/*
Adds a view model and screen that display cleanup results, support prune-all previews, request confirmation, run cleanup and reload the preview.
App integration and validation
Homebrew/*, Homebrew.xcodeproj/project.pbxproj, Package.swift, Sources/BrewAccessibilityID/AXID.swift, Sources/BrewAppEnvironment/*, BrewUITests/*, Tests/BrewAccessibilityIDTests/*, README.md
Connects the Cleanup feature to the app environment and sidebar. Adds accessibility identifiers, UI test scenarios, navigation checks and user-facing documentation.

Priority: ➖ Normal

Merge Risk: 🔵 Low · up to ecd91

Merge Risk: 🔵 Low · up to ecd91

The cleanup test suite may fail intermittently on slow workers; replace the fixed deadline with deterministic readiness before relying on it.

Pre-merge checks: Passed 4
Check name Status Explanation
Description check Passed The description clearly explains the Cleanup tab, preview and execution behaviour, validation, scope, and known test limitations. It is directly related to the changeset.
Title check Passed The title, "Add a Cleanup tab for brew cleanup", is concise, specific, and accurately summarises the primary change.
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments: 3
Sources/BrewFeatureCleanup/Views/CleanupView.swift: 2

94-94: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Expose one ViewModel property for the disabled state of the controls.

Both the "Check Again" button and the prune-all toggle compose viewModel.isRefreshing || viewModel.isCleaningUp in the view. The two sites must stay in step. The repository guideline requires one ViewModel property for each UI decision.

Add a derived property to CleanupViewModel and bind to it.

Proposed fix
 // CleanupViewModel.swift
+    /// Header controls wait while a preview or a cleanup run is in progress.
+    var areHeaderControlsDisabled: Bool {
+        isRefreshing || isCleaningUp
+    }
 // CleanupView.swift, headerControls
-            .disabled(viewModel.isRefreshing || viewModel.isCleaningUp)
+            .disabled(viewModel.areHeaderControlsDisabled)
 // CleanupView.swift, pruneAllToggle
-            .disabled(viewModel.isRefreshing || viewModel.isCleaningUp)
+            .disabled(viewModel.areHeaderControlsDisabled)

As per coding guidelines: "Expose one ViewModel property for each UI decision instead of composing multiple raw flags in a view."

Also applies to: 110-110

Source: Coding guidelines


140-140: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Replace the fixed font size with a theme token.

.font(.system(size: 44)) uses a fixed size. It does not scale with Dynamic Type. It is also a magic number in a view. The design system requires relative SwiftUI font styles. The coding conventions require the theme for typography.

Use a semantic font token from BrewUIComponents, or add one for large empty-state icons.

As per coding guidelines: "Support Dynamic Type – Yes – use relative SwiftUI font styles, not fixed sizes" and "Extend semantic tokens … instead of introducing raw colours or magic numbers in views."

Source: Coding guidelines

Tests/BrewFeatureCleanupTests/CleanupViewModelTests.swift: 1

153-164: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Wait for cleanup completion, not scheduler yields.

The tests wait for !viewModel.isCleaningUp, but waitUntil only performs 500 Task.yield() calls. The cleanup task can still be awaiting the command centre or the repository reload when those iterations finish. Use a target-shared readiness helper or an explicit test completion signal. Do not rely on the phase stream alone because the view model reloads before it clears isCleaningUp.

Source: Coding guidelines


ℹ️ Review info
⚙️ Run configuration
  • Source mode: Auto
  • Repository: Homebrew/BrewUI
  • Branch: main
  • File: .coderabbit.config.ts
  • Commit: e3510ee
  • Layers: Repository TS config (base), Organization UI (inherited)
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 1d009767-a908-49a6-a1e7-11c08b0a7557
📥 Commits

Reviewing files that changed from the base of the PR and between e3510ee and 13f5df2.

📒 Files selected for processing: 41
  • ARCHITECTURE.md
  • BrewUITests/Fixtures/ScenarioFixtures.swift
  • BrewUITests/Harness/BrewUITestScenario.swift
  • BrewUITests/Screens/CleanupScreen.swift
  • BrewUITests/Screens/Sidebar.swift
  • BrewUITests/Tests/CleanupUITests.swift
  • BrewUITests/Tests/NavigationUITests.swift
  • Homebrew.xcodeproj/project.pbxproj
  • Homebrew/BrewApp.swift
  • Homebrew/Features/MainWindow/Views/MainWindowView.swift
  • Homebrew/Localizable.xcstrings
  • Homebrew/Views/MainSidebarView.swift
  • Homebrew/Views/SidebarItem.swift
  • Package.swift
  • README.md
  • Sources/BrewAccessibilityID/AXID.swift
  • Sources/BrewAppEnvironment/RepositoryEnvironment.swift
  • Sources/BrewAppEnvironment/UnimplementedRepositories.swift
  • Sources/BrewCLI/LiveBrewMutatingCommandFactory.swift
  • Sources/BrewCore/Models/CleanupReport+Placeholdable.swift
  • Sources/BrewCore/Models/CleanupReport.swift
  • Sources/BrewCore/Operations/BrewCommands.swift
  • Sources/BrewCore/Operations/BrewMutatingCommandFactory.swift
  • Sources/BrewCore/Operations/BrewOperationModels.swift
  • Sources/BrewFeatureCleanup/Resources/Localizable.xcstrings
  • Sources/BrewFeatureCleanup/ViewModels/CleanupRemovalItem.swift
  • Sources/BrewFeatureCleanup/ViewModels/CleanupViewModel.swift
  • Sources/BrewFeatureCleanup/Views/CleanupRoot.swift
  • Sources/BrewFeatureCleanup/Views/CleanupView.swift
  • Sources/BrewRepositories/BrewCleanupRepository.swift
  • Sources/BrewRepositories/CleanupOutputParser.swift
  • Sources/BrewRepositoryInterfaces/CommandJob.swift
  • Sources/BrewRepositoryInterfaces/Fakes/Stubs.swift
  • Sources/BrewRepositoryInterfaces/PreviewSupport/PreviewSupport+Cleanup.swift
  • Sources/BrewRepositoryInterfaces/Protocols/CleanupRepository.swift
  • Tests/BrewAccessibilityIDTests/AXIDTests.swift
  • Tests/BrewCoreTests/BrewCommandsTests.swift
  • Tests/BrewCoreTests/BrewOperationIDTests.swift
  • Tests/BrewFeatureCleanupTests/CleanupViewModelTests.swift
  • Tests/BrewRepositoriesTests/BrewCleanupRepositoryTests.swift
  • Tests/BrewRepositoriesTests/CleanupOutputParserTests.swift

Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 7 remain after this review.

Comment thread Sources/BrewRepositories/BrewCleanupRepository.swift

@MikeMcQuaid MikeMcQuaid left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

brew cleanup is run automatically. Why does it need dedicated UI?

- Clear a stale refresh failure when the preview switches option.
- Expose one view model property for disabling the header controls.
- Wait on a deadline in the view model tests rather than a yield count.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@tongliuTL

Copy link
Copy Markdown
Author

@coderabbitai Nitpicks, addressed in 2f2b5a7:

  • Header controls: added CleanupViewModel.areHeaderControlsDisabled; both controls bind to it.
  • Test waits: waitUntil now polls against a 5s deadline instead of 500 yields.
  • Fixed 44pt icon: kept on purpose. It matches the Doctor empty state (DoctorView.swift:97), and BrewFonts has no icon-sized token. Happy to change both in a separate PR if maintainers want one.

@tongliuTL

tongliuTL commented Oct 23, 2026 •

Copy link
Copy Markdown
Author

@MikeMcQuaid Fair point — automatic cleanup covers most of it. What it doesn't give a GUI user:

  • Visibility: what would be removed and how much space it frees. The automatic run is silent and only happens as part of brew install/upgrade/reinstall (a full sweep at most every 30 days).
  • --prune=all: clearing the whole download cache on demand. Automatic cleanup only removes downloads older than 120 days.
  • Parity for Cakebrew users: this is the one tool I missed after switching. Cleaning up on demand meant opening Terminal.

If you'd rather not have a dedicated tab, I'm happy to shrink it to a "Clean Up" action inside Doctor or wherever you see best fit.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments: 1
Tests/BrewFeatureCleanupTests/CleanupViewModelTests.swift: 1

158-158: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

Replace the wall-clock deadline with a readiness signal.

If command-centre execution or the subsequent reload takes more than five seconds on a busy test worker, waitUntil records a failure even when both complete correctly. Wait for an explicit completion signal from the test double. Use an injected clock if the test must check timeout behaviour. As per coding guidelines, “Drive timeout tests from explicit readiness signals and injected clocks rather than short wall-clock deadlines.”

Source: Coding guidelines


ℹ️ Review info
⚙️ Run configuration
  • Source mode: Auto
  • Repository: Homebrew/BrewUI
  • Branch: main
  • File: .coderabbit.config.ts
  • Commit: e3510ee
  • Layers: Repository TS config (base), Organization UI (inherited)
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: b768f31c-a0a1-4acf-8780-ccf503559130
📥 Commits

Reviewing files that changed from the base of the PR and between 13f5df2 and 2f2b5a7.

📒 Files selected for processing: 5
  • Sources/BrewFeatureCleanup/ViewModels/CleanupViewModel.swift
  • Sources/BrewFeatureCleanup/Views/CleanupView.swift
  • Sources/BrewRepositories/BrewCleanupRepository.swift
  • Tests/BrewFeatureCleanupTests/CleanupViewModelTests.swift
  • Tests/BrewRepositoriesTests/BrewCleanupRepositoryTests.swift
🚧 Files skipped from review as they are similar to previous changes: 1
  • Tests/BrewRepositoriesTests/BrewCleanupRepositoryTests.swift

Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 7 remain after this review.

@coderabbitai

coderabbitai Bot commented Oct 23, 2026

Copy link
Copy Markdown

CodeRabbit chat interactions are restricted to organization members for this repository. Ask an organization member to interact with CodeRabbit, or set chat.allow_non_org_members: true in your configuration.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@tongliuTL
tongliuTL requested a review from MikeMcQuaid October 24, 2026 00:57

@MikeMcQuaid MikeMcQuaid left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please don't request my rereview. Want @graeme's thoughts before we go further here.

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.

2 participants