Skip to content

Add a global command palette powered by TanStack Hotkeys#10

Merged
matfire merged 4 commits intomainfrom
feat/add-command-palette-with-hotkeys
Mar 21, 2026
Merged

Add a global command palette powered by TanStack Hotkeys#10
matfire merged 4 commits intomainfrom
feat/add-command-palette-with-hotkeys

Conversation

@matfire
Copy link
Copy Markdown
Member

@matfire matfire commented Mar 21, 2026

Summary

  • add a global command palette using cmdk and TanStack Hotkeys
  • support commands for adding a project, opening the selected project in a separate window, toggling theme, toggling fullscreen, and toggling the projects sidebar
  • initialize project state at the provider level so palette actions work outside /projects
  • add TanStack Hotkeys devtools and Tauri fullscreen permissions required for window fullscreen actions

Testing

  • bun run build
  • ./node_modules/.bin/tsc --noEmit

Summary by CodeRabbit

  • New Features

    • Searchable command palette (Mod+K) with grouped actions, shortcuts, and global hotkeys; auto-closes on blur.
    • New themed toaster for styled notifications.
    • Desktop fullscreen support and keyboard toggles; sidebar can be toggled via a global event.
  • Refactor

    • Theme system reworked to separate system theme from user preference and expose a resolved theme and toggle.
  • Chores

    • Added hotkeys, devtools, command UI, theme, and toaster runtime dependencies; lifecycle handling for project store initialization.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 21, 2026

Warning

Rate limit exceeded

@matfire has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 19 minutes and 0 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9f82298f-8ed7-46b4-9b3f-f7608f28a9d3

📥 Commits

Reviewing files that changed from the base of the PR and between 1030d09 and 582dca2.

📒 Files selected for processing (1)
  • src/components/command-palette/command-palette.tsx
📝 Walkthrough

Walkthrough

Adds a cmdk-based command palette with global hotkeys and hotkeys devtools, injects a HotkeysProvider, adds a themed Sonner toaster, expands Tauri fullscreen capabilities, refactors theme resolution to include a system-aware resolved theme, auto-initializes/disposes projects store, and wires a sidebar toggle event.

Changes

Cohort / File(s) Summary
Dependencies & Tauri capabilities
package.json, src-tauri/capabilities/default.json, src-tauri/capabilities/desktop.json
Added runtime deps: @tanstack/react-hotkeys, @tanstack/react-hotkeys-devtools, cmdk, next-themes, sonner. Added core:window:allow-is-fullscreen and core:window:allow-set-fullscreen to Tauri capability permissions.
Command palette feature
src/components/command-palette/command-palette.tsx, src/components/command-palette/index.ts
New exported CommandPalette component implementing searchable/grouped commands, global hotkeys (toggle, direct shortcuts, ESC, fullscreen via Tauri), safe execution wrapper, sidebar toggle event firing, and an index re-export.
Command UI primitives
src/components/ui/command.tsx
New cmdk/dialog wrapper primitives: Command, CommandDialog, CommandInput, CommandList, CommandEmpty, CommandGroup, CommandSeparator, CommandItem, CommandShortcut with standardized styling and disabled handling.
Toaster & theme integration
src/components/ui/sonner.tsx, src/contexts/theme-context.tsx
New Toaster component using Sonner, wired to theme via resolvedTheme. Theme provider refactored to track systemTheme, compute resolvedTheme, centralize theme application, expose resolvedTheme and toggleThemePreference.
Sidebar event & providers
src/components/ui/sidebar.tsx, src/contexts/app-providers.tsx
Exported SIDEBAR_TOGGLE_EVENT and wired SidebarProvider to listen for it. Wrapped provider tree with HotkeysProvider in AppProviders.
Projects lifecycle & routes
src/contexts/projects-context.tsx, src/routes/projects.tsx
ProjectsProvider now auto-initializes and disposes projects store on mount/unmount. Projects route usage narrowed to read-only fields; manual init/dispose removals.
Root integration & devtools
src/routes/__root.tsx
Root now renders CommandPalette and Toaster inside app, and adds hotkeysDevtoolsPlugin() to TanStack devtools plugin list.
Logging helper
src/lib/logger.ts
Added exported processLogger with error(message, error?) delegating to console.error.

Sequence Diagram

sequenceDiagram
    participant User as User
    participant Hotkeys as HotkeysProvider
    participant Palette as CommandPalette
    participant Theme as ThemeContext
    participant Tauri as TauriWindow
    participant Sidebar as SidebarComponent

    User->>Hotkeys: press Mod+K
    Hotkeys->>Palette: emit toggle
    Palette->>Palette: setIsOpen(true)
    Palette-->>User: show dialog

    User->>Palette: select "Toggle Theme"
    Palette->>Theme: toggleThemePreference()
    Theme->>Theme: update preference & compute resolvedTheme
    Theme-->>User: apply resolved theme
    Palette->>Palette: close

    User->>Hotkeys: press fullscreen hotkey
    Hotkeys->>Palette: trigger fullscreen command
    Palette->>Tauri: getCurrentWindow().setFullscreen(...)
    Tauri-->>User: window fullscreen toggled
    Palette->>Palette: close

    User->>Palette: select "Toggle Sidebar"
    Palette->>Sidebar: dispatch SIDEBAR_TOGGLE_EVENT
    Sidebar->>Sidebar: toggleSidebar()
    Palette->>Palette: close
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Poem

