feat(ui): redesign the default shortcut layout around a mod+k leader
Single chords stay for everyday actions; open/go actions move to two-step mod+k sequences; held mod+digit switches header session tabs and held mod+alt+digit switches context panel surfaces. Rare actions leave the shortcut schema for the command palette, every remaining action ships with a default binding, and stored overrides from the old layout reset once. Key matching now follows the physical key on non-Latin layouts and for Option-modified digits on macOS, including in the recording dialog.
This commit is contained in:
@@ -20,7 +20,6 @@ import { useAgentMemorySync } from '@/hooks/useAgentMemorySync';
|
||||
import { usePwaInstallPrompt } from '@/hooks/usePwaInstallPrompt';
|
||||
import { useWindowTitle } from '@/hooks/useWindowTitle';
|
||||
import { useConfigStore } from '@/stores/useConfigStore';
|
||||
import { useKeybind } from '@/hooks/useKeybind';
|
||||
import { isDesktopLocalOriginActive, isDesktopShell, restartDesktopApp, invokeDesktop } from '@/lib/desktop';
|
||||
import {
|
||||
getInjectedBootOutcome,
|
||||
@@ -723,10 +722,13 @@ function App({ apis }: AppProps) {
|
||||
|
||||
useSessionStatusBootstrap({ enabled: embeddedBackgroundWorkEnabled });
|
||||
|
||||
useKeybind('toggle_memory_debug', () => {
|
||||
if (embeddedSessionChat) return false;
|
||||
setShowMemoryDebug((previous) => !previous);
|
||||
});
|
||||
// Palette-only action: the memory debug panel has no keyboard shortcut.
|
||||
React.useEffect(() => {
|
||||
if (embeddedSessionChat) return;
|
||||
const handleToggle = () => setShowMemoryDebug((previous) => !previous);
|
||||
window.addEventListener('openchamber:memory-debug-toggle', handleToggle);
|
||||
return () => window.removeEventListener('openchamber:memory-debug-toggle', handleToggle);
|
||||
}, [embeddedSessionChat]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (embeddedSessionChat) {
|
||||
|
||||
@@ -1471,17 +1471,6 @@ export const Header: React.FC = () => {
|
||||
setIsDesktopServicesOpen(true);
|
||||
void refreshCurrentInstanceLabel();
|
||||
},
|
||||
// The desktop menu holds one destination now, so this shortcut opens it
|
||||
// rather than cycling. The binding is kept: it is user-configurable and
|
||||
// silently dropping it would break existing setups.
|
||||
cycle_services_tab: () => {
|
||||
if (servicesTabs.length === 0) return false;
|
||||
setIsDesktopServicesOpen(true);
|
||||
void refreshCurrentInstanceLabel();
|
||||
},
|
||||
toggle_context_plan: () => {
|
||||
handleOpenContextPlan();
|
||||
},
|
||||
});
|
||||
|
||||
const desktopSidebarActions = (
|
||||
|
||||
@@ -53,17 +53,19 @@ export const KeyboardShortcutsSettings: React.FC = () => {
|
||||
persist(nextOverrides);
|
||||
};
|
||||
const shortcutDisplay = (action: CustomizableShortcutAction): string => {
|
||||
const isSurfaceSwitch = action.id === 'switch_context_surface';
|
||||
const combo = isSurfaceSwitch
|
||||
const isPrefixStyle = 'prefixStyle' in action && action.prefixStyle;
|
||||
const combo = isPrefixStyle
|
||||
? getEffectiveShortcutPrefix(action.id, shortcutOverrides)
|
||||
: getEffectiveShortcutCombo(action.id, shortcutOverrides);
|
||||
const formatted = formatShortcutForDisplay(
|
||||
combo,
|
||||
t('settings.openchamber.keyboardShortcuts.unassigned'),
|
||||
);
|
||||
return isSurfaceSwitch && combo && combo !== UNASSIGNED_SHORTCUT
|
||||
? `${formatted}${t('settings.openchamber.keyboardShortcuts.action.switch_context_surface.suffix')}`
|
||||
: formatted;
|
||||
if (!isPrefixStyle || !combo || combo === UNASSIGNED_SHORTCUT) return formatted;
|
||||
const suffix = action.id === 'switch_session_tab'
|
||||
? t('settings.openchamber.keyboardShortcuts.action.switch_session_tab.suffix')
|
||||
: t('settings.openchamber.keyboardShortcuts.action.switch_context_surface.suffix');
|
||||
return `${formatted}${suffix}`;
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -104,15 +106,17 @@ export const KeyboardShortcutsSettings: React.FC = () => {
|
||||
>
|
||||
{t('settings.openchamber.keyboardShortcuts.actions.edit')}
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="xs"
|
||||
className="!font-normal"
|
||||
onClick={() => resetOne(action.id)}
|
||||
>
|
||||
{t('settings.common.actions.reset')}
|
||||
</Button>
|
||||
{action.id in shortcutOverrides ? (
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="xs"
|
||||
className="!font-normal"
|
||||
onClick={() => resetOne(action.id)}
|
||||
>
|
||||
{t('settings.common.actions.reset')}
|
||||
</Button>
|
||||
) : null}
|
||||
</SettingsFieldRow>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -4,7 +4,8 @@ import { settleShortcutRecordingState, updateShortcutRecordingState } from './Sh
|
||||
const emptyState = { chords: [], livePreview: null, settled: false };
|
||||
|
||||
function keyEvent(key: string, modifiers: Partial<Record<'altKey' | 'ctrlKey' | 'metaKey' | 'shiftKey', boolean>> = {}) {
|
||||
return { key, repeat: false, isComposing: false, altKey: false, ctrlKey: false, metaKey: false, shiftKey: false, ...modifiers };
|
||||
const code = /^[a-z]$/i.test(key) ? `Key${key.toUpperCase()}` : /^[0-9]$/.test(key) ? `Digit${key}` : key;
|
||||
return { key, code, repeat: false, isComposing: false, altKey: false, ctrlKey: false, metaKey: false, shiftKey: false, ...modifiers };
|
||||
}
|
||||
|
||||
describe('ShortcutRecordingDialog recording state', () => {
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
getShortcutBindingConflicts,
|
||||
isRiskyBrowserShortcut,
|
||||
keyToShortcutToken,
|
||||
resolveShortcutEventKey,
|
||||
normalizeCombo,
|
||||
type ShortcutActionId,
|
||||
type ShortcutBindingConflict,
|
||||
@@ -27,6 +28,7 @@ const SECOND_CHORD_TIMEOUT_MS = 3000;
|
||||
|
||||
interface RecordingKeyboardEvent {
|
||||
altKey: boolean;
|
||||
code: string;
|
||||
ctrlKey: boolean;
|
||||
isComposing: boolean;
|
||||
key: string;
|
||||
@@ -84,7 +86,7 @@ function keyboardEventToCombo(event: RecordingKeyboardEvent): ShortcutCombo | nu
|
||||
if (MODIFIER_KEYS.has(event.key.toLowerCase())) return null;
|
||||
if (getPhysicalKeyCount(event, true) > MAX_SHORTCUT_KEY_COUNT) return null;
|
||||
|
||||
const key = keyToShortcutToken(event.key);
|
||||
const key = keyToShortcutToken(resolveShortcutEventKey(event));
|
||||
if (!key) return null;
|
||||
|
||||
const parts: string[] = [];
|
||||
@@ -200,7 +202,8 @@ export const ShortcutRecordingDialog: React.FC<ShortcutRecordingDialogProps> = (
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
if (phase === 'keyup' && action?.id === 'switch_context_surface' && recording.chords.length === 0) {
|
||||
const isPrefixStyleAction = Boolean(action && 'prefixStyle' in action && action.prefixStyle);
|
||||
if (phase === 'keyup' && isPrefixStyleAction && recording.chords.length === 0) {
|
||||
const modifierCombo = modifierKeyUpToCombo(event);
|
||||
if (modifierCombo) {
|
||||
setRecording({ chords: [modifierCombo], livePreview: null, settled: true });
|
||||
@@ -209,6 +212,7 @@ export const ShortcutRecordingDialog: React.FC<ShortcutRecordingDialogProps> = (
|
||||
}
|
||||
const nextRecording = updateShortcutRecordingState(recording, {
|
||||
altKey: event.altKey,
|
||||
code: event.nativeEvent.code,
|
||||
ctrlKey: event.ctrlKey,
|
||||
isComposing: event.nativeEvent.isComposing,
|
||||
key: event.key,
|
||||
@@ -216,7 +220,7 @@ export const ShortcutRecordingDialog: React.FC<ShortcutRecordingDialogProps> = (
|
||||
repeat: event.repeat,
|
||||
shiftKey: event.shiftKey,
|
||||
}, phase);
|
||||
setRecording(action?.id === 'switch_context_surface' && nextRecording.chords.length > 1
|
||||
setRecording(isPrefixStyleAction && nextRecording.chords.length > 1
|
||||
? recording
|
||||
: nextRecording);
|
||||
};
|
||||
|
||||
@@ -26,6 +26,21 @@ export const useSessionSearchEffects = ({
|
||||
return () => window.cancelAnimationFrame(raf);
|
||||
}, [enabled, isSessionSearchOpen, sessionSearchInputRef]);
|
||||
|
||||
// The open_session_list shortcut lands here when the sidebar is visible:
|
||||
// the session list is already on screen, so the shortcut opens its search.
|
||||
React.useEffect(() => {
|
||||
if (!enabled || typeof window === 'undefined') {
|
||||
return;
|
||||
}
|
||||
const handleOpenRequest = () => {
|
||||
setIsSessionSearchOpen(true);
|
||||
sessionSearchInputRef.current?.focus();
|
||||
sessionSearchInputRef.current?.select();
|
||||
};
|
||||
window.addEventListener('openchamber:sidebar-session-search', handleOpenRequest);
|
||||
return () => window.removeEventListener('openchamber:sidebar-session-search', handleOpenRequest);
|
||||
}, [enabled, setIsSessionSearchOpen, sessionSearchInputRef]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!enabled || !isSessionSearchOpen || typeof document === 'undefined') {
|
||||
return;
|
||||
|
||||
@@ -37,7 +37,8 @@ import { toast } from '@/components/ui';
|
||||
import { FileTypeIcon } from '@/components/icons/FileTypeIcon';
|
||||
import type { Session } from '@opencode-ai/sdk/v2';
|
||||
import { createWorktreeSession } from '@/lib/worktreeSessionCreator';
|
||||
import { formatShortcutForDisplay, getEffectiveShortcutCombo } from '@/lib/shortcuts';
|
||||
import { formatShortcutForDisplay, getEffectiveShortcutCombo, shortcutRegistry } from '@/lib/shortcuts';
|
||||
import { showOpenCodeStatus } from '@/lib/openCodeStatus';
|
||||
import { canUseElectronDesktopIPC, invokeDesktop, isDesktopShell, isVSCodeRuntime, isWebRuntime } from '@/lib/desktop';
|
||||
import { SETTINGS_PAGE_METADATA, type SettingsRuntimeContext } from '@/lib/settings/metadata';
|
||||
|
||||
@@ -230,6 +231,25 @@ export const CommandPalette: React.FC = () => {
|
||||
if (currentDirectory) openContextOverview(currentDirectory);
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: 'cycle-theme',
|
||||
title: t('commandPalette.item.cycleTheme'),
|
||||
icon: <Icon name="palette" className="mr-2 h-4 w-4" />,
|
||||
shortcutId: 'cycle_theme',
|
||||
searchText: t('commandPalette.item.cycleTheme'),
|
||||
onSelect: run(() => {
|
||||
shortcutRegistry.invoke('cycle_theme');
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: 'open-status',
|
||||
title: t('commandPalette.item.showOpenCodeStatus'),
|
||||
icon: <Icon name="pulse" className="mr-2 h-4 w-4" />,
|
||||
searchText: t('commandPalette.item.showOpenCodeStatus'),
|
||||
onSelect: run(() => {
|
||||
void showOpenCodeStatus();
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: 'open-settings',
|
||||
title: t('commandPalette.item.openSettings'),
|
||||
@@ -239,6 +259,15 @@ export const CommandPalette: React.FC = () => {
|
||||
onSelect: run(() => setSettingsDialogOpen(true)),
|
||||
},
|
||||
];
|
||||
list.push({
|
||||
id: 'toggle-memory-debug',
|
||||
title: t('commandPalette.item.toggleMemoryDebug'),
|
||||
icon: <Icon name="bug" className="mr-2 h-4 w-4" />,
|
||||
searchText: t('commandPalette.item.toggleMemoryDebug'),
|
||||
onSelect: run(() => {
|
||||
window.dispatchEvent(new CustomEvent('openchamber:memory-debug-toggle'));
|
||||
}),
|
||||
});
|
||||
if (canUseElectronDesktopIPC()) {
|
||||
list.splice(1, 0, {
|
||||
id: 'new-mini-chat',
|
||||
|
||||
@@ -10,6 +10,7 @@ import { Icon } from "@/components/icon/Icon";
|
||||
import { useUIStore } from "@/stores/useUIStore";
|
||||
import {
|
||||
getEffectiveShortcutCombo,
|
||||
getEffectiveShortcutPrefix,
|
||||
getShortcutAction,
|
||||
formatShortcutForDisplay,
|
||||
type ShortcutActionId,
|
||||
@@ -156,24 +157,6 @@ export const HelpDialog: React.FC = () => {
|
||||
{
|
||||
categoryKey: "helpDialog.section.panels",
|
||||
items: [
|
||||
{
|
||||
id: 'toggle_right_sidebar',
|
||||
descriptionKey: 'helpDialog.item.toggleRightSidebar',
|
||||
icon: "layout-right",
|
||||
keys: '',
|
||||
},
|
||||
{
|
||||
id: 'open_right_sidebar_git',
|
||||
descriptionKey: 'helpDialog.item.openRightSidebarGitTab',
|
||||
icon: "git-branch",
|
||||
keys: '',
|
||||
},
|
||||
{
|
||||
id: 'open_right_sidebar_files',
|
||||
descriptionKey: 'helpDialog.item.openRightSidebarFilesTab',
|
||||
icon: "layout-right",
|
||||
keys: '',
|
||||
},
|
||||
{
|
||||
id: 'toggle_terminal',
|
||||
descriptionKey: 'helpDialog.item.toggleTerminalDock',
|
||||
@@ -187,14 +170,13 @@ export const HelpDialog: React.FC = () => {
|
||||
keys: '',
|
||||
},
|
||||
{
|
||||
id: 'toggle_context_plan',
|
||||
descriptionKey: 'helpDialog.item.togglePlanContextPanel',
|
||||
icon: "time",
|
||||
keys: '',
|
||||
keys: [`${formatShortcutForDisplay(getEffectiveShortcutPrefix('switch_context_surface', shortcutOverrides))} + 1...0`],
|
||||
descriptionKey: "helpDialog.item.switchContextSurface",
|
||||
icon: "layout-right",
|
||||
},
|
||||
{
|
||||
keys: [`${formatShortcutForDisplay('mod')} + 1...0`],
|
||||
descriptionKey: "helpDialog.item.switchContextSurface",
|
||||
keys: [`${formatShortcutForDisplay(getEffectiveShortcutPrefix('switch_session_tab', shortcutOverrides))} + 1...9`],
|
||||
descriptionKey: "helpDialog.item.switchSessionTab",
|
||||
icon: "layout-right",
|
||||
},
|
||||
],
|
||||
@@ -214,12 +196,6 @@ export const HelpDialog: React.FC = () => {
|
||||
icon: "stack",
|
||||
keys: '',
|
||||
},
|
||||
{
|
||||
id: 'cycle_services_tab',
|
||||
descriptionKey: 'helpDialog.item.cycleServicesTab',
|
||||
icon: "stack",
|
||||
keys: '',
|
||||
},
|
||||
{
|
||||
id: 'open_settings',
|
||||
descriptionKey: "helpDialog.item.openSettings",
|
||||
@@ -258,6 +234,12 @@ export const HelpDialog: React.FC = () => {
|
||||
const descriptionKey = shortcut.descriptionKey
|
||||
?? (action?.customizable ? action.settingsLabelKey : undefined);
|
||||
if (!descriptionKey) return null;
|
||||
// This dialog lists what the keyboard can do right now;
|
||||
// an action without a binding belongs to the command
|
||||
// palette and Settings, not here.
|
||||
if (shortcut.id && !getEffectiveShortcutCombo(shortcut.id, shortcutOverrides)) {
|
||||
return null;
|
||||
}
|
||||
const displayKeys = shortcut.id
|
||||
? renderShortcut(
|
||||
shortcut.id,
|
||||
@@ -320,7 +302,7 @@ export const HelpDialog: React.FC = () => {
|
||||
• {t('helpDialog.proTips.recentSessions')}
|
||||
</li>
|
||||
<li>
|
||||
• {t('helpDialog.proTips.themeCycling')}
|
||||
• {t('helpDialog.proTips.leaderSequences')}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -2,16 +2,18 @@ import type React from 'react';
|
||||
|
||||
import { isIMECompositionEvent } from '@/lib/ime';
|
||||
|
||||
function getDropdownNavigationKey(event: Pick<KeyboardEvent, 'key' | 'ctrlKey' | 'metaKey' | 'altKey' | 'shiftKey'>): 'ArrowDown' | 'ArrowUp' | null {
|
||||
function getDropdownNavigationKey(event: Pick<KeyboardEvent, 'key' | 'code' | 'ctrlKey' | 'metaKey' | 'altKey' | 'shiftKey'>): 'ArrowDown' | 'ArrowUp' | null {
|
||||
if (!event.ctrlKey || event.metaKey || event.altKey || event.shiftKey) return null;
|
||||
if (event.key.toLowerCase() === 'n') return 'ArrowDown';
|
||||
if (event.key.toLowerCase() === 'p') return 'ArrowUp';
|
||||
// `code` covers non-Latin layouts, where `key` is the layout's own letter.
|
||||
if (event.key.toLowerCase() === 'n' || event.code === 'KeyN') return 'ArrowDown';
|
||||
if (event.key.toLowerCase() === 'p' || event.code === 'KeyP') return 'ArrowUp';
|
||||
return null;
|
||||
}
|
||||
|
||||
type DropdownNavigationEvent = Pick<
|
||||
React.KeyboardEvent<HTMLElement>,
|
||||
| 'altKey'
|
||||
| 'code'
|
||||
| 'ctrlKey'
|
||||
| 'defaultPrevented'
|
||||
| 'isPropagationStopped'
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import { isTerminalEventTarget } from '@/lib/terminalFocus';
|
||||
import { useSessionUIStore } from '@/sync/session-ui-store';
|
||||
import { closeSessionTabAndActivateNeighbour } from '@/lib/sessionTabs';
|
||||
import { activateSessionTabByIndex, closeSessionTabAndActivateNeighbour } from '@/lib/sessionTabs';
|
||||
import { useSelectionStore } from '@/sync/selection-store';
|
||||
import * as sessionActions from '@/sync/session-actions';
|
||||
import { normalizeContextPanelDirectoryKey, useUIStore } from '@/stores/useUIStore';
|
||||
@@ -11,13 +11,14 @@ import { useKeybinds } from '@/hooks/useKeybind';
|
||||
import { createWorktreeSession } from '@/lib/worktreeSessionCreator';
|
||||
import { useConfigStore } from '@/stores/useConfigStore';
|
||||
import { canUseElectronDesktopIPC, invokeDesktop, isVSCodeRuntime } from '@/lib/desktop';
|
||||
import { showOpenCodeStatus } from '@/lib/openCodeStatus';
|
||||
import {
|
||||
eventMatchesShortcut,
|
||||
eventMatchesShortcutPrefix,
|
||||
getEffectiveShortcutCombo,
|
||||
getEffectiveShortcutPrefix,
|
||||
normalizeCombo,
|
||||
resolveShortcutEventDigit,
|
||||
resolveShortcutEventKey,
|
||||
ShortcutDispatcher,
|
||||
shortcutRegistry,
|
||||
type ShortcutActionId,
|
||||
@@ -123,8 +124,18 @@ export const useKeyboardShortcuts = () => {
|
||||
},
|
||||
open_session_list: () => {
|
||||
const state = useUIStore.getState();
|
||||
if (state.isMobile) state.setSessionSwitcherOpen(true);
|
||||
else state.setSessionDropdownOpen(true);
|
||||
if (state.isMobile) {
|
||||
state.setSessionSwitcherOpen(true);
|
||||
return;
|
||||
}
|
||||
// The switcher dropdown only mounts while the sidebar is collapsed;
|
||||
// with the sidebar visible the list is already on screen, so the
|
||||
// shortcut opens the sidebar's session search instead.
|
||||
if (state.isSidebarOpen) {
|
||||
window.dispatchEvent(new CustomEvent('openchamber:sidebar-session-search'));
|
||||
return;
|
||||
}
|
||||
state.setSessionDropdownOpen(true);
|
||||
},
|
||||
toggle_prompt_navigator: () => {
|
||||
const state = useUIStore.getState();
|
||||
@@ -146,9 +157,6 @@ export const useKeyboardShortcuts = () => {
|
||||
}
|
||||
state.togglePromptNavigatorPanel();
|
||||
},
|
||||
open_status: () => {
|
||||
void showOpenCodeStatus();
|
||||
},
|
||||
open_help: () => {
|
||||
useUIStore.getState().toggleHelpDialog();
|
||||
},
|
||||
@@ -230,25 +238,6 @@ export const useKeyboardShortcuts = () => {
|
||||
useSelectionStore.getState().saveSessionAgentSelection(sessionId, next);
|
||||
}
|
||||
},
|
||||
toggle_right_sidebar: () => {
|
||||
const state = useUIStore.getState();
|
||||
if (state.isMobile || !currentDirectory) return false;
|
||||
const directory = normalizeContextPanelDirectoryKey(currentDirectory);
|
||||
const panel = state.contextPanelByDirectory[directory];
|
||||
if (panel?.isOpen) state.closeContextPanel(directory);
|
||||
else if (panel?.activeTabId) state.setActiveContextPanelTab(directory, panel.activeTabId);
|
||||
else state.openContextSurface(directory, 'git');
|
||||
},
|
||||
open_right_sidebar_git: () => {
|
||||
const state = useUIStore.getState();
|
||||
if (state.isMobile || !currentDirectory) return false;
|
||||
state.openContextSurface(normalizeContextPanelDirectoryKey(currentDirectory), 'git');
|
||||
},
|
||||
open_right_sidebar_files: () => {
|
||||
const state = useUIStore.getState();
|
||||
if (state.isMobile || !currentDirectory) return false;
|
||||
state.openContextSurface(normalizeContextPanelDirectoryKey(currentDirectory), 'file');
|
||||
},
|
||||
toggle_terminal: () => {
|
||||
if (useUIStore.getState().isMobile) return false;
|
||||
return toggleTerminalSurface();
|
||||
@@ -482,8 +471,9 @@ export const useKeyboardShortcuts = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
const switchSurfaceDigit = event.key.length === 1 && event.key >= '0' && event.key <= '9'
|
||||
? (event.key === '0' ? 10 : Number(event.key))
|
||||
const rawDigit = resolveShortcutEventDigit(event);
|
||||
const switchSurfaceDigit = rawDigit !== null
|
||||
? (rawDigit === '0' ? 10 : Number(rawDigit))
|
||||
: null;
|
||||
const switchSurfacePrefix = getEffectiveShortcutPrefix(
|
||||
'switch_context_surface',
|
||||
@@ -514,13 +504,32 @@ export const useKeyboardShortcuts = () => {
|
||||
}
|
||||
}
|
||||
|
||||
const sessionTabDigit = rawDigit !== null && rawDigit !== '0' ? Number(rawDigit) : null;
|
||||
if (
|
||||
sessionTabDigit !== null
|
||||
&& !event.repeat
|
||||
&& !isVSCodeRuntime()
|
||||
&& useUIStore.getState().sessionTabsEnabled
|
||||
&& eventMatchesShortcutPrefix(
|
||||
event,
|
||||
getEffectiveShortcutPrefix('switch_session_tab', useUIStore.getState().shortcutOverrides),
|
||||
heldKeysRef.current,
|
||||
)
|
||||
&& activateSessionTabByIndex(sessionTabDigit - 1)
|
||||
) {
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
if (dispatcher.dispatch(event)) event.preventDefault();
|
||||
};
|
||||
const handleKeyHoldDown = (event: KeyboardEvent) => {
|
||||
heldKeysRef.current.add(event.key.toLowerCase());
|
||||
heldKeysRef.current.add(resolveShortcutEventKey(event).toLowerCase());
|
||||
};
|
||||
const handleKeyUp = (event: KeyboardEvent) => {
|
||||
heldKeysRef.current.delete(event.key.toLowerCase());
|
||||
heldKeysRef.current.delete(resolveShortcutEventKey(event).toLowerCase());
|
||||
};
|
||||
const handleBlur = () => {
|
||||
heldKeysRef.current.clear();
|
||||
|
||||
@@ -1080,9 +1080,8 @@ export const settingsDict = {
|
||||
'settings.openchamber.keyboardShortcuts.action.toggle_terminal_expanded.label': 'Terminal erweitert umschalten',
|
||||
'settings.openchamber.keyboardShortcuts.action.add_selection_to_chat.label': 'Auswahl zum Chat hinzufügen',
|
||||
'settings.openchamber.keyboardShortcuts.action.toggle_sidebar.label': 'Seitenleiste umschalten',
|
||||
'settings.openchamber.keyboardShortcuts.action.toggle_right_sidebar.label': 'Rechte Seitenleiste umschalten',
|
||||
'settings.openchamber.keyboardShortcuts.action.open_right_sidebar_git.label': 'Git-Tab der rechten Seitenleiste öffnen',
|
||||
'settings.openchamber.keyboardShortcuts.action.open_right_sidebar_files.label': 'Datei-Tab der rechten Seitenleiste öffnen',
|
||||
'settings.openchamber.keyboardShortcuts.action.switch_session_tab.label': 'Sitzungs-Tab wechseln',
|
||||
'settings.openchamber.keyboardShortcuts.action.switch_session_tab.suffix': ' + 1…9',
|
||||
'settings.openchamber.keyboardShortcuts.action.switch_context_surface.label': 'Kontextpanel-Oberfläche wechseln',
|
||||
'settings.openchamber.keyboardShortcuts.action.switch_context_surface.suffix': ' + 1…0',
|
||||
'settings.openchamber.keyboardShortcuts.action.new_chat.label': 'Neue Sitzung',
|
||||
@@ -1090,9 +1089,7 @@ export const settingsDict = {
|
||||
'settings.openchamber.keyboardShortcuts.action.new_chat_worktree.label': 'Neuer Worktree-Entwurf',
|
||||
'settings.openchamber.keyboardShortcuts.action.new_mini_chat.label': 'Neues Mini-Chat-Fenster',
|
||||
'settings.openchamber.keyboardShortcuts.action.open_help.label': 'Tastenkürzel öffnen',
|
||||
'settings.openchamber.keyboardShortcuts.action.toggle_context_plan.label': 'Plan-Kontextpanel umschalten',
|
||||
'settings.openchamber.keyboardShortcuts.action.toggle_services_menu.label': 'Dienstemenü umschalten',
|
||||
'settings.openchamber.keyboardShortcuts.action.cycle_services_tab.label': 'Dienste-Tab durchschalten',
|
||||
'settings.openchamber.keyboardShortcuts.action.cycle_theme.label': 'Thema wechseln',
|
||||
'settings.openchamber.keyboardShortcuts.action.cycle_agent.label': 'Agent wechseln',
|
||||
'settings.openchamber.keyboardShortcuts.action.cycle_favorite_model_forward.label': 'Favorites Modell vorwärts durchschalten',
|
||||
|
||||
@@ -1684,22 +1684,18 @@ export const dict = {
|
||||
'helpDialog.item.focusChatInput': 'Chat-Eingabe fokussieren',
|
||||
'helpDialog.item.togglePromptNavigator': 'Aufforderungs-Navigator umschalten',
|
||||
'helpDialog.item.abortActiveRun': 'Aktuelle Ausführung abbrechen (Doppeltaste)',
|
||||
'helpDialog.item.toggleRightSidebar': 'Rechte Seitenleiste umschalten',
|
||||
'helpDialog.item.openRightSidebarGitTab': 'Git-Registerkarte der rechten Seitenleiste öffnen',
|
||||
'helpDialog.item.openRightSidebarFilesTab': 'Datei-Registerkarte der rechten Seitenleiste öffnen',
|
||||
'helpDialog.item.toggleTerminalDock': 'Terminal-Dock umschalten',
|
||||
'helpDialog.item.toggleTerminalExpanded': 'Terminal erweitert umschalten',
|
||||
'helpDialog.item.togglePlanContextPanel': 'Plan-Kontext-Panel umschalten',
|
||||
'helpDialog.item.cycleTheme': 'Thema wechseln (Hell → Dunkel → System)',
|
||||
'helpDialog.item.switchSessionTab': 'Sitzungs-Tab wechseln',
|
||||
'helpDialog.item.switchContextSurface': 'Kontextpanel-Oberfläche wechseln (Zahlentaste)',
|
||||
'helpDialog.item.toggleServicesMenu': 'Dienstemenü umschalten',
|
||||
'helpDialog.item.cycleServicesTab': 'Dienste-Registerkarte durchgehen',
|
||||
'helpDialog.item.openSettings': 'Einstellungen öffnen',
|
||||
'helpDialog.keyCombiner.or': 'oder',
|
||||
'helpDialog.proTips.title': 'Pro-Tipps:',
|
||||
'helpDialog.proTips.commandPalette': 'Verwenden Sie die Befehlspalette ({shortcut}), um schnell auf alle Aktionen zuzugreifen',
|
||||
'helpDialog.proTips.recentSessions': 'Die 5 zuletzt verwendeten Sitzungen erscheinen in der Befehlspalette',
|
||||
'helpDialog.proTips.themeCycling': 'Themenwechsel merken sich Ihre Einstellung über Sitzungen hinweg',
|
||||
'helpDialog.proTips.leaderSequences': 'Zweistufige Kürzel: erst die Kombination, dann die zweite Taste — Esc bricht ab',
|
||||
'header.actions.rightSidebarWithShortcut': 'Rechte Seitenleiste ({shortcut})',
|
||||
'header.actions.toggleRightSidebarAria': 'Rechte Seitenleiste umschalten',
|
||||
'header.actions.openAppMenu': 'OpenChamber-Menü',
|
||||
@@ -2293,6 +2289,9 @@ export const dict = {
|
||||
'commandPalette.item.toggleSidebar': 'Seitenleiste umschalten',
|
||||
'commandPalette.item.showContextUsage': 'Kontextnutzung anzeigen',
|
||||
'commandPalette.item.toggleTerminal': 'Terminal umschalten',
|
||||
'commandPalette.item.cycleTheme': 'Thema wechseln',
|
||||
'commandPalette.item.showOpenCodeStatus': 'OpenCode-Status anzeigen',
|
||||
'commandPalette.item.toggleMemoryDebug': 'Memory-Debug-Panel umschalten',
|
||||
'commandPalette.item.openSettings': 'Einstellungen öffnen...',
|
||||
'commandPalette.session.untitled': 'Unbenannte Sitzung',
|
||||
'openCodeStatusDialog.title': 'OpenCode-Status',
|
||||
|
||||
@@ -1142,9 +1142,8 @@ export const settingsDict = {
|
||||
'settings.openchamber.keyboardShortcuts.action.toggle_terminal_expanded.label': 'Toggle terminal expanded',
|
||||
'settings.openchamber.keyboardShortcuts.action.add_selection_to_chat.label': 'Add selection to chat',
|
||||
'settings.openchamber.keyboardShortcuts.action.toggle_sidebar.label': 'Toggle sidebar',
|
||||
'settings.openchamber.keyboardShortcuts.action.toggle_right_sidebar.label': 'Toggle context panel',
|
||||
'settings.openchamber.keyboardShortcuts.action.open_right_sidebar_git.label': 'Open Git surface',
|
||||
'settings.openchamber.keyboardShortcuts.action.open_right_sidebar_files.label': 'Open Files surface',
|
||||
'settings.openchamber.keyboardShortcuts.action.switch_session_tab.label': 'Switch session tab',
|
||||
'settings.openchamber.keyboardShortcuts.action.switch_session_tab.suffix': ' + 1…9',
|
||||
'settings.openchamber.keyboardShortcuts.action.switch_context_surface.label': 'Switch context panel surface',
|
||||
'settings.openchamber.keyboardShortcuts.action.switch_context_surface.suffix': ' + 1…0',
|
||||
'settings.openchamber.keyboardShortcuts.action.new_chat.label': 'New session',
|
||||
@@ -1152,9 +1151,7 @@ export const settingsDict = {
|
||||
'settings.openchamber.keyboardShortcuts.action.new_chat_worktree.label': 'New worktree draft',
|
||||
'settings.openchamber.keyboardShortcuts.action.new_mini_chat.label': 'New Mini Chat window',
|
||||
'settings.openchamber.keyboardShortcuts.action.open_help.label': 'Open keyboard shortcuts',
|
||||
'settings.openchamber.keyboardShortcuts.action.toggle_context_plan.label': 'Toggle plan context panel',
|
||||
'settings.openchamber.keyboardShortcuts.action.toggle_services_menu.label': 'Toggle services menu',
|
||||
'settings.openchamber.keyboardShortcuts.action.cycle_services_tab.label': 'Cycle services tab',
|
||||
'settings.openchamber.keyboardShortcuts.action.cycle_theme.label': 'Cycle theme',
|
||||
'settings.openchamber.keyboardShortcuts.action.cycle_agent.label': 'Cycle agent',
|
||||
'settings.openchamber.keyboardShortcuts.action.cycle_favorite_model_forward.label': 'Cycle favorite model forward',
|
||||
|
||||
@@ -1858,22 +1858,18 @@ export const dict = {
|
||||
'helpDialog.item.focusChatInput': 'Focus Chat Input',
|
||||
'helpDialog.item.togglePromptNavigator': 'Toggle Prompt Navigator',
|
||||
'helpDialog.item.abortActiveRun': 'Abort active run (double press)',
|
||||
'helpDialog.item.toggleRightSidebar': 'Toggle context panel',
|
||||
'helpDialog.item.openRightSidebarGitTab': 'Open Git surface',
|
||||
'helpDialog.item.openRightSidebarFilesTab': 'Open Files surface',
|
||||
'helpDialog.item.toggleTerminalDock': 'Toggle Terminal Dock',
|
||||
'helpDialog.item.toggleTerminalExpanded': 'Toggle Terminal Expanded',
|
||||
'helpDialog.item.togglePlanContextPanel': 'Toggle Plan Context Panel',
|
||||
'helpDialog.item.switchSessionTab': 'Switch Session Tab',
|
||||
'helpDialog.item.switchContextSurface': 'Switch Context Panel Surface (number key)',
|
||||
'helpDialog.item.cycleTheme': 'Cycle Theme (Light → Dark → System)',
|
||||
'helpDialog.item.toggleServicesMenu': 'Toggle Services Menu',
|
||||
'helpDialog.item.cycleServicesTab': 'Cycle Services Tab',
|
||||
'helpDialog.item.openSettings': 'Open Settings',
|
||||
'helpDialog.keyCombiner.or': 'or',
|
||||
'helpDialog.proTips.title': 'Pro Tips:',
|
||||
'helpDialog.proTips.commandPalette': 'Use Command Palette ({shortcut}) to quickly access all actions',
|
||||
'helpDialog.proTips.recentSessions': 'The 5 most recent sessions appear in the Command Palette',
|
||||
'helpDialog.proTips.themeCycling': 'Theme cycling remembers your preference across sessions',
|
||||
'helpDialog.proTips.leaderSequences': 'Two-step shortcuts: press the first combo, then the second key — Esc cancels',
|
||||
'header.actions.rightSidebarWithShortcut': 'Right sidebar ({shortcut})',
|
||||
'header.actions.toggleRightSidebarAria': 'Toggle right sidebar',
|
||||
'header.actions.openAppMenu': 'OpenChamber menu',
|
||||
@@ -2483,6 +2479,9 @@ export const dict = {
|
||||
'commandPalette.item.toggleSidebar': 'Toggle Sidebar',
|
||||
'commandPalette.item.showContextUsage': 'Show Context Usage',
|
||||
'commandPalette.item.toggleTerminal': 'Toggle Terminal',
|
||||
'commandPalette.item.cycleTheme': 'Cycle theme',
|
||||
'commandPalette.item.showOpenCodeStatus': 'Show OpenCode status',
|
||||
'commandPalette.item.toggleMemoryDebug': 'Toggle memory debug panel',
|
||||
'commandPalette.item.openSettings': 'Open Settings...',
|
||||
'commandPalette.session.untitled': 'Untitled Session',
|
||||
'openCodeStatusDialog.title': 'OpenCode Status',
|
||||
|
||||
@@ -1110,9 +1110,8 @@ export const settingsDict = {
|
||||
"settings.openchamber.keyboardShortcuts.action.toggle_terminal_expanded.label": "Expandir o contraer terminal",
|
||||
"settings.openchamber.keyboardShortcuts.action.add_selection_to_chat.label": "Agregar selección al chat",
|
||||
"settings.openchamber.keyboardShortcuts.action.toggle_sidebar.label": "Mostrar u ocultar barra lateral",
|
||||
"settings.openchamber.keyboardShortcuts.action.toggle_right_sidebar.label": 'Alternar panel de contexto',
|
||||
"settings.openchamber.keyboardShortcuts.action.open_right_sidebar_git.label": 'Abrir superficie de Git',
|
||||
"settings.openchamber.keyboardShortcuts.action.open_right_sidebar_files.label": 'Abrir superficie de archivos',
|
||||
"settings.openchamber.keyboardShortcuts.action.switch_session_tab.label": "Cambiar pestaña de sesión",
|
||||
"settings.openchamber.keyboardShortcuts.action.switch_session_tab.suffix": " + 1…9",
|
||||
"settings.openchamber.keyboardShortcuts.action.switch_context_surface.label": "Cambiar superficie del panel de contexto",
|
||||
"settings.openchamber.keyboardShortcuts.action.switch_context_surface.suffix": " + 1…0",
|
||||
"settings.openchamber.keyboardShortcuts.action.new_chat.label": "Nueva sesión",
|
||||
@@ -1120,9 +1119,7 @@ export const settingsDict = {
|
||||
"settings.openchamber.keyboardShortcuts.action.new_chat_worktree.label": "Nuevo borrador de worktree",
|
||||
"settings.openchamber.keyboardShortcuts.action.new_mini_chat.label": "Nueva ventana Mini Chat",
|
||||
"settings.openchamber.keyboardShortcuts.action.open_help.label": "Abrir atajos de teclado",
|
||||
"settings.openchamber.keyboardShortcuts.action.toggle_context_plan.label": "Alternar panel de plan de contexto",
|
||||
"settings.openchamber.keyboardShortcuts.action.toggle_services_menu.label": "Mostrar u ocultar menú de servicios",
|
||||
"settings.openchamber.keyboardShortcuts.action.cycle_services_tab.label": "Cambiar pestaña de servicios",
|
||||
"settings.openchamber.keyboardShortcuts.action.cycle_theme.label": "Cambiar tema",
|
||||
"settings.openchamber.keyboardShortcuts.action.cycle_agent.label": "Cambiar agente",
|
||||
"settings.openchamber.keyboardShortcuts.action.cycle_favorite_model_forward.label": "Siguiente modelo favorito",
|
||||
|
||||
@@ -1836,22 +1836,18 @@ export const dict: Record<I18nKey, string> = {
|
||||
"helpDialog.item.focusChatInput": "Enfocar entrada de chat",
|
||||
"helpDialog.item.togglePromptNavigator": "Mostrar u ocultar navegador de prompts",
|
||||
"helpDialog.item.abortActiveRun": "Detener ejecución activa (doble presionar)",
|
||||
"helpDialog.item.toggleRightSidebar": 'Alternar panel de contexto',
|
||||
"helpDialog.item.openRightSidebarGitTab": 'Abrir superficie de Git',
|
||||
"helpDialog.item.openRightSidebarFilesTab": 'Abrir superficie de archivos',
|
||||
"helpDialog.item.toggleTerminalDock": "Mostrar u ocultar dock de terminal",
|
||||
"helpDialog.item.toggleTerminalExpanded": "Expandir o contraer terminal",
|
||||
"helpDialog.item.togglePlanContextPanel": "Alternar panel de contexto del plan",
|
||||
"helpDialog.item.cycleTheme": "Cambiar tema (Claro → Oscuro → Sistema)",
|
||||
"helpDialog.item.switchSessionTab": "Cambiar pestaña de sesión",
|
||||
"helpDialog.item.switchContextSurface": "Cambiar superficie del panel de contexto (tecla numérica)",
|
||||
"helpDialog.item.toggleServicesMenu": "Mostrar u ocultar menú de servicios",
|
||||
"helpDialog.item.cycleServicesTab": "Cambiar pestaña de servicios",
|
||||
"helpDialog.item.openSettings": "Abrir configuración",
|
||||
"helpDialog.keyCombiner.or": "o",
|
||||
"helpDialog.proTips.title": "Consejos:",
|
||||
"helpDialog.proTips.commandPalette": "Usa la paleta de comandos ({shortcut}) para acceder rápidamente a todas las acciones",
|
||||
"helpDialog.proTips.recentSessions": "Las cinco sesiones más recientes aparecen en la paleta de comandos",
|
||||
"helpDialog.proTips.themeCycling": "El ciclo de tema recuerda tu preferencia entre sesiones",
|
||||
"helpDialog.proTips.leaderSequences": "Atajos en dos pasos: pulsa la combinación y luego la segunda tecla; Esc cancela",
|
||||
"header.actions.rightSidebarWithShortcut": "Barra lateral derecha ({shortcut})",
|
||||
"header.actions.toggleRightSidebarAria": "Mostrar u ocultar barra lateral derecha",
|
||||
"header.actions.openAppMenu": "Menú de OpenChamber",
|
||||
@@ -2449,6 +2445,9 @@ export const dict: Record<I18nKey, string> = {
|
||||
"commandPalette.item.toggleSidebar": "Mostrar u ocultar barra lateral",
|
||||
"commandPalette.item.showContextUsage": "Mostrar uso del contexto",
|
||||
"commandPalette.item.toggleTerminal": "Mostrar u ocultar terminal",
|
||||
"commandPalette.item.cycleTheme": "Cambiar tema",
|
||||
"commandPalette.item.showOpenCodeStatus": "Mostrar estado de OpenCode",
|
||||
"commandPalette.item.toggleMemoryDebug": "Alternar panel de depuración de memoria",
|
||||
"commandPalette.item.openSettings": "Abrir configuración...",
|
||||
"commandPalette.session.untitled": "Sesión sin título",
|
||||
"openCodeStatusDialog.title": "Estado de OpenCode",
|
||||
|
||||
@@ -1028,9 +1028,8 @@ export const settingsDict = {
|
||||
'settings.openchamber.keyboardShortcuts.action.toggle_terminal_expanded.label': 'Terminal à bascule étendu',
|
||||
'settings.openchamber.keyboardShortcuts.action.add_selection_to_chat.label': 'Ajouter la sélection au chat',
|
||||
'settings.openchamber.keyboardShortcuts.action.toggle_sidebar.label': 'Basculer la barre latérale',
|
||||
'settings.openchamber.keyboardShortcuts.action.toggle_right_sidebar.label': 'Afficher/masquer le panneau de contexte',
|
||||
'settings.openchamber.keyboardShortcuts.action.open_right_sidebar_git.label': 'Ouvrir la surface Git',
|
||||
'settings.openchamber.keyboardShortcuts.action.open_right_sidebar_files.label': 'Ouvrir la surface Fichiers',
|
||||
'settings.openchamber.keyboardShortcuts.action.switch_session_tab.label': 'Basculer l’onglet de session',
|
||||
'settings.openchamber.keyboardShortcuts.action.switch_session_tab.suffix': ' + 1…9',
|
||||
'settings.openchamber.keyboardShortcuts.action.switch_context_surface.label': 'Basculer la surface du panneau contextuel',
|
||||
'settings.openchamber.keyboardShortcuts.action.switch_context_surface.suffix': ' + 1…0',
|
||||
'settings.openchamber.keyboardShortcuts.action.new_chat.label': 'Nouvelle session',
|
||||
@@ -1038,9 +1037,7 @@ export const settingsDict = {
|
||||
'settings.openchamber.keyboardShortcuts.action.new_chat_worktree.label': 'Nouvelle ébauche d\'worktree',
|
||||
'settings.openchamber.keyboardShortcuts.action.new_mini_chat.label': 'Nouvelle fenêtre de mini-chat',
|
||||
'settings.openchamber.keyboardShortcuts.action.open_help.label': 'Ouvrir les raccourcis clavier',
|
||||
'settings.openchamber.keyboardShortcuts.action.toggle_context_plan.label': 'Basculer le panneau contextuel du plan',
|
||||
'settings.openchamber.keyboardShortcuts.action.toggle_services_menu.label': 'Basculer le menu des services',
|
||||
'settings.openchamber.keyboardShortcuts.action.cycle_services_tab.label': 'Onglet Services vélo',
|
||||
'settings.openchamber.keyboardShortcuts.action.cycle_theme.label': 'Thème du cycle',
|
||||
'settings.openchamber.keyboardShortcuts.action.cycle_agent.label': 'Agent de cycle',
|
||||
'settings.openchamber.keyboardShortcuts.action.cycle_favorite_model_forward.label': 'Faire avancer le modèle favori',
|
||||
|
||||
@@ -1616,22 +1616,18 @@ export const dict = {
|
||||
'helpDialog.item.focusChatInput': 'Concentration sur la saisie du chat',
|
||||
'helpDialog.item.togglePromptNavigator': 'Afficher ou masquer le navigateur de prompts',
|
||||
'helpDialog.item.abortActiveRun': 'Abandonner l’exécution active (double pression)',
|
||||
'helpDialog.item.toggleRightSidebar': 'Afficher/masquer le panneau de contexte',
|
||||
'helpDialog.item.openRightSidebarGitTab': 'Ouvrir la surface Git',
|
||||
'helpDialog.item.openRightSidebarFilesTab': 'Ouvrir la surface Fichiers',
|
||||
'helpDialog.item.toggleTerminalDock': 'Basculer la station d\'accueil du terminal',
|
||||
'helpDialog.item.toggleTerminalExpanded': 'Terminal à bascule étendu',
|
||||
'helpDialog.item.togglePlanContextPanel': 'Toggle Panneau contextuel du plan',
|
||||
'helpDialog.item.cycleTheme': 'Basculer le thème (clair → sombre → système)',
|
||||
'helpDialog.item.switchSessionTab': 'Basculer l’onglet de session',
|
||||
'helpDialog.item.switchContextSurface': 'Basculer la surface du panneau contextuel (touche numérique)',
|
||||
'helpDialog.item.toggleServicesMenu': 'Basculer le menu des services',
|
||||
'helpDialog.item.cycleServicesTab': 'Onglet Services de vélo',
|
||||
'helpDialog.item.openSettings': 'Ouvrir les paramètres',
|
||||
'helpDialog.keyCombiner.or': 'ou',
|
||||
'helpDialog.proTips.title': 'Conseils de pro :',
|
||||
'helpDialog.proTips.commandPalette': 'Utilisez la palette de commandes ({shortcut}) pour accéder rapidement à toutes les actions',
|
||||
'helpDialog.proTips.recentSessions': 'Les 5 sessions les plus récentes apparaissent dans la palette de commandes',
|
||||
'helpDialog.proTips.themeCycling': 'Le cyclisme thématique mémorise vos préférences au fil des sessions',
|
||||
'helpDialog.proTips.leaderSequences': 'Raccourcis en deux temps : appuyez sur la combinaison, puis sur la seconde touche — Échap annule',
|
||||
'header.actions.rightSidebarWithShortcut': 'Barre latérale droite ({shortcut})',
|
||||
'header.actions.toggleRightSidebarAria': 'Basculer la barre latérale droite',
|
||||
'header.actions.openAppMenu': 'Menu de OpenChamber',
|
||||
@@ -2187,6 +2183,9 @@ export const dict = {
|
||||
'commandPalette.item.toggleSidebar': 'Basculer la barre latérale',
|
||||
'commandPalette.item.showContextUsage': 'Afficher l\'utilisation du contexte',
|
||||
'commandPalette.item.toggleTerminal': 'Basculer le terminal',
|
||||
'commandPalette.item.cycleTheme': 'Changer de thème',
|
||||
'commandPalette.item.showOpenCodeStatus': 'Afficher le statut OpenCode',
|
||||
'commandPalette.item.toggleMemoryDebug': 'Basculer le panneau de débogage mémoire',
|
||||
'commandPalette.item.openSettings': 'Ouvrez les paramètres...',
|
||||
'commandPalette.session.untitled': 'Session sans titre',
|
||||
'openCodeStatusDialog.title': 'Statut OpenCode',
|
||||
|
||||
@@ -1143,9 +1143,8 @@ export const settingsDict = {
|
||||
'settings.openchamber.keyboardShortcuts.action.toggle_terminal_expanded.label': 'ターミナル拡大の切替',
|
||||
'settings.openchamber.keyboardShortcuts.action.add_selection_to_chat.label': '選択範囲をチャットに追加',
|
||||
'settings.openchamber.keyboardShortcuts.action.toggle_sidebar.label': 'サイドバーの切替',
|
||||
'settings.openchamber.keyboardShortcuts.action.toggle_right_sidebar.label': 'コンテキストパネルの表示切替',
|
||||
'settings.openchamber.keyboardShortcuts.action.open_right_sidebar_git.label': 'Git サーフェスを開く',
|
||||
'settings.openchamber.keyboardShortcuts.action.open_right_sidebar_files.label': 'ファイルサーフェスを開く',
|
||||
'settings.openchamber.keyboardShortcuts.action.switch_session_tab.label': 'セッションタブを切り替え',
|
||||
'settings.openchamber.keyboardShortcuts.action.switch_session_tab.suffix': ' + 1…9',
|
||||
'settings.openchamber.keyboardShortcuts.action.switch_context_surface.label': 'コンテキストパネルのサーフェスを切り替え',
|
||||
'settings.openchamber.keyboardShortcuts.action.switch_context_surface.suffix': ' + 1…0',
|
||||
'settings.openchamber.keyboardShortcuts.action.new_chat.label': '新しい Session',
|
||||
@@ -1153,9 +1152,7 @@ export const settingsDict = {
|
||||
'settings.openchamber.keyboardShortcuts.action.new_chat_worktree.label': '新しい Worktree 下書き',
|
||||
'settings.openchamber.keyboardShortcuts.action.new_mini_chat.label': '新しいミニチャットウィンドウ',
|
||||
'settings.openchamber.keyboardShortcuts.action.open_help.label': 'キーボードショートカットを開く',
|
||||
'settings.openchamber.keyboardShortcuts.action.toggle_context_plan.label': '計画コンテキストパネルの切替',
|
||||
'settings.openchamber.keyboardShortcuts.action.toggle_services_menu.label': 'サービスメニューの切替',
|
||||
'settings.openchamber.keyboardShortcuts.action.cycle_services_tab.label': 'サービスタブを順に切替',
|
||||
'settings.openchamber.keyboardShortcuts.action.cycle_theme.label': 'テーマを順に切替',
|
||||
'settings.openchamber.keyboardShortcuts.action.cycle_agent.label': 'Agent を順に切替',
|
||||
'settings.openchamber.keyboardShortcuts.action.cycle_favorite_model_forward.label': 'お気に入りモデルを次へ',
|
||||
|
||||
@@ -1854,22 +1854,18 @@ export const dict: Record<I18nKey, string> = {
|
||||
'helpDialog.item.focusChatInput': 'チャット入力にフォーカス',
|
||||
'helpDialog.item.togglePromptNavigator': 'プロンプトナビゲーターの表示切替',
|
||||
'helpDialog.item.abortActiveRun': 'アクティブな実行を中止(ダブルプレス)',
|
||||
'helpDialog.item.toggleRightSidebar': 'コンテキストパネルの表示切替',
|
||||
'helpDialog.item.openRightSidebarGitTab': 'Git サーフェスを開く',
|
||||
'helpDialog.item.openRightSidebarFilesTab': 'ファイルサーフェスを開く',
|
||||
'helpDialog.item.toggleTerminalDock': 'ターミナルドックの切り替え',
|
||||
'helpDialog.item.toggleTerminalExpanded': 'ターミナル展開の切り替え',
|
||||
'helpDialog.item.togglePlanContextPanel': '計画コンテキストパネルの切り替え',
|
||||
'helpDialog.item.cycleTheme': 'テーマ切り替え(ライト→ダーク→システム)',
|
||||
'helpDialog.item.switchSessionTab': 'セッションタブを切り替え',
|
||||
'helpDialog.item.switchContextSurface': 'コンテキストパネルのサーフェスを切り替え(数字キー)',
|
||||
'helpDialog.item.toggleServicesMenu': 'サービスの切り替え',
|
||||
'helpDialog.item.cycleServicesTab': 'サービス変数の切り替え',
|
||||
'helpDialog.item.openSettings': '設定を開く',
|
||||
'helpDialog.keyCombiner.or': 'または',
|
||||
'helpDialog.proTips.title': 'プロのヒント:',
|
||||
'helpDialog.proTips.commandPalette': 'コマンドパレット({shortcut})を使うとすべての操作にすばやくアクセスできます',
|
||||
'helpDialog.proTips.recentSessions': '最近の5つのセッションがコマンドパレットに表示されます',
|
||||
'helpDialog.proTips.themeCycling': 'テーマの切り替えはセッション間で設定が記憶されます',
|
||||
'helpDialog.proTips.leaderSequences': '2段階ショートカット:組み合わせを押してから2つ目のキーを押します(Escで取消)',
|
||||
'header.actions.rightSidebarWithShortcut': '右サイドバー({shortcut})',
|
||||
'header.actions.toggleRightSidebarAria': '右サイドバーの切り替え',
|
||||
'header.actions.openAppMenu': 'OpenChamberメニュー',
|
||||
@@ -2482,6 +2478,9 @@ export const dict: Record<I18nKey, string> = {
|
||||
'commandPalette.item.toggleSidebar': 'サイドバーの切り替え',
|
||||
'commandPalette.item.showContextUsage': 'コンテキスト使用量を表示',
|
||||
'commandPalette.item.toggleTerminal': 'ターミナルの切り替え',
|
||||
'commandPalette.item.cycleTheme': 'テーマを順に切替',
|
||||
'commandPalette.item.showOpenCodeStatus': 'OpenCode のステータスを表示',
|
||||
'commandPalette.item.toggleMemoryDebug': 'メモリデバッグパネルの切替',
|
||||
'commandPalette.item.openSettings': '設定を開く...',
|
||||
'commandPalette.session.untitled': '無題のセッション',
|
||||
'openCodeStatusDialog.title': 'OpenCodeステータス',
|
||||
|
||||
@@ -1110,9 +1110,8 @@ export const settingsDict = {
|
||||
'settings.openchamber.keyboardShortcuts.action.toggle_terminal_expanded.label': '터미널 확장 토글',
|
||||
'settings.openchamber.keyboardShortcuts.action.add_selection_to_chat.label': '선택 내용을 채팅에 추가',
|
||||
'settings.openchamber.keyboardShortcuts.action.toggle_sidebar.label': '사이드바 토글',
|
||||
'settings.openchamber.keyboardShortcuts.action.toggle_right_sidebar.label': '컨텍스트 패널 표시 전환',
|
||||
'settings.openchamber.keyboardShortcuts.action.open_right_sidebar_git.label': 'Git 서피스 열기',
|
||||
'settings.openchamber.keyboardShortcuts.action.open_right_sidebar_files.label': '파일 서피스 열기',
|
||||
'settings.openchamber.keyboardShortcuts.action.switch_session_tab.label': '세션 탭 전환',
|
||||
'settings.openchamber.keyboardShortcuts.action.switch_session_tab.suffix': ' + 1…9',
|
||||
'settings.openchamber.keyboardShortcuts.action.switch_context_surface.label': '컨텍스트 패널 서피스 전환',
|
||||
'settings.openchamber.keyboardShortcuts.action.switch_context_surface.suffix': ' + 1…0',
|
||||
'settings.openchamber.keyboardShortcuts.action.new_chat.label': '새 세션',
|
||||
@@ -1120,9 +1119,7 @@ export const settingsDict = {
|
||||
'settings.openchamber.keyboardShortcuts.action.new_chat_worktree.label': '새 worktree 초안',
|
||||
'settings.openchamber.keyboardShortcuts.action.new_mini_chat.label': '새 Mini Chat 창',
|
||||
'settings.openchamber.keyboardShortcuts.action.open_help.label': '키보드 단축키 열기',
|
||||
'settings.openchamber.keyboardShortcuts.action.toggle_context_plan.label': '계획 컨텍스트 패널 토글',
|
||||
'settings.openchamber.keyboardShortcuts.action.toggle_services_menu.label': '서비스 메뉴 토글',
|
||||
'settings.openchamber.keyboardShortcuts.action.cycle_services_tab.label': '서비스 탭 순환',
|
||||
'settings.openchamber.keyboardShortcuts.action.cycle_theme.label': '테마 순환',
|
||||
'settings.openchamber.keyboardShortcuts.action.cycle_agent.label': '에이전트 순환',
|
||||
'settings.openchamber.keyboardShortcuts.action.cycle_favorite_model_forward.label': '즐겨찾기 모델 앞으로 순환',
|
||||
|
||||
@@ -1860,22 +1860,18 @@ export const dict: Record<I18nKey, string> = {
|
||||
'helpDialog.item.focusChatInput': '채팅 입력창으로 포커스 이동',
|
||||
'helpDialog.item.togglePromptNavigator': '프롬프트 탐색기 표시/숨기기',
|
||||
'helpDialog.item.abortActiveRun': '활성 실행 중단(두 번 누르기)',
|
||||
'helpDialog.item.toggleRightSidebar': '컨텍스트 패널 표시 전환',
|
||||
'helpDialog.item.openRightSidebarGitTab': 'Git 서피스 열기',
|
||||
'helpDialog.item.openRightSidebarFilesTab': '파일 서피스 열기',
|
||||
'helpDialog.item.toggleTerminalDock': '터미널 독 전환',
|
||||
'helpDialog.item.toggleTerminalExpanded': '터미널 펼치기/접기',
|
||||
'helpDialog.item.togglePlanContextPanel': '플랜 컨텍스트 패널 전환',
|
||||
'helpDialog.item.cycleTheme': '테마 순환(라이트 → 다크 → 시스템)',
|
||||
'helpDialog.item.switchSessionTab': '세션 탭 전환',
|
||||
'helpDialog.item.switchContextSurface': '컨텍스트 패널 서피스 전환(숫자 키)',
|
||||
'helpDialog.item.toggleServicesMenu': '서비스 메뉴 전환',
|
||||
'helpDialog.item.cycleServicesTab': '서비스 탭 순환',
|
||||
'helpDialog.item.openSettings': '설정 열기',
|
||||
'helpDialog.keyCombiner.or': '또는',
|
||||
'helpDialog.proTips.title': '팁:',
|
||||
'helpDialog.proTips.commandPalette': '명령 팔레트({shortcut})로 모든 작업에 빠르게 접근하세요',
|
||||
'helpDialog.proTips.recentSessions': '최근 세션 5개가 명령 팔레트에 표시됩니다',
|
||||
'helpDialog.proTips.themeCycling': '테마 순환은 세션 간에도 선호 설정을 기억합니다',
|
||||
'helpDialog.proTips.leaderSequences': '2단계 단축키: 조합을 누른 뒤 두 번째 키를 누르세요 (Esc로 취소)',
|
||||
'header.actions.rightSidebarWithShortcut': '오른쪽 사이드바 ({shortcut})',
|
||||
'header.actions.toggleRightSidebarAria': '오른쪽 사이드바 토글',
|
||||
'header.actions.openAppMenu': 'OpenChamber 메뉴',
|
||||
@@ -2483,6 +2479,9 @@ export const dict: Record<I18nKey, string> = {
|
||||
'commandPalette.item.toggleSidebar': '토글 사이드바',
|
||||
'commandPalette.item.showContextUsage': '컨텍스트 사용량 표시',
|
||||
'commandPalette.item.toggleTerminal': '토글 터미널',
|
||||
'commandPalette.item.cycleTheme': '테마 순환',
|
||||
'commandPalette.item.showOpenCodeStatus': 'OpenCode 상태 표시',
|
||||
'commandPalette.item.toggleMemoryDebug': '메모리 디버그 패널 토글',
|
||||
'commandPalette.item.openSettings': '설정... 열기',
|
||||
'commandPalette.session.untitled': '제목 없는 세션',
|
||||
'openCodeStatusDialog.title': 'OpenCode 상태',
|
||||
|
||||
@@ -818,7 +818,6 @@ export const settingsDict = {
|
||||
'settings.openchamber.keyboardShortcuts.action.cycle_favorite_model_backward.label': 'Przełącz ulubiony model wstecz',
|
||||
'settings.openchamber.keyboardShortcuts.action.open_model_selector.label': 'Otwórz wybór modelu',
|
||||
'settings.openchamber.keyboardShortcuts.action.cycle_favorite_model_forward.label': 'Przełącz ulubiony model w przód',
|
||||
'settings.openchamber.keyboardShortcuts.action.cycle_services_tab.label': 'Przełącz zakładkę usług',
|
||||
'settings.openchamber.keyboardShortcuts.action.cycle_theme.label': 'Przełącz motyw',
|
||||
'settings.openchamber.keyboardShortcuts.action.cycle_agent.label': 'Przełącz agenta',
|
||||
'settings.openchamber.keyboardShortcuts.action.expand_input.label': 'Rozwiń pole wprowadzania',
|
||||
@@ -831,13 +830,11 @@ export const settingsDict = {
|
||||
'settings.openchamber.keyboardShortcuts.action.open_command_palette.label': 'Otwórz paletę poleceń',
|
||||
'settings.openchamber.keyboardShortcuts.action.open_go_to_line.label': 'Przejdź do linii (edytor plików)',
|
||||
'settings.openchamber.keyboardShortcuts.action.open_help.label': 'Otwórz skróty klawiszowe',
|
||||
'settings.openchamber.keyboardShortcuts.action.open_right_sidebar_files.label': 'Otwórz powierzchnię plików',
|
||||
'settings.openchamber.keyboardShortcuts.action.switch_session_tab.label': 'Przełącz kartę sesji',
|
||||
'settings.openchamber.keyboardShortcuts.action.switch_session_tab.suffix': ' + 1…9',
|
||||
'settings.openchamber.keyboardShortcuts.action.switch_context_surface.label': 'Przełącz powierzchnię panelu kontekstu',
|
||||
'settings.openchamber.keyboardShortcuts.action.switch_context_surface.suffix': ' + 1…0',
|
||||
'settings.openchamber.keyboardShortcuts.action.open_right_sidebar_git.label': 'Otwórz powierzchnię Git',
|
||||
'settings.openchamber.keyboardShortcuts.action.open_settings.label': 'Otwórz ustawienia',
|
||||
'settings.openchamber.keyboardShortcuts.action.toggle_context_plan.label': 'Przełącz panel kontekstu planu',
|
||||
'settings.openchamber.keyboardShortcuts.action.toggle_right_sidebar.label': 'Przełącz panel kontekstu',
|
||||
'settings.openchamber.keyboardShortcuts.action.toggle_services_menu.label': 'Przełącz menu usług',
|
||||
'settings.openchamber.keyboardShortcuts.action.add_selection_to_chat.label': 'Dodaj zaznaczenie do czatu',
|
||||
'settings.openchamber.keyboardShortcuts.action.toggle_sidebar.label': 'Przełącz pasek boczny',
|
||||
|
||||
@@ -1452,6 +1452,9 @@ export const dict: Record<I18nKey, string> = {
|
||||
'commandPalette.item.showSessionSwitcher': 'Pokaż przełącznik sesji',
|
||||
'commandPalette.item.toggleSidebar': 'Przełącz panel boczny',
|
||||
'commandPalette.item.toggleTerminal': 'Przełącz terminal',
|
||||
'commandPalette.item.cycleTheme': 'Przełącz motyw',
|
||||
'commandPalette.item.showOpenCodeStatus': 'Pokaż status OpenCode',
|
||||
'commandPalette.item.toggleMemoryDebug': 'Przełącz panel debugowania pamięci',
|
||||
'commandPalette.session.untitled': 'Nienazwana sesja',
|
||||
'commandPalette.title': 'Paleta poleceń',
|
||||
'contextPanel.actions.closePanel': 'Zamknij panel',
|
||||
@@ -2452,7 +2455,6 @@ export const dict: Record<I18nKey, string> = {
|
||||
'helpDialog.item.createNewSession': 'Utwórz nową sesję',
|
||||
'helpDialog.item.createNewWorktreeDraft': 'Utwórz nowy szkic drzewa pracy',
|
||||
'helpDialog.item.cycleAgent': 'Przełącz agenta (w polu czatu)',
|
||||
'helpDialog.item.cycleServicesTab': 'Przełącz kartę usług',
|
||||
'helpDialog.item.cycleTheme': 'Przełącz motyw (Jasny → Ciemny → Systemowy)',
|
||||
'helpDialog.item.cycleThinkingVariant': 'Przełącz wariant myślenia (skrót globalny)',
|
||||
'helpDialog.item.focusChatInput': 'Ustaw fokus na polu czatu',
|
||||
@@ -2461,13 +2463,10 @@ export const dict: Record<I18nKey, string> = {
|
||||
'helpDialog.item.newWindow': 'Nowe okno (tylko desktop)',
|
||||
'helpDialog.item.openCommandPalette': 'Otwórz paletę poleceń',
|
||||
'helpDialog.item.openModelSelector': 'Otwórz selektor modeli',
|
||||
'helpDialog.item.openRightSidebarFilesTab': 'Otwórz powierzchnię plików',
|
||||
'helpDialog.item.openRightSidebarGitTab': 'Otwórz powierzchnię Git',
|
||||
'helpDialog.item.openSettings': 'Otwórz ustawienia',
|
||||
'helpDialog.item.showKeyboardShortcuts': 'Pokaż skróty klawiaturowe (to okno)',
|
||||
'helpDialog.item.switchSessionTab': 'Przełącz kartę sesji',
|
||||
'helpDialog.item.switchContextSurface': 'Przełącz powierzchnię panelu kontekstu (klawisz liczbowy)',
|
||||
'helpDialog.item.togglePlanContextPanel': 'Przełącz panel kontekstu planu',
|
||||
'helpDialog.item.toggleRightSidebar': 'Przełącz panel kontekstu',
|
||||
'helpDialog.item.toggleServicesMenu': 'Przełącz menu usług',
|
||||
'helpDialog.item.toggleSessionSidebar': 'Przełącz panel sesji',
|
||||
'helpDialog.item.addSelectionToChat': 'Dodaj zaznaczenie do czatu',
|
||||
@@ -2476,7 +2475,7 @@ export const dict: Record<I18nKey, string> = {
|
||||
'helpDialog.keyCombiner.or': 'lub',
|
||||
'helpDialog.proTips.commandPalette': 'Użyj Palety poleceń ({shortcut}), aby szybko uzyskać dostęp do wszystkich akcji',
|
||||
'helpDialog.proTips.recentSessions': '5 ostatnich sesji pojawia się w Palecie poleceń',
|
||||
'helpDialog.proTips.themeCycling': 'Przełączanie motywów zapamiętuje twoje preferencje między sesjami',
|
||||
'helpDialog.proTips.leaderSequences': 'Skróty dwustopniowe: naciśnij kombinację, potem drugi klawisz — Esc anuluje',
|
||||
'helpDialog.proTips.title': 'Wskazówki:',
|
||||
'helpDialog.section.interface': 'Interfejs',
|
||||
'helpDialog.section.navigationCommands': 'Nawigacja i polecenia',
|
||||
|
||||
@@ -1110,9 +1110,8 @@ export const settingsDict = {
|
||||
"settings.openchamber.keyboardShortcuts.action.toggle_terminal_expanded.label": "Expandir ou recolher terminal",
|
||||
"settings.openchamber.keyboardShortcuts.action.add_selection_to_chat.label": "Adicionar seleção ao chat",
|
||||
"settings.openchamber.keyboardShortcuts.action.toggle_sidebar.label": "Mostrar ou ocultar barra lateral",
|
||||
"settings.openchamber.keyboardShortcuts.action.toggle_right_sidebar.label": 'Alternar painel de contexto',
|
||||
"settings.openchamber.keyboardShortcuts.action.open_right_sidebar_git.label": 'Abrir superfície do Git',
|
||||
"settings.openchamber.keyboardShortcuts.action.open_right_sidebar_files.label": 'Abrir superfície de arquivos',
|
||||
"settings.openchamber.keyboardShortcuts.action.switch_session_tab.label": "Alternar aba de sessão",
|
||||
"settings.openchamber.keyboardShortcuts.action.switch_session_tab.suffix": " + 1…9",
|
||||
"settings.openchamber.keyboardShortcuts.action.switch_context_surface.label": "Alternar superfície do painel de contexto",
|
||||
"settings.openchamber.keyboardShortcuts.action.switch_context_surface.suffix": " + 1…0",
|
||||
"settings.openchamber.keyboardShortcuts.action.new_chat.label": "Nova sessão",
|
||||
@@ -1120,9 +1119,7 @@ export const settingsDict = {
|
||||
"settings.openchamber.keyboardShortcuts.action.new_chat_worktree.label": "Novo rascunho de worktree",
|
||||
"settings.openchamber.keyboardShortcuts.action.new_mini_chat.label": "Nova janela Mini Chat",
|
||||
"settings.openchamber.keyboardShortcuts.action.open_help.label": "Abrir atalhos de teclado",
|
||||
"settings.openchamber.keyboardShortcuts.action.toggle_context_plan.label": "Alternar painel de plano de contexto",
|
||||
"settings.openchamber.keyboardShortcuts.action.toggle_services_menu.label": "Mostrar ou ocultar menu de serviços",
|
||||
"settings.openchamber.keyboardShortcuts.action.cycle_services_tab.label": "Alternar aba de serviços",
|
||||
"settings.openchamber.keyboardShortcuts.action.cycle_theme.label": "Alternar tema",
|
||||
"settings.openchamber.keyboardShortcuts.action.cycle_agent.label": "Alternar agente",
|
||||
"settings.openchamber.keyboardShortcuts.action.cycle_favorite_model_forward.label": "Próximo modelo favorito",
|
||||
|
||||
@@ -1836,22 +1836,18 @@ export const dict: Record<I18nKey, string> = {
|
||||
"helpDialog.item.focusChatInput": "Focar entrada do chat",
|
||||
"helpDialog.item.togglePromptNavigator": "Mostrar ou ocultar navegador de prompts",
|
||||
"helpDialog.item.abortActiveRun": "Interromper execução ativa (duplo clique)",
|
||||
"helpDialog.item.toggleRightSidebar": 'Alternar painel de contexto',
|
||||
"helpDialog.item.openRightSidebarGitTab": 'Abrir superfície do Git',
|
||||
"helpDialog.item.openRightSidebarFilesTab": 'Abrir superfície de arquivos',
|
||||
"helpDialog.item.toggleTerminalDock": "Mostrar ou ocultar dock de terminal",
|
||||
"helpDialog.item.toggleTerminalExpanded": "Expandir ou recolher o terminal",
|
||||
"helpDialog.item.togglePlanContextPanel": "Alternar painel de contexto do plano",
|
||||
"helpDialog.item.cycleTheme": "Alternar tema (Claro → Escuro → Sistema)",
|
||||
"helpDialog.item.switchSessionTab": "Alternar aba de sessão",
|
||||
"helpDialog.item.switchContextSurface": "Alternar superfície do painel de contexto (tecla numérica)",
|
||||
"helpDialog.item.toggleServicesMenu": "Mostrar ou ocultar menu de serviços",
|
||||
"helpDialog.item.cycleServicesTab": "Alternar aba de serviços",
|
||||
"helpDialog.item.openSettings": "Abrir configurações",
|
||||
"helpDialog.keyCombiner.or": "ou",
|
||||
"helpDialog.proTips.title": "Dicas:",
|
||||
"helpDialog.proTips.commandPalette": "Use a paleta de comandos ({shortcut}) para acessar rapidamente todas as ações",
|
||||
"helpDialog.proTips.recentSessions": "As cinco sessões mais recentes aparecem na paleta de comandos",
|
||||
"helpDialog.proTips.themeCycling": "A alternância de tema lembra sua preferência entre sessões",
|
||||
"helpDialog.proTips.leaderSequences": "Atalhos em duas etapas: pressione a combinação e depois a segunda tecla — Esc cancela",
|
||||
"header.actions.rightSidebarWithShortcut": "Barra lateral direita ({shortcut})",
|
||||
"header.actions.toggleRightSidebarAria": "Mostrar ou ocultar barra lateral direita",
|
||||
"header.actions.openAppMenu": "Menu do OpenChamber",
|
||||
@@ -2449,6 +2445,9 @@ export const dict: Record<I18nKey, string> = {
|
||||
"commandPalette.item.toggleSidebar": "Mostrar ou ocultar barra lateral",
|
||||
"commandPalette.item.showContextUsage": "Mostrar uso do contexto",
|
||||
"commandPalette.item.toggleTerminal": "Mostrar ou ocultar terminal",
|
||||
"commandPalette.item.cycleTheme": "Alternar tema",
|
||||
"commandPalette.item.showOpenCodeStatus": "Mostrar status do OpenCode",
|
||||
"commandPalette.item.toggleMemoryDebug": "Alternar painel de depuração de memória",
|
||||
"commandPalette.item.openSettings": "Abrir configurações...",
|
||||
"commandPalette.session.untitled": "Sessão sem título",
|
||||
"openCodeStatusDialog.title": "Status do OpenCode",
|
||||
|
||||
@@ -1110,9 +1110,8 @@ export const settingsDict = {
|
||||
"settings.openchamber.keyboardShortcuts.action.toggle_terminal_expanded.label": "Розгорнути або згорнути термінал",
|
||||
"settings.openchamber.keyboardShortcuts.action.add_selection_to_chat.label": "Додати виділення в чат",
|
||||
"settings.openchamber.keyboardShortcuts.action.toggle_sidebar.label": "Перемкнути бічну панель",
|
||||
"settings.openchamber.keyboardShortcuts.action.toggle_right_sidebar.label": 'Перемкнути контекстну панель',
|
||||
"settings.openchamber.keyboardShortcuts.action.open_right_sidebar_git.label": 'Відкрити поверхню Git',
|
||||
"settings.openchamber.keyboardShortcuts.action.open_right_sidebar_files.label": 'Відкрити поверхню файлів',
|
||||
"settings.openchamber.keyboardShortcuts.action.switch_session_tab.label": "Перемкнути вкладку сесії",
|
||||
"settings.openchamber.keyboardShortcuts.action.switch_session_tab.suffix": " + 1…9",
|
||||
"settings.openchamber.keyboardShortcuts.action.switch_context_surface.label": "Перемкнути поверхню панелі контексту",
|
||||
"settings.openchamber.keyboardShortcuts.action.switch_context_surface.suffix": " + 1…0",
|
||||
"settings.openchamber.keyboardShortcuts.action.new_chat.label": "Нова сесія",
|
||||
@@ -1120,9 +1119,7 @@ export const settingsDict = {
|
||||
"settings.openchamber.keyboardShortcuts.action.new_chat_worktree.label": "Нова чернетка worktree",
|
||||
"settings.openchamber.keyboardShortcuts.action.new_mini_chat.label": "Нове вікно Mini Chat",
|
||||
"settings.openchamber.keyboardShortcuts.action.open_help.label": "Відкрити комбінації клавіш",
|
||||
"settings.openchamber.keyboardShortcuts.action.toggle_context_plan.label": "Перемкнути контекстну панель плану",
|
||||
"settings.openchamber.keyboardShortcuts.action.toggle_services_menu.label": "Перемкнути меню сервісів",
|
||||
"settings.openchamber.keyboardShortcuts.action.cycle_services_tab.label": "Перемкнути вкладку сервісів",
|
||||
"settings.openchamber.keyboardShortcuts.action.cycle_theme.label": "Перемкнути тему",
|
||||
"settings.openchamber.keyboardShortcuts.action.cycle_agent.label": "Перемкнути агента",
|
||||
"settings.openchamber.keyboardShortcuts.action.cycle_favorite_model_forward.label": "Перемкнути улюблену модель вперед",
|
||||
|
||||
@@ -1836,22 +1836,18 @@ export const dict: Record<I18nKey, string> = {
|
||||
"helpDialog.item.focusChatInput": "Фокус на полі вводу чату",
|
||||
"helpDialog.item.togglePromptNavigator": "Показати або приховати навігатор промптів",
|
||||
"helpDialog.item.abortActiveRun": "Перервати активний запуск (подвійне натискання)",
|
||||
"helpDialog.item.toggleRightSidebar": 'Перемкнути контекстну панель',
|
||||
"helpDialog.item.openRightSidebarGitTab": 'Відкрити поверхню Git',
|
||||
"helpDialog.item.openRightSidebarFilesTab": 'Відкрити поверхню файлів',
|
||||
"helpDialog.item.toggleTerminalDock": "Перемкнути панель терміналу",
|
||||
"helpDialog.item.toggleTerminalExpanded": "Розгорнути або згорнути термінал",
|
||||
"helpDialog.item.togglePlanContextPanel": "Перемкнути панель контексту плану",
|
||||
"helpDialog.item.cycleTheme": "Перемкнути тему (Світла → Темна → Системна)",
|
||||
"helpDialog.item.switchSessionTab": "Перемкнути вкладку сесії",
|
||||
"helpDialog.item.switchContextSurface": "Перемкнути поверхню панелі контексту (цифрова клавіша)",
|
||||
"helpDialog.item.toggleServicesMenu": "Перемкнути меню сервісів",
|
||||
"helpDialog.item.cycleServicesTab": "Перемкнути вкладку сервісів",
|
||||
"helpDialog.item.openSettings": "Відкрити налаштування",
|
||||
"helpDialog.keyCombiner.or": "або",
|
||||
"helpDialog.proTips.title": "Поради:",
|
||||
"helpDialog.proTips.commandPalette": "Використовуйте палітру команд ({shortcut}), щоб швидко перейти до будь-якої дії",
|
||||
"helpDialog.proTips.recentSessions": "5 останніх сесій відображаються на панелі команд",
|
||||
"helpDialog.proTips.themeCycling": "Перемикання теми запам’ятовує ваші переваги протягом сесій",
|
||||
"helpDialog.proTips.leaderSequences": "Двокрокові шорткати: натисни комбінацію, потім другу клавішу — Esc скасовує",
|
||||
"header.actions.rightSidebarWithShortcut": "Права бічна панель ({shortcut})",
|
||||
"header.actions.toggleRightSidebarAria": "Перемкнути праву бічну панель",
|
||||
"header.actions.openAppMenu": "Меню OpenChamber",
|
||||
@@ -2449,6 +2445,9 @@ export const dict: Record<I18nKey, string> = {
|
||||
"commandPalette.item.toggleSidebar": "Перемкнути бічну панель",
|
||||
"commandPalette.item.showContextUsage": "Показати використання контексту",
|
||||
"commandPalette.item.toggleTerminal": "Перемкнути термінал",
|
||||
"commandPalette.item.cycleTheme": "Перемкнути тему",
|
||||
"commandPalette.item.showOpenCodeStatus": "Показати статус OpenCode",
|
||||
"commandPalette.item.toggleMemoryDebug": "Показати/сховати панель memory debug",
|
||||
"commandPalette.item.openSettings": "Відкрити налаштування...",
|
||||
"commandPalette.session.untitled": "Сесія без назви",
|
||||
"openCodeStatusDialog.title": "Статус OpenCode",
|
||||
|
||||
@@ -1110,9 +1110,8 @@ export const settingsDict = {
|
||||
'settings.openchamber.keyboardShortcuts.action.toggle_terminal_expanded.label': '切换终端展开',
|
||||
'settings.openchamber.keyboardShortcuts.action.add_selection_to_chat.label': '将选中内容添加到聊天',
|
||||
'settings.openchamber.keyboardShortcuts.action.toggle_sidebar.label': '切换侧边栏',
|
||||
'settings.openchamber.keyboardShortcuts.action.toggle_right_sidebar.label': '切换上下文面板',
|
||||
'settings.openchamber.keyboardShortcuts.action.open_right_sidebar_git.label': '打开 Git 界面',
|
||||
'settings.openchamber.keyboardShortcuts.action.open_right_sidebar_files.label': '打开文件界面',
|
||||
'settings.openchamber.keyboardShortcuts.action.switch_session_tab.label': '切换会话标签页',
|
||||
'settings.openchamber.keyboardShortcuts.action.switch_session_tab.suffix': ' + 1…9',
|
||||
'settings.openchamber.keyboardShortcuts.action.switch_context_surface.label': '切换上下文面板界面',
|
||||
'settings.openchamber.keyboardShortcuts.action.switch_context_surface.suffix': ' + 1…0',
|
||||
'settings.openchamber.keyboardShortcuts.action.new_chat.label': '新建会话',
|
||||
@@ -1120,9 +1119,7 @@ export const settingsDict = {
|
||||
'settings.openchamber.keyboardShortcuts.action.new_chat_worktree.label': '新建工作树草稿',
|
||||
'settings.openchamber.keyboardShortcuts.action.new_mini_chat.label': '新建 Mini Chat 窗口',
|
||||
'settings.openchamber.keyboardShortcuts.action.open_help.label': '打开键盘快捷键',
|
||||
'settings.openchamber.keyboardShortcuts.action.toggle_context_plan.label': '切换上下文面板中的计划',
|
||||
'settings.openchamber.keyboardShortcuts.action.toggle_services_menu.label': '切换服务菜单',
|
||||
'settings.openchamber.keyboardShortcuts.action.cycle_services_tab.label': '轮换服务菜单标签',
|
||||
'settings.openchamber.keyboardShortcuts.action.cycle_theme.label': '轮换主题',
|
||||
'settings.openchamber.keyboardShortcuts.action.cycle_agent.label': '轮换智能体',
|
||||
'settings.openchamber.keyboardShortcuts.action.cycle_favorite_model_forward.label': '向前轮换收藏模型',
|
||||
|
||||
@@ -1824,22 +1824,18 @@ export const dict: Record<I18nKey, string> = {
|
||||
'helpDialog.item.focusChatInput': '聚焦聊天输入框',
|
||||
'helpDialog.item.togglePromptNavigator': '显示或隐藏提示词导航',
|
||||
'helpDialog.item.abortActiveRun': '中止当前运行(双击)',
|
||||
'helpDialog.item.toggleRightSidebar': '切换上下文面板',
|
||||
'helpDialog.item.openRightSidebarGitTab': '打开 Git 界面',
|
||||
'helpDialog.item.openRightSidebarFilesTab': '打开文件界面',
|
||||
'helpDialog.item.toggleTerminalDock': '切换终端停靠栏',
|
||||
'helpDialog.item.toggleTerminalExpanded': '切换终端展开状态',
|
||||
'helpDialog.item.togglePlanContextPanel': '切换计划上下文面板',
|
||||
'helpDialog.item.cycleTheme': '循环切换主题(浅色 → 深色 → 跟随系统)',
|
||||
'helpDialog.item.switchSessionTab': '切换会话标签页',
|
||||
'helpDialog.item.switchContextSurface': '切换上下文面板界面(数字键)',
|
||||
'helpDialog.item.toggleServicesMenu': '切换服务菜单',
|
||||
'helpDialog.item.cycleServicesTab': '循环服务标签',
|
||||
'helpDialog.item.openSettings': '打开设置',
|
||||
'helpDialog.keyCombiner.or': '或',
|
||||
'helpDialog.proTips.title': '使用提示:',
|
||||
'helpDialog.proTips.commandPalette': '使用命令面板({shortcut})可快速访问所有操作',
|
||||
'helpDialog.proTips.recentSessions': '最近 5 个会话会显示在命令面板中',
|
||||
'helpDialog.proTips.themeCycling': '主题循环会记住你在各会话中的偏好',
|
||||
'helpDialog.proTips.leaderSequences': '两段式快捷键:先按组合键,再按第二个键(Esc 取消)',
|
||||
'header.actions.rightSidebarWithShortcut': '右侧边栏({shortcut})',
|
||||
'header.actions.toggleRightSidebarAria': '切换右侧边栏',
|
||||
'header.actions.openAppMenu': 'OpenChamber 菜单',
|
||||
@@ -2449,6 +2445,9 @@ export const dict: Record<I18nKey, string> = {
|
||||
'commandPalette.item.toggleSidebar': '切换侧边栏',
|
||||
'commandPalette.item.showContextUsage': '显示上下文用量',
|
||||
'commandPalette.item.toggleTerminal': '切换终端',
|
||||
'commandPalette.item.cycleTheme': '轮换主题',
|
||||
'commandPalette.item.showOpenCodeStatus': '显示 OpenCode 状态',
|
||||
'commandPalette.item.toggleMemoryDebug': '切换内存调试面板',
|
||||
'commandPalette.item.openSettings': '打开设置...',
|
||||
'commandPalette.session.untitled': '未命名会话',
|
||||
'openCodeStatusDialog.title': 'OpenCode 状态',
|
||||
|
||||
@@ -1017,9 +1017,8 @@ export const settingsDict = {
|
||||
'settings.openchamber.keyboardShortcuts.action.toggle_terminal_expanded.label': '切換終端機展開',
|
||||
'settings.openchamber.keyboardShortcuts.action.add_selection_to_chat.label': '將選取內容加入聊天',
|
||||
'settings.openchamber.keyboardShortcuts.action.toggle_sidebar.label': '切換側邊欄',
|
||||
'settings.openchamber.keyboardShortcuts.action.toggle_right_sidebar.label': '切換上下文面板',
|
||||
'settings.openchamber.keyboardShortcuts.action.open_right_sidebar_git.label': '開啟 Git 介面',
|
||||
'settings.openchamber.keyboardShortcuts.action.open_right_sidebar_files.label': '開啟檔案介面',
|
||||
'settings.openchamber.keyboardShortcuts.action.switch_session_tab.label': '切換工作階段分頁',
|
||||
'settings.openchamber.keyboardShortcuts.action.switch_session_tab.suffix': ' + 1…9',
|
||||
'settings.openchamber.keyboardShortcuts.action.switch_context_surface.label': '切換上下文面板介面',
|
||||
'settings.openchamber.keyboardShortcuts.action.switch_context_surface.suffix': ' + 1…0',
|
||||
'settings.openchamber.keyboardShortcuts.action.new_chat.label': '新建工作階段',
|
||||
@@ -1027,9 +1026,7 @@ export const settingsDict = {
|
||||
'settings.openchamber.keyboardShortcuts.action.new_chat_worktree.label': '新增 worktree 草稿',
|
||||
'settings.openchamber.keyboardShortcuts.action.new_mini_chat.label': '新建 Mini Chat 視窗',
|
||||
'settings.openchamber.keyboardShortcuts.action.open_help.label': '開啟鍵盤快速鍵',
|
||||
'settings.openchamber.keyboardShortcuts.action.toggle_context_plan.label': '切換上下文面板中的計畫',
|
||||
'settings.openchamber.keyboardShortcuts.action.toggle_services_menu.label': '切換服務選單',
|
||||
'settings.openchamber.keyboardShortcuts.action.cycle_services_tab.label': '輪換服務選單分頁',
|
||||
'settings.openchamber.keyboardShortcuts.action.cycle_theme.label': '輪換主題',
|
||||
'settings.openchamber.keyboardShortcuts.action.cycle_agent.label': '輪換 agent',
|
||||
'settings.openchamber.keyboardShortcuts.action.cycle_favorite_model_forward.label': '向前輪換收藏模型',
|
||||
|
||||
@@ -1828,22 +1828,18 @@ export const dict: Record<I18nKey, string> = {
|
||||
'helpDialog.item.focusChatInput': '聚焦聊天輸入框',
|
||||
'helpDialog.item.togglePromptNavigator': '顯示或隱藏提示詞導覽',
|
||||
'helpDialog.item.abortActiveRun': '中止目前執行(連按兩下)',
|
||||
'helpDialog.item.toggleRightSidebar': '切換上下文面板',
|
||||
'helpDialog.item.openRightSidebarGitTab': '開啟 Git 介面',
|
||||
'helpDialog.item.openRightSidebarFilesTab': '開啟檔案介面',
|
||||
'helpDialog.item.toggleTerminalDock': '切換終端機停靠欄',
|
||||
'helpDialog.item.toggleTerminalExpanded': '切換終端機展開狀態',
|
||||
'helpDialog.item.togglePlanContextPanel': '切換計畫上下文面板',
|
||||
'helpDialog.item.cycleTheme': '循環切換主題(淺色 → 深色 → 跟隨系統)',
|
||||
'helpDialog.item.switchSessionTab': '切換工作階段分頁',
|
||||
'helpDialog.item.switchContextSurface': '切換上下文面板介面(數字鍵)',
|
||||
'helpDialog.item.toggleServicesMenu': '切換服務選單',
|
||||
'helpDialog.item.cycleServicesTab': '循環服務標籤',
|
||||
'helpDialog.item.openSettings': '開啟設定',
|
||||
'helpDialog.keyCombiner.or': '或',
|
||||
'helpDialog.proTips.title': '使用提示:',
|
||||
'helpDialog.proTips.commandPalette': '使用命令面板({shortcut})可快速存取所有操作',
|
||||
'helpDialog.proTips.recentSessions': '最近 5 個會话會顯示在命令面板中',
|
||||
'helpDialog.proTips.themeCycling': '主題循環會記住你在各會話中的偏好',
|
||||
'helpDialog.proTips.leaderSequences': '兩段式快捷鍵:先按組合鍵,再按第二個鍵(Esc 取消)',
|
||||
'header.actions.rightSidebarWithShortcut': '右側邊欄({shortcut})',
|
||||
'header.actions.toggleRightSidebarAria': '切換右側邊欄',
|
||||
'header.actions.openAppMenu': 'OpenChamber 選單',
|
||||
@@ -2453,6 +2449,9 @@ export const dict: Record<I18nKey, string> = {
|
||||
'commandPalette.item.toggleSidebar': '切換側邊欄',
|
||||
'commandPalette.item.showContextUsage': '顯示上下文用量',
|
||||
'commandPalette.item.toggleTerminal': '切換終端機',
|
||||
'commandPalette.item.cycleTheme': '輪換主題',
|
||||
'commandPalette.item.showOpenCodeStatus': '顯示 OpenCode 狀態',
|
||||
'commandPalette.item.toggleMemoryDebug': '切換記憶體偵錯面板',
|
||||
'commandPalette.item.openSettings': '開啟設定...',
|
||||
'commandPalette.session.untitled': '未命名會話',
|
||||
'openCodeStatusDialog.title': 'OpenCode 狀態',
|
||||
|
||||
@@ -9,6 +9,23 @@ import { useSessionUIStore } from '@/sync/session-ui-store';
|
||||
* count as neighbours — the same rule the strip uses for rendering. The
|
||||
* session itself is never touched.
|
||||
*/
|
||||
/**
|
||||
* Activate the nth (0-based) header session tab, counting only tabs whose
|
||||
* session is present in the loaded session list — the same rule the strip
|
||||
* uses for rendering, so the digit matches what the user sees.
|
||||
*/
|
||||
export const activateSessionTabByIndex = (index: number): boolean => {
|
||||
const { tabIds } = useSessionTabsStore.getState();
|
||||
const sessionsById = new Map(
|
||||
useGlobalSessionsStore.getState().activeSessions.map((session) => [session.id, session] as const),
|
||||
);
|
||||
const renderable = tabIds.filter((id) => sessionsById.has(id));
|
||||
const session = renderable[index] ? sessionsById.get(renderable[index]) : null;
|
||||
if (!session) return false;
|
||||
useSessionUIStore.getState().setCurrentSession(session.id, resolveGlobalSessionDirectory(session));
|
||||
return true;
|
||||
};
|
||||
|
||||
export const closeSessionTabAndActivateNeighbour = (sessionId: string): void => {
|
||||
const { tabIds, closeTab } = useSessionTabsStore.getState();
|
||||
if (!tabIds.includes(sessionId)) return;
|
||||
|
||||
@@ -25,9 +25,9 @@ Component interaction keys that are not application commands, such as list navig
|
||||
|
||||
# Binding rules
|
||||
|
||||
Bindings remain persisted as `Record<string, string>`. Each binding has one chord or at most two space-separated chords, such as `mod+s p`. `mod` is the platform-neutral primary modifier (Command on macOS, Control elsewhere), while `alt` is the platform-neutral alternate modifier (Option on macOS, Alt elsewhere); `command`, `cmd`, `meta`, and `option` are accepted input aliases but normalize to those canonical tokens. `normalizeCombo`, `parseShortcut`, `formatShortcutForDisplay`, and `getShortcutConflict` provide the shared parsing and validation behavior. Display formatting uses macOS keyboard symbols (`⌘`, `⌥`, `⌃`, `⇧`) on macOS and named modifiers (`Ctrl`, `Alt`, `Shift`) elsewhere, including tooltip and accessible text consumers. A single chord conflicts with a sequence sharing its first chord; sibling sequences are valid.
|
||||
Bindings remain persisted as `Record<string, string>`. Each binding has one chord or at most two space-separated chords, such as `mod+k p`. `mod` is the platform-neutral primary modifier (Command on macOS, Control elsewhere), while `alt` is the platform-neutral alternate modifier (Option on macOS, Alt elsewhere); `command`, `cmd`, `meta`, and `option` are accepted input aliases but normalize to those canonical tokens. `normalizeCombo`, `parseShortcut`, `formatShortcutForDisplay`, and `getShortcutConflict` provide the shared parsing and validation behavior. Display formatting uses macOS keyboard symbols (`⌘`, `⌥`, `⌃`, `⇧`) on macOS and named modifiers (`Ctrl`, `Alt`, `Shift`) elsewhere, including tooltip and accessible text consumers. A single chord conflicts with a sequence sharing its first chord; sibling sequences are valid.
|
||||
|
||||
Contextual internal commands may deliberately share a sequence leader. The single-chord handler gets the first chance to handle the event; returning `false` lets the dispatcher start the sequence. The active file editor therefore owns `mod+s` for saving, while a mounted but unfocused editor yields `mod+s p`, `mod+s g`, and `mod+s l` to the draft target pickers and session list.
|
||||
The default layout follows three modes: single chords for everyday actions, the `mod+k` leader for open/go actions (`mod+k p`, `mod+k g`, `mod+k l`, `mod+k t`, `mod+k n`, `mod+k i`, `mod+k h`), and held digit prefixes — held `mod` + digit switches header session tabs, held `mod+alt` + digit switches context panel surfaces. Every schema action ships with a default binding; palette-only commands (context surfaces, OpenCode status, memory debug) live outside the schema and the palette invokes their owning modules directly. Single-chord handlers still get the first chance at a leader's chord; returning `false` lets the dispatcher arm the sequence.
|
||||
|
||||
The internal `switch_tab_*` bindings remain available to mobile handlers. Desktop numeric context-surface switching is resolved by the configurable `switch_context_surface` prefix before normal dispatcher matching and falls through on mobile.
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { describe, expect, test } from 'bun:test';
|
||||
|
||||
import {
|
||||
eventMatchesShortcut,
|
||||
eventMatchesShortcutPrefix,
|
||||
formatShortcutForDisplay,
|
||||
getEffectiveShortcutPrefix,
|
||||
@@ -9,12 +10,13 @@ import {
|
||||
isShortcutPrefixHeld,
|
||||
normalizeCombo,
|
||||
parseShortcut,
|
||||
resolveShortcutEventDigit,
|
||||
UNASSIGNED_SHORTCUT,
|
||||
} from './index';
|
||||
|
||||
describe('getEffectiveShortcutPrefix', () => {
|
||||
test('falls back to the action default (bare mod) when unset', () => {
|
||||
expect(getEffectiveShortcutPrefix('switch_context_surface', {})).toBe('mod');
|
||||
test('falls back to the action default (bare mod+alt) when unset', () => {
|
||||
expect(getEffectiveShortcutPrefix('switch_context_surface', {})).toBe('mod+alt');
|
||||
});
|
||||
|
||||
test('honors modifier + key overrides', () => {
|
||||
@@ -126,3 +128,31 @@ describe('platform shortcut labels', () => {
|
||||
expect(formatShortcutForDisplay('alt', 'Unassigned', 'other')).toBe('Alt');
|
||||
});
|
||||
});
|
||||
|
||||
describe('layout-independent key matching', () => {
|
||||
const event = (overrides: Partial<KeyboardEvent>): KeyboardEvent =>
|
||||
// SAFETY: the matcher only reads the modifier flags, key, and code
|
||||
// provided here; a full KeyboardEvent is not constructible in bun tests.
|
||||
({ altKey: false, ctrlKey: false, metaKey: false, shiftKey: false, key: '', code: '', ...overrides }) as KeyboardEvent;
|
||||
|
||||
test('a non-Latin layout letter matches through the physical key code', () => {
|
||||
expect(eventMatchesShortcut(event({ ctrlKey: true, key: 'л', code: 'KeyK' }), 'mod+k')).toBe(true);
|
||||
expect(eventMatchesShortcut(event({ key: 'з', code: 'KeyP' }), 'p')).toBe(true);
|
||||
});
|
||||
|
||||
test('macOS Option symbol substitution matches through the digit code', () => {
|
||||
expect(eventMatchesShortcut(event({ ctrlKey: true, altKey: true, key: '¡', code: 'Digit1' }), 'mod+alt+1')).toBe(true);
|
||||
});
|
||||
|
||||
test('Latin layouts that move keys keep their key-based meaning', () => {
|
||||
// Dvorak: physical KeyT produces "y"; the binding follows the character.
|
||||
expect(eventMatchesShortcut(event({ ctrlKey: true, key: 'y', code: 'KeyT' }), 'mod+y')).toBe(true);
|
||||
expect(eventMatchesShortcut(event({ ctrlKey: true, key: 'y', code: 'KeyT' }), 'mod+t')).toBe(false);
|
||||
});
|
||||
|
||||
test('resolveShortcutEventDigit reads the digit from the code under Option', () => {
|
||||
expect(resolveShortcutEventDigit({ key: '¡', code: 'Digit1' })).toBe('1');
|
||||
expect(resolveShortcutEventDigit({ key: '5', code: 'Digit5' })).toBe('5');
|
||||
expect(resolveShortcutEventDigit({ key: 'a', code: 'KeyA' })).toBe(null);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -247,6 +247,50 @@ export function isRiskyBrowserShortcut(combo: ShortcutCombo): boolean {
|
||||
});
|
||||
}
|
||||
|
||||
const CODE_KEY_MAP = new Map<string, string>([
|
||||
['Comma', ','],
|
||||
['Period', '.'],
|
||||
['Slash', '/'],
|
||||
['Backquote', '`'],
|
||||
['BracketLeft', '['],
|
||||
['BracketRight', ']'],
|
||||
['Semicolon', ';'],
|
||||
['Quote', "'"],
|
||||
['Minus', '-'],
|
||||
['Equal', '='],
|
||||
]);
|
||||
|
||||
function keyFromEventCode(code: string): string | null {
|
||||
if (code.startsWith('Key') && code.length === 4) return code.slice(3).toLowerCase();
|
||||
if (code.startsWith('Digit') && code.length === 6) return code.slice(5);
|
||||
return CODE_KEY_MAP.get(code) ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* The character a physical key press should match against bindings. `key`
|
||||
* carries the layout-produced character: Option on macOS substitutes symbols
|
||||
* ("¡" for ⌥1) and non-Latin layouts substitute their own alphabet ("л" for
|
||||
* K). Both keep the physical key in `code`, so those two cases fall back to
|
||||
* it; Latin layouts that MOVE keys (Dvorak, AZERTY) keep their `key`-based
|
||||
* meaning untouched.
|
||||
*/
|
||||
export function resolveShortcutEventKey(
|
||||
event: Pick<KeyboardEvent, 'key' | 'code' | 'altKey'>,
|
||||
): string {
|
||||
const raw = event.key;
|
||||
if (event.altKey) return keyFromEventCode(event.code) ?? raw;
|
||||
if (raw.length === 1 && raw.charCodeAt(0) > 127) return keyFromEventCode(event.code) ?? raw;
|
||||
return raw;
|
||||
}
|
||||
|
||||
/** The digit a press addresses, layout- and Option-proof via `code`. */
|
||||
export function resolveShortcutEventDigit(
|
||||
event: Pick<KeyboardEvent, 'key' | 'code'>,
|
||||
): string | null {
|
||||
if (event.code.startsWith('Digit') && event.code.length === 6) return event.code.slice(5);
|
||||
return event.key.length === 1 && event.key >= '0' && event.key <= '9' ? event.key : null;
|
||||
}
|
||||
|
||||
export function eventMatchesShortcut(
|
||||
event: KeyboardEvent | React.KeyboardEvent,
|
||||
combo: ShortcutCombo,
|
||||
@@ -280,16 +324,7 @@ export function eventMatchesShortcut(
|
||||
if (event.ctrlKey && !ctrlUsedAsMod) return false;
|
||||
}
|
||||
|
||||
let eventKeyRaw = event.key;
|
||||
if (event.altKey) {
|
||||
if (event.code.startsWith('Key') && event.code.length === 4) {
|
||||
eventKeyRaw = event.code.slice(3).toLowerCase();
|
||||
} else if (event.code.startsWith('Digit') && event.code.length === 6) {
|
||||
eventKeyRaw = event.code.slice(5);
|
||||
}
|
||||
}
|
||||
|
||||
return keyToShortcutToken(eventKeyRaw) === keyToShortcutToken(chord.key);
|
||||
return keyToShortcutToken(resolveShortcutEventKey(event)) === keyToShortcutToken(chord.key);
|
||||
}
|
||||
|
||||
export function isShortcutPrefixHeld(prefixCombo: ShortcutCombo, heldKeys: ReadonlySet<string>): boolean {
|
||||
|
||||
@@ -5,7 +5,6 @@ type ShortcutCategory = 'session' | 'models' | 'panels' | 'navigation' | 'applic
|
||||
type ShortcutConfig = {
|
||||
id: string;
|
||||
defaultBinding: ShortcutCombo;
|
||||
allowsSequenceFallback?: true;
|
||||
/** The binding is a bare-modifier chord prefix (completed by another key);
|
||||
conflict resolution compares its prefix rather than a full combo. */
|
||||
prefixStyle?: true;
|
||||
@@ -17,12 +16,18 @@ type ShortcutConfig = {
|
||||
}
|
||||
);
|
||||
|
||||
// Default layout, unified around three modes:
|
||||
// - Single chords for everyday actions.
|
||||
// - The mod+k leader for "open/go" actions, second key mnemonic.
|
||||
// - Held mod + digit switches header session tabs; held mod+alt + digit
|
||||
// switches context panel surfaces (mod+shift+digit is reserved by macOS
|
||||
// screenshots).
|
||||
// Everything else lives only in the command palette, outside this schema.
|
||||
const SHORTCUT_GROUPS = {
|
||||
session: [
|
||||
{
|
||||
id: 'add_selection_to_chat',
|
||||
defaultBinding: 'mod+l',
|
||||
allowsSequenceFallback: true,
|
||||
customizable: true,
|
||||
settingsLabelKey:
|
||||
'settings.openchamber.keyboardShortcuts.action.add_selection_to_chat.label',
|
||||
@@ -35,7 +40,7 @@ const SHORTCUT_GROUPS = {
|
||||
},
|
||||
{
|
||||
id: 'open_timeline_dialog',
|
||||
defaultBinding: 'mod+t',
|
||||
defaultBinding: 'mod+k t',
|
||||
customizable: true,
|
||||
settingsLabelKey:
|
||||
'settings.openchamber.keyboardShortcuts.action.open_timeline_dialog.label',
|
||||
@@ -54,21 +59,21 @@ const SHORTCUT_GROUPS = {
|
||||
},
|
||||
{
|
||||
id: 'open_draft_project_picker',
|
||||
defaultBinding: 'mod+s p',
|
||||
defaultBinding: 'mod+k p',
|
||||
customizable: true,
|
||||
settingsLabelKey:
|
||||
'settings.openchamber.keyboardShortcuts.action.open_draft_project_picker.label',
|
||||
},
|
||||
{
|
||||
id: 'open_draft_worktree_picker',
|
||||
defaultBinding: 'mod+s g',
|
||||
defaultBinding: 'mod+k g',
|
||||
customizable: true,
|
||||
settingsLabelKey:
|
||||
'settings.openchamber.keyboardShortcuts.action.open_draft_worktree_picker.label',
|
||||
},
|
||||
{
|
||||
id: 'open_session_list',
|
||||
defaultBinding: 'mod+s l',
|
||||
defaultBinding: 'mod+k l',
|
||||
customizable: true,
|
||||
settingsLabelKey:
|
||||
'settings.openchamber.keyboardShortcuts.action.open_session_list.label',
|
||||
@@ -146,65 +151,45 @@ const SHORTCUT_GROUPS = {
|
||||
},
|
||||
{
|
||||
id: 'toggle_sidebar',
|
||||
defaultBinding: 'mod+alt+l',
|
||||
defaultBinding: 'mod+b',
|
||||
customizable: true,
|
||||
settingsLabelKey: 'settings.openchamber.keyboardShortcuts.action.toggle_sidebar.label',
|
||||
},
|
||||
{
|
||||
id: 'toggle_prompt_navigator',
|
||||
defaultBinding: 'mod+alt+p',
|
||||
defaultBinding: 'mod+k n',
|
||||
customizable: true,
|
||||
settingsLabelKey:
|
||||
'settings.openchamber.keyboardShortcuts.action.toggle_prompt_navigator.label',
|
||||
},
|
||||
{
|
||||
id: 'toggle_right_sidebar',
|
||||
defaultBinding: 'mod+b',
|
||||
customizable: true,
|
||||
settingsLabelKey:
|
||||
'settings.openchamber.keyboardShortcuts.action.toggle_right_sidebar.label',
|
||||
},
|
||||
{
|
||||
id: 'open_right_sidebar_git',
|
||||
defaultBinding: 'mod+shift+g',
|
||||
customizable: true,
|
||||
settingsLabelKey:
|
||||
'settings.openchamber.keyboardShortcuts.action.open_right_sidebar_git.label',
|
||||
},
|
||||
{
|
||||
id: 'open_right_sidebar_files',
|
||||
defaultBinding: 'mod+shift+f',
|
||||
customizable: true,
|
||||
settingsLabelKey:
|
||||
'settings.openchamber.keyboardShortcuts.action.open_right_sidebar_files.label',
|
||||
},
|
||||
{
|
||||
id: 'switch_context_surface',
|
||||
id: 'switch_session_tab',
|
||||
defaultBinding: 'mod',
|
||||
// The binding is a bare modifier acting as a chord prefix (completed by
|
||||
// a digit); conflict resolution must compare its PREFIX, not a combo.
|
||||
prefixStyle: true,
|
||||
customizable: true,
|
||||
settingsLabelKey:
|
||||
'settings.openchamber.keyboardShortcuts.action.switch_session_tab.label',
|
||||
},
|
||||
{
|
||||
id: 'switch_context_surface',
|
||||
defaultBinding: 'mod+alt',
|
||||
prefixStyle: true,
|
||||
customizable: true,
|
||||
settingsLabelKey:
|
||||
'settings.openchamber.keyboardShortcuts.action.switch_context_surface.label',
|
||||
},
|
||||
{
|
||||
id: 'toggle_context_plan',
|
||||
defaultBinding: 'mod+shift+p',
|
||||
customizable: true,
|
||||
settingsLabelKey:
|
||||
'settings.openchamber.keyboardShortcuts.action.toggle_context_plan.label',
|
||||
},
|
||||
{
|
||||
id: 'toggle_services_menu',
|
||||
defaultBinding: 'mod+shift+s',
|
||||
defaultBinding: 'mod+k i',
|
||||
customizable: true,
|
||||
settingsLabelKey:
|
||||
'settings.openchamber.keyboardShortcuts.action.toggle_services_menu.label',
|
||||
},
|
||||
],
|
||||
navigation: [
|
||||
{ id: 'save_file', defaultBinding: 'mod+s', customizable: false, allowsSequenceFallback: true },
|
||||
{ id: 'save_file', defaultBinding: 'mod+s', customizable: false },
|
||||
{ id: 'find_in_file', defaultBinding: 'mod+f', customizable: false },
|
||||
{
|
||||
id: 'open_go_to_line',
|
||||
@@ -212,13 +197,6 @@ const SHORTCUT_GROUPS = {
|
||||
customizable: true,
|
||||
settingsLabelKey: 'settings.openchamber.keyboardShortcuts.action.open_go_to_line.label',
|
||||
},
|
||||
{
|
||||
id: 'cycle_services_tab',
|
||||
defaultBinding: 'mod+shift+[',
|
||||
customizable: true,
|
||||
settingsLabelKey:
|
||||
'settings.openchamber.keyboardShortcuts.action.cycle_services_tab.label',
|
||||
},
|
||||
],
|
||||
application: [
|
||||
{
|
||||
@@ -228,7 +206,6 @@ const SHORTCUT_GROUPS = {
|
||||
settingsLabelKey:
|
||||
'settings.openchamber.keyboardShortcuts.action.open_command_palette.label',
|
||||
},
|
||||
{ id: 'open_status', defaultBinding: 'mod+shift+o', customizable: false },
|
||||
{
|
||||
id: 'open_settings',
|
||||
defaultBinding: 'mod+comma',
|
||||
@@ -237,17 +214,16 @@ const SHORTCUT_GROUPS = {
|
||||
},
|
||||
{
|
||||
id: 'open_help',
|
||||
defaultBinding: 'mod+.',
|
||||
defaultBinding: 'mod+k h',
|
||||
customizable: true,
|
||||
settingsLabelKey: 'settings.openchamber.keyboardShortcuts.action.open_help.label',
|
||||
},
|
||||
{
|
||||
id: 'cycle_theme',
|
||||
defaultBinding: 'mod+/',
|
||||
defaultBinding: 'mod+k c',
|
||||
customizable: true,
|
||||
settingsLabelKey: 'settings.openchamber.keyboardShortcuts.action.cycle_theme.label',
|
||||
},
|
||||
{ id: 'toggle_memory_debug', defaultBinding: 'mod+shift+d', customizable: false },
|
||||
],
|
||||
} as const satisfies Record<ShortcutCategory, readonly ShortcutConfig[]>;
|
||||
|
||||
|
||||
@@ -8,6 +8,8 @@ export {
|
||||
keyToShortcutToken,
|
||||
normalizeCombo,
|
||||
parseShortcut,
|
||||
resolveShortcutEventDigit,
|
||||
resolveShortcutEventKey,
|
||||
UNASSIGNED_SHORTCUT,
|
||||
} from './bindings';
|
||||
export type { ShortcutCombo } from './bindings';
|
||||
|
||||
@@ -39,6 +39,14 @@ export class ShortcutRegistry {
|
||||
return this.handlers.get(actionId)?.[0]?.handler;
|
||||
}
|
||||
|
||||
/** Runs an action outside keyboard dispatch (command palette). Bypasses
|
||||
suspension: the invoking surface, not the keyboard, owns the gesture. */
|
||||
invoke(actionId: ShortcutActionId): boolean {
|
||||
const handler = this.handlers.get(actionId)?.[0]?.handler;
|
||||
if (!handler) return false;
|
||||
return handler(new KeyboardEvent('keydown')) !== false;
|
||||
}
|
||||
|
||||
/** Temporarily disables every registered application shortcut. */
|
||||
suspend(): () => void {
|
||||
this.suspensionCount += 1;
|
||||
|
||||
@@ -49,13 +49,31 @@ describe('shortcut schema', () => {
|
||||
))).toBe(true);
|
||||
});
|
||||
|
||||
test('includes session prefix bindings and metadata', () => {
|
||||
expect(getShortcutAction('open_draft_project_picker')?.defaultBinding).toBe('mod+s p');
|
||||
expect(getShortcutAction('open_draft_worktree_picker')?.defaultBinding).toBe('mod+s g');
|
||||
expect(getShortcutAction('open_session_list')?.defaultBinding).toBe('mod+s l');
|
||||
test('keeps the mod+k leader for open/go actions', () => {
|
||||
expect(getShortcutAction('open_draft_project_picker')?.defaultBinding).toBe('mod+k p');
|
||||
expect(getShortcutAction('open_draft_worktree_picker')?.defaultBinding).toBe('mod+k g');
|
||||
expect(getShortcutAction('open_session_list')?.defaultBinding).toBe('mod+k l');
|
||||
expect(getShortcutAction('open_timeline_dialog')?.defaultBinding).toBe('mod+k t');
|
||||
expect(getShortcutAction('toggle_prompt_navigator')?.defaultBinding).toBe('mod+k n');
|
||||
expect(getShortcutAction('toggle_services_menu')?.defaultBinding).toBe('mod+k i');
|
||||
expect(getShortcutAction('open_help')?.defaultBinding).toBe('mod+k h');
|
||||
expect(getShortcutAction('cycle_theme')?.defaultBinding).toBe('mod+k c');
|
||||
expect(getShortcutAction('focus_input')?.category).toBe('session');
|
||||
});
|
||||
|
||||
test('splits the held digit prefixes between session tabs and surfaces', () => {
|
||||
expect(getShortcutAction('switch_session_tab')?.defaultBinding).toBe('mod');
|
||||
expect(getShortcutAction('switch_context_surface')?.defaultBinding).toBe('mod+alt');
|
||||
});
|
||||
|
||||
test('every action ships with a default binding', () => {
|
||||
// Palette-only commands live outside this schema entirely; an action in
|
||||
// the schema without a binding would be dead weight in Settings.
|
||||
for (const action of SHORTCUT_SCHEMA) {
|
||||
expect(getEffectiveShortcutCombo(action.id)).not.toBe('');
|
||||
}
|
||||
});
|
||||
|
||||
test('preserves valid overrides and falls back from malformed bindings', () => {
|
||||
expect(getEffectiveShortcutCombo('new_chat', { new_chat: 'mod+k' })).toBe('mod+k');
|
||||
expect(getEffectiveShortcutCombo('new_chat', { new_chat: 'mod+k x y' })).toBe('mod+n');
|
||||
@@ -73,10 +91,8 @@ describe('shortcut schema', () => {
|
||||
.find((conflict) => conflict.action.id === 'find_in_file');
|
||||
const internalPrefixConflict = getShortcutBindingConflicts('new_chat', 'mod+s x')
|
||||
.find((conflict) => conflict.action.id === 'save_file');
|
||||
const contextualPrefixConflict = getShortcutBindingConflicts('focus_input', 'mod+l l')
|
||||
.find((conflict) => conflict.action.id === 'add_selection_to_chat');
|
||||
const contextualLeaderConflict = getShortcutBindingConflicts('add_selection_to_chat', 'mod+s')
|
||||
.find((conflict) => conflict.action.id === 'open_draft_project_picker');
|
||||
const leaderPrefixConflict = getShortcutBindingConflicts('new_chat', 'mod+k')
|
||||
.find((conflict) => conflict.action.id === 'open_session_list');
|
||||
const blockingPrefixConflict = getShortcutBindingConflicts('new_chat', 'mod+p x')
|
||||
.find((conflict) => conflict.action.id === 'open_command_palette');
|
||||
|
||||
@@ -84,10 +100,9 @@ describe('shortcut schema', () => {
|
||||
expect(customizableConflict?.action.customizable).toBe(true);
|
||||
expect(internalConflict?.kind).toBe('exact');
|
||||
expect(internalConflict?.action.customizable).toBe(false);
|
||||
expect(internalPrefixConflict?.kind).toBe('contextual-prefix');
|
||||
expect(internalPrefixConflict?.kind).toBe('prefix');
|
||||
expect(internalPrefixConflict?.action.customizable).toBe(false);
|
||||
expect(contextualPrefixConflict?.kind).toBe('contextual-prefix');
|
||||
expect(contextualLeaderConflict?.kind).toBe('contextual-prefix');
|
||||
expect(leaderPrefixConflict?.kind).toBe('prefix');
|
||||
expect(blockingPrefixConflict?.kind).toBe('prefix');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -15,29 +15,14 @@ export type ShortcutAction = (typeof SHORTCUT_SCHEMA)[number];
|
||||
export type ShortcutActionId = ShortcutAction['id'];
|
||||
export type ShortcutCategory = ShortcutAction['category'];
|
||||
export type CustomizableShortcutAction = Extract<ShortcutAction, { customizable: true }>;
|
||||
/** 'contextual-prefix' is kept in the union for the recording dialog's
|
||||
messaging even though no default layout produces it any more. */
|
||||
export type ShortcutBindingConflictKind = ShortcutConflict | 'contextual-prefix';
|
||||
export type ShortcutBindingConflict = {
|
||||
action: ShortcutAction;
|
||||
kind: ShortcutBindingConflictKind;
|
||||
};
|
||||
|
||||
function allowsContextualPrefix(
|
||||
action: ShortcutAction,
|
||||
combo: ShortcutCombo,
|
||||
candidate: ShortcutAction,
|
||||
candidateCombo: ShortcutCombo,
|
||||
): boolean {
|
||||
const chordCount = parseShortcut(combo)?.chords.length;
|
||||
const candidateChordCount = parseShortcut(candidateCombo)?.chords.length;
|
||||
if (chordCount === 1 && candidateChordCount === 2) {
|
||||
return 'allowsSequenceFallback' in action && action.allowsSequenceFallback;
|
||||
}
|
||||
if (chordCount === 2 && candidateChordCount === 1) {
|
||||
return 'allowsSequenceFallback' in candidate && candidate.allowsSequenceFallback;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function getShortcutAction(id: string): ShortcutAction | undefined {
|
||||
return SHORTCUT_SCHEMA.find((action) => action.id === id);
|
||||
}
|
||||
@@ -54,7 +39,8 @@ export function getEffectiveShortcutCombo(
|
||||
): ShortcutCombo {
|
||||
const action = getShortcutAction(actionId);
|
||||
if (!action) return '';
|
||||
if (!action.customizable) return action.defaultBinding;
|
||||
const defaultBinding = action.defaultBinding === UNASSIGNED_SHORTCUT ? '' : action.defaultBinding;
|
||||
if (!action.customizable) return defaultBinding;
|
||||
|
||||
const override = overrides?.[actionId];
|
||||
if (typeof override === 'string') {
|
||||
@@ -63,7 +49,7 @@ export function getEffectiveShortcutCombo(
|
||||
if (isValidShortcutCombo(normalized)) return normalized;
|
||||
}
|
||||
|
||||
return action.defaultBinding;
|
||||
return defaultBinding;
|
||||
}
|
||||
|
||||
export function getEffectiveShortcutPrefix(
|
||||
@@ -100,12 +86,7 @@ export function getShortcutBindingConflicts(
|
||||
: getEffectiveShortcutCombo(candidate.id, overrides);
|
||||
const kind = getShortcutConflict(combo, candidateCombo);
|
||||
if (!kind) continue;
|
||||
conflicts.push({
|
||||
action: candidate,
|
||||
kind: kind === 'prefix' && allowsContextualPrefix(action, combo, candidate, candidateCombo)
|
||||
? 'contextual-prefix'
|
||||
: kind,
|
||||
});
|
||||
conflicts.push({ action: candidate, kind });
|
||||
}
|
||||
return conflicts;
|
||||
}
|
||||
|
||||
@@ -2412,7 +2412,7 @@ export const useUIStore = create<UIStore>()(
|
||||
{
|
||||
name: 'ui-store',
|
||||
storage: createDeferredSafeJSONStorage(),
|
||||
version: 17,
|
||||
version: 18,
|
||||
migrate: (persistedState, version) => {
|
||||
if (!persistedState || typeof persistedState !== 'object') {
|
||||
return persistedState;
|
||||
@@ -2433,6 +2433,15 @@ export const useUIStore = create<UIStore>()(
|
||||
delete state.expandedEditorToolbar;
|
||||
}
|
||||
|
||||
// v17 -> v18: the default shortcut layout was redesigned around the
|
||||
// mod+k leader and the held digit prefixes. Old overrides were
|
||||
// recorded against the previous defaults (e.g. a bare 'mod' surface
|
||||
// prefix now collides with session tabs), so custom bindings start
|
||||
// fresh on the new system.
|
||||
if (version < 18) {
|
||||
delete state.shortcutOverrides;
|
||||
}
|
||||
|
||||
// v13 -> v14: the separate 'preview' surface merged into 'browser'.
|
||||
// Stored preview tabs keep their URL and become browser tabs; their
|
||||
// id encodes the mode, so it is rebuilt rather than left dangling.
|
||||
|
||||
@@ -172,12 +172,12 @@ checks:
|
||||
preconditions:
|
||||
- Open a draft session with project and worktree selectors mounted.
|
||||
steps:
|
||||
- Trigger Mod + S, P and verify the project picker opens.
|
||||
- Trigger Mod + K, P and verify the project picker opens.
|
||||
- Press Escape once and verify it closes.
|
||||
- Trigger Mod + S, G and verify the worktree picker opens.
|
||||
- Trigger Mod + K, G and verify the worktree picker opens.
|
||||
- Press Escape once and verify it closes.
|
||||
assertions:
|
||||
- A contextual Mod + S owner yields when its context is inactive.
|
||||
- The Mod + K leader arms without a visible menu and completes on the second key.
|
||||
- Each sequence opens only its target picker.
|
||||
- One non-IME Escape closes either controlled picker.
|
||||
evidence:
|
||||
|
||||
Reference in New Issue
Block a user