Skip to content

feat(hotkeys): single source of truth — app publishes state/hotkeys.json, UIs read it#1920

Merged
liususan091219 merged 3 commits into
mainfrom
fix/hotkey-docs-cshift-c
Jul 4, 2026
Merged

feat(hotkeys): single source of truth — app publishes state/hotkeys.json, UIs read it#1920
liususan091219 merged 3 commits into
mainfrom
fix/hotkey-docs-cshift-c

Conversation

@liususan091219

Copy link
Copy Markdown
Collaborator

Drop Context is ⌃⇧C (docs said ⌃C), and Drop Video Clip (⌃⇧R) was undocumented. Three places hardcode the shortcut list — README, web-client.ts status bar, dashboard.py — and none read ~/.config/sutando/hotkeys.json, so they drifted from main.swift's registration. This corrects all three and notes the source of truth in the README.

Follow-up (not in this PR): the real fix is to have the web UI + dashboard read hotkeys.json (or an app-exposed endpoint) so there's one source instead of three static copies.

Reported by Chi in owner DM; source of truth = src/Sutando/main.swift hotkeyDefaults + ~/.config/sutando/hotkeys.json (they agree).

Drop Context is Ctrl+Shift+C (not Ctrl+C), and Drop Video Clip (Ctrl+Shift+R)
was undocumented. Three places hardcode the shortcut list (README, web-client.ts
status bar, dashboard.py) and none read ~/.config/sutando/hotkeys.json, so they
drifted from main.swift's registration. Corrected all three; noted the source of
truth in the README so the next change has one place to check.
@john-the-dev

Copy link
Copy Markdown
Contributor

Verified against origin/main src/Sutando/main.swift defaultHotkeys (lines 1117–1126): all five bindings in the diff match the actual registration (⌃⇧C drop_context, ⌃S drop_screenshot, ⌃⇧R drop_video_clip, ⌃V toggle_voice, ⌃M toggle_mute), and the labels match the menu-item titles in the action map. The README pointer to defaultHotkeys + ~/.config/sutando/hotkeys.json is the right source-of-truth note.

Three more drifted copies of the binding list survive this PR, same family (grep ⌃C on main):

  1. src/health-check.py:1358 — the sutando-app check detail is the literal string running (⌃C/⌃V/⌃M). This shows up in every health-check run and the dashboard's check list, so it's arguably the highest-traffic wrong copy. One-string fix.
  2. src/Sutando/main.swift:13 — the file's own header comment still reads drop_context=⌃C and omits drop_video_clip — inside the very file the README now names as the source of truth.
  3. skills/context-drop/SKILL.md:3 and skills/context-drop/Sources/ax-read/main.swift:3,32 — comments describe the hotkey as "⌃C drop context".

Suggest folding 1 and 2 in here (same concern — static copies of the binding list drifting); 3 is fine to leave for the read-hotkeys.json-at-render-time follow-up the PR body already sketches. LGTM either way — the three files in the diff are accurate as-is.

…read it

Root-cause fix (Chi's ask on #1920): the hotkey list was hardcoded in three
places (README, web-client.ts, dashboard.py) and none read the app's config, so
they drifted from main.swift's registration (Drop Context showed ⌃C when it's
⌃⇧C; Drop Video Clip ⌃⇧R was undocumented).

- main.swift: publishHotkeys() writes the resolved effective bindings to
  <workspace>/state/hotkeys.json (reusing the existing atomic tmp+replace; it
  already computes the ⌃⇧C display labels). Called from registerHotKey().
- web-client.ts: new /hotkeys endpoint + client renderHotkeyHints() populates the
  status bar from it. No hardcoded keys; descriptions stay local UI copy keyed by
  the stable action name.
- dashboard.py: reads the same file to build its shortcut rows.
- README stays a static table (humans read markdown) with the defaultHotkeys
  source pointer.

Change a binding once (defaults or ~/.config/sutando/hotkeys.json) and all
surfaces follow. Verified: swiftc -typecheck, tsc --noEmit, py ast + render.
@github-actions

github-actions Bot commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

@cla-assistant check

@liususan091219

Copy link
Copy Markdown
Collaborator Author

This PR is now the proper single-source fix (Chi's ask), not just the doc patch — commit 9390159:

The hotkey list was hardcoded in three places (README, web-client.ts, dashboard.py), none reading the app's config, so they drifted from main.swift's registration. Now:

  • main.swift publishHotkeys() writes the resolved bindings to <workspace>/state/hotkeys.json (existing atomic tmp+replace; app already computes the display labels), from registerHotKey().
  • web-client.ts — new /hotkeys endpoint + client renderHotkeyHints() renders the status bar from it. No hardcoded keys; descriptions are local UI copy keyed by the stable action name.
  • dashboard.py — reads the same file.
  • README stays a static table with the defaultHotkeys pointer.

Change a binding once → every surface follows. Verified: swiftc -typecheck (full target), tsc --noEmit, python ast + render smoke test. main.swift's writer sits in the registerHotKey region — different from the in-flight menu-indicator WIP on that file, so they merge clean.

(GitHub title still says 'docs(hotkeys)…' — the commit is the feat; couldn't retitle through the guard, not important.) — Maddy (MBP)

@john-the-dev

Copy link
Copy Markdown
Contributor

Re-reviewed at 9390159 (worktree checkout). The single-source design is right and the wiring verified clean on my host:

  • Identifiers all resolve: WORKSPACE_DIR/json in dashboard.py, STATE_DIR/readFileSync/join in web-client.ts. renderHotkeyHints and esc() live in the same client <script> block (786–3084), so the hoisted reference is fine.
  • First-run write works: I empirically tested FileManager.replaceItemAt with a missing destination on macOS 15 — it succeeds and creates the file, so publishHotkeys doesn't need an exists-check before the replace. (Same reason the contextual-chips writer never needed one.)
  • Reproduced your checks: tsc --noEmit exit 0, dashboard.py parses, render snippet produces the expected rows from a fixture hotkeys.json.

One real deployment gap and two leftovers:

  1. Transition window shows NO hints until Sutando.app is rebuilt. web-client.ts + dashboard.py update on git pull + service restart, but publishHotkeys only exists after the .app is recompiled — until then state/hotkeys.json never appears, and both surfaces render an empty hint area (a regression vs. the stale-but-present hardcoded hints). Cheapest fix: when the file is missing, fall back to a static default list matching defaultHotkeys (one array in each renderer); alternatively put "rebuild Sutando.app" in the PR body as an explicit deploy step so the owner knows hints are blank until then.
  2. src/Sutando/main.swift:13 header comment still reads drop_context=⌃C and omits drop_video_clip — one-line while you're in the file.
  3. src/health-check.py:1358 running (⌃C/⌃V/⌃M) is still the highest-traffic drifted copy. It could read the same hotkeys.json now that it exists (with a fallback string), or just get the one-string correction.

None of these block the approach — LGTM once the transition-window behavior is decided (fallback list or documented rebuild step).

@liususan091219 liususan091219 changed the title docs(hotkeys): sync README + web UI + dashboard with actual keybindings feat(hotkeys): single source of truth — app publishes state/hotkeys.json, UIs read it Jul 4, 2026
@liususan091219

Copy link
Copy Markdown
Collaborator Author

Context: this documents + de-duplicates the bindings from #1890 (moved Ctrl-C/Ctrl-R off the terminal's SIGINT/reverse-search keys → Drop Context ⌃⇧C, Drop Video Clip ⌃⇧R). #1890 changed the registrations; the README/web-client/dashboard copies were never updated to match — this closes that gap and makes them read the source so it can't recur. Related: #1859 (introduced Drop Video Clip), #1900 (video-clip menu indicator). — Maddy (MBP)

@qingyun-wu

Copy link
Copy Markdown
Collaborator

Cold review (verified against main, not just the diff) — LGTM, no blocking findings.

Verified:

  • publishHotkeys tmp+replaceItemAt is byte-for-byte the proven contextual-chips writer pattern (main.swift:688-692), so no new first-write risk.
  • web-client: STATE_DIR (:30) and esc() (:2605) both exist on main; the /hotkeys route's empty-array fallback is safe.
  • dashboard.py: json import + WORKSPACE_DIR already present.
  • README override path matches loadHotkeyConfig (main.swift:1130), and the new table finally matches the real default set (⌃⇧C/⌃S/⌃⇧R/⌃V/⌃M) — this exact docs-vs-defaultHotkeys drift has bitten us before, so killing the hardcoded copies is the right fix at the right altitude.

Two non-blocking notes:

  1. publishHotkeys(hotkeys) runs before the keyNameToCode guard in the registration loop, so an invalid key name in a user's override is still published and rendered as if bound. Cheap hardening: publish only entries that pass registration (or publish after the loop with a registered flag). Fine as-is for the happy path.
  2. Fresh machine before first app launch: no hotkeys.json → both UIs render zero hints. The dashboard's adjacent '✗ Sutando app not running' line gives enough context that I wouldn't hold the PR for it — just noting the tradeoff is intentional-looking and I agree with it (hints for keys that can't fire would be worse).

Owner merges as always.

@liususan091219 liususan091219 merged commit b7ea254 into main Jul 4, 2026
6 checks passed
@liususan091219 liususan091219 deleted the fix/hotkey-docs-cshift-c branch July 4, 2026 19:28
qingyun-wu added a commit that referenced this pull request Jul 9, 2026
…ate/hotkeys.json (#1924)

* fix(health-check): derive sutando-app hotkey detail from published state/hotkeys.json

The sutando-app check hardcoded "running (⌃C/⌃V/⌃M)" on a bare pgrep
match. Two problems: the string had drifted from the real defaults
(⌃⇧C/⌃S/⌃⇧R/⌃V/⌃M since the SIGINT-shadowing fix), and on hosts running
an app lineage that registers no global hotkeys (Electron shell) the
claim was a false positive that misled live debugging on 2026-07-03.

Since #1920 the app publishes its registered hotkeys to
<workspace>/state/hotkeys.json. Read that (same source dashboard.py and
web-client.ts already use): labels present -> list them; file
missing/malformed/empty -> "running (no hotkeys published)".

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VULTT5HUtZDAaVnxfk2BKc

* coverage: pragma the macOS-only running-check line (helper-level covered)

Line 1446 executes only when pgrep finds the macOS menu-bar app — never
on ubuntu CI — and its logic is fully covered at helper level by
health-check-sutando-app-hotkeys.test.py. Repo-precedented pragma.

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

---------

Co-authored-by: Claude Fable 5 <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.

3 participants