Repository navigation
fix(menu-bar): initial active-mode bullet + ⌃S hotkey doc - #604
Conversation
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
left a comment
There was a problem hiding this comment.
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.
* 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>
Summary
Two small UX bugs caught while testing PR #600.
1. Mode dropdown bullet missing on
Mode: Activeafter most restartspollVoiceMode()short-circuits viavoiceMode != newModeto skip redundant updates. But the FIRST tick after launch matches the defaultvoiceMode = \"active\"against the file sentinel's "active" and bails — soupdateModeMenuItem()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 ofapplicationDidFinishLaunching. Idempotent. Bullet renders immediately whether or not the subsequent poll fires an update.2.
⌃S(drop_screenshot) hotkey undocumentedregisterHotKey()registers⌃Sfor 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 / ⌃Monly. Added the⌃S<kbd>entry to both surfaces.Verification
⌃Skbd label renders in both.Test plan
⌃S Drop screenshotrow visible⌃S drop screenshotchip visible🤖 Generated with Claude Code