Skip to content

Fix the macOS Dock icon toggle, menubar icon, and menu labels - #154

Open
edward-rosado wants to merge 8 commits into
ItsRiprod:mainfrom
edward-rosado:fix/macos-dock-and-menubar
Open

Fix the macOS Dock icon toggle, menubar icon, and menu labels#154
edward-rosado wants to merge 8 commits into
ItsRiprod:mainfrom
edward-rosado:fix/macos-dock-and-menubar

Conversation

@edward-rosado

Copy link
Copy Markdown
Contributor

macOS fixes found while using DeskThing daily: a Dock-icon toggle that didn't work, two mislabelled menu items, a menubar icon that only rendered correctly on a dark menubar, and an app icon that renders as a blank square for anyone with monochrome app icons enabled.

Every claim below was checked by driving the real menu with System Events and screenshotting the result, not by reading the code.

The Dock icon toggle

Three separate faults in one menu item.

It said the wrong thing. "Toggle Dock Icon" names the control rather than the action. Now "Hide Dock Icon" or "Show Dock Icon" depending on state.

It raced. The implementation was:

app.dock.isVisible() ? app.dock.hide() : app.dock.show()

app.dock.show() returns a Promise and wasn't awaited, while hide() and isVisible() are synchronous. isVisible() read during a transition reports the state being left, so a second click picked the wrong branch — hide, show, hide in quick succession left the icon visible.

