Skip to content

Fix and polish the tray: a real icon, card clicks, and macOS Tcl discovery - #12

Open
cat0825 wants to merge 2 commits into
llm-learner:mainfrom
cat0825:logo-and-card
Open

cat0825 wants to merge 2 commits into
llm-learner:mainfrom
cat0825:logo-and-card

Conversation

@cat0825

@cat0825 cat0825 commented Sep 26, 2026 •

Copy link
Copy Markdown
Contributor

Four things the tray needed, all in the same subsystem. If you would rather take them one at a time, the two bug fixes (Tcl discovery, the refresh button) are the ones worth splitting out; the rest is presentation.

1. The icon was not authored for a menu bar slot

A slot is 18pt, so 64px covers it at 3.5× and the Windows tray at 4×. The artwork was a 1254px canvas whose ring interior was filled with opaque white — invisible against a white page, a white blob on a dark menu bar — and 383,807 bytes of it.

Rebuilt from the same artwork:

  • interior and background made transparent, so only the ring remains
  • cropped to the ink and resampled to the 64px that ICON_PX already hands over
  • menubar-icon.png for a light menu bar, menubar-icon-dark.png for a dark one, where the navy arc is lifted toward a light slate and the blue accent is left alone. custom_icon_path() already prefers a -dark sibling, so this replaces the per-pixel lighten_for_dark() pass with a hand-tuned file

383,807 bytes → 8,789, and the drawn fallback is untouched — it still renders at 64px with a transparent corner and a white centre, exactly as test_tray.py asserts.

A precedence bug that shipping an icon exposes

custom_icon_path() returns the first file that exists, and the bundled default was listed before ~/.headroom/icon.png — so once an icon ships, a per-user icon can never win. The per-user file now comes first, and a test pins the order. This was dead code before; it would have become a silent bug the moment an icon landed.

2. A detached venv process lost Tcl

A venv interpreter resolves tcl_library from its base prefix, but a process started with start_new_session loses that and dies with:

Cannot find a usable init.tcl

That reads as a missing Tk, but it is a search-path problem — and it is exactly the setup the README recommends (~/.headroom/venv plus requirements-desktop.txt), so the macOS menu bar could not start at all in the documented configuration. Setting TCL_LIBRARY/TK_LIBRARY at import time makes every launch path behave the same.

3. The refresh button could wedge permanently

The reader thread published its result with a plain put(), so busy was cleared only when a result arrived. A reader that raised outside the caught set — or hit a full queue — left every later refresh a silent no-op. That is exactly what "clicking refresh does nothing" looks like from the outside. The result is now published from a finally, a full queue is drained rather than dropped, and a click shows the loading label immediately because the percentage rarely changes between two reads.

4. The card itself

  • The refresh button is a flat pill rather than a tk.Button — Aqua ignores a button's background, so the old one rendered as the system default, not as designed.
  • The meter is a thin rounded line instead of a heavy bar; the percentage is split from its label; the mood caption sits under a hairline.
  • Fonts come from one UI_FONT/CJK_FONT pair, so the card follows the platform instead of hard-coded Segoe UI/Microsoft YaHei UI.
  • The mood image is now clickable in the card as it is in the web dashboard: it hops and plays one of the bundled clips with afplay, rotating so repeated clicks do not repeat the same sound. Tk cannot decode video, so the tray card stays audio-only.

Docs

The icon was undocumented — grep HEADROOM_ICON README.md returned nothing. README, README.zh-CN and SKILL.md now describe the bundled pair, the -dark sibling, HEADROOM_ICON, ~/.headroom/icon.png, and the drawn fallback.

Tests

TrayIconTests (custom icon resolution, the -dark sibling, the per-user precedence), TclLibraryTests (the search path, and that existing variables are left alone) and two new desktop tests for the reader and the mood click. Full suite passes on CPython 3.10, 3.11, 3.12 and 3.13, and with the tray dependencies installed so the tray tests actually execute rather than skip.

One note on that last point: test_tray.py only runs on Windows, so an earlier revision of this branch that raised ICON_PX to 72 passed locally and failed there. Reverted — 64 is the size draw_icon is drawn on, and raising it without rescaling that geometry would have left the drawn fallback off-centre.

Three things the tray needed, all in the same subsystem.

The icon was not authored for a menu bar slot. A slot is 18pt, so 64px covers
it at 3.5x and the Windows tray at 4x -- but the artwork was a 1254px canvas
whose ring interior was filled with opaque white: invisible against a white
page, a white blob on a dark menu bar, and 383,807 bytes of it. Rebuilt from
the same artwork: interior and background made transparent so only the ring
remains, cropped to the ink, resampled to the 64px ICON_PX already hands over.
menubar-icon.png for a light menu bar and menubar-icon-dark.png for a dark
one, where the navy arc is lifted toward a light slate and the blue accent is
left alone. 383,807 bytes -> 8,789.

That exposed a precedence bug: custom_icon_path() returns the first file that
exists, and the bundled default was listed before ~/.headroom/icon.png, so a
per-user icon could never win once an icon shipped. The per-user file now
comes first, and a test pins it.

A detached venv process lost Tcl. A venv interpreter resolves tcl_library from
its base prefix, but a process started with start_new_session loses that and
dies with "Cannot find a usable init.tcl" -- which reads as a missing Tk but
is a search-path problem. That is exactly the setup the README recommends
(~/.headroom/venv), so the menu bar could not start at all. Setting
TCL_LIBRARY/TK_LIBRARY at import time makes every launch path behave the same.

The card itself: the refresh button is a flat pill rather than a Tk button
(Aqua ignores a button's background), the meter is a thin rounded line instead
of a heavy bar, the percentage is split from its label, and the mood caption
sits under a hairline. Fonts come from one UI_FONT/CJK_FONT pair so the card
follows the platform instead of hard-coded Segoe UI.

The icon was undocumented. README, README.zh-CN and SKILL.md now describe the
bundled pair, the -dark sibling, HEADROOM_ICON and ~/.headroom/icon.png, and
the drawn fallback.

Tests pass on CPython 3.10, 3.11, 3.12 and 3.13, and with the tray
dependencies installed so TrayIconTests actually run.
…nswer a click

The reader thread published its result with a plain put(), so `busy` was
cleared only when a result arrived. A reader that raised outside the caught set
-- or hit a full queue -- left every later refresh a silent no-op, which is
exactly what "clicking refresh does nothing" looks like from the outside. The
result is now published from a finally, and a full queue is drained rather than
dropped. Clicking refresh also shows the loading label immediately, because the
percentage rarely changes between two reads and the click otherwise looks
ignored.

Clicking the mood image did nothing in the card either, although the web
dashboard plays a clip for it. The card now hops the image and plays one of the
bundled audio clips with afplay, rotating so repeated clicks do not repeat the
same sound. Tk cannot decode video, so the tray card stays audio-only; the
animated clips remain in the web dashboard.
@cat0825 cat0825 changed the title Give the tray a real icon, polish the card, and fix Tcl discovery on macOS Fix and polish the tray: a real icon, card clicks, and macOS Tcl discovery Sep 26, 2026
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