diff --git a/backend/src/services/assistant-mode.ts b/backend/src/services/assistant-mode.ts index fdde614e1..8f44f38e3 100644 --- a/backend/src/services/assistant-mode.ts +++ b/backend/src/services/assistant-mode.ts @@ -12,7 +12,7 @@ import { fileExists, ensureDirectoryExists, } from './file-operations' -import { ASSISTANT_NOTIFICATION_LIMITS, OpenCodeConfigSchema } from '@opencode-manager/shared/schemas' +import { ASSISTANT_NOTIFICATION_LIMITS, OpenCodeConfigSchema, DEFAULT_KEYBOARD_SHORTCUTS, DEFAULT_LEADER_KEY } from '@opencode-manager/shared/schemas' import { MANAGER_COLOR_THEME_ID, OPENCODE_THEMES } from '@opencode-manager/shared/themes' import { ASSISTANT_REPO_ID, ASSISTANT_REPO_PATH, ASSISTANT_OPENCODE_DIR_NAME } from '@opencode-manager/shared/utils' import { getAssistantModePath, getReposPath } from '@opencode-manager/shared/config/env' @@ -699,6 +699,18 @@ Partial object with any of the allowed keys. **Response:** Returns the updated settings object with the same structure as GET. +### Keyboard Shortcut Preferences + +\`keyboardShortcuts\`, \`leaderKey\`, and \`directShortcuts\` control keyboard bindings. + +**Actions:** ${Object.keys(DEFAULT_KEYBOARD_SHORTCUTS).join(', ')} + +**Key format:** modifiers joined with \`+\`, using \`Ctrl\`, \`Cmd\`, \`Alt\`, and \`Shift\` (for example \`Ctrl+Alt+U\` or \`Shift+Tab\`). \`Cmd\` is the Command key on macOS and Ctrl on other platforms. An empty string unbinds the action. + +**Leader vs direct:** by default a binding is a leader binding, pressed after the \`leaderKey\` (default \`${DEFAULT_LEADER_KEY}\`). List an action in \`directShortcuts\` to fire it without the leader key. + +**Sending changes:** \`keyboardShortcuts\` and \`directShortcuts\` are replaced wholesale. Read the current \`keyboardShortcuts\` (and \`directShortcuts\` when changing binding kinds) from GET /settings, modify only the actions you need, and send the complete map or list back; actions absent from the stored map use their built-in defaults. + ### POST /assistant/reload Reload the OpenCode configuration without restarting the server, rebuilding every loaded location. OpenCode re-reads \`opencode.json\`/\`opencode.jsonc\`, agents, commands, skills, plugins, and \`AGENTS.md\`; running sessions, including this one, keep running. Use this after editing \`.opencode/agents/assistant.md\` or other workspace files so changes take effect on the next message. Returns \`400\` with \`validationIssues\` when the OpenCode configuration is invalid. diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index fcbe2caa7..06d5782e9 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -24,6 +24,7 @@ import { useMobileTabBar } from '@/hooks/useMobileTabBar' import { TTSProvider } from './contexts/TTSContext' import { ThemeProvider, useThemeMode } from './contexts/ThemeContext' import { AuthProvider } from './contexts/AuthContext' +import { KeyboardShortcutsProvider } from './contexts/KeyboardShortcutsContext' import { EventProvider, usePermissions, useEventContext } from '@/contexts/EventContext' import { SwipeNavigationProvider, useSwipeNavigation } from '@/contexts/SwipeNavigationContext' import { PermissionRequestDialog } from './components/session/PermissionRequestDialog' @@ -34,6 +35,7 @@ import { onNotificationClick } from '@/lib/serviceWorker' import { useAuth } from '@/hooks/useAuth' import { useOpenCodeFailureToast } from '@/hooks/useOpenCodeFailureToast' import { useOpenCodeServerActions } from '@/hooks/useOpenCodeServerActions' +import { useGlobalShortcutActions } from '@/hooks/useGlobalShortcutActions' import { RestartServerDialog } from '@/components/settings/RestartServerDialog' const queryClient = new QueryClient({ @@ -116,6 +118,11 @@ function PermissionDialogWrapper() { ) } +function GlobalShortcuts() { + useGlobalShortcutActions() + return null +} + function AppShell() { const navigate = useNavigate() const location = useLocation() @@ -188,21 +195,24 @@ function AppShell() { -
- -
- -
-
- - - - - - - - - + + +
+ +
+ +
+
+ + + + + + + + + +
diff --git a/frontend/src/api/types/settings.ts b/frontend/src/api/types/settings.ts index 9893a7041..302b5d372 100644 --- a/frontend/src/api/types/settings.ts +++ b/frontend/src/api/types/settings.ts @@ -4,6 +4,7 @@ import { DEFAULT_KEYBOARD_SHORTCUTS, DEFAULT_USER_PREFERENCES, DEFAULT_LEADER_KEY, + DEFAULT_DIRECT_SHORTCUTS, BLOCKED_SERVER_ENV_KEYS, selectPreferredOpenCodeConfigSourceName, type TTSConfig, @@ -27,8 +28,8 @@ import type { NotificationPreferences } from '@opencode-manager/shared/types' import { saveFile } from '@/lib/download' export type { TTSConfig, STTConfig, OpenCodeConfigFile, OpenCodeConfigSourceFile, OpenCodeConfigSourceName, UpdateOpenCodeConfigRequest, ModelConfig, ProviderConfig, SandboxPreferences, NotificationPreferences, SkillFileInfo, CreateSkillRequest, UpdateSkillRequest, SkillScope, InstallSkillFromGithubRequest, InstallSkillResponse } -export type { UserPreferences, UpdateSettingsRequest, CustomCommand, GitCredential, GitIdentity, GitIdentityProfile } from '@opencode-manager/shared' -export { DEFAULT_TTS_CONFIG, DEFAULT_STT_CONFIG, DEFAULT_KEYBOARD_SHORTCUTS, DEFAULT_USER_PREFERENCES, DEFAULT_LEADER_KEY, BLOCKED_SERVER_ENV_KEYS } +export type { UserPreferences, UpdateSettingsRequest, CustomCommand, GitCredential, GitIdentity, GitIdentityProfile, KeyboardShortcutAction } from '@opencode-manager/shared' +export { DEFAULT_TTS_CONFIG, DEFAULT_STT_CONFIG, DEFAULT_KEYBOARD_SHORTCUTS, DEFAULT_USER_PREFERENCES, DEFAULT_LEADER_KEY, DEFAULT_DIRECT_SHORTCUTS, BLOCKED_SERVER_ENV_KEYS } export { isOpenCodeConfigSourceName } from '@opencode-manager/shared' export function getOpenCodeConfigSources(config: OpenCodeConfigFile): OpenCodeConfigSourceFile[] { diff --git a/frontend/src/components/agent/AgentQuickSelect.tsx b/frontend/src/components/agent/AgentQuickSelect.tsx index ab6594263..fad74bdbd 100644 --- a/frontend/src/components/agent/AgentQuickSelect.tsx +++ b/frontend/src/components/agent/AgentQuickSelect.tsx @@ -54,7 +54,6 @@ export function AgentQuickSelect({ const buttonContent = (