Skip to content

Windows tray: menu parity with macOS, movable panel, UTF-8 hook output#75

Merged
aqua5230 merged 8 commits into
mainfrom
agent/windows-tray-menu-parity
Jul 16, 2026
Merged

Windows tray: menu parity with macOS, movable panel, UTF-8 hook output#75
aqua5230 merged 8 commits into
mainfrom
agent/windows-tray-menu-parity

Conversation

@aqua5230

Copy link
Copy Markdown
Owner

Summary

  • Align the Windows tray right-click menu with macOS: AI daily report, hide-sections toggles, quota threshold notifications (pystray toasts), session-resume and terse-mode toggles (talent_market stays excluded: its vendored CLI is macOS-only)
  • Make the panel window movable: drag the top handle or any empty area of a quota card (temporary pywebview-drag-region, so buttons inside cards still click normally); position persists across opens, clamped to the work area
  • The in-panel "Switch Panel" button now opens a full localized overlay menu (accordion submenus, scrollable) instead of cycling themes; card drag-to-reorder is disabled on Windows to keep a single drag gesture
  • Force UTF-8 hook/CLI output on Windows: statusLine progress bars rendered as U+FFFD through the cp950 pipe, and --setup crashed with UnicodeEncodeError printing ✓ on a cp950 console

Testing

  • ruff check --exclude release-win . / mypy . --exclude release-win clean
  • pytest -q: 737 passed, 2 skipped (POSIX-only) on Windows
  • Manually exercised on Windows 10: menu actions, card/window drag, accordion submenus, statusLine bars after re-setup, --setup exit 0 on cp950

🤖 Generated with Claude Code

aqua5230 and others added 8 commits July 16, 2026 17:43
Adds the five macOS menu features the Windows tray was missing: AI
daily-report link, hide-sections submenu (Claude/Codex/Antigravity),
quota threshold notifications via pystray toasts, session-resume and
terse-mode toggles backed by the shared session_hooks module.
talent_market stays excluded (its vendored CLI is macOS-only).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Injects a shared drag handle (pywebview-drag-region) into every panel
theme via the JS shim, small enough not to overlap the quota-card
drag-to-reorder areas. The window position is saved to prefs on hide,
restored (clamped to the work area) on the next open, and a new tray
menu item resets it to the default tray-adjacent placement.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The in-panel "Switch Panel" button used to cycle to the next theme;
it now opens a localized HTML overlay menu mirroring the macOS popup:
theme submenu with the active panel checked, hide-sections toggles,
AI daily report, refresh, launch-at-login, quota notifications,
session-resume, terse mode, reset panel position, update check, and
quit. Menu state is re-queried from Python on every open, and theme
switches keep the deferred-reload guard that prevents blank windows.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The statusLine hook printed through a cp950-encoded pipe on Windows,
so Claude Code (which decodes UTF-8) rendered the progress-bar glyphs
as U+FFFD; and --setup crashed with UnicodeEncodeError printing its
localized check-mark message to a cp950 console. All hook scripts and
the setup/session-hooks CLI paths now reconfigure stdout/stderr to
UTF-8 on Windows (guarded, stdlib-only, Python 3.9-safe).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The tests reached the sys module through the module under test
(usage_statusline.sys), which mypy strict rejects as an implicit
re-export; patching the imported sys module directly is the same
object and behavior.

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

Reorders the panel menu to mirror the macOS grouping with the
Windows-only utilities at the bottom, expands the theme/hide-sections
submenus in place (indented accordion inside the scrollable menu) so
long lists are never clipped by the window, and neutralizes the quota
card drag-to-reorder gesture on Windows via a capture-phase listener
- it was too easy to confuse with the window drag handle. Saved card
order still renders, and macOS keeps its drag behavior.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Pressing an empty area of the Claude/Codex/Antigravity cards now moves
the whole window (a temporary pywebview-drag-region on the card, so
buttons and links inside keep their normal click path), with grab and
grabbing cursors on both the cards and the top handle. The in-panel
menu drops Check for Updates, Reset Panel Position, and Quit - all
still reachable from the tray menu or the panel footer - ending at
Refresh Now.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The new switch-panel menu test built the full menu (including the
login-item toggle) without mocking win_login_item.is_enabled, which
imports winreg — crashing CI's macOS check job.

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

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e57c24f397

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread usage_statusline.py
Comment on lines +34 to +41
def _configure_windows_utf8_output() -> None:
"""Make hook output UTF-8 when Claude Code reads a Windows pipe."""
if os.name != "nt":
return
for stream in (sys.stdout, sys.stderr):
with suppress(AttributeError, OSError, ValueError):
# Test runners and embedders may replace the TextIOWrapper streams.
cast(Any, stream).reconfigure(encoding="utf-8")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Bump copied hook versions

This UTF-8 setup is added to scripts that are copied into ~/.claude, but their installed-version gates remain unchanged: setup_hook.needs_update() only refreshes usage-statusline.py when the installed __version__ differs from HOOK_VERSION, and the resume hook uses the same RESUME_HOOK_VERSION pattern. For users upgrading with existing 1.0/1.6 hooks, startup self-heal treats the old copies as current, so the Windows cp950 fix never reaches them. Please bump the hook version constants whenever adding behavior to copied hook scripts.

Useful? React with 👍 / 👎.

Comment on lines 52 to +53
def main() -> None:
_configure_windows_utf8_output()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Decode forwarded hook output as UTF-8

In Windows coexistence mode, this only reconfigures the forwarder's own stdout; _run_hook still uses subprocess.run(..., text=True, capture_output=True) without an encoding, so Python decodes the child hook's now-UTF-8 stdout with the process locale such as cp950. When the status line contains block/emoji/check glyphs, that raises UnicodeDecodeError and is caught as an empty string, dropping the usage line for users with another statusLine tool. Capture bytes or decode captured stdout as UTF-8 instead of relying on the locale.

Useful? React with 👍 / 👎.

@aqua5230
aqua5230 merged commit 236ede8 into main Jul 16, 2026
5 checks passed
@aqua5230
aqua5230 deleted the agent/windows-tray-menu-parity branch July 16, 2026 11:36
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