Visibility is now tracked as intent rather than interrogated from the system. Clicks update the desired state synchronously (so the label is right the instant it's clicked) and a single settle loop drives the real dock toward it, absorbing anything that arrives mid-flight. A burst of clicks produces one final state instead of a race.

And the real killer: buildMainWindow() called app.dock.show() unconditionally — "ensure the taskbar icon is showing on mac" — and it runs from the tray click, the activate handler, the single-instance handler and both menus. So hiding worked and was silently undone moments later. That's why it looked broken rather than merely racy. It now shows the icon only if the user hasn't asked for it hidden.

I confirmed app.dock.hide() itself is blameless by isolating it in a throwaway Electron app: with a window open, with a Dock menu set, with a Tray — the tile disappears immediately, first call, every time. The API was never the problem, so something had to be turning it back on.

Menu labels

  • "Open Desktop" → "Open DeskThing". It reads as a system feature, and sat directly above "Open DeskThing Client" where the pairing made it more confusing rather than less.
  • "Hide Tray Icon" → "Hide Dock Icon" in the Dock menu. It called app.dock.hide(); it was hiding the Dock icon, not the tray icon. It now goes through the same shared state, so both menus tell the same story.

The menubar icon

Two bugs.

It was not a template image. The macOS asset was a colour glyph drawn literally, so it only ever looked right against one menubar appearance. It's now black-plus-alpha and flagged with setTemplateImage(true), which lets the system recolour it for light and dark.

The mark dissolved at 16px. The brand gear is a thin outline; at that size the strokes fall below a pixel and smear into grey, conspicuously mushy next to the solid glyphs every other menubar item uses. The menubar now uses a filled silhouette of the same gear, plus the stacked wordmark at 18pt with a proper @2x representation (exactly double the 1x dimensions, as macOS requires — without it a retina menubar upscales the soft 1x copy).

The first-close notification also reached for the macOS tray asset. Notifications don't apply the template treatment, so that rendered as a black-on-black smudge; it now uses the colour asset on every platform.

The app icon, and monochrome mode

This is the part worth arguing about, so here is the reasoning rather than just the artwork.

With macOS monochrome/tinted app icons enabled, the system keeps only the artwork's shape and supplies its own plate. The existing icon is a small mark on an opaque dark plate, so the plate becomes the shape and the icon renders as a completely blank rounded square — indistinguishable from any other app. I verified this by pushing the current icon through the same transform, and by screenshotting a real Dock.

That also means a gear can't be the answer: System Settings is a gear, and once colour is discarded there's nothing left to tell them apart.

So the icon is now the wordmark — the one part of the brand that is unmistakably this product — as "desk" over "thing", filling the frame, on transparency. It's the project's own vector logo rendered at 20×, not redrawn; the wordmark is roughly 6:1 and hopeless in a square, so it's split between the k and the t, with the split point taken from the logo's own geometry (the k ends at x=84.27, the t begins at x=85.34).

It's green rather than the logo's white because with no plate behind it the Dock panel is light in Light Mode, and white on white is nothing at all — rendering the installed icon over a light background showed only the green dot over the i.

If you'd rather keep the current mark, the bug is still real and the last four commits are separable — the behavioural fixes stand on their own. I'd rather you had the diagnosis than my design.

Tests

10 tests for the visibility state machine, driven through a fake dock that reproduces the real API's asymmetry: async show(), synchronous hide() and isVisible(). That asymmetry is the bug — a fake without it passes against the broken version.

Typecheck clean.

Not addressed

If "Show recent applications in Dock" is on, the app also appears in the Recents section, and nothing an app does can remove it from there — hiding the Dock icon removes the running-app tile while the Recents tile stays, which looks like the toggle doing nothing. Out of scope here, but worth knowing before anyone reports it as still broken.

🤖 Generated with Claude Code

edward-rosado and others added 8 commits August 6, 2026 12:15
The app icon could not stay a gear. macOS System Settings is a gear, and
with monochrome app icons turned on the colour that told them apart is
discarded — leaving two grey gears in the Dock.

Worse, the old icon was a small mark on an opaque dark plate. Monochrome
mode keeps only the artwork's shape, and an opaque plate is a rounded
square, so the whole icon rendered as a blank rounded square with nothing
in it at all. That is why swapping the mark underneath changed nothing
visible.

So the icon is now the wordmark, which is the one part of the brand that
is unmistakably this product: "desk" over "thing", filling the frame, on
transparency. "deskthing" is about 6:1 and hopeless in a square, so it is
split between the k and the t — the split point taken from the logo's own
geometry, where the k ends at x=84.27 and the t begins at x=85.34.

Artwork is the project's real vector logo rendered at 20x, not redrawn
and not upscaled from a small raster.

The menubar keeps the gear, which is still the right shape at 16px where
a wordmark is unreadable, but it is now a proper template image and is
flagged as one. It was neither before: a colour glyph, drawn literally,
so it only ever looked right on one menubar appearance.

The first-close notification now uses the colour asset on every platform.
It was reaching for the macOS tray asset, which is black plus alpha, and
notifications do not apply the template treatment — a black-on-black
smudge.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The logo's wordmark is white, which is right on the site's black page and
wrong for an icon with nothing behind it: the Dock panel is light in
Light Mode, and white on white is nothing at all. Rendering the installed
icon over a light background showed only the green dot over the i.

Brand green sits mid-luminance and reads on either appearance. Monochrome
app icons discard the colour and keep the shape, so that case is
unaffected.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The brand gear is a thin outline. At 16px those strokes fall below a
pixel and dissolve into grey mush — next to the crisp solid glyphs every
other menubar item uses, it read as broken rather than subtle.

The menubar now uses a filled silhouette of the same gear: the outline is
flood-filled to a solid shape and the hub is punched back out. Identical
geometry, but it survives being 16 pixels tall.

(The obvious first attempt — flood-filling the mask as cropped — fills
the entire frame, because a mask cropped tight to its shape has no
exterior to flood from. It needs padding first.)

And the item now carries the title "DeskThing". A 16px glyph cannot hold
a wordmark, so the menubar item's own title is the only place the name
can actually be read.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The gear plus a "DeskThing" title said the name but ate a lot of bar. The
Dock already solves this: the wordmark stacked as desk over thing is
close to square, so it fits the menubar's height without spending width.

Sized at 22pt tall, which is where the two lines stay legible — 16pt
turns them to mush, and I checked by rendering all three and looking
rather than assuming.

Ships a proper @2x representation, at exactly twice the 1pt dimensions
as macOS requires. That is what actually renders on a retina display, so
without it the menubar would show the soft 34x23 version upscaled.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
At 23pt it stood taller than every neighbouring glyph in the bar. 18pt
sits level with them and, checked at retina scale where the @2x actually
renders, both lines stay legible.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
'Open Desktop' reads as a system thing rather than this app, and sat
directly above 'Open DeskThing Client' where the pairing made it more
confusing, not less. Changed in the menubar and the Dock menu together,
since both carried the same label.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three problems in one menu item.

The label was 'Toggle Dock Icon', which names the control instead of the
action. It now reads 'Hide Dock Icon' or 'Show Dock Icon' depending on
the current state.

The toggle itself was:

    app.dock.isVisible() ? app.dock.hide() : app.dock.show()

app.dock.show() is asynchronous and was not awaited, while hide() and
isVisible() are synchronous. isVisible() read during a transition reports
the state being left, so a second click picked the wrong branch and the
icon ended up inverted — hide, show, hide in quick succession left it
visible.

Visibility is now tracked as intent rather than interrogated from the
system. Clicks update the desired state synchronously, so the label is
right the instant it is clicked, and a single settle loop drives the real
dock towards it, absorbing anything that arrives mid-flight. A burst of
clicks produces one final state instead of a race.

The menu is rebuilt on change rather than built once, and setupTray now
refuses to construct a second Tray — calling it again would have added a
second menubar icon.

10 tests, against a fake dock that reproduces the real API's asymmetry:
async show, sync hide and isVisible. That asymmetry is the whole bug, and
a fake without it passes against the broken version.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Hiding the Dock icon worked and was then silently undone.
buildMainWindow() called app.dock.show() unconditionally — 'ensure the
taskbar icon is showing on mac' — and it runs from the tray click, the
'activate' handler, the single-instance handler and both menus. So the
icon vanished and reappeared moments later, which is why the toggle
looked broken rather than racy.

It now shows the icon only if the user has not asked for it hidden.

Proved by isolating the API in a throwaway Electron app: app.dock.hide()
removes the tile immediately, first call, with a window open. The API was
never the problem, so the fault had to be something turning it back on.

Also relabelled the Dock menu's 'Hide Tray Icon', which hid the Dock icon
rather than the tray icon, and routed it through the same state so both
menus agree.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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