feat: update the shortcuts in the desktop app for macOS (#96)

* feat(ui): update keyboard shortcuts to use platform-appropriate modifier keys

* fix(macos): update keyboard shortcuts to use Cmd modifier

Change Command Palette shortcut from Ctrl+X to Cmd+K
Update all menu shortcuts to use Cmd instead of Ctrl on macOS

* Fix linter issue
This commit is contained in:
Michael Sakhniuk
2026-01-03 02:18:57 +02:00
committed by GitHub
parent 025a9a6b80
commit ff825c7651
9 changed files with 215 additions and 88 deletions
+4 -4
View File
@@ -12,7 +12,7 @@ import { useConfigStore } from '@/stores/useConfigStore';
import { useSessionStore } from '@/stores/useSessionStore';
import { ContextUsageDisplay } from '@/components/ui/ContextUsageDisplay';
import { useDeviceInfo } from '@/lib/device';
import { cn } from '@/lib/utils';
import { cn, getModifierLabel, hasModifier } from '@/lib/utils';
import { useDiffFileCount } from '@/components/views/DiffView';
interface TabConfig {
@@ -277,7 +277,7 @@ export const Header: React.FC = () => {
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if ((e.metaKey || e.ctrlKey) && !e.shiftKey && !e.altKey) {
if (hasModifier(e) && !e.shiftKey && !e.altKey) {
const num = parseInt(e.key, 10);
if (num >= 1 && num <= tabs.length) {
e.preventDefault();
@@ -410,7 +410,7 @@ export const Header: React.FC = () => {
</button>
</TooltipTrigger>
<TooltipContent>
<p>Command Palette (Ctrl+X)</p>
<p>Command Palette ({getModifierLabel()}+K)</p>
</TooltipContent>
</Tooltip>
<Tooltip delayDuration={500}>
@@ -425,7 +425,7 @@ export const Header: React.FC = () => {
</button>
</TooltipTrigger>
<TooltipContent>
<p>Keyboard Shortcuts (Ctrl+H)</p>
<p>Keyboard Shortcuts ({getModifierLabel()}+H)</p>
</TooltipContent>
</Tooltip>
</div>
@@ -5,7 +5,7 @@ import { useThemeSystem } from '@/contexts/useThemeSystem';
import type { ThemeMode } from '@/types/theme';
import { useUIStore } from '@/stores/useUIStore';
import { useMessageQueueStore } from '@/stores/messageQueueStore';
import { cn } from '@/lib/utils';
import { cn, getModifierLabel } from '@/lib/utils';
import { ButtonSmall } from '@/components/ui/button-small';
import { NumberInput } from '@/components/ui/number-input';
import { isVSCodeRuntime } from '@/lib/desktop';
@@ -355,8 +355,8 @@ export const OpenChamberVisualSettings: React.FC<OpenChamberVisualSettingsProps>
</label>
<p className="typography-meta text-muted-foreground pl-5">
{queueModeEnabled
? 'Enter queues messages, Ctrl+Enter sends immediately.'
: 'Enter sends immediately, Ctrl+Enter queues messages.'}
? `Enter queues messages, ${getModifierLabel()}+Enter sends immediately.`
: `Enter sends immediately, ${getModifierLabel()}+Enter queues messages.`}
</p>
</div>
)}
@@ -16,6 +16,7 @@ import { useThemeSystem } from '@/contexts/useThemeSystem';
import { useDeviceInfo } from '@/lib/device';
import { RiAddLine, RiChatAi3Line, RiCheckLine, RiCodeLine, RiComputerLine, RiGitBranchLine, RiLayoutLeftLine, RiMoonLine, RiQuestionLine, RiRestartLine, RiSettings3Line, RiSunLine, RiTerminalBoxLine } from '@remixicon/react';
import { reloadOpenCodeConfiguration } from '@/stores/useAgentsStore';
import { getModifierLabel } from '@/lib/utils';
export const CommandPalette: React.FC = () => {
const {
@@ -121,42 +122,42 @@ export const CommandPalette: React.FC = () => {
<CommandItem onSelect={handleOpenSessionList}>
<RiLayoutLeftLine className="mr-2 h-4 w-4" />
<span>Open Session List</span>
<CommandShortcut>Ctrl + L</CommandShortcut>
<CommandShortcut>{getModifierLabel()} + L</CommandShortcut>
</CommandItem>
<CommandItem onSelect={handleCreateSession}>
<RiAddLine className="mr-2 h-4 w-4" />
<span>New Session</span>
<CommandShortcut>Ctrl + N</CommandShortcut>
<CommandShortcut>{getModifierLabel()} + N</CommandShortcut>
</CommandItem>
<CommandItem onSelect={handleOpenAdvancedSession}>
<RiGitBranchLine className="mr-2 h-4 w-4" />
<span>New Session with Worktree</span>
<CommandShortcut>Shift + Ctrl + N</CommandShortcut>
<CommandShortcut>Shift + {getModifierLabel()} + N</CommandShortcut>
</CommandItem>
<CommandItem onSelect={handleShowHelp}>
<RiQuestionLine className="mr-2 h-4 w-4" />
<span>Keyboard Shortcuts</span>
<CommandShortcut>Ctrl + H</CommandShortcut>
<CommandShortcut>{getModifierLabel()} + H</CommandShortcut>
</CommandItem>
<CommandItem onSelect={handleOpenDiffPanel}>
<RiCodeLine className="mr-2 h-4 w-4" />
<span>Open Diff Panel</span>
<CommandShortcut>Ctrl + E</CommandShortcut>
<CommandShortcut>{getModifierLabel()} + E</CommandShortcut>
</CommandItem>
<CommandItem onSelect={handleOpenGitPanel}>
<RiGitBranchLine className="mr-2 h-4 w-4" />
<span>Open Git Panel</span>
<CommandShortcut>Ctrl + G</CommandShortcut>
<CommandShortcut>{getModifierLabel()} + G</CommandShortcut>
</CommandItem>
<CommandItem onSelect={handleOpenTerminal}>
<RiTerminalBoxLine className="mr-2 h-4 w-4" />
<span>Open Terminal</span>
<CommandShortcut>Ctrl + T</CommandShortcut>
<CommandShortcut>{getModifierLabel()} + T</CommandShortcut>
</CommandItem>
<CommandItem onSelect={handleOpenSettings}>
<RiSettings3Line className="mr-2 h-4 w-4" />
<span>Open Settings</span>
<CommandShortcut>Ctrl + ,</CommandShortcut>
<CommandShortcut>{getModifierLabel()} + ,</CommandShortcut>
</CommandItem>
<CommandItem onSelect={handleReloadConfiguration}>
<RiRestartLine className="mr-2 h-4 w-4" />
+129 -38
View File
@@ -1,41 +1,66 @@
import React from 'react';
import React from "react";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog';
import { useUIStore } from '@/stores/useUIStore';
import { RiAddLine, RiArrowUpSLine, RiArrowUpWideLine, RiBrainAi3Line, RiCloseCircleLine, RiCodeLine, RiCommandLine, RiGitBranchLine, RiLayoutLeftLine, RiPaletteLine, RiQuestionLine, RiSettings3Line, RiTerminalBoxLine, RiText } from '@remixicon/react';
} from "@/components/ui/dialog";
import { useUIStore } from "@/stores/useUIStore";
import {
RiAddLine,
RiArrowUpSLine,
RiBrainAi3Line,
RiCloseCircleLine,
RiCodeLine,
RiCommandLine,
RiGitBranchLine,
RiLayoutLeftLine,
RiPaletteLine,
RiQuestionLine,
RiSettings3Line,
RiTerminalBoxLine,
RiText,
} from "@remixicon/react";
import { getModifierLabel } from "@/lib/utils";
const renderKeyToken = (token: string, index: number) => {
const normalized = token.trim().toLowerCase();
if (normalized === 'ctrl' || normalized === 'control') {
if (normalized === "ctrl" || normalized === "control") {
return <RiArrowUpSLine key={`ctrl-${index}`} className="h-3.5 w-3.5" />;
}
if (normalized === 'shift' || normalized === '⇧') {
return <RiArrowUpWideLine key={`shift-${index}`} className="h-3.5 w-3.5" />;
}
if (normalized === '⌘' || normalized === 'cmd' || normalized === 'command' || normalized === 'meta') {
if (
normalized === "⌘" ||
normalized === "cmd" ||
normalized === "command" ||
normalized === "meta"
) {
return <RiCommandLine key={`cmd-${index}`} className="h-3.5 w-3.5" />;
}
return <span key={`key-${index}`} className="text-xs font-medium">{token.trim()}</span>;
return (
<span key={`key-${index}`} className="text-xs font-medium">
{token.trim()}
</span>
);
};
const renderKeyCombo = (combo: string) => {
const tokens = combo.split('+').map((token) => token.trim()).filter(Boolean);
const tokens = combo
.split("+")
.map((token) => token.trim())
.filter(Boolean);
if (tokens.length === 0) {
return combo.trim();
}
return tokens.map((token, index) => (
<React.Fragment key={`${token}-${index}`}>
{index > 0 && <span className="text-muted-foreground text-[10px]">+</span>}
{index > 0 && (
<span className="text-muted-foreground text-[10px]">+</span>
)}
{renderKeyToken(token, index)}
</React.Fragment>
));
@@ -57,35 +82,85 @@ type ShortcutSection = {
export const HelpDialog: React.FC = () => {
const { isHelpDialogOpen, setHelpDialogOpen } = useUIStore();
const mod = getModifierLabel();
const shortcuts: ShortcutSection[] = [
{
category: "Navigation & Commands",
items: [
{ keys: ["Ctrl + X"], description: "Open Command Palette", icon: RiCommandLine },
{ keys: ["Ctrl + H"], description: "Show Keyboard Shortcuts (this dialog)", icon: RiQuestionLine },
{ keys: ["Ctrl + L"], description: "Toggle Session Sidebar", icon: RiLayoutLeftLine },
{ keys: ["Ctrl + M"], description: "Open Model Selector", icon: RiBrainAi3Line },
]
{
keys: [`${mod} + K`],
description: "Open Command Palette",
icon: RiCommandLine,
},
{
keys: [`${mod} + H`],
description: "Show Keyboard Shortcuts (this dialog)",
icon: RiQuestionLine,
},
{
keys: [`${mod} + L`],
description: "Toggle Session Sidebar",
icon: RiLayoutLeftLine,
},
{
keys: [`${mod} + M`],
description: "Open Model Selector",
icon: RiBrainAi3Line,
},
],
},
{
category: "Session Management",
items: [
{ keys: ["Ctrl + N"], description: "Create New Session", icon: RiAddLine },
{ keys: ["Shift + Ctrl + N"], description: "Open Worktree Creator", icon: RiGitBranchLine },
{ keys: ["Ctrl + I"], description: "Focus Chat Input", icon: RiText },
{ keys: ["Esc + Esc"], description: "Abort active run (double press)", icon: RiCloseCircleLine },
]
{
keys: [`${mod} + N`],
description: "Create New Session",
icon: RiAddLine,
},
{
keys: [`Shift + ${mod} + N`],
description: "Open Worktree Creator",
icon: RiGitBranchLine,
},
{ keys: [`${mod} + I`], description: "Focus Chat Input", icon: RiText },
{
keys: ["Esc + Esc"],
description: "Abort active run (double press)",
icon: RiCloseCircleLine,
},
],
},
{
category: "Interface",
items: [
{ keys: ["⌘ + /", "Ctrl + /"], description: "Cycle Theme (Light → Dark → System)", icon: RiPaletteLine },
{ keys: ["Ctrl + E"], description: "Open Diff Panel", icon: RiCodeLine },
{ keys: ["Ctrl + G"], description: "Open Git Panel", icon: RiGitBranchLine },
{ keys: ["Ctrl + T"], description: "Open Terminal", icon: RiTerminalBoxLine },
{ keys: ["Ctrl + ,"], description: "Open Settings", icon: RiSettings3Line },
]
}
{
keys: [`${mod} + /`],
description: "Cycle Theme (Light → Dark → System)",
icon: RiPaletteLine,
},
{
keys: [`${mod} + E`],
description: "Open Diff Panel",
icon: RiCodeLine,
},
{
keys: [`${mod} + G`],
description: "Open Git Panel",
icon: RiGitBranchLine,
},
{
keys: [`${mod} + T`],
description: "Open Terminal",
icon: RiTerminalBoxLine,
},
{
keys: [`${mod} + ,`],
description: "Open Settings",
icon: RiSettings3Line,
},
],
},
];
return (
@@ -117,12 +192,21 @@ export const HelpDialog: React.FC = () => {
{shortcut.icon && (
<shortcut.icon className="h-3.5 w-3.5 text-muted-foreground" />
)}
<span className="typography-meta">{shortcut.description}</span>
<span className="typography-meta">
{shortcut.description}
</span>
</div>
<div className="flex items-center gap-1">
{(Array.isArray(shortcut.keys) ? shortcut.keys : shortcut.keys.split(' / ')).map((keyCombo: string, i: number) => (
{(Array.isArray(shortcut.keys)
? shortcut.keys
: shortcut.keys.split(" / ")
).map((keyCombo: string, i: number) => (
<React.Fragment key={`${keyCombo}-${i}`}>
{i > 0 && <span className="typography-meta text-muted-foreground mx-1">or</span>}
{i > 0 && (
<span className="typography-meta text-muted-foreground mx-1">
or
</span>
)}
<kbd className="inline-flex items-center gap-1 px-1.5 py-0.5 typography-meta font-mono bg-muted rounded border border-border/20">
{renderKeyCombo(keyCombo)}
</kbd>
@@ -141,11 +225,18 @@ export const HelpDialog: React.FC = () => {
<RiQuestionLine className="h-3.5 w-3.5 text-muted-foreground mt-0.5" />
<div className="typography-meta text-muted-foreground">
<p className="font-medium mb-1">Pro Tips:</p>
<ul className="space-y-0.5 typography-meta">
<li> Use Command Palette (Ctrl + X) to quickly access all actions</li>
<li> The 5 most recent sessions appear in the Command Palette</li>
<li> Theme cycling remembers your preference across sessions</li>
</ul>
<ul className="space-y-0.5 typography-meta">
<li>
Use Command Palette ({mod} + K) to quickly access all
actions
</li>
<li>
The 5 most recent sessions appear in the Command Palette
</li>
<li>
Theme cycling remembers your preference across sessions
</li>
</ul>
</div>
</div>
</div>
@@ -1,5 +1,5 @@
import React from 'react';
import { cn } from '@/lib/utils';
import { cn, getModifierLabel } from '@/lib/utils';
import { SIDEBAR_SECTIONS } from '@/constants/sidebar';
import type { SidebarSection } from '@/constants/sidebar';
import { RiArrowLeftSLine, RiCloseLine } from '@remixicon/react';
@@ -244,7 +244,7 @@ export const SettingsView: React.FC<SettingsViewProps> = ({ onClose, forceMobile
};
// Keyboard shortcut display based on platform
const shortcutKey = isMacPlatform ? '⌘' : 'Ctrl';
const shortcutKey = getModifierLabel();
// Desktop padding for Mac titlebar area
const desktopPaddingClass = React.useMemo(() => {