Skip to content

feat(shortcuts): surface hotkey tooltips across desktop UI#6439

Open
wolfiesch wants to merge 2 commits into
stablyai:mainfrom
wolfiesch:wolfiesch/hotkey-tooltip-expansion
Open

feat(shortcuts): surface hotkey tooltips across desktop UI#6439
wolfiesch wants to merge 2 commits into
stablyai:mainfrom
wolfiesch:wolfiesch/hotkey-tooltip-expansion

Conversation

@wolfiesch

@wolfiesch wolfiesch commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Summary

Surfaces keyboard-shortcut affordances (tooltips and menu shortcut chips) consistently across the desktop UI. Wherever an action already had a binding, the chrome now shows the shortcut on hover or in its context/dropdown menu; where an affordance existed without a configurable action, a configurable action was added so the chip can render.

Audited surfaces: app chrome / titlebar, tab strip (editor + browser tabs), browser pane (back / forward / reload), editor path menu + rich-markdown actions, left sidebar (nav, toolbar, worktree card + context menu), right sidebar activity bar, floating workspace controls, terminal search, and the Cmd-J command palette.

New configurable actions:

  • workspace.togglePin — default Cmd+Opt+P (macOS).
  • workspace.copyPath — unbound by default.
  • workspace.sleep — unbound by default.
  • tab.togglePin — unbound by default.

conflictGroup: workspace-pin is added to workspace.togglePin and tab.togglePin so Settings cannot allow one to silently shadow the other. Workspace pin/copy/sleep are routed through the App/main-process IPC path rather than duplicate handlers in Terminal.tsx. Unbound actions intentionally render no chip unless the user assigns a binding.

Screenshots

A representative set is below. Captures were curated to one example per surface; redundant and unclear shots were dropped.

Ships by default — these chips/tooltips appear out of the box:

sidebar-search-inline-shortcut (⌘J) terminal-search-next-tooltip terminal-search-previous-tooltip browser-back-tooltip (representative of back/forward/reload) floating-workspace-toggle-tooltip

Mixed (default + demo binding) — the workspace context menu shows the real default Pin chip (workspace.togglePin, Cmd+Opt+P on macOS) alongside Copy Path / Sleep, which are unbound by default and shown here with a temporary demo binding:

workspace-context-menu — Pin is a real default; Copy Path/Sleep are demo-bound

Testing

  • pnpm lint — not run repo-wide; ran pnpm run lint:react-doctor:changed (warnings only, in pre-existing areas).
  • pnpm typecheckpnpm run typecheck:web passed; LSP reported no TypeScript diagnostics on changed files.
  • pnpm test — not run repo-wide; ran the targeted shortcut/menu Vitest suite (8 files, 149 tests passed).
  • pnpm build — not run.
  • Added or updated high-quality tests — added/updated keybinding, window-shortcut-policy, sidebar nav/toolbar, and floating-terminal toggle tests covering the new actions and chip rendering.

Scoped checks were chosen to cover the changed files; repo-wide lint/test/build were not run in this environment. Local Node may differ from the repo's pinned Node; CI behavior is not claimed.

AI Review Report

Ran an agent self-review across the diff focused on shortcut wiring and cross-platform behavior. Checked:

  • Cross-platform shortcuts/labels: bindings use runtime platform checks; default Cmd+Opt+P is mac-specific and degrades to unbound elsewhere rather than hardcoding a chord.
  • Conflict handling: conflictGroup: workspace-pin prevents silent shadowing between workspace and tab pin.
  • No new path assumptions; no / vs \ hardcoding introduced.
  • Unbound-by-default actions verified to render no chip unless customized.

Flagged and resolved during development: duplicate pin/copy/sleep handling was consolidated onto the App/main IPC path instead of Terminal.tsx.

Security Audit

  • Input handling: no new user-input parsing beyond keybinding strings, which flow through the existing keybindings validation.
  • Command execution: none added.
  • Path handling: unchanged; no new filesystem path construction.
  • Auth / secrets: none touched.
  • IPC: workspace pin/copy/sleep use the existing preload-exposed App/main IPC surface; new channels mirror existing typed patterns in src/preload. No raw/unvalidated payloads introduced.
  • Dependencies: none added.

