feat(shortcuts): global keyboard shortcuts + help directory#4126
Conversation
…elp directory Add a Hermes-inspired global shortcut layer and a discoverable help directory. Shortcuts (mod = ⌘ on macOS / Ctrl elsewhere), on top of the existing mod+1..5 / mod+, navigation and mod+K palette: - New Chat — mod+N (reuses useHomeNav) - Toggle Sidebar — mod+B (layoutSlice.toggleSidebar on the app shell) - Command Palette — mod+K, mod+P - Keyboard Shortcuts — mod+/ and ? globalActions becomes the single declarative source of truth (label, group, shortcut, alias keys, allow-in-input) for the palette, the help directory, and the real key bindings. CommandProvider wires the side-effecting handlers via a ref (mount-once) and renders the new help overlay; the palette and help overlay are mutually exclusive. Help directory lives in two places, both driven live from the command registry so they can never drift from the real bindings: - a ?/⌘/ modal overlay (KeyboardShortcutsModal) - Settings → Keyboard Shortcuts (KeyboardShortcutsPanel) Tests: globalActions handler/alias/dispose coverage, CommandProvider overlay + mutual-exclusion (re-enabling the previously-stubbed help tests), and shortcutsView grouping/ordering/empty state. test-utils gains the thread reducer so CommandProvider (via useHomeNav) renders under the shared store.
…ches Hermes-style modifier split for the numbered shortcuts: - Navigation tabs move to the Control key — `ctrl+1..5` on macOS (physical Control, distinct from ⌘), folding to `mod+1..5` on Windows/Linux (our matcher treats a bare `ctrl+N` as unreachable there), i.e. Ctrl+N physically on every OS. Settings keeps the conventional ⌘, . - Profile switches (⌘1..4 on macOS) are wired but inert: registered bound-but-disabled and hidden from the palette/help directory behind a PROFILES_ENABLED flag, ready to light up when the profiles feature lands. Adds a Profiles group to GROUP_ORDER / the help display order, and drops the now unnecessary exhaustive-deps disable in CommandProvider.
…r wallet button
- Add a keyboard glyph for the Settings → Keyboard Shortcuts entry
(settingsNavIcons) and a reusable `keyboard` NavIcon for the collapsed rail.
- Replace the wallet quick-access button in the sidebar header and collapsed rail
with a keyboard-shortcuts button that opens the help directory via
registry.runAction('meta.keyboard-shortcuts'). Wallet balances remains
reachable from Settings → Data.
- Simplify CollapsedNavRail's settingsActive now that the wallet rail item is
gone (Settings lights up on /settings/* including the wallet sub-page).
- Update shell tests for the wallet→shortcuts swap.
Extend command-palette.spec.ts: open the help directory via mod+/ (asserting the live grouped list renders the new global actions) and via the bare ? key, with Esc close in both.
The keyboard-shortcuts help overlay + shared list use the standard white/neutral modal styling (ModalShell), which the commands/ directory's cmd-token lint (lint:commands-tokens) forbids. Relocate shortcutsView + KeyboardShortcutsModal (and their test) to components/shortcuts/ and repoint imports; Kbd stays in commands/ and is imported across.
|
Warning Review limit reached
More reviews will be available in 46 minutes and 50 seconds. Learn how PR review limits work. Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file). ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits. 🚦 How do rate limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
📝 WalkthroughWalkthroughThis PR adds a handler-driven global command system, a reusable new-chat hook, keyboard-shortcuts list and modal UI, shell entry points for shortcuts, and a settings page for keyboard shortcuts. It also updates translations, tests, and coverage docs. ChangesKeyboard shortcuts command surface
Sequence Diagram(s)sequenceDiagram
participant CommandProvider
participant registerGlobalActions
participant registry
participant hotkeyManager
participant KeyboardShortcutsModal
CommandProvider->>registerGlobalActions: stable handlers from handlersRef
registerGlobalActions->>registry: register action handlers
registerGlobalActions->>hotkeyManager: bind command shortcuts
hotkeyManager->>CommandProvider: invoke shortcut handler
CommandProvider->>KeyboardShortcutsModal: set shortcutsOpen true
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 099aa287ad
ℹ️ 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".
The New Chat shortcut used useHomeNav(), whose off-chat branch only navigates to /chat and lets Conversations own blank-thread landing — but that restores the persisted selectedThreadId first, so from a non-chat route ⌘N reopened the previous conversation instead of starting fresh (flagged in PR review). Add a dedicated useNewChat() hook that always selects/creates a blank thread (reusing an existing empty one, else creating) and navigates straight to it, regardless of route. CommandProvider's newChat handler now uses it. Covered by useNewChat.test.tsx.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 23450c2773
ℹ️ 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".
… hint key - useNewChat: a thread counts as empty only when it has neither a server messageCount nor cached messages. Right after the first send, addMessageLocal populates messagesByThreadId while messageCount can still read 0, so the old count-only check could reuse (and reopen) that now-occupied conversation. - CommandPalette footer: advertise ⌘/ (allowed while the search box is focused) via <Kbd>, instead of "Press ? for all shortcuts" — `?` is intentionally skipped inside inputs, so it never worked from the focused palette. Reuses the shared shortcuts.title string.
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (2)
app/src/components/shortcuts/shortcutsView.tsx (1)
86-104: 🔒 Security & Privacy | 🔵 TrivialTranslate shortcut labels and headings via
useT()
groupandaction.labelcome fromapp/src/lib/commands/globalActions.tsas hard-coded English strings, so this view still bypasses i18n. Store translation keys in the registry or translate these values at render time.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/src/components/shortcuts/shortcutsView.tsx` around lines 86 - 104, The shortcuts view still renders hard-coded English values for the section heading and action label; update ShortcutsView to use useT() when displaying group and action.label, or switch the globalActions registry to store translation keys instead of raw strings. Locate the rendering in shortcutsView.tsx around the groups.map and items.map blocks, and ensure both the group header and each action row resolve through the translation function so the UI is fully localized.Source: Coding guidelines
app/test/e2e/specs/command-palette.spec.ts (1)
208-214: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winExercise the actual focused-input contract in these shortcut tests.
Right now both cases fire from whatever focus state the page happens to have, so they can still pass if
allowInInputregresses. The contract inapp/src/lib/commands/globalActions.tsis specifically thatmod+/should open from a focused input while bare?should not, so these tests should first focus a real editable field and assert that behavior explicitly.Also applies to: 245-249
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/test/e2e/specs/command-palette.spec.ts` around lines 208 - 214, Update the keyboard-shortcuts e2e specs to exercise the focused-input behavior explicitly: in the command-palette tests, first focus a real editable input before triggering the shortcuts, then assert that `globalActions` allows `mod+/` to open the help from that focused state while bare `?` does not. Use the existing `dispatchKey` helper and the keyboard-shortcuts/help list selectors in `command-palette.spec.ts` so the tests verify the `allowInInput` contract rather than relying on the page’s default focus.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@app/src/components/commands/CommandProvider.tsx`:
- Around line 57-77: The CommandProvider effect is synchronously calling
setGlobalFrame inside useEffect, which violates the
react-hooks/set-state-in-effect rule. Move the frame derivation out of the
effect and derive it during render or via a memoized value, then keep the effect
in CommandProvider focused only on registering and disposing global actions with
stableHandlers, registerGlobalActions, and hotkeyManager cleanup.
In `@app/src/components/layout/shell/useNewChat.ts`:
- Around line 45-52: The New Chat flow in useNewChat currently swallows
createNewThread failures in an empty catch, so the rejection is invisible.
Update the createNewThread().unwrap() chain to handle errors by surfacing them
through the app’s existing error/logging path or dispatching a user-visible
failure state, and keep the behavior tied to createNewThread, setSelectedThread,
and loadThreadMessages so the failure is no longer silently ignored.
In `@app/src/lib/commands/globalActions.ts`:
- Line 8: The command palette and shortcuts directory are exposing hardcoded
English group headings and label strings, so move these UI texts behind
translation keys and resolve them through the existing useT-backed i18n layer
before registration. Update GROUP_ORDER and the related label definitions in
globalActions to use locale-driven keys, then add the new entries to en.ts and
every locale catalog so the shortcut surface is fully localized.
In `@app/test/e2e/specs/command-palette.spec.ts`:
- Around line 233-238: The Escape fallback in the command palette test is
dispatched on the wrong target, so the ModalShell useEscapeKey listener on
document will not receive it. Update the fallback path in
command-palette.spec.ts around the browser.keys('Escape') try/catch to send
dispatchKey('Escape') to document instead of window, using the existing
dispatchKey helper so the overlay closes reliably.
---
Nitpick comments:
In `@app/src/components/shortcuts/shortcutsView.tsx`:
- Around line 86-104: The shortcuts view still renders hard-coded English values
for the section heading and action label; update ShortcutsView to use useT()
when displaying group and action.label, or switch the globalActions registry to
store translation keys instead of raw strings. Locate the rendering in
shortcutsView.tsx around the groups.map and items.map blocks, and ensure both
the group header and each action row resolve through the translation function so
the UI is fully localized.
In `@app/test/e2e/specs/command-palette.spec.ts`:
- Around line 208-214: Update the keyboard-shortcuts e2e specs to exercise the
focused-input behavior explicitly: in the command-palette tests, first focus a
real editable input before triggering the shortcuts, then assert that
`globalActions` allows `mod+/` to open the help from that focused state while
bare `?` does not. Use the existing `dispatchKey` helper and the
keyboard-shortcuts/help list selectors in `command-palette.spec.ts` so the tests
verify the `allowInInput` contract rather than relying on the page’s default
focus.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: a65d6d1d-2e6f-4204-985b-e60ab4cb243c
📒 Files selected for processing (36)
app/src/components/commands/CommandProvider.tsxapp/src/components/commands/__tests__/CommandProvider.test.tsxapp/src/components/layout/shell/CollapsedNavRail.test.tsxapp/src/components/layout/shell/CollapsedNavRail.tsxapp/src/components/layout/shell/RootShellLayout.tsxapp/src/components/layout/shell/SidebarHeader.test.tsxapp/src/components/layout/shell/SidebarHeader.tsxapp/src/components/layout/shell/navIcons.tsxapp/src/components/layout/shell/useNewChat.test.tsxapp/src/components/layout/shell/useNewChat.tsapp/src/components/settings/layout/settingsNavIcons.tsxapp/src/components/settings/panels/KeyboardShortcutsPanel.tsxapp/src/components/settings/settingsRouteElements.tsxapp/src/components/settings/settingsRouteRegistry.tsapp/src/components/shortcuts/KeyboardShortcutsModal.tsxapp/src/components/shortcuts/__tests__/shortcutsView.test.tsxapp/src/components/shortcuts/shortcutsView.tsxapp/src/lib/commands/__tests__/globalActions.test.tsxapp/src/lib/commands/globalActions.tsapp/src/lib/i18n/ar.tsapp/src/lib/i18n/bn.tsapp/src/lib/i18n/de.tsapp/src/lib/i18n/en.tsapp/src/lib/i18n/es.tsapp/src/lib/i18n/fr.tsapp/src/lib/i18n/hi.tsapp/src/lib/i18n/id.tsapp/src/lib/i18n/it.tsapp/src/lib/i18n/ko.tsapp/src/lib/i18n/pl.tsapp/src/lib/i18n/pt.tsapp/src/lib/i18n/ru.tsapp/src/lib/i18n/zh-CN.tsapp/src/test/test-utils.tsxapp/test/e2e/specs/command-palette.spec.tsdocs/TEST-COVERAGE-MATRIX.md
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 539f66638b
ℹ️ 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".
- useNewChat: log createNewThread rejections instead of swallowing them in an empty catch, so the primary New Chat path is diagnosable (review feedback). - command-palette.spec.ts: extract closeOverlayWithEscape() with a document-targeted synthetic-Escape last resort — ModalShell's useEscapeKey binds to document, so a window-dispatched fallback would miss it.
Route the command palette / shortcuts-directory action labels and group headings through useT() instead of shipping hardcoded English (project i18n guideline). - Action gains an optional labelKey resolved at display time (English `label` kept as fallback + for cmdk search); globalActions registers a labelKey per action and exports a GROUP_LABEL_KEYS map for the headings. - CommandPalette + ShortcutsList resolve labelKey/group via t(). - Add shortcuts.action.* and shortcuts.group.* keys to en.ts and all 13 locale catalogs (parity check passes).
Extend useNewChat's empty-thread guard to also exclude the currently-selected thread. An in-flight first send is tracked only locally on the active conversation (count still 0, not yet in messagesByThreadId), so without this the shortcut could reopen the conversation the user is sending in. Skipping the selected thread + the cache check closes that pre-fulfillment window.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f863f41fc5
ℹ️ 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".
…Chat Replace the blanket "exclude the selected thread" guard with an occupancy check: a thread is reusable when it has no message count, no cached messages, and no in-flight assistant turn (chatRuntime.streamingAssistantByThread, whose `started` lifecycle is set the moment the user sends). This reuses a genuinely-blank current chat instead of piling up empties, while still never reopening a conversation with a send in flight — reconciling both review rounds.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b102af5cea
ℹ️ 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".
…g send Two review fixes: - chat.new (⌘N/Ctrl+N) gains allowInInput: true so it fires while the chat composer textarea is focused (the normal state when starting fresh) and preventDefault stops the OS "new window" accelerator swallowing it. - Lift the optimistic-send signal into Redux: chatRuntimeSlice gains pendingSendThreadIds with mark/clear reducers, set synchronously the instant a user sends (mirrored from Conversations' existing pending-send helpers, before addMessageLocal is awaited). useNewChat now also excludes pending-send threads, closing the earliest window where messageCount, messagesByThreadId and streamingAssistantByThread are all still empty.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b2da69bb72
ℹ️ 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".
Apply the same isThreadVisibleInTab(thread, GENERAL_TAB_VALUE) filter the /chat landing uses before treating a thread as reusable, so ⌘N/Ctrl+N from Settings or another route never lands on a hidden blank task/subconscious/parented session instead of opening a fresh general chat. New regression test covers it.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c6ac89506b
ℹ️ 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".
| void dispatch(createNewThread()) | ||
| .unwrap() |
There was a problem hiding this comment.
Guard New Chat against duplicate creates
When there is no reusable blank thread and the user invokes New Chat twice before the first createNewThread().unwrap() settles (for example double-tapping Cmd/Ctrl+N while the core is slow), this branch dispatches another create because no in-flight create is recorded. Both thunks can create and then select/navigate to their own fresh threads, leaving an extra blank conversation despite the hook's blank-thread reuse contract; add a local/Redux pending-create guard or optimistic placeholder before dispatching.
Useful? React with 👍 / 👎.
Summary
hotkeyManager+ command registry).?/ ⌘/).ctrl+1..5on macOS, folding tomod+1..5on Win/Linux); profile switches (⌘1..4) are wired but hidden until the profiles feature exists.?/⌘/ modal overlay and Settings → Keyboard Shortcuts.Problem
The app already had a command palette (⌘K) and a few nav shortcuts, but no discoverable map of what's available — the palette even advertised "Press ? for all shortcuts" with no handler behind it. Users had no fast way to start a new chat, toggle the sidebar, or learn the shortcut set.
Solution
globalActions.tsis the single declarative source of truth (label, group, primary shortcut, alias keys, per-comboallowInInput, enabled/visibility) feeding the palette, the help directory, and the actual key bindings.mod+/is allowed while inputs are focused (so it can replace the palette mid-search); the bare?is not, so typing "?" in chat never hijacks it. Palette and help overlay are mutually exclusive.CommandProviderwires the side-effecting handlers through a ref so the hotkey frame registers once and never tears down on navigation.useNewChathook that always selects/creates a blank thread regardless of route (a review caught that reusing the Home-nav path would reopen the persisted conversation from non-chat routes).ctrl+Nas macOS-only (physical Control), so nav usesctrlon macOS andmodelsewhere — Ctrl+N physically on every OS. Profile shortcuts (mod+N= ⌘N on macOS) are registered bound-but-disabled and hidden behind aPROFILES_ENABLEDflag, ready to light up when profiles land.ModalShellstyling, so they live incomponents/shortcuts/(outsidecomponents/commands/, whosecmd-*-token lint forbids neutral colours).Submission Checklist
globalActions,CommandProvideroverlays,shortcutsViewgrouping,useNewChat, shell button swaps,command-palette.spec.ts). CIcoverage-gateverifies.docs/TEST-COVERAGE-MATRIX.md## RelatedCloses.Impact
Related
PROFILES_ENABLED).AI Authored PR Metadata (required for Codex/Linear PRs)
Linear Issue
Commit & Branch
Validation Run
pnpm --filter openhuman-app format:checkpnpm typecheckglobalActions,CommandProvider,shortcutsView,useNewChat,SidebarHeader,CollapsedNavRail; E2E spec added (command-palette.spec.ts) — runs in CI.rust:checkhook passed).Validation Blocked
command:N/Aerror:N/Aimpact:N/ABehavior Changes
Parity Contract
PROFILES_ENABLED;?gated out of text inputs; palette/help mutually exclusive.Duplicate / Superseded PR Handling
Summary by CodeRabbit