Commit graph

75 commits

Author SHA1 Message Date
maxsoch af220a4ee6 strip the build-id suffix from cask version labels
Some casks (claude and others) version themselves as
"user-facing-version,build-id" — the hash after the comma only
feeds brew's own download URL and was never meant for display,
but showed up verbatim in the Outdated menu. displayVersion() now
truncates at the first comma for both the installed and target
version shown in the label; brew commands are unaffected since
they operate on the package name, never this string.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 18:02:46 +02:00
maxsoch 210a8167b8 Merge branch 'fix/upgrade-only-menu' 2026-07-14 17:54:40 +02:00
maxsoch 778671e751 keep the Upgrade only… submenu visible, grey out the empty kind
It only appeared when both formulae and casks were outdated; with a
single kind it vanished entirely. Now it shows whenever anything is
outdated, with the empty kind greyed out (autoenablesItems is off —
AppKit would otherwise re-enable the items from the responder chain
on every menu open).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 17:54:40 +02:00
maxsoch 390e7e526b Merge branch 'feature/running-apps' 2026-07-14 17:52:56 +02:00
maxsoch 9131c88a64 detect running apps: skip their casks on upgrade, name them in the dialog
RunningApps maps installed casks to their .app bundles via the
Caskroom copy (no brew process needed) and checks them against
NSWorkspace's running applications. Two settings in a new "Running
apps" section: auto-upgrade skips running casks (on by default —
that flow has no dialog to warn anyone), manual bulk upgrades can opt
in. The auto-upgrade filter runs before the trigger decision so a
permanently running app cannot start an upgrade cycle that would skip
it anyway. The confirmation dialog now names the apps actually
running instead of a blanket warning, and stays silent when none are.
Single-package upgrades from the Outdated menu always proceed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 17:52:56 +02:00
maxsoch 9f54e27e47 release 0.2
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 23:24:08 +02:00
maxsoch 3d6f39048a Merge branch 'fix/upgrade-failures' 2026-07-13 19:22:54 +02:00
maxsoch 861c356e80 upgrade formulae one at a time and surface failed upgrades
brew's batch upgrade --formula errors out on the first broken formula
(no bottle for this macOS, deprecated, …), blocking every other
pending formula — and the failure only showed in the logs, so the
menu silently fell back to "N outdated" as if nothing had run.
Formulae now upgrade individually like casks already did, failures
of any kind (including a failed reinstall fallback) are collected,
and the status line reports them once after the closing refetch.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 19:22:54 +02:00
maxsoch 44705cccae Merge branch 'feature/menu-shortcuts' 2026-07-13 19:11:39 +02:00
maxsoch 1c997751b7 add keyboard shortcuts for Cleanup (C) and Tips (T)
The ⌥ purge alternate carries the same key equivalent as Cleanup —
alternate pairing requires it — so ⌥C triggers the purge variant.
The shortcut list in the Tips window is updated accordingly.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 19:11:39 +02:00
maxsoch 03c8250def Merge branch 'fix/upgrading-icon' 2026-07-13 19:08:59 +02:00
maxsoch 29501abcd6 show the updating icon during upgrades and cleanup, not just checks
Only fetchOutdated ever touched the menu bar icon, so during an
upgrade it kept showing "updates available". Every long-running
operation now sets the updating icon when it starts; paths that end
without a refetch (cleanup, failed refresh) restore the icon of the
last known state via a new cachedStateIcon() helper. Single-package
upgrades also gained a status line, they showed nothing before.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 19:08:59 +02:00
maxsoch 20ca403d53 Merge branch 'feature/tips' 2026-07-13 19:04:08 +02:00
maxsoch 19e63f0dcd add a Tips window for the app's less discoverable features
Lists the ⌥ cleanup variant, menu keyboard shortcuts, selective
upgrades, the self-updating cask reinstall behavior and the Logs
reflex. Content is a plain array in TipsWindowController — adding a
tip is one entry. No pbxproj change needed: the project uses Xcode 16
synchronized groups, so new files in BrewBar/ are picked up.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 19:04:08 +02:00
maxsoch 149f394ecf Merge branch 'feature/upgrade-menu' 2026-07-13 18:58:32 +02:00
maxsoch 2931b80074 fall back to reinstall when a cask cannot be upgraded as-is
Self-updating apps (Zen, Chrome, …) drift from what brew installed;
brew then refuses to upgrade the cask in place, prints a warning and
does nothing, so the package stayed outdated forever. Detect the
warning and run the fix brew itself suggests (reinstall --cask
--force). Single-package upgrades now also route casks through
upgrade --cask instead of a bare brew upgrade.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 18:58:32 +02:00
maxsoch c4baca221d split Upgrade All into formulae & casks plus selective upgrades
"Upgrade All" becomes "Upgrade formulae & casks", and an "Upgrade
only…" submenu offers Formulae/Casks with live counts — shown only
when both kinds are outdated, since with a single kind the main item
already upgrades exactly what there is. All three actions share one
parametrized upgrade(formulae:casks:) flow; the confirmation dialog
lists only the selected packages and drops the running-app warning
when no casks are involved.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 18:57:18 +02:00
maxsoch 6a1c92a6fd Merge branch 'feature/cleanup'
Resolved by combining both sides in upgradeSingle (two-param runBrew
closure from fix/offline-refresh + cleanup hook from feature/cleanup)
and adapting the cleanup branch's runBrew calls to the new signature.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 18:36:08 +02:00
maxsoch 4494b937bf Merge branch 'fix/offline-refresh' 2026-07-13 18:34:14 +02:00
maxsoch e2413e52d2 add brew cleanup: menu action, ⌥ purge variant, optional post-upgrade run
A Cleanup menu item runs brew cleanup; holding ⌥ swaps it for a purge
variant (cleanup --prune=all, which also drops cached downloads of
current versions). A new opt-out-by-default setting interposes a plain
cleanup between the end of any upgrade path and its closing refetch.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 19:37:41 +02:00
maxsoch 64b9bae564 surface brew failures instead of reporting a false all-up-to-date
runBrew now reports the process exit status to its completion handler.
refreshAll and fetchOutdated stop on failure and show "Check failed —
see Logs": NWPathMonitor cannot see a network that is up but broken
(captive portal, dead DNS, git host down), while the exit code of the
brew command itself catches every failure mode. Upgrade commands keep
ignoring the flag on purpose — a failed upgrade leaves the package in
the outdated list at the next fetch, so the final state stays correct.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 19:28:09 +02:00
maxsoch c6372ece05 skip refreshes while offline, catch up when the connection returns
brew outdated never touches the network: offline, a refresh silently
succeeded on stale local tap data and reported "all up to date". An
NWPathMonitor now gates refreshAll — offline shows a dedicated status
instead, and every offline→online transition triggers a refresh, which
covers the launch refresh, scheduled refreshes skipped during an
outage, and clearing the offline status when the connection returns.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 19:27:41 +02:00
maxsoch 3f130af56a re-release as 0.1: app is still pre-release quality
Replace the premature 1.0 with 0.1 to signal beta status. Document the
0.x version scheme and the brew-never-downgrades caveat in RELEASING.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 18:52:17 +02:00
maxsoch 324bd8b3ba update README: features, brew install, build instructions
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 06:41:18 +02:00
maxsoch 73e0db3519 cask: document quarantine removal, --no-quarantine no longer exists
Recent Homebrew removed the --no-quarantine option entirely, so the
install docs and cask now instruct clearing the quarantine attribute
with xattr after install/upgrade. The cask prints it as a caveat at
install time.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 06:38:10 +02:00
maxsoch 19c5436441 cask: use symbol form for macOS requirement (brew deprecation)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 06:36:20 +02:00
maxsoch de59c46cbe lower deployment target to macOS 13 Ventura
15.6 was just the Xcode default from project creation, not a real
requirement — the newest API in use is SMAppService (macOS 13). The
compiler enforces the audit: everything builds and tests pass at 13.0.
Release zip rebuilt; cask gets the new sha256 and a :ventura floor.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 06:33:07 +02:00
maxsoch 4da19145a8 document the release process in RELEASING.md
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 06:30:06 +02:00
maxsoch 5c6b46ef7a add Homebrew cask so BrewBar installs via brew
The repo doubles as a Homebrew tap: brew tap accepts an explicit git
URL, so Casks/brewbar.rb here is enough — no separate homebrew-* repo.
The cask downloads the zipped Release build from the matching Forgejo
release (v<version>) and verifies it against the pinned sha256. dist/
(local release artifacts) is gitignored.

