Skip to content

fix(menu-bar): initial active-mode bullet + ⌃S hotkey doc - #604

Merged
sonichi merged 2 commits into
mainfrom
fix/menu-bar-active-bullet-and-screenshot-hotkey-doc
May 5, 2026
Merged

sonichi merged 2 commits into
mainfrom
fix/menu-bar-active-bullet-and-screenshot-hotkey-doc

Conversation

@sonichi

@sonichi sonichi commented May 5, 2026

Copy link
Copy Markdown
Owner

Summary

Two small UX bugs caught while testing PR #600.

1. Mode dropdown bullet missing on Mode: Active after most restarts

pollVoiceMode() short-circuits via voiceMode != newMode to skip redundant updates. But the FIRST tick after launch matches the default voiceMode = \"active\" against the file sentinel's "active" and bails — so updateModeMenuItem() never runs, and the menu items stay at creation-time titles (\" Mode: Active\", two spaces, no ●).

Common case = every restart that lands in active mode = the majority.

Fix: call updateModeMenuItem() once at the end of applicationDidFinishLaunching. Idempotent. Bullet renders immediately whether or not the subsequent poll fires an update.

2. ⌃S (drop_screenshot) hotkey undocumented

registerHotKey() registers ⌃S for screenshot drop (src/Sutando/main.swift:944), but the web UI status bar (src/web-client.ts:646) and dashboard shortcut block (src/dashboard.py:315) both list ⌃C / ⌃V / ⌃M only. Added the ⌃S <kbd> entry to both surfaces.

Verification

  • Sutando.app rebuilt + relaunched: bullet now appears next to "Mode: Active" on first launch with no mode change required.
  • web-client + dashboard kickstarted: ⌃S kbd label renders in both.

Test plan

  • swiftc -O clean
  • manual menu inspection: bullet present immediately on launch
  • dashboard.py manual reload: ⌃S Drop screenshot row visible
  • web-client.ts compile + reload: ⌃S drop screenshot chip visible

🤖 Generated with Claude Code

sonichi and others added 2 commits May 5, 2026 13:26
Two related menu-bar UX gaps caught 2026-05-05:

1. **Mode dropdown bullet missing on Active when active is the default.**
   `pollVoiceMode()` short-circuits via `voiceMode != newMode` to avoid
   redundant updates, but the very FIRST tick after launch matches the
   default `voiceMode = "active"` against the file's "active" sentinel
   and bails — so `updateModeMenuItem()` never runs and the menu items
   stay at their creation-time titles ("  Mode: Active"), no `●`. Common
   case: any restart that lands in active mode (the majority).

   Fix: call `updateModeMenuItem()` once at end of
   `applicationDidFinishLaunching` so the bullet renders immediately
   whether or not pollVoiceMode subsequently fires an update.

2. **⌃S (drop_screenshot) hotkey was registered but undocumented in the
   web UI status bar (`src/web-client.ts:646`) and the dashboard shortcut
   block (`src/dashboard.py:315`).** Both surfaces listed ⌃C / ⌃V / ⌃M
   only. Added the ⌃S kbd line to both.

Verified locally:
- Sutando.app rebuilt + relaunched: bullet now appears next to "Mode:
  Active" immediately on launch.