🐰 I hopped in with hotkeys and a palette so bright,

Commands at a whisk, toggling day into night.
Themes flip and windows stretch full to the sky,
Sidebar listens, projects wake with a sigh.
A rabbitly cheer for features that fly.

🚥 Pre-merge checks | ✅ 2 | ❌ 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 (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: adding a global command palette powered by TanStack Hotkeys, which is the primary feature introduced across the changeset.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/add-command-palette-with-hotkeys

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 and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
src/components/command-palette/command-palette.tsx (2)

86-94: Extract duplicated fullscreen toggle logic.

The fullscreen toggle implementation appears twice: in the command's onSelect (lines 86-94) and in handleToggleFullscreen (lines 204-212). Consider extracting to a single shared function.

♻️ Proposed refactor
+  const handleToggleFullscreen = async () => {
+    if (!isTauri()) {
+      return;
+    }
+
+    const appWindow = getCurrentWindow();
+    const isFullscreen = await appWindow.isFullscreen();
+    await appWindow.setFullscreen(!isFullscreen);
+  };
+
   const commands = useMemo<PaletteCommand[]>(
     () => [
       // ... other commands
       {
         id: "toggle-fullscreen",
         label: "Toggle fullscreen",
         group: "Window",
         icon: Maximize,
-        onSelect: async () => {
-          if (!isTauri()) {
-            return;
-          }
-
-          const appWindow = getCurrentWindow();
-          const isFullscreen = await appWindow.isFullscreen();
-          await appWindow.setFullscreen(!isFullscreen);
-        },
+        onSelect: handleToggleFullscreen,
         disabled: !isTauri(),
         // ...
       },
     ],
-    [/* deps */],
+    [/* add handleToggleFullscreen to deps */],
   );
-
-  const handleToggleFullscreen = async () => {
-    // remove duplicate
-  };

Also applies to: 204-212

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/command-palette/command-palette.tsx` around lines 86 - 94,
Duplicate fullscreen toggle logic exists in the command's onSelect handler and
in handleToggleFullscreen; extract that logic into a single reusable async
function (e.g., toggleFullscreen) that checks isTauri(), calls
getCurrentWindow(), reads isFullscreen() and calls setFullscreen(!isFullscreen),
then replace the bodies of onSelect and handleToggleFullscreen to simply call
this new toggleFullscreen function to remove duplication and keep behavior
identical.

112-121: Remove redundant location.pathname from dependencies.

location.pathname is only used to compute isSidebarAvailable (line 49), which is already listed in the dependency array. Including both is redundant.

♻️ Proposed fix
     [
       isSidebarAvailable,
-      location.pathname,
       openProject,
       openProjectInSeparateWindow,
       resolvedTheme,
       selectedProjectId,
       toggleThemePreference,
     ],
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/command-palette/command-palette.tsx` around lines 112 - 121,
The dependency array for the hook includes a redundant dependency: remove
location.pathname from the dependency list and keep isSidebarAvailable (which
already derives from location.pathname) to avoid double-triggering; update the
array used in the hook inside command-palette.tsx (the one currently listing
isSidebarAvailable, location.pathname, openProject, openProjectInSeparateWindow,
resolvedTheme, selectedProjectId, toggleThemePreference) so it omits
location.pathname but retains isSidebarAvailable and the other handlers/state
values.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/components/command-palette/command-palette.tsx`:
- Around line 86-94: Duplicate fullscreen toggle logic exists in the command's
onSelect handler and in handleToggleFullscreen; extract that logic into a single
reusable async function (e.g., toggleFullscreen) that checks isTauri(), calls
getCurrentWindow(), reads isFullscreen() and calls setFullscreen(!isFullscreen),
then replace the bodies of onSelect and handleToggleFullscreen to simply call
this new toggleFullscreen function to remove duplication and keep behavior
identical.
- Around line 112-121: The dependency array for the hook includes a redundant
dependency: remove location.pathname from the dependency list and keep
isSidebarAvailable (which already derives from location.pathname) to avoid
double-triggering; update the array used in the hook inside command-palette.tsx
(the one currently listing isSidebarAvailable, location.pathname, openProject,
openProjectInSeparateWindow, resolvedTheme, selectedProjectId,
toggleThemePreference) so it omits location.pathname but retains
isSidebarAvailable and the other handlers/state values.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: bcb275e6-c6cd-4206-bbc2-836e416ae874

📥 Commits

Reviewing files that changed from the base of the PR and between 60cb0d2 and f1ce100.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (12)
  • package.json
  • src-tauri/capabilities/default.json
  • src-tauri/capabilities/desktop.json
  • src/components/command-palette/command-palette.tsx
  • src/components/command-palette/index.ts
  • src/components/ui/command.tsx
  • src/components/ui/sidebar.tsx
  • src/contexts/app-providers.tsx
  • src/contexts/projects-context.tsx
  • src/contexts/theme-context.tsx
  • src/routes/__root.tsx
  • src/routes/projects.tsx

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/components/command-palette/command-palette.tsx (1)

165-227: Consider extracting shared hotkey options.

The repeated hotkey option objects are mostly identical; extracting shared config would reduce duplication and make future keybinding changes less error-prone.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/command-palette/command-palette.tsx` around lines 165 - 227,
Extract the repeated hotkey option object into a shared constant (e.g.,
hotkeyOptionsBase) and reuse it in each useHotkey call, then extend/override
only the differing fields per-hotkey (for example create variants that set
enabled: !isOpen, enabled: !isOpen && Boolean(selectedProjectId), and enabled:
!isOpen && isTauri()). Update the useHotkey calls (the ones invoking
openProject, openProjectInSeparateWindow, toggleThemePreference,
toggleFullscreen) to spread hotkeyOptionsBase and then provide the per-call
enabled value so behavior (ignoreInputs/preventDefault) remains identical while
removing duplication.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/components/command-palette/command-palette.tsx`:
- Around line 133-140: The runCommand executor currently awaits
command.onSelect() with no error handling; wrap all command and hotkey
invocations (the runCommand function and any hotkey-triggered calls that
currently call command.onSelect or similar handlers) in a single safe executor
that performs try/catch, logs the error via the existing logger, and surfaces a
user-facing toast/error notification; specifically, change runCommand to return
immediately for disabled commands, then call a new safeExecute(async () =>
command.onSelect()) which catches errors and calls processLogger.error(...) and
showToast/errorToast(...), and update any hotkey handlers that directly call
command.onSelect to use safeExecute as well so all async actions have consistent
handling.

---

Nitpick comments:
In `@src/components/command-palette/command-palette.tsx`:
- Around line 165-227: Extract the repeated hotkey option object into a shared
constant (e.g., hotkeyOptionsBase) and reuse it in each useHotkey call, then
extend/override only the differing fields per-hotkey (for example create
variants that set enabled: !isOpen, enabled: !isOpen &&
Boolean(selectedProjectId), and enabled: !isOpen && isTauri()). Update the
useHotkey calls (the ones invoking openProject, openProjectInSeparateWindow,
toggleThemePreference, toggleFullscreen) to spread hotkeyOptionsBase and then
provide the per-call enabled value so behavior (ignoreInputs/preventDefault)
remains identical while removing duplication.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e0c22fd2-3fdf-4109-818e-37e805bbfd55

📥 Commits

Reviewing files that changed from the base of the PR and between f1ce100 and fc2401c.

📒 Files selected for processing (1)
  • src/components/command-palette/command-palette.tsx

Comment thread src/components/command-palette/command-palette.tsx
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
src/components/command-palette/command-palette.tsx (1)

140-149: Unnecessary dependency in useMemo.

safeExecute is included in the dependency array but is not used within the commands definition. The wrapping happens in runCommand, not inside this useMemo. This causes unnecessary re-computation of commands when safeExecute reference changes.

Proposed fix
     [
       isSidebarAvailable,
       openProject,
       openProjectInSeparateWindow,
       resolvedTheme,
-      safeExecute,
       selectedProjectId,
       toggleFullscreen,
       toggleThemePreference,
     ],
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/command-palette/command-palette.tsx` around lines 140 - 149,
The useMemo that builds the commands array is including safeExecute in its
dependency list even though safeExecute is not referenced inside the commands
definition; remove safeExecute from the dependency array so commands only depend
on actual values used (isSidebarAvailable, openProject,
openProjectInSeparateWindow, resolvedTheme, selectedProjectId, toggleFullscreen,
toggleThemePreference). Keep safeExecute referenced where it’s actually used (in
runCommand) and ensure tests/usage still work after removing it from the useMemo
dependencies.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/components/command-palette/command-palette.tsx`:
- Around line 140-149: The useMemo that builds the commands array is including
safeExecute in its dependency list even though safeExecute is not referenced
inside the commands definition; remove safeExecute from the dependency array so
commands only depend on actual values used (isSidebarAvailable, openProject,
openProjectInSeparateWindow, resolvedTheme, selectedProjectId, toggleFullscreen,
toggleThemePreference). Keep safeExecute referenced where it’s actually used (in
runCommand) and ensure tests/usage still work after removing it from the useMemo
dependencies.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 05d4429f-5e9d-44c9-8081-dccf1ab73256

📥 Commits

Reviewing files that changed from the base of the PR and between fc2401c and 1030d09.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (5)
  • package.json
  • src/components/command-palette/command-palette.tsx
  • src/components/ui/sonner.tsx
  • src/lib/logger.ts
  • src/routes/__root.tsx
✅ Files skipped from review due to trivial changes (2)
  • package.json
  • src/lib/logger.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/routes/__root.tsx

@matfire matfire merged commit 5d50374 into main Mar 21, 2026
1 check passed
@github-actions github-actions Bot mentioned this pull request Mar 21, 2026
@coderabbitai coderabbitai Bot mentioned this pull request Mar 21, 2026
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