macOS: menu bar status icon renders black (never green) on a dark menu bar while streaming
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 19.9k
- Forks
- 2.3k
- Avg merge
- 2d 7h
- Merged PRs (30d)
- 30
Description
Summary
While a session is streaming, the menu bar status icon renders black on a dark menu bar, while every other status icon on the system renders white. It also never shows the green streaming color that the code intends. The count text next to it is green, so the two halves of the same status item disagree.
The icon is not hidden — it is drawn, in the one color that is invisible against a dark bar.
Relationship to ee122c4b5
This is not the general dark-mode bug fixed by ee122c4b5 ("fix(menubar): follow system Dark Mode so the status icon is visible", 2026-06-25). That fix was already in the tree when this was observed.
jcode runs as a bare Mach-O with no Info.plist, so AppKit pins the process to light Aqua; sync_app_appearance corrects that by reading AppleInterfaceStyle. What is reported here survives that fix, because setContentTintColor takes the button out of the appearance/vibrancy path sync_app_appearance depends on — so the tinted state is wrong even though the process appearance is right.
Environment
- macOS 26.4.1 (25E253), Apple silicon, Dark Mode
- Observed on jcode v0.77.19; the responsible code path is unchanged on master as of v0.81.4
Repro
- macOS in Dark Mode.
- Run the menu bar extra (
jcode menubar). - Start a session and send a prompt, so
counts.streaming > 0. - The icon renders black while neighbouring system status icons render white, and it never shows the green it is supposed to. The count beside it does turn green.
One caveat on scope: what I observed directly is the black, never-green icon. That the idle state is unaffected is inferred from the code — contentTintColor is only set when counts.streaming > 0, and clearing it restores the normal appearance path — rather than something I watched toggle. If the icon is also wrong at rest on your machine, the cause is different and this analysis does not cover it.
Cause
src/cli/commands/menubar.rs keeps a template image on the button and tints it per refresh:
icon.setTemplate(true);
button.setImage(Some(icon));
// ...
let tint: Option<Retained<NSColor>> = if counts.streaming > 0 {
Some(streaming_color())
} else {
None
};
button.setContentTintColor(tint.as_deref());
Setting any contentTintColor on a status item button opts it out of the menu bar's vibrancy filter — the same mechanism sync_app_appearance relies on to make a template image render light on a dark bar. The template then draws its literal black artwork, and the green that was requested is never applied. Hence both halves of the symptom at once: black instead of white, and black instead of green.
The count is an NSAttributedString with an explicit foreground color rather than a template image, which is why it still turns green.
The tint call dates to f7f80dd2b ("menubar: color-code the count by streaming state", v0.31.1), which introduced the color for the count and tinted the icon alongside it.
Evidence
Rendered the status button offscreen and sampled mean luminance over the glyph's pixels (0.0 = black, 1.0 = white):
| Configuration | Glyph luminance |
|---|---|
template image + contentTintColor (current, streaming) |
0.000 |
| non-template palette image (proposed, streaming) | 0.630 |
| plain template image (current, idle) | 0.794 |
0.000 confirms the glyph is drawn fully black rather than tinted green or omitted.
Expected
While streaming, the icon shows the accent green. At rest it keeps following the menu bar appearance as it does today. Neither state ever renders black on a dark bar.
Suggested fix
Stop tinting the button; swap the image instead:
- idle: plain template image, so the existing appearance/vibrancy handling from ee122c4b5 continues to apply;
- streaming: a non-template image built with
NSImageSymbolConfiguration::configurationWithPaletteColors, with the accent color baked in.
Clear contentTintColor so the button never leaves the vibrancy path.
Tradeoffs
A non-template image does not adapt to menu bar appearance, which is acceptable here because the streaming state is deliberately a fixed accent color — the same one the count already uses. The idle state stays a template image and keeps adapting.
Investigated and written with agent assistance.
Agent: jcode v0.77.19-dev (claude-opus-5)
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in src/cli/commands/menubar.rs and trace the status button image and contentTintColor handling during refresh. Verify the idle and streaming states in macOS Dark Mode, then run the existing menubar-related checks or reproduce with jcode menubar; done means the streaming icon is green, the idle icon follows appearance, and neither is black on a dark bar.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- macos, rust
- Domain
- cli, desktop
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 76/100