- web-client + dashboard restarted: ⌃S kbd label rendered in both surfaces.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Per Chi's review 2026-05-05: CLAUDE.md and README.md both duplicated the
hotkey list in 4 places combined, and ALL 4 spots stayed stale when ⌃S
was registered (drift caught when Chi noticed ⌃S missing from dashboard
+ web-client UI surfaces, fixed in PR #604's earlier commit).

Two design choices applied here:

1. **CLAUDE.md** is the agent-facing operations manual; the agent reads
   it on every session. A static hotkey list there means stale answers
   when the agent is asked "what hotkeys do I have". Replace the list
   with a pointer to `~/.config/sutando/hotkeys.json` and `src/Sutando/
   main.swift:944` (the registration). When the user asks, the agent
   reads those sources directly — no drift surface.

2. **README.md** is public-facing. Keep one canonical hotkey table at
   §Keyboard shortcuts (line 268), and change the 3 other mentions
   (line 126 banner, line 193 services table, line 310 Accessibility
   note) to reference the canonical table. Drift surface goes from 4
   spots → 1 spot. Plus add ⌃S to the canonical table (was missing).

The structural fix (build script that generates a hotkeys-snippet.md
partial from hotkeys.json, included in CLAUDE.md and README.md via
comment markers) is deferred — Chi tagged it as the right long-term
answer when there's slack, but quick patch lands now.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

@sonichi sonichi left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

LGTM-shape (commenting since GH won't approve sonichi PRs).

Fix 1 (main.swift, mode bullet): traced. pollVoiceMode() does its first tick before any state divergence; the short-circuit on voiceMode != newMode correctly fires when sentinel matches the "active" default → updateModeMenuItem() never runs → menu items stuck at creation-time titles with no ●. Calling it once at end-of-launch closes the gap unconditionally. Idempotent — even if poll later drives the same update, the menu just gets re-rendered identically. ✓

Fix 2 (⌃S docs): verified the wiring exists already in source — defaultHotkeyConfig at main.swift:949 includes drop_screenshot=⌃S, registerHotKey() at line 1006 maps it. The PR adds the missing doc rows to README + dashboard + web-client status bar to match. Consistent across all three surfaces. ✓

CLAUDE.md change (live-config pointer instead of static list): good drift-resistant pattern. Replaces a 3-line list that would silently rot behind future hotkey additions with a pointer to ~/.config/sutando/hotkeys.json (per-user override; absent on a fresh install, which is fine — defaultHotkeyConfig at main.swift:949-957 is the source of truth). The runtime cost when answering "what hotkeys do I have" goes from "read CLAUDE.md" to "grep main.swift + maybe read JSON" — small price for accuracy. ✓

Verified locally: ~/.config/sutando/hotkeys.json doesn't exist on this Mini; defaults are wired, no breakage. ⌃S binding is real.

Tiny non-blocker observation:

  • The CLAUDE.md change asks future-me to "read those sources — don't quote a static list from this file." If a future drift-check skim happens to grab the README's static list (which now includes ⌃S), the same drift returns through a different surface. The README list is by-design (user-facing doc); just noting that the drift-resistance only protects CLAUDE.md.

Approve. None of this rises to request-changes.

@sonichi
sonichi merged commit 9b9ae02 into main May 5, 2026
1 check passed
@sonichi
sonichi deleted the fix/menu-bar-active-bullet-and-screenshot-hotkey-doc branch May 5, 2026 20:43
liususan091219 pushed a commit to liususan091219/sutando-fork that referenced this pull request May 28, 2026
* fix(menu-bar): render initial active-mode bullet + document ⌃S hotkey

Two related menu-bar UX gaps caught 2026-05-05:

1. **Mode dropdown bullet missing on Active when active is the default.**
   `pollVoiceMode()` short-circuits via `voiceMode != newMode` to avoid
   redundant updates, but the very FIRST tick after launch matches the
   default `voiceMode = "active"` against the file's "active" sentinel
   and bails — so `updateModeMenuItem()` never runs and the menu items
   stay at their creation-time titles ("  Mode: Active"), no `●`. Common
   case: any restart that lands in active mode (the majority).

   Fix: call `updateModeMenuItem()` once at end of
   `applicationDidFinishLaunching` so the bullet renders immediately
   whether or not pollVoiceMode subsequently fires an update.

2. **⌃S (drop_screenshot) hotkey was registered but undocumented in the
   web UI status bar (`src/web-client.ts:646`) and the dashboard shortcut
   block (`src/dashboard.py:315`).** Both surfaces listed ⌃C / ⌃V / ⌃M
   only. Added the ⌃S kbd line to both.

Verified locally:
- Sutando.app rebuilt + relaunched: bullet now appears next to "Mode:
  Active" immediately on launch.
- web-client + dashboard restarted: ⌃S kbd label rendered in both surfaces.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs: replace static hotkey lists with single canonical table + pointers

Per Chi's review 2026-05-05: CLAUDE.md and README.md both duplicated the
hotkey list in 4 places combined, and ALL 4 spots stayed stale when ⌃S
was registered (drift caught when Chi noticed ⌃S missing from dashboard
+ web-client UI surfaces, fixed in PR sonichi#604's earlier commit).

Two design choices applied here:

1. **CLAUDE.md** is the agent-facing operations manual; the agent reads
   it on every session. A static hotkey list there means stale answers
   when the agent is asked "what hotkeys do I have". Replace the list
   with a pointer to `~/.config/sutando/hotkeys.json` and `src/Sutando/
   main.swift:944` (the registration). When the user asks, the agent
   reads those sources directly — no drift surface.

2. **README.md** is public-facing. Keep one canonical hotkey table at
   §Keyboard shortcuts (line 268), and change the 3 other mentions
   (line 126 banner, line 193 services table, line 310 Accessibility
   note) to reference the canonical table. Drift surface goes from 4
   spots → 1 spot. Plus add ⌃S to the canonical table (was missing).

The structural fix (build script that generates a hotkeys-snippet.md
partial from hotkeys.json, included in CLAUDE.md and README.md via
comment markers) is deferred — Chi tagged it as the right long-term
answer when there's slack, but quick patch lands now.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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