feat(web): add Cmd+, shortcut to open settings with platform shortcut badge (#1173)

Register a capture-phase Cmd+, (mac) / Ctrl+, (win/linux) listener in App.tsx that opens Settings, and show a shortcut badge on the Settings menu item in both AvatarMenu and EntryView. Extract the duplicated isMac platform check into a shared isMacPlatform() utility in utils/platform.ts, replacing inline copies in FileWorkspace and ProjectView as well.
This commit is contained in:
Botshelo Brandon Tidimalo 2026-05-11 05:43:57 +02:00 committed by GitHub
parent 2838a28585
commit 979733d39b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 28 additions and 6 deletions

View file

@ -34,6 +34,7 @@ import {
syncMediaProvidersToDaemon,
} from './state/config';
import { applyAppearanceToDocument } from './state/appearance';
import { isMacPlatform } from './utils/platform';
import {
createProject,
deleteProject as deleteProjectApi,
@ -717,6 +718,22 @@ export function App() {
setSettingsOpen(true);
}, []);
// Cmd+, (mac) / Ctrl+, (win/linux) opens Settings. Capture phase so we
// beat the browser's default Preferences dialog. Platform-gated so
// meta/ctrl don't conflict across OS.
useEffect(() => {
const onKeyDown = (e: KeyboardEvent) => {
const primary = isMacPlatform() ? e.metaKey && !e.ctrlKey : e.ctrlKey && !e.metaKey;
if (primary && !e.shiftKey && !e.altKey && e.key === ',') {
if (e.isComposing) return;
e.preventDefault();
openSettings();
}
};
window.addEventListener('keydown', onKeyDown, { capture: true });
return () => window.removeEventListener('keydown', onKeyDown, { capture: true });
}, [openSettings]);
// Explicit enabled toggle — true = wake, false = tuck. Persists to
// localStorage so the overlay state survives across reloads. We keep
// `adopted` untouched so the entry-view CTA does not regress to

View file

@ -5,6 +5,7 @@ import { Icon } from './Icon';
import { renderModelOptions } from './modelOptions';
import type { AgentInfo, AppConfig, ExecMode } from '../types';
import { apiProtocolLabel } from '../utils/apiProtocol';
import { isMacPlatform } from '../utils/platform';
interface Props {
config: AppConfig;
@ -265,6 +266,7 @@ export function AvatarMenu({
<Icon name="settings" size={14} />
</span>
<span>{t('avatar.settings')}</span>
<span className="avatar-item-meta">{isMacPlatform() ? '⌘,' : 'Ctrl+,'}</span>
</button>
{onBack ? (
<button

View file

@ -34,6 +34,7 @@ import { PetRail } from './pet/PetRail';
import { PromptTemplatePreviewModal } from './PromptTemplatePreviewModal';
import { PromptTemplatesTab } from './PromptTemplatesTab';
import { apiProtocolLabel } from '../utils/apiProtocol';
import { isMacPlatform } from '../utils/platform';
type TopTab = 'designs' | 'examples' | 'design-systems' | 'image-templates' | 'video-templates';
@ -451,6 +452,7 @@ export function EntryView({
<Icon name="settings" size={14} />
</span>
<span>{t('avatar.settings')}</span>
<span className="avatar-item-meta">{isMacPlatform() ? '⌘,' : 'Ctrl+,'}</span>
</button>
</div>
) : null}

View file

@ -6,6 +6,7 @@ import {
type DragEvent as ReactDragEvent,
} from 'react';
import { useT } from '../i18n';
import { isMacPlatform } from '../utils/platform';
import {
deleteProjectFile,
fetchProjectFileText,
@ -332,10 +333,8 @@ export function FileWorkspace({
// text fields, and on win/linux we don't steal Cmd+P (rare but possible
// on remapped keyboards).
useEffect(() => {
const isMac =
typeof navigator !== 'undefined' && /Mac|iPod|iPhone|iPad/.test(navigator.platform);
const onKeyDown = (e: KeyboardEvent) => {
const primary = isMac ? e.metaKey && !e.ctrlKey : e.ctrlKey && !e.metaKey;
const primary = isMacPlatform() ? e.metaKey && !e.ctrlKey : e.ctrlKey && !e.metaKey;
if (primary && !e.shiftKey && !e.altKey && e.key.toLowerCase() === 'p') {
if (e.isComposing) return;
e.preventDefault();

View file

@ -34,6 +34,7 @@ import { useProjectFileEvents, type ProjectEvent } from '../providers/project-ev
import { composeSystemPrompt, type ResearchOptions } from '@open-design/contracts';
import { navigate } from '../router';
import { agentDisplayName, agentModelDisplayName } from '../utils/agentLabels';
import { isMacPlatform } from '../utils/platform';
import {
apiProtocolAgentId,
apiProtocolModelLabel,
@ -1956,10 +1957,8 @@ export function ProjectView({
// Quick Switcher shortcut. ⌘+Shift+K is free (⌘+P is the only
// existing primary-modifier shortcut on this surface).
useEffect(() => {
const isMac =
typeof navigator !== 'undefined' && /Mac|iPod|iPhone|iPad/.test(navigator.platform);
const onKeyDown = (e: KeyboardEvent) => {
const primary = isMac ? e.metaKey && !e.ctrlKey : e.ctrlKey && !e.metaKey;
const primary = isMacPlatform() ? e.metaKey && !e.ctrlKey : e.ctrlKey && !e.metaKey;
if (primary && e.shiftKey && !e.altKey && e.key.toLowerCase() === 'k') {
if (e.isComposing) return;
if (!designMdState.exists) return;

View file

@ -0,0 +1,3 @@
export function isMacPlatform(): boolean {
return typeof navigator !== 'undefined' && /Mac|iPod|iPhone|iPad/.test(navigator.platform);
}