Notes

  • macOS-specific default (Cmd+Opt+P) for workspace.togglePin; other platforms start unbound.
  • Several demoed chips require a user binding to appear (listed under Screenshots); this is intended product behavior, not a missing default.
  • SSH/remote: changes are renderer/UI and IPC-routing only; no assumptions about local-only filesystem behavior.

X: @wolfie_

@wolfiesch wolfiesch marked this pull request as ready for review June 26, 2026 23:25
@coderabbitai

coderabbitai Bot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: a3522978-bcd6-4154-9888-8be37cd43b97

📥 Commits

Reviewing files that changed from the base of the PR and between e071774 and 825ed2e.

📒 Files selected for processing (21)
  • src/main/window/createMainWindow.ts
  • src/preload/api-types.ts
  • src/preload/index.ts
  • src/renderer/src/App.tsx
  • src/renderer/src/components/GitHubItemDialog.tsx
  • src/renderer/src/components/LinearItemDrawer.tsx
  • src/renderer/src/components/PullRequestPage.tsx
  • src/renderer/src/components/TaskPage.tsx
  • src/renderer/src/components/Terminal.tsx
  • src/renderer/src/components/TerminalSearch.tsx
  • src/renderer/src/components/WorktreeJumpPalette.tsx
  • src/renderer/src/components/browser-pane/BrowserPane.tsx
  • src/renderer/src/components/editor/EditorPanelHeader.tsx
  • src/renderer/src/components/editor/RichMarkdownToolbar.tsx
  • src/renderer/src/components/editor/RichMarkdownToolbarButton.tsx
  • src/renderer/src/components/floating-terminal/FloatingTerminalPanel.tsx
  • src/renderer/src/components/floating-terminal/FloatingTerminalToggleButton.test.tsx
  • src/renderer/src/components/floating-terminal/FloatingTerminalToggleButton.tsx
  • src/renderer/src/components/floating-terminal/FloatingTerminalWindowControls.tsx
  • src/renderer/src/components/right-sidebar/activity-bar-buttons.tsx
  • src/renderer/src/components/right-sidebar/index.tsx
💤 Files with no reviewable changes (21)
  • src/renderer/src/components/Terminal.tsx
  • src/renderer/src/components/floating-terminal/FloatingTerminalPanel.tsx
  • src/main/window/createMainWindow.ts
  • src/renderer/src/components/floating-terminal/FloatingTerminalToggleButton.test.tsx
  • src/renderer/src/components/editor/RichMarkdownToolbarButton.tsx
  • src/preload/api-types.ts
  • src/renderer/src/components/right-sidebar/activity-bar-buttons.tsx
  • src/renderer/src/components/editor/EditorPanelHeader.tsx
  • src/preload/index.ts
  • src/renderer/src/components/floating-terminal/FloatingTerminalWindowControls.tsx
  • src/renderer/src/components/TerminalSearch.tsx
  • src/renderer/src/App.tsx
  • src/renderer/src/components/LinearItemDrawer.tsx
  • src/renderer/src/components/editor/RichMarkdownToolbar.tsx
  • src/renderer/src/components/right-sidebar/index.tsx
  • src/renderer/src/components/PullRequestPage.tsx
  • src/renderer/src/components/GitHubItemDialog.tsx
  • src/renderer/src/components/floating-terminal/FloatingTerminalToggleButton.tsx
  • src/renderer/src/components/browser-pane/BrowserPane.tsx
  • src/renderer/src/components/TaskPage.tsx
  • src/renderer/src/components/WorktreeJumpPalette.tsx

📝 Walkthrough

Walkthrough

This PR adds three new workspace keyboard shortcut actions (workspace.togglePin, workspace.copyPath, workspace.sleep) and a new tab pin action (tab.togglePin). These are registered as keybinding definitions, resolved through WindowShortcutAction, forwarded via IPC from the main process through the preload layer to renderer IPC event handlers. In parallel, the PR broadly updates tooltip content across many renderer components—editor toolbar, browser pane, terminal search, floating-terminal controls, sidebar, tab bar, and comment composers—to display structured ShortcutKeyCombo UI elements instead of plain shortcut label strings.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: adding shortcut tooltips across the desktop UI.
Description check ✅ Passed The description follows the template and covers summary, screenshots, testing, review, security, and notes with substantive details.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (4)
src/renderer/src/components/tab-bar/TabBar.tsx (1)

