Skip to content

feat(menu-bar): show the app icon with a dot when an update is available - #1554

Merged
robinebers merged 1 commit into
mainfrom
capy/show-the-app-icon
Nov 3, 2026
Merged

robinebers merged 1 commit into
mainfrom
capy/show-the-app-icon

Conversation

@robinebers

@robinebers robinebers commented Nov 3, 2026 •

Copy link
Copy Markdown
Owner

TL;DR — When Sparkle finds an update, the menu-bar status item always ends with the OpenUsage app icon, with a small dot at its top-right. It sits after the limits and any +7 count, and shows even when Show In Menu Bar would otherwise hide the app icon.

What was happening

  • A scheduled update check only surfaced as the Update Available banner inside the panel. Nothing in the menu bar said an update was waiting, so users who rarely open the panel never saw it.

What this changes

  • MenuBarContent gains updateAvailable. StatusItemController reads UpdaterController.availableUpdateVersion != nil inside its existing observation, so the strip redraws when an update is found and when it clears.
  • MenuBarStripRenderer draws the app icon last in the strip (after the +N count) when updateAvailable is set. With nothing running out, App Icon Only, or Hide From Screen Share concealing, it shows the app icon on its own with the dot.
  • The dot is drawn into the template image, cut out of the mark with a 1 pt gap, so it follows light and dark menu bars and the highlighted state. No colour.
  • With no update, the status item is unchanged: same image, same size, same accessibility text.
  • README, docs/menu-bar.md, docs/updates.md, and CHANGELOG.

Heads-up

  • How the app learns about an update: no new mechanism. It reuses Sparkle's gentle-reminder delegate already in UpdaterController (supportsGentleScheduledUpdateReminders + standardUserDriverShouldHandleShowingScheduledUpdate → false + standardUserDriverWillHandleShowingUpdate), which sets availableUpdateVersion from the scheduled background check without showing any dialog. Other menu-bar apps do the same (read via GitHits: jordanbaird/ice Ice/Updates/UpdatesManager.swift, exo-explore/exo app/EXO/EXO/Services/SparkleUpdater.swift). A quiet checkForUpdateInformation probe would add a second check cycle beside Sparkle's own schedule, so I didn't add one.
  • UpdaterController is not changed. The dot follows availableUpdateVersion, so it clears exactly when the banner does: on the banner's close (snooze), when the user attends to the update, and when the update session ends (installed or skipped). This keeps SCO-66's update restore free to change the menu, Settings, and banner code.
  • A user-initiated Check for Updates… shows Sparkle's own window and never sets availableUpdateVersion, so it does not add the dot; only the background check does.
  • Dev builds have no feed, so the tests and renders inject the state through MenuBarContent.updateAvailable rather than a live appcast. Not checked in the running app with a real update.

Tests

MenuBarUpdateIconTests (new): the plain app icon with no update; the dotted icon with nothing running out, at the app icon's size, a template, with ink at the top-right and identical pixels outside that corner; and with a strip plus +7, the strip widens, stays a template, and its rightmost column is the dot (top half) where it was the +7 capsule before.

On the Mac mini (macOS 15.7.7, nice -n 10 script/test_quiet.sh --filter …):

  • Red with the tests and the model/controller changes but without the renderer change: 2 of 3 fail ("the app icon drew nothing", "far right is not the icon's top-right dot", and the icon-only case returns nil). The no-update case passes, as it should.
  • Green with the full change: MenuBarUpdateIconTests 3/3, MenuBarStripTrimTests 5/5, MenuBarStripGateTests 2/2, MenuBarContentTests 13/13.

Screenshots

Offscreen renders of the status item image (a scratch test, not committed), top to bottom: app icon only without and with an update, three limits without and with an update, three limits plus +7 without and with an update.

Light menu bar:
light

Dark menu bar:
dark

Open in Capy


Note

Low Risk
Menu-bar rendering and accessibility only; reuses existing UpdaterController.availableUpdateVersion with no changes to update or auth logic.

Overview
When Sparkle’s background check finds an update (same signal as the in-panel Update Available banner), the menu bar status item now ends with the OpenUsage app icon and a small template dot at its top-right—after limit labels and any +N overflow, and even when App Icon Only, nothing is running out, or screen-share hiding would normally show only the plain icon.

MenuBarContent gains updateAvailable (including VoiceOver text). StatusItemController takes UpdaterController, observes availableUpdateVersion != nil, and passes the flag into strip rebuilds. MenuBarStripRenderer adds appIconWithUpdateDot (dot cut out from the mark) and appends that icon to the strip when the flag is set.

User-initiated Check for Updates… does not set this state, so behavior there is unchanged. Docs (README, CHANGELOG, menu-bar.md, updates.md) and MenuBarUpdateIconTests cover layout, template/dot pixels, and accessibility.

Reviewed by Cursor Bugbot for commit 855b839. Bugbot is set up for automated code reviews on this repo. Configure here.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

@pullfrog pullfrog 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.

✅ No new issues found.

Reviewed changes Reviewed the complete diff and traced update-state propagation, redraw gating, rendering, and accessibility through the existing menu-bar and updater code.

  • Update state: The status item observes the same availability state as the banner and carries it through ranking refreshes, including icon-only and screen-share concealment modes.
  • Rendering and accessibility: A dotted template app icon appears after limits and overflow, or alone when no limits are shown; accessibility text includes update availability.
  • Regression coverage and docs: Added pixel/layout tests and updated README, changelog, and behavior docs. Changed Swift files passed syntax parsing here; macOS rendering tests and live Sparkle integration could not be run on this Linux runner.

Pullfrog  | View workflow run | Using gpt-6-astra | 𝕏

@robinebers
robinebers merged commit 161f19b into main Nov 3, 2026
5 of 7 checks passed
@robinebers
robinebers deleted the capy/show-the-app-icon branch November 3, 2026 06:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant