Skip to content

Comments

feat: add tauri-plugin-shortcut with centralized shortcut registry#4056

Open
devin-ai-integration[bot] wants to merge 13 commits intomainfrom
devin/1771395727-shortcut-registry-plugin
Open

feat: add tauri-plugin-shortcut with centralized shortcut registry#4056
devin-ai-integration[bot] wants to merge 13 commits intomainfrom
devin/1771395727-shortcut-registry-plugin

Conversation

@devin-ai-integration
Copy link
Contributor

@devin-ai-integration devin-ai-integration bot commented Feb 18, 2026

feat: add tauri-plugin-shortcut with centralized shortcut registry

Summary

Creates a new tauri-plugin-shortcut Rust plugin that serves as the single source of truth for all keyboard shortcuts in the app. The Rust side declares all shortcuts; the TS side reads from the registry and registers handlers as needed.

Rust side (plugins/shortcut/):

  • ShortcutId enum with #[serde(rename_all = "snake_case")] — all 36 shortcut identifiers as type-safe enum variants, exported via specta as a TS string literal union
  • ShortcutDef struct with typed id: ShortcutId, keys, category, description, scope (Global vs Scoped)
  • Static registry of all shortcuts in registry.rs
  • Single get_all_shortcuts command exposed via specta
  • doc.rs module for deterministic keyboard shortcuts documentation generation

TS side:

  • ShortcutId type generated by specta — compile-time enforcement that all shortcut references are valid
  • useShortcutRegistry hook — shared React Query fetch with keysMap for O(1) lookup by ShortcutId
  • useShortcutKeys(id: ShortcutId) — convenience hook to read a single key binding from the registry
  • useScopedShortcut(id, handler, options?, deps?) — wrapper hook that combines useShortcutKeys + useHotkeys into a single call, automatically guarding with enabled: !!keys
  • useGlobalShortcuts hook reads all key bindings from the registry via k(id: ShortcutId) helper (no hardcoded key strings)
  • All scoped shortcuts (toggle_sidebar, focus_search, open_note_dialog, switch_to_enhanced/raw/transcript, prev/next_panel_tab, transcript_search, play_pause_audio, undo_delete) use useScopedShortcut — no more manual useShortcutKeys + useHotkeys two-step pattern
  • Scoped shortcuts remain in their original components — they are NOT moved to global scope
  • The old useTabsShortcuts function (~225 lines in body/index.tsx) is removed; its logic now lives in useGlobalShortcuts

Doc generation (plugins/shortcut/):

  • doc.rs with format_keys_as_kbd() (converts "mod+shift+n"<kbd>⌘</kbd> + <kbd>⇧</kbd> + <kbd>N</kbd>), render_table() for markdown table generation, and build_sections() to group shortcuts by category
  • Askama template assets/keyboard-shortcuts.mdx.jinja — uses {%- for %} whitespace trimming and {{ section.table }} (table rendered in Rust) to stay dprint-compatible
  • export_docs test in lib.rs — running cargo test -p tauri-plugin-shortcut generates both TS bindings and apps/web/content/docs/faq/6.keyboard-shortcuts.mdx
  • display_name() on ShortcutCategory for human-readable section headers (e.g. Tabs → "Notes & Tabs", View → "Sidebar & Panels")
  • Ordered ShortcutCategory enum variants control doc section order (Navigation → View → Tabs → Search → Editor)

CI:

  • Added tauri-plugin-shortcut to the Windows CI exclude list in desktop_ci.yaml

Review & Testing Checklist for Human

  • Verify shortcuts work after app launch — all shortcuts now depend on async getAllShortcuts() resolving via React Query. Until the query completes, shortcuts are disabled. Open the app and confirm shortcuts activate within ~1s.
  • Test ALL global shortcutsmod+n, mod+t, mod+w, mod+1-9, mod+alt+left/right, mod+shift+t/c/o/comma/l/f/n, mod+j, mod+,
  • Test scoped shortcuts in their correct contextmod+\ (sidebar), mod+k (search), mod+o (empty tab), mod+f (transcript), mod+h (find/replace), alt+s/m/t (editor tabs), ctrl+alt+left/right (panel nav in note-input), space (audio playback), mod+z (undo delete)
  • Verify mod+w close-while-listening behavior — complex branching (listening → confirmation, pinned → unpin, chat_support → CLOSE event, else → close). Logic was moved from useTabsShortcuts in body/index.tsx to useGlobalShortcuts in a different component context.
  • Verify mod+, splitKey special case — this shortcut uses splitKey: "|" option in useGlobalShortcuts. Confirm it still works through the registry.
  • Verify generated keyboard shortcuts doc renders correctly — check https://hyprnote.com/docs/faq/keyboard-shortcuts (or local dev server) to ensure the MDX renders properly with correct kbd styling and no parsing errors

Recommended test plan: Open the app, create tabs of different types (empty, note, calendar, session with active listening), and systematically test each shortcut. Pay special attention to shortcuts with complex logic (mod+w) and scoped shortcuts that should only work in specific contexts.

Notes

  • The Rust registry is now the authoritative source for all key bindings. TS reads from it dynamically rather than hardcoding strings.
  • Type safety: The ShortcutId enum provides compile-time validation that all shortcut references are valid. Typos in shortcut IDs are now caught at build time rather than silently failing at runtime.
  • useScopedShortcut wrapper: Simplifies scoped shortcut registration by combining useShortcutKeys + useHotkeys into one call. Automatically sets enabled: !!keys to prevent invalid handlers.
  • Startup race condition: Shortcuts are disabled (enabled: false) until the registry query completes. This prevents useHotkeys("") from registering invalid handlers, but means there's a brief window (~1s) on app launch where no shortcuts work.
  • Doc generation: Running pnpm codegen in plugins/shortcut/ (which runs cargo test -p tauri-plugin-shortcut) now generates both TS bindings and the keyboard shortcuts MDX doc. The doc is deterministically generated from the registry using askama templates. Table rendering is done in Rust (render_table()) to keep the Jinja template dprint-compatible (no markdown table syntax in the template).
  • Backslash escaping: The mod+\ shortcut required special handling — backslash is escaped as \\ in MDX output to prevent it from escaping the closing </kbd> tag.
  • Scoped shortcuts remain in their original components for proper context scoping, but now read their key bindings from the Rust registry.
  • The old useTabsShortcuts function was removed from body/index.tsx; its logic now lives in useGlobalShortcuts in _layout.tsx.

Link to Devin run: https://app.devin.ai/sessions/0fda949a1eda4eed97e4ae4099fdb145
Requested by: @yujonglee

- Create plugins/shortcut/ with types, registry, and get_all_shortcuts command
- Wire plugin into app (Cargo.toml, lib.rs, capabilities, package.json)
- Create useGlobalShortcuts hook centralizing ~13 global shortcuts
- Migrate shortcuts from body/index.tsx, chat.ts, settings.ts to hook
- Mount useGlobalShortcuts in main layout

Co-Authored-By: yujonglee <yujonglee.dev@gmail.com>
@devin-ai-integration
Copy link
Contributor Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR that start with 'DevinAI' or '@devin'.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

@netlify
Copy link

netlify bot commented Feb 18, 2026

Deploy Preview for hyprnote canceled.

Name Link
🔨 Latest commit a4cc066
🔍 Latest deploy log https://app.netlify.com/projects/hyprnote/deploys/699a92e92fe6050008c2315b

@netlify
Copy link

netlify bot commented Feb 18, 2026

Deploy Preview for hyprnote-storybook canceled.

Name Link
🔨 Latest commit a4cc066
🔍 Latest deploy log https://app.netlify.com/projects/hyprnote-storybook/deploys/699a92e88a6d790008709238

devin-ai-integration bot and others added 12 commits February 18, 2026 06:42
Co-Authored-By: yujonglee <yujonglee.dev@gmail.com>
Co-Authored-By: yujonglee <yujonglee.dev@gmail.com>
… in TS

- Create useShortcutRegistry hook with shared query and keysMap lookup
- Refactor useGlobalShortcuts to use registry keys via k(id) helper
- Update all scoped shortcuts (leftsidebar, search, empty, note-input,
  settings, ai, transcript search, audio playback, undo-delete) to
  read keys from the Rust registry
- Add enabled guards so shortcuts only activate after registry loads

Co-Authored-By: yujonglee <yujonglee.dev@gmail.com>
Co-Authored-By: yujonglee <yujonglee.dev@gmail.com>
Co-Authored-By: yujonglee <yujonglee.dev@gmail.com>
Co-Authored-By: yujonglee <yujonglee.dev@gmail.com>
Co-Authored-By: yujonglee <yujonglee.dev@gmail.com>
Add askama template-based doc generation for the keyboard shortcuts MDX.
Running 'cargo test -p tauri-plugin-shortcut' (codegen) now generates both
TS bindings and the docs page from the Rust shortcut registry.

- Add doc.rs with key-to-kbd formatting and section builder
- Add askama template for keyboard-shortcuts.mdx
- Add export_docs test that renders template to MDX output
- Reorder ShortcutCategory variants to control doc section order
- Add display_name() to ShortcutCategory for doc section headers

Co-Authored-By: yujonglee <yujonglee.dev@gmail.com>
Co-Authored-By: yujonglee <yujonglee.dev@gmail.com>
Co-Authored-By: yujonglee <yujonglee.dev@gmail.com>
Co-Authored-By: yujonglee <yujonglee.dev@gmail.com>
Co-Authored-By: yujonglee <yujonglee.dev@gmail.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