Skip to content

feat(desktop): always-on widget with a machine-wide global face - #5

Closed
fzn0728 wants to merge 1 commit into
SergioChan:mainfrom
fzn0728:feat/always-on-desktop-widget
Closed

feat(desktop): always-on widget with a machine-wide global face#5
fzn0728 wants to merge 1 commit into
SergioChan:mainfrom
fzn0728:feat/always-on-desktop-widget

Conversation

@fzn0728

@fzn0728 fzn0728 commented Aug 16, 2026

Copy link
Copy Markdown
Collaborator

Outcome

The Claude companion currently has exactly one reason to be on screen: a frontmost Claude window with an exactly-bound Code session. Every other moment — a Chat window, a Codex window in front, the desktop — it is invisible, so the numbers it collects are only reachable while you happen to be in the one place you already know what you spent.

This adds a second, non-session face so the widget can stay useful outside a bound session, and a menu-bar item so it is findable at all times.

User-visible behavior

New "global" face. When there is no exact session binding, the panel can render machine-wide totals — today, current streak, lifetime — read from the existing usage-history cache, with the identity handle in the header and the gauge parked at idle. It is a different face, not a fallback: status: "global", binding.exact: false, no session id, no per-session numbers.

Menu-bar item. A gauge status item with Show Widget, Always Show on Desktop, Open Dashboard…, Grant Accessibility Access…, and Quit. The two toggles persist to visibility.json in the existing state directory, so a relaunch keeps the user's choice. The app is a Dock-less LaunchAgent accessory, so before this there was no user-reachable surface to hide, show, or quit it.

Quit actually quits. Quit and the settings power button now launchctl bootout the LaunchAgent before terminating, so KeepAlive stops resurrecting the process. Previously quitting was a no-op from the user's point of view.

Codex window following. A frontmost Codex window gets the global face pinned to it, the same way a Claude window does.

Invariant this deliberately changes — please review this part first

Claude compatibility check 3 in CONTRIBUTING reads: "Chat, Cowork, Settings, auxiliary windows, and non-frontmost Claude hide the panel."

This PR changes that. Those cases now show the global face instead of hiding, and with Always Show on Desktop enabled the panel also parks on the desktop when no host is frontmost.

I read the intent behind that invariant as never show one session's numbers while the user is looking at something else, and that intent is kept strictly: the non-session face carries no session id and no session totals, and binding.exact stays false. But the letter of the rule changes, and it is your call whether that trade is right. Both behaviors are reachable — Show Widget off restores full hiding, Always Show on Desktop off restores host-only visibility — and I can flip the defaults to opt-in if you would rather the current behavior stay the default.

The exact-binding invariant itself is untouched: a frontmost Claude window with a Code session still binds only by exact local_<uuid> and still gets the session face, with no recency or display-name fallback anywhere in the new path.

Implementation

  • overlay-bridge.mjs — new global-snapshot command returning identity plus usage-history totals, memoized for 60s because the native layer polls on a steady cadence. No new collection: it reads the cache that community sync already maintains.
  • runtime/token-meter-ui.jsdata-mode=global face; settings identity, version line, and privacy toggle rendering factored out and shared with the session face rather than duplicated.
  • TokenMeterClaudeOverlay.swiftStatusBarController, WidgetPreferences, LaunchAgent bootout, codexBundleID window following.

Security and privacy

No new data leaves the machine and no new collection is added. The global face reads the same local usage-history cache the meter already builds, the privacy toggle keeps working identically on both faces, and visibility.json holds two booleans. The companion stays independent of Claude.app — nothing here quits, relaunches, or touches the host.

Test evidence

  • npm test — 180/180 pass.
  • npm run check — clean (73 JS, 22 shell, 20 Markdown files).
  • swiftc -typecheck on all three native sources — exit 0, no diagnostics.
  • New regression tests at the public seams: test/claude-overlay-bridge.test.mjs asserts the global-snapshot response shape over the real stdio protocol, and test/runtime-ui-layout.test.mjs asserts the global face renders machine-wide totals with no session.

Branch is rebased onto af48705 (v0.3.1) and is 1 commit ahead, 0 behind.

Not covered, flagging honestly

  • No live multi-host validation run, so no Codex version to cite for it. The Codex-window path is exercised only by the typecheck and the unit seams above.
  • Docs and screenshots are not updated. This does change public behavior, so docs/install-claude-desktop.md and the integration README likely want a paragraph on the menu bar and the two visibility toggles — happy to add that here once you have said whether the defaults should stay as they are, since the wording depends on that answer.

The overlay no longer lives only inside a bound Claude Desktop session.
When no exact session binding exists, the widget can now show a "global"
face with machine-wide totals across Claude Code, Codex, and Cline.

- overlay-bridge: new global-snapshot command returning identity plus
  usage-history totals (today, lifetime, streak), memoized for 60s
- runtime UI: data-mode=global face — TODAY label, identity header,
  lifetime/streak from meterStats, idle gauge; settings identity
  rendering shared between session and global faces
- native overlay: menu-bar StatusBarController with show/always-visible
  toggles persisted to visibility.json, LaunchAgent bootout on quit so
  KeepAlive stops resurrecting the app, and Codex window following via
  codexBundleID

Tests: 145/145 pass (node --test).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@vercel

vercel Bot commented Aug 16, 2026

Copy link
Copy Markdown

@fzn0728 is attempting to deploy a commit to the t54 labs Team on Vercel.

A member of the Team first needs to authorize it.

@fzn0728

fzn0728 commented Aug 16, 2026

Copy link
Copy Markdown
Collaborator Author

Withdrawing this for now — I want to rethink the product rationale for an always-on face before asking you to weigh in, particularly the visibility trade-off it makes against Claude compatibility invariant 3. Will reopen or resubmit once that's settled. Sorry for the noise.

@fzn0728 fzn0728 closed this Aug 16, 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