997-1028: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Effect re-runs on every render because togglePinned is a fresh dependency.

togglePinned is recreated on each render, so the [activeItem, togglePinned] dependency array never compares equal and the effect tears down and re-adds the window listener every render. Wrap togglePinned in useCallback (or read activeItem via a ref and drop it from deps) so the listener is registered once.

♻️ Suggested approach
-  const togglePinned = (item: TabItem): void => {
+  const togglePinned = useCallback((item: TabItem): void => {
     // pinTab/unpinTab mirror the change to the host for remote-server tabs.
     if (item.isPinned) {
       unpinTab(item.unifiedTabId)
       return
     }
     if (item.type === 'editor' && onPinFile) {
       onPinFile(item.data.id, item.unifiedTabId)
       return
     }
     pinTab(item.unifiedTabId)
-  }
+  }, [onPinFile, pinTab, unpinTab])

Source: Linters/SAST tools

src/shared/keybindings.ts (1)

325-356: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Document the shared pin-conflict behavior.

workspace.togglePin and tab.togglePin intentionally share workspace-pin, and only the workspace action gets a default chord. That coupling is easy to break later unless the reason lives next to the data. Add a short Why: comment by the shared conflict group/defaults.

Suggested comment
   {
     id: 'workspace.togglePin',
     title: 'Pin / Unpin Workspace',
     group: 'Global',
     scope: 'global',
+    // Why: shared with tab.togglePin so settings cannot let one pin shortcut
+    // silently shadow the other across scopes.
     conflictGroup: 'workspace-pin',
     searchKeywords: ['shortcut', 'global', 'workspace', 'worktree', 'pin', 'unpin'],
     defaultBindings: {
       darwin: ['Mod+Alt+P'],
       linux: [],
       win32: []
     },
     allowInTerminal: true
   },

As per coding guidelines, "When writing or modifying code driven by a design doc or non-obvious constraint, add a comment explaining why the code behaves the way it does."

Also applies to: 662-670

Source: Coding guidelines

src/shared/window-shortcut-policy.ts (1)

216-226: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add the routing rationale next to these workspace actions.

These branches now depend on the same main-process interception path as workspace.create, but that requirement is only documented for the create action. A short Why: comment here would make the browser-guest/contentEditable constraint much easier to preserve.

Suggested comment
+  // Why: route these workspace actions through the main-process allowlist so
+  // they still fire from browser guests and contentEditable surfaces.
   if (actionMatches('workspace.togglePin', input, platform, keybindings, options)) {
     return { type: 'toggleCurrentWorkspacePin' }
   }

As per coding guidelines, "When writing or modifying code driven by a design doc or non-obvious constraint, add a comment explaining why the code behaves the way it does."

Source: Coding guidelines

src/renderer/src/components/floating-terminal/FloatingTerminalWindowControls.tsx (1)

165-196: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Optional: collapse the duplicated ShortcutKeyCombo block.

Both branches of the maximized/restore ternary render the identical ShortcutKeyCombo from maximizeShortcut; only the label text differs. You can keep the conditional on the label and hoist the combo so it isn't duplicated.

♻️ Proposed simplification
-        <TooltipContent side="bottom" sideOffset={6} className="flex items-center gap-2">
-          {maximized ? (
-            <>
-              <span>
-                {translate(
-                  'auto.components.floating.terminal.FloatingTerminalWindowControls.b5686fee1e',
-                  'Restore'
-                )}
-              </span>
-              {maximizeShortcut.keys.length > 0 && (
-                <ShortcutKeyCombo
-                  keys={maximizeShortcut.keys}
-                  doubleTap={maximizeShortcut.doubleTap}
-                />
-              )}
-            </>
-          ) : (
-            <>
-              <span>
-                {translate(
-                  'auto.components.floating.terminal.FloatingTerminalWindowControls.109870e023',
-                  'Maximize'
-                )}
-              </span>
-              {maximizeShortcut.keys.length > 0 && (
-                <ShortcutKeyCombo
-                  keys={maximizeShortcut.keys}
-                  doubleTap={maximizeShortcut.doubleTap}
-                />
-              )}
-            </>
-          )}
-        </TooltipContent>
+        <TooltipContent side="bottom" sideOffset={6} className="flex items-center gap-2">
+          <span>
+            {maximized
+              ? translate(
+                  'auto.components.floating.terminal.FloatingTerminalWindowControls.b5686fee1e',
+                  'Restore'
+                )
+              : translate(
+                  'auto.components.floating.terminal.FloatingTerminalWindowControls.109870e023',
+                  'Maximize'
+                )}
+          </span>
+          {maximizeShortcut.keys.length > 0 && (
+            <ShortcutKeyCombo
+              keys={maximizeShortcut.keys}
+              doubleTap={maximizeShortcut.doubleTap}
+            />
+          )}
+        </TooltipContent>

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 9c75dfdc-40eb-4577-99b7-d8e849b87fe3

