1jehuang / 1jehuang/jcode

macOS: menu bar status icon renders black (never green) on a dark menu bar while streaming

Open
#1,124 2 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

autonomous: clear bug priority: low triage: reproducible ux
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

  1. macOS in Dark Mode.
  2. Run the menu bar extra (jcode menubar).
  3. Start a session and send a prompt, so counts.streaming > 0.
  4. 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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.