Install:
  brew tap maxsoch/brewbar https://forgejo.socheleau.fr/maxsoch/BrewBar.git
  brew install --cask --no-quarantine maxsoch/brewbar/brewbar

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 06:21:39 +02:00
maxsoch ec33fbcf6c Merge branch 'serial-brew': brew commands run one at a time
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 06:10:54 +02:00
maxsoch 9575339209 Merge branch 'notifications-fix': explain notification permission state
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 06:08:55 +02:00
maxsoch 323f64f921 Merge branch 'log-window-ui': monospace log window with smarter autoscroll
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 06:07:28 +02:00
maxsoch ecb6728f42 Merge branch 'window-activation': windows front and center, Cmd-Tab entry while open
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 06:06:20 +02:00
maxsoch fe469d9157 fix: explain notification permission state instead of failing silently
requestAuthorization only shows the system prompt once ever; when
permission is already denied it silently returns false and the checkbox
just snapped off with no explanation. toggleNotify now checks the
authorization status first: already-authorized enables directly,
not-determined triggers the system prompt (errors surfaced in an
alert), and denied shows an alert with an Open System Settings button
pointing at the Notifications pane.

Also adds a UNUserNotificationCenterDelegate so banners are shown even
while BrewBar is the active app — otherwise macOS suppresses them,
which reads as "notifications don't work" right after enabling.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 06:00:45 +02:00
maxsoch 5b1820b7e1 fix: run brew commands on a serial queue, one at a time
runBrew dispatched onto the global concurrent queue, so overlapping
operations (refresh timer firing mid-upgrade, quick manual actions)
ran multiple brew processes at once — each able to summon its own sudo
password dialog, hence duplicate password windows. A private serial
queue makes every brew command wait for the previous one, so a second
dialog cannot appear while one is open. Log output no longer
interleaves either.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 05:59:48 +02:00
maxsoch 0309bd38d7 improve log window: monospace, scrollbar, smarter autoscroll
Monospaced font and semantic colors (dark-mode correct), visible
vertical scroller (was missing), text inset, centered window with a
proper title. Autoscroll now only follows the tail when already at the
bottom instead of yanking the scroll position on every log line.

