feat(desktop): pin command palette actions to the sidebar

GA
2mo ago
+587-50

What & why

Closes #7623.

The home sidebar's nav is fixed, so any action that isn't built in has to be searched for in the command palette every time. The only escape hatch so far is a dedicated setting per action (#7191 did this for Usage). This makes it general: any command palette action can be pinned to the sidebar, straight from the palette.

  • Pin from the palette. Every command row gets a pin toggle — visible on hover / on the highlighted row, and reachable with Tab + Enter. Pinning doesn't run the command or close the palette, and focus stays in the search input.
  • Pinned actions in the sidebar. They render under the built-in nav in the v2 home sidebar, in both the expanded sidebar and the collapsed rail, using the command's own icon and title.
  • Same behaviour as the palette. Clicking a pinned action does what selecting it in the palette does. Commands that open a sub-palette (Toggle theme, Settings) open the palette on that frame.
  • Unpin with the same toggle in the palette, or from the sidebar button's context menu.

Pins are stored as command ids and resolved against the live palette (findCommandById walks children and honours when), so nested commands like Settings → Appearance work, titles and icons stay current (e.g. Mute/Unmute notifications), and an action that doesn't apply in the current context is absent rather than shown dead.

Notes for review

  • layout.tsx is mostly re-indentation — review with whitespace hidden. The sidebar needs the command context to run commands, so CommandPaletteHost now wraps the dashboard layout (it already accepted children) instead of sitting beside it. Children keep the same element identity, so provider state changes don't re-render the layout.
  • Persisted state policy (apps/desktop/AGENTS.md): pinned-sidebar-commands-v1 is a singleton list of command ids, registered in the persisted-key registry and synced across windows. What bounds it: capped at 8. Who deletes it: unpinning removes the id; the oldest pin falls off at the cap, which also retires ids of commands that no longer exist. When the feature dies: one key to move to DEAD_KEYS.
  • The pin toggle only renders when the v2 sidebar is enabled, since that's the only place pins show up — same gating as the Usage switch.
  • The Show usage tab on sidebar setting is left as is; this PR doesn't touch it.

How I tested it

macOS Electron dev app (setup.local.sh stack), driven over CDP with real mouse/keyboard input against this worktree's renderer:

  • Pinned Usage from the palette with a mouse click: sidebar entry appears immediately, palette stays open, command doesn't run, focus stays in the input.
  • Pinned Toggle theme with Tab + Enter; clicking it in the sidebar opens the palette on the theme frame.
  • Pinned nested Settings → Appearance; clicking it navigates to /settings/appearance.
  • Clicking pinned Usage navigates to /settings/usage.
  • Collapsed rail shows the pinned icon with its tooltip.
  • Unpinned via the sidebar context menu and via the palette toggle.
  • Pins survive a renderer reload.
  • A console.error hook stayed empty across the pin and run-from-sidebar flows (it was not re-installed after the reload step, so the unpin and nested-command checks ran without it).
BeforePin toggle in the palette
beforepin toggle
Pinned → shows in the sidebarSub-palette action run from the sidebar
pinnedsubmenu
Unpin from the context menuCollapsed rail
unpincollapsed

Automated:

  • bun run lint clean, bun run typecheck 42/42 packages, bun run check:i18n clean.
  • Desktop bun test: 3,958 pass, 0 fail — includes new tests for the pin store (toggle, order, cap) and findCommandById (top-level, static and context-built children, when), plus the persisted-key registry test.
  • packages/i18n tests: 157 pass.

Checklist

  • PR title follows conventional commits (type(scope): subject)
  • bun run lint and bun run typecheck pass (CI fails on lint warnings too)
  • "Allow edits from maintainers" is checked on fork PRs
  • Translated the two new strings across all supported locales

🤖 Generated with Claude Code

https://claude.ai/code/session_01Kwgmy5zmGyMyXRMzQFroMb


Summary by cubic

Lets you pin any command palette action to the sidebar so you don't have to search for it every time. Closes #7623.

  • Adds a pin toggle to every command row, reachable on hover or with Tab + Enter.
  • Pinned actions appear under the built-in nav in the expanded and collapsed sidebar; clicking them runs the command, including sub-palette frames.
  • Unpin via the palette toggle or the sidebar button's context menu.
  • Pins are stored as a capped list of command ids, synced across windows; commands that don't apply in the current context are hidden.

Summary by CodeRabbit

  • New Features

    • Pin commands directly from the command palette for quick access.
    • View and run pinned commands from the dashboard sidebar in expanded or collapsed layouts.
    • Pinned commands persist across sessions and synchronize across windows.
    • Up to eight commands can be pinned, with the oldest removed when the limit is reached.
  • Localization

    • Added translated pin and unpin labels across supported languages.
  • Tests

    • Added coverage for command lookup and pinned-command behavior.