📥 Commits

Reviewing files that changed from the base of the PR and between 996ab4a and ea34a2f.

📒 Files selected for processing (38)
  • src/main/window/createMainWindow.ts
  • src/preload/api-types.ts
  • src/preload/index.ts
  • src/renderer/src/App.tsx
  • src/renderer/src/components/GitHubItemDialog.tsx
  • src/renderer/src/components/LinearItemDrawer.tsx
  • src/renderer/src/components/PullRequestPage.tsx
  • src/renderer/src/components/TaskPage.tsx
  • src/renderer/src/components/Terminal.tsx
  • src/renderer/src/components/TerminalSearch.tsx
  • src/renderer/src/components/WorktreeJumpPalette.tsx
  • src/renderer/src/components/browser-pane/BrowserPane.tsx
  • src/renderer/src/components/editor/EditorPanelHeader.tsx
  • src/renderer/src/components/editor/RichMarkdownToolbar.tsx
  • src/renderer/src/components/editor/RichMarkdownToolbarButton.tsx
  • src/renderer/src/components/floating-terminal/FloatingTerminalPanel.tsx
  • src/renderer/src/components/floating-terminal/FloatingTerminalToggleButton.test.tsx
  • src/renderer/src/components/floating-terminal/FloatingTerminalToggleButton.tsx
  • src/renderer/src/components/floating-terminal/FloatingTerminalWindowControls.tsx
  • src/renderer/src/components/right-sidebar/activity-bar-buttons.tsx
  • src/renderer/src/components/right-sidebar/index.tsx
  • src/renderer/src/components/right-sidebar/right-sidebar-titlebar-drag-regions.render.test.tsx
  • src/renderer/src/components/sidebar/SidebarNav.test.tsx
  • src/renderer/src/components/sidebar/SidebarNav.tsx
  • src/renderer/src/components/sidebar/SidebarToolbar.test.tsx
  • src/renderer/src/components/sidebar/SidebarToolbar.tsx
  • src/renderer/src/components/sidebar/WorktreeCard.tsx
  • src/renderer/src/components/sidebar/WorktreeContextMenu.tsx
  • src/renderer/src/components/tab-bar/BrowserTab.tsx
  • src/renderer/src/components/tab-bar/EditorFileTabContextMenu.tsx
  • src/renderer/src/components/tab-bar/SortableTabContextMenu.tsx
  • src/renderer/src/components/tab-bar/TabBar.tsx
  • src/renderer/src/hooks/useIpcEvents.ts
  • src/renderer/src/web/web-preload-api.ts
  • src/shared/keybindings.test.ts
  • src/shared/keybindings.ts
  • src/shared/window-shortcut-policy.test.ts
  • src/shared/window-shortcut-policy.ts

Comment thread src/renderer/src/components/tab-bar/TabBar.tsx
Expose shortcut chips and tooltips on shortcut-backed affordances across
app chrome, tab strip, browser, editor, sidebar, right sidebar, floating
workspace, terminal search, and the command palette.

Add configurable actions: workspace.togglePin (default Cmd+Opt+P),
workspace.copyPath, workspace.sleep, and tab.togglePin (unbound by
default). Add conflictGroup workspace-pin so Settings cannot allow silent
shortcut shadowing. Route workspace pin/copy/sleep through the App/main
IPC path.
@wolfiesch wolfiesch force-pushed the wolfiesch/hotkey-tooltip-expansion branch from e071774 to 825ed2e Compare June 28, 2026 22:35
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.

2 participants