Also sets isReleasedWhenClosed = false, fixing a latent crash on
close-then-reopen (the settings window already had this guard).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 05:59:11 +02:00
maxsoch 59683ee8a9 fix: bring windows to front and into Cmd-Tab while open
Accessory apps never appear in the Cmd-Tab switcher and macOS does not
activate them when they open windows — the log window opened behind
other apps (settings only worked via a manual activate call).

presentWindow(of:) now switches the activation policy to .regular
while a BrewBar window is open, giving a Dock icon and a Cmd-Tab entry;
a willClose observer drops back to .accessory once the last window is
gone. Used by both showLogs and showSettings.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 05:58:23 +02:00
maxsoch 25e4476317 Merge branch 'per-cask-upgrades': sudo prompts name the app being upgraded
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 05:56:40 +02:00
maxsoch 0020316dab upgrade casks one at a time so sudo prompts name the app
Batched cask upgrades give no per-package hook, so the password dialog
could only echo the whole command. upgradeCasksSequentially chains one
"upgrade --cask <name>" per outdated cask (from the typed cache), each
with an askpass message naming that cask; the status line follows along
("Upgrading tailscale-app..."). Used by both Upgrade All and
auto-upgrade; formulae stay batched since they don't need sudo.

Explicitly named casks upgrade even when self-updating, so the
per-cask commands need no --greedy. Casks that turn outdated only
after the preceding brew update surface in the final refetch.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 05:45:35 +02:00
maxsoch 0def852416 let brew callers customize the sudo dialog message
runBrew gains an optional askpassMessage; the dialog reads "BrewBar
needs your administrator password to <message>." and defaults to naming
the brew command. Groundwork for per-cask upgrade prompts that name the
app instead of the command.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 05:44:32 +02:00
maxsoch 89921275b8 show the triggering brew command in the sudo password dialog
The askpass script is now rewritten before each brew invocation with
the current command baked into the dialog text ("...to run: brew
upgrade tailscale-app") instead of a generic message written once at
launch. The command text is whitelisted to characters that cannot
break out of the shell/AppleScript quoting it is embedded in.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 22:41:42 +02:00
maxsoch 421ae5c40d Merge branch 'outdated-json': typed outdated parsing and type-aware auto-upgrade
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 22:39:50 +02:00
maxsoch c296c03ee5 update menu and count before auto-upgrade starts
fetchOutdated returned before refreshing the UI when auto-upgrade
triggered, so the menu showed stale state for the whole upgrade. Now
the fetched list lands in the menu, status line and menu bar count
first; runAutoUpgrade then overwrites the status with its own message.
Notification stays after the post-upgrade refetch — leftovers only.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 22:28:17 +02:00
maxsoch 813fc47461 fetch outdated as JSON; auto-upgrade only for matching package kinds
fetchOutdated now uses --json and the typed parser, so cachedOutdated
knows formulae from casks. The auto-upgrade trigger checks each kind
against its own toggle: an outdated cask no longer starts a pointless
"upgrade --formula" run (previously any outdated package triggered
whichever auto-upgrades were enabled). runAutoUpgrade takes explicit
formulae/casks flags instead of re-reading settings.

Bonus from the version info now available: outdated menu items and the
Upgrade All confirmation show "name installed → current" instead of
bare names. The legacy line parser and its tests are removed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 22:27:37 +02:00
maxsoch 7b3468126a add typed JSON parser for brew outdated
parseOutdatedJSON decodes brew outdated --json into OutdatedPackages,
which separates formulae from casks — the plain-text output is just
names with no type information. Codable structs mirror the JSON shape,
with convertFromSnakeCase mapping installed_versions to Swift naming.
Undecodable input (brew error text, empty output) parses as .none.

The legacy line parser stays until callers switch over next commit.
Covered by 4 new unit tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 22:26:12 +02:00
maxsoch 3a5a7ee623 explain the greedy setting in the settings window
Add a note under the greedy checkbox spelling out what self-updating
casks are and that the setting affects both the outdated listing and
upgrades (Upgrade All and auto-upgrade). The indented-gray-note layout
is extracted into a noteRow helper, now shared with the cask warning.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 22:18:23 +02:00
maxsoch 741f9572b7 fix: Upgrade All must pass --greedy when greedy listing is on
--greedy exists on both brew commands: outdated --greedy lists
self-updating casks, upgrade --greedy actually upgrades them (plain
upgrade skips them, per brew's own help). The listing and auto-upgrade
paths had the flag but manual Upgrade All didn't, so greedy-listed
casks would survive Upgrade All and reappear as outdated forever.

Single-package upgrades don't need it: brew upgrades an explicitly
named cask even when it self-updates.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 22:16:22 +02:00
maxsoch 0bc18b79b8 make the Upgrade All confirmation dialog optional, on by default
New "Ask for confirmation before Upgrade All" checkbox; when disabled,
Upgrade All runs immediately. Auto-upgrade is unaffected either way —
it never prompts.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 22:09:26 +02:00
maxsoch 29b9412a34 add optional outdated count next to the menu bar icon
Off by default. The status item button shows both image and title
(imagePosition = .imageLeft), so the icon stays visible with the count
beside it. Cleared when up to date or when the setting is off; toggling
applies immediately from the cached list without refetching.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 22:08:51 +02:00
maxsoch 32d81423ab add update notifications, only when new packages appear
Off by default. Enabling the checkbox requests notification permission
(UNUserNotificationCenter); if denied, the checkbox reverts — the auth
callback arrives on a background queue, so the UI update hops to main.

notifyIfNeeded remembers the last notified set and stays silent while
the outdated list is unchanged, so an hourly refresh doesn't re-ping
about the same packages. The set resets as packages get upgraded.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 22:08:10 +02:00