Fix the macOS Dock icon toggle, menubar icon, and menu labels - #154
Open
edward-rosado wants to merge 8 commits into
Open
Fix the macOS Dock icon toggle, menubar icon, and menu labels#154edward-rosado wants to merge 8 commits into
edward-rosado wants to merge 8 commits into
Conversation
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.show()returns a Promise and wasn't awaited, whilehide()andisVisible()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()calledapp.dock.show()unconditionally — "ensure the taskbar icon is showing on mac" — and it runs from the tray click, theactivatehandler, 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
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
@2xrepresentation (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(), synchronoushide()andisVisible(). 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