diff --git a/frontend/src/components/ui-new/dialogs/KeyboardShortcutsDialog.tsx b/frontend/src/components/ui-new/dialogs/KeyboardShortcutsDialog.tsx
index 790b769138..5fbe7ea1ce 100644
--- a/frontend/src/components/ui-new/dialogs/KeyboardShortcutsDialog.tsx
+++ b/frontend/src/components/ui-new/dialogs/KeyboardShortcutsDialog.tsx
@@ -29,6 +29,7 @@ interface ShortcutGroup {
function useShortcutGroups(): ShortcutGroup[] {
const { config } = useUserSystem();
+ const { t } = useTranslation('common');
const sendShortcut = config?.send_message_shortcut ?? 'ModifierEnter';
return useMemo(() => {
@@ -37,36 +38,43 @@ function useShortcutGroups(): ShortcutGroup[] {
// Quick Actions - single key shortcuts
const quickActions: ShortcutGroup = {
- name: 'Quick Actions',
+ name: t('shortcuts.groups.quickActions'),
shortcuts: [
- { keys: '?', description: 'Show this help' },
- { keys: 'Esc', description: 'Close/cancel' },
- { keys: 'C', description: 'Create new task' },
- { keys: 'D', description: 'Delete selected' },
- { keys: '/', description: 'Focus search' },
+ { keys: '?', description: t('shortcuts.actions.showHelp') },
+ { keys: 'Esc', description: t('shortcuts.actions.closeCancel') },
+ { keys: 'C', description: t('shortcuts.actions.createNewTask') },
+ { keys: 'D', description: t('shortcuts.actions.deleteSelected') },
+ { keys: '/', description: t('shortcuts.actions.focusSearch') },
],
};
// Navigation - Vim-style
const navigation: ShortcutGroup = {
- name: 'Navigation',
+ name: t('shortcuts.groups.navigation'),
shortcuts: [
- { keys: 'J', description: 'Move down' },
- { keys: 'K', description: 'Move up' },
- { keys: 'H', description: 'Move left' },
- { keys: 'L', description: 'Move right' },
+ { keys: 'J', description: t('shortcuts.actions.moveDown') },
+ { keys: 'K', description: t('shortcuts.actions.moveUp') },
+ { keys: 'H', description: t('shortcuts.actions.moveLeft') },
+ { keys: 'L', description: t('shortcuts.actions.moveRight') },
],
};
const modifiers: ShortcutGroup = {
- name: 'Modifiers',
+ name: t('shortcuts.groups.modifiers'),
shortcuts: [
- { keys: [mod, 'K'], description: 'Open command bar' },
+ {
+ keys: [mod, 'K'],
+ description: t('shortcuts.actions.openCommandBar'),
+ },
sendShortcut === 'Enter'
- ? { keys: enterKey, description: 'Send message', useHintKey: true }
+ ? {
+ keys: enterKey,
+ description: t('shortcuts.actions.sendMessage'),
+ useHintKey: true,
+ }
: {
keys: [mod, enterKey],
- description: 'Send message',
+ description: t('shortcuts.actions.sendMessage'),
useHintKey: true,
},
],
@@ -84,7 +92,10 @@ function useShortcutGroups(): ShortcutGroup[] {
sequentialByFirstKey.get(firstKey)!.push({
keys: formatSequentialKeys(binding.keys),
- description: binding.description,
+ description: t(
+ `shortcuts.actions.${binding.actionId}`,
+ binding.description
+ ),
hasScope: hasWorkspaceScope,
});
}
@@ -92,25 +103,41 @@ function useShortcutGroups(): ShortcutGroup[] {
// Create named groups for sequential shortcuts
const sequentialGroups: ShortcutGroup[] = [
{
- name: 'Go To (G ...)',
+ name: t('shortcuts.groups.goTo'),
shortcuts: sequentialByFirstKey.get('g') || [],
},
{
- name: 'Workspace (W ...)',
+ name: t('shortcuts.groups.workspace'),
shortcuts: sequentialByFirstKey.get('w') || [],
},
- { name: 'View (V ...)', shortcuts: sequentialByFirstKey.get('v') || [] },
- { name: 'Git (X ...)', shortcuts: sequentialByFirstKey.get('x') || [] },
- { name: 'Yank (Y ...)', shortcuts: sequentialByFirstKey.get('y') || [] },
{
- name: 'Toggle (T ...)',
+ name: t('shortcuts.groups.view'),
+ shortcuts: sequentialByFirstKey.get('v') || [],
+ },
+ {
+ name: t('shortcuts.groups.issues'),
+ shortcuts: sequentialByFirstKey.get('i') || [],
+ },
+ {
+ name: t('shortcuts.groups.git'),
+ shortcuts: sequentialByFirstKey.get('x') || [],
+ },
+ {
+ name: t('shortcuts.groups.yank'),
+ shortcuts: sequentialByFirstKey.get('y') || [],
+ },
+ {
+ name: t('shortcuts.groups.toggle'),
shortcuts: sequentialByFirstKey.get('t') || [],
},
- { name: 'Run (R ...)', shortcuts: sequentialByFirstKey.get('r') || [] },
+ {
+ name: t('shortcuts.groups.run'),
+ shortcuts: sequentialByFirstKey.get('r') || [],
+ },
].filter((g) => g.shortcuts.length > 0);
return [quickActions, navigation, modifiers, ...sequentialGroups];
- }, [sendShortcut]);
+ }, [sendShortcut, t]);
}
function ShortcutRow({ item }: { item: ShortcutItem }) {
diff --git a/frontend/src/components/ui-new/scope/NewDesignScope.tsx b/frontend/src/components/ui-new/scope/NewDesignScope.tsx
index 31ec7f1cde..ca016f7619 100644
--- a/frontend/src/components/ui-new/scope/NewDesignScope.tsx
+++ b/frontend/src/components/ui-new/scope/NewDesignScope.tsx
@@ -10,6 +10,7 @@ import { UserProvider } from '@/contexts/remote/UserContext';
import { SequenceTrackerProvider } from '@/keyboard/SequenceTracker';
import { SequenceIndicator } from '@/keyboard/SequenceIndicator';
import { useWorkspaceShortcuts } from '@/keyboard/useWorkspaceShortcuts';
+import { useIssueShortcuts } from '@/keyboard/useIssueShortcuts';
import { ExecutionProcessesProvider } from '@/contexts/ExecutionProcessesContext';
import { LogsPanelProvider } from '@/contexts/LogsPanelContext';
import NiceModal from '@ebay/nice-modal-react';
@@ -46,6 +47,7 @@ function KeyboardShortcutsHandler() {
{ scope: Scope.GLOBAL }
);
useWorkspaceShortcuts();
+ useIssueShortcuts();
return null;
}
diff --git a/frontend/src/components/ui-new/views/KanbanIssuePanel.tsx b/frontend/src/components/ui-new/views/KanbanIssuePanel.tsx
index 58b64b7e15..28c869fc5a 100644
--- a/frontend/src/components/ui-new/views/KanbanIssuePanel.tsx
+++ b/frontend/src/components/ui-new/views/KanbanIssuePanel.tsx
@@ -160,7 +160,18 @@ export function KanbanIssuePanel({
const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === 'Escape') {
- onClose();
+ const target = e.target as HTMLElement;
+ const isEditable =
+ target.tagName === 'INPUT' ||
+ target.tagName === 'TEXTAREA' ||
+ target.isContentEditable;
+ if (isEditable) {
+ target.blur();
+ (e.currentTarget as HTMLElement).focus();
+ e.stopPropagation();
+ } else {
+ onClose();
+ }
}
};
@@ -173,8 +184,9 @@ export function KanbanIssuePanel({
return (
{/* Header */}
diff --git a/frontend/src/i18n/locales/en/common.json b/frontend/src/i18n/locales/en/common.json
index dd4bdf4df8..8079b8ffa6 100644
--- a/frontend/src/i18n/locales/en/common.json
+++ b/frontend/src/i18n/locales/en/common.json
@@ -360,7 +360,66 @@
"title": "Keyboard Shortcuts",
"inWorkspace": "(in workspace)",
"sequentialHint": "Sequential shortcuts: Press the first key, then the second within 500ms.",
- "configurableHint": "Configurable in Settings → General → Message Input"
+ "configurableHint": "Configurable in Settings → General → Message Input",
+ "groups": {
+ "quickActions": "Quick Actions",
+ "navigation": "Navigation",
+ "modifiers": "Modifiers",
+ "goTo": "Go To (G ...)",
+ "workspace": "Workspace (W ...)",
+ "view": "View (V ...)",
+ "issues": "Issues (I ...)",
+ "git": "Git (X ...)",
+ "yank": "Yank (Y ...)",
+ "toggle": "Toggle (T ...)",
+ "run": "Run (R ...)"
+ },
+ "actions": {
+ "showHelp": "Show this help",
+ "closeCancel": "Close/cancel",
+ "createNewTask": "Create new task",
+ "deleteSelected": "Delete selected",
+ "focusSearch": "Focus search",
+ "moveDown": "Move down",
+ "moveUp": "Move up",
+ "moveLeft": "Move left",
+ "moveRight": "Move right",
+ "openCommandBar": "Open command bar",
+ "sendMessage": "Send message",
+ "settings": "Go to Settings",
+ "new-workspace": "Go to New Workspace",
+ "duplicate-workspace": "Duplicate workspace",
+ "rename-workspace": "Rename workspace",
+ "pin-workspace": "Pin/Unpin workspace",
+ "archive-workspace": "Archive workspace",
+ "delete-workspace": "Delete workspace",
+ "toggle-changes-mode": "Toggle Changes panel",
+ "toggle-logs-mode": "Toggle Logs panel",
+ "toggle-preview-mode": "Toggle Preview panel",
+ "toggle-left-sidebar": "Toggle Left Sidebar",
+ "toggle-left-main-panel": "Toggle Chat panel",
+ "create-issue": "Create Issue",
+ "change-issue-status": "Change Status",
+ "change-issue-priority": "Change Priority",
+ "change-assignees": "Change Assignees",
+ "make-sub-issue-of": "Make Sub-issue of",
+ "add-sub-issue": "Add Sub-issue",
+ "remove-parent-issue": "Remove Parent",
+ "link-workspace": "Link Workspace",
+ "duplicate-issue": "Duplicate Issue",
+ "delete-issue": "Delete Issue",
+ "git-create-pr": "Create Pull Request",
+ "git-merge": "Merge branch",
+ "git-rebase": "Rebase branch",
+ "git-push": "Push changes",
+ "copy-path": "Copy path",
+ "copy-raw-logs": "Copy raw logs",
+ "toggle-dev-server": "Toggle dev server",
+ "toggle-wrap-lines": "Toggle line wrapping",
+ "run-setup-script": "Run setup script",
+ "run-cleanup-script": "Run cleanup script",
+ "run-archive-script": "Run archive script"
+ }
},
"typeahead": {
"tags": "Tags",
diff --git a/frontend/src/i18n/locales/es/common.json b/frontend/src/i18n/locales/es/common.json
index 153b00ed9c..852ac8dbf2 100644
--- a/frontend/src/i18n/locales/es/common.json
+++ b/frontend/src/i18n/locales/es/common.json
@@ -350,7 +350,66 @@
"title": "Atajos de Teclado",
"inWorkspace": "(en espacio de trabajo)",
"sequentialHint": "Atajos secuenciales: Presiona la primera tecla, luego la segunda en 500ms.",
- "configurableHint": "Configurable en Ajustes → General → Entrada de Mensaje"
+ "configurableHint": "Configurable en Ajustes → General → Entrada de Mensaje",
+ "groups": {
+ "quickActions": "Acciones Rápidas",
+ "navigation": "Navegación",
+ "modifiers": "Modificadores",
+ "goTo": "Ir a (G ...)",
+ "workspace": "Espacio de trabajo (W ...)",
+ "view": "Vista (V ...)",
+ "issues": "Issues (I ...)",
+ "git": "Git (X ...)",
+ "yank": "Copiar (Y ...)",
+ "toggle": "Alternar (T ...)",
+ "run": "Ejecutar (R ...)"
+ },
+ "actions": {
+ "showHelp": "Mostrar esta ayuda",
+ "closeCancel": "Cerrar/cancelar",
+ "createNewTask": "Crear nueva tarea",
+ "deleteSelected": "Eliminar seleccionado",
+ "focusSearch": "Enfocar búsqueda",
+ "moveDown": "Mover abajo",
+ "moveUp": "Mover arriba",
+ "moveLeft": "Mover a la izquierda",
+ "moveRight": "Mover a la derecha",
+ "openCommandBar": "Abrir barra de comandos",
+ "sendMessage": "Enviar mensaje",
+ "settings": "Ir a Ajustes",
+ "new-workspace": "Ir a Nuevo espacio de trabajo",
+ "duplicate-workspace": "Duplicar espacio de trabajo",
+ "rename-workspace": "Renombrar espacio de trabajo",
+ "pin-workspace": "Fijar/Desfijar espacio de trabajo",
+ "archive-workspace": "Archivar espacio de trabajo",
+ "delete-workspace": "Eliminar espacio de trabajo",
+ "toggle-changes-mode": "Alternar panel de cambios",
+ "toggle-logs-mode": "Alternar panel de registros",
+ "toggle-preview-mode": "Alternar panel de vista previa",
+ "toggle-left-sidebar": "Alternar barra lateral izquierda",
+ "toggle-left-main-panel": "Alternar panel de chat",
+ "create-issue": "Crear issue",
+ "change-issue-status": "Cambiar estado",
+ "change-issue-priority": "Cambiar prioridad",
+ "change-assignees": "Cambiar asignados",
+ "make-sub-issue-of": "Hacer sub-issue de",
+ "add-sub-issue": "Agregar sub-issue",
+ "remove-parent-issue": "Quitar padre",
+ "link-workspace": "Vincular espacio de trabajo",
+ "duplicate-issue": "Duplicar issue",
+ "delete-issue": "Eliminar issue",
+ "git-create-pr": "Crear Pull Request",
+ "git-merge": "Fusionar rama",
+ "git-rebase": "Rebase de rama",
+ "git-push": "Enviar cambios",
+ "copy-path": "Copiar ruta",
+ "copy-raw-logs": "Copiar registros sin formato",
+ "toggle-dev-server": "Alternar servidor de desarrollo",
+ "toggle-wrap-lines": "Alternar ajuste de línea",
+ "run-setup-script": "Ejecutar script de configuración",
+ "run-cleanup-script": "Ejecutar script de limpieza",
+ "run-archive-script": "Ejecutar script de archivado"
+ }
},
"typeahead": {
"tags": "Etiquetas",
diff --git a/frontend/src/i18n/locales/fr/common.json b/frontend/src/i18n/locales/fr/common.json
index 6cab196727..39891a843d 100644
--- a/frontend/src/i18n/locales/fr/common.json
+++ b/frontend/src/i18n/locales/fr/common.json
@@ -350,7 +350,66 @@
"title": "Raccourcis Clavier",
"inWorkspace": "(dans l'espace de travail)",
"sequentialHint": "Raccourcis séquentiels : Appuyez sur la première touche, puis sur la seconde en 500ms.",
- "configurableHint": "Configurable dans Paramètres → Général → Saisie de Message"
+ "configurableHint": "Configurable dans Paramètres → Général → Saisie de Message",
+ "groups": {
+ "quickActions": "Actions Rapides",
+ "navigation": "Navigation",
+ "modifiers": "Modificateurs",
+ "goTo": "Aller à (G ...)",
+ "workspace": "Espace de travail (W ...)",
+ "view": "Vue (V ...)",
+ "issues": "Tickets (I ...)",
+ "git": "Git (X ...)",
+ "yank": "Copier (Y ...)",
+ "toggle": "Basculer (T ...)",
+ "run": "Exécuter (R ...)"
+ },
+ "actions": {
+ "showHelp": "Afficher cette aide",
+ "closeCancel": "Fermer/annuler",
+ "createNewTask": "Créer une nouvelle tâche",
+ "deleteSelected": "Supprimer la sélection",
+ "focusSearch": "Aller à la recherche",
+ "moveDown": "Déplacer vers le bas",
+ "moveUp": "Déplacer vers le haut",
+ "moveLeft": "Déplacer vers la gauche",
+ "moveRight": "Déplacer vers la droite",
+ "openCommandBar": "Ouvrir la barre de commandes",
+ "sendMessage": "Envoyer le message",
+ "settings": "Aller aux Paramètres",
+ "new-workspace": "Aller au Nouvel espace de travail",
+ "duplicate-workspace": "Dupliquer l'espace de travail",
+ "rename-workspace": "Renommer l'espace de travail",
+ "pin-workspace": "Épingler/Désépingler l'espace de travail",
+ "archive-workspace": "Archiver l'espace de travail",
+ "delete-workspace": "Supprimer l'espace de travail",
+ "toggle-changes-mode": "Basculer le panneau Modifications",
+ "toggle-logs-mode": "Basculer le panneau Logs",
+ "toggle-preview-mode": "Basculer le panneau Aperçu",
+ "toggle-left-sidebar": "Basculer la barre latérale gauche",
+ "toggle-left-main-panel": "Basculer le panneau Chat",
+ "create-issue": "Créer un ticket",
+ "change-issue-status": "Changer le statut",
+ "change-issue-priority": "Changer la priorité",
+ "change-assignees": "Changer les assignés",
+ "make-sub-issue-of": "Transformer en sous-ticket de",
+ "add-sub-issue": "Ajouter un sous-ticket",
+ "remove-parent-issue": "Retirer le parent",
+ "link-workspace": "Lier un espace de travail",
+ "duplicate-issue": "Dupliquer le ticket",
+ "delete-issue": "Supprimer le ticket",
+ "git-create-pr": "Créer une Pull Request",
+ "git-merge": "Fusionner la branche",
+ "git-rebase": "Rebaser la branche",
+ "git-push": "Pousser les modifications",
+ "copy-path": "Copier le chemin",
+ "copy-raw-logs": "Copier les logs bruts",
+ "toggle-dev-server": "Basculer le serveur de développement",
+ "toggle-wrap-lines": "Basculer le retour à la ligne",
+ "run-setup-script": "Exécuter le script de configuration",
+ "run-cleanup-script": "Exécuter le script de nettoyage",
+ "run-archive-script": "Exécuter le script d'archivage"
+ }
},
"typeahead": {
"tags": "Tags",
diff --git a/frontend/src/i18n/locales/ja/common.json b/frontend/src/i18n/locales/ja/common.json
index 46bbd485f8..118909e162 100644
--- a/frontend/src/i18n/locales/ja/common.json
+++ b/frontend/src/i18n/locales/ja/common.json
@@ -350,7 +350,66 @@
"title": "キーボードショートカット",
"inWorkspace": "(ワークスペース内)",
"sequentialHint": "シーケンシャルショートカット:最初のキーを押し、500ms以内に2番目のキーを押してください。",
- "configurableHint": "設定 → 一般 → メッセージ入力で設定可能"
+ "configurableHint": "設定 → 一般 → メッセージ入力で設定可能",
+ "groups": {
+ "quickActions": "クイックアクション",
+ "navigation": "ナビゲーション",
+ "modifiers": "修飾キー",
+ "goTo": "移動 (G ...)",
+ "workspace": "ワークスペース (W ...)",
+ "view": "表示 (V ...)",
+ "issues": "課題 (I ...)",
+ "git": "Git (X ...)",
+ "yank": "コピー (Y ...)",
+ "toggle": "切り替え (T ...)",
+ "run": "実行 (R ...)"
+ },
+ "actions": {
+ "showHelp": "このヘルプを表示",
+ "closeCancel": "閉じる/キャンセル",
+ "createNewTask": "新しいタスクを作成",
+ "deleteSelected": "選択を削除",
+ "focusSearch": "検索にフォーカス",
+ "moveDown": "下に移動",
+ "moveUp": "上に移動",
+ "moveLeft": "左に移動",
+ "moveRight": "右に移動",
+ "openCommandBar": "コマンドバーを開く",
+ "sendMessage": "メッセージを送信",
+ "settings": "設定に移動",
+ "new-workspace": "新しいワークスペースに移動",
+ "duplicate-workspace": "ワークスペースを複製",
+ "rename-workspace": "ワークスペースの名前を変更",
+ "pin-workspace": "ワークスペースをピン留め/解除",
+ "archive-workspace": "ワークスペースをアーカイブ",
+ "delete-workspace": "ワークスペースを削除",
+ "toggle-changes-mode": "変更パネルの切り替え",
+ "toggle-logs-mode": "ログパネルの切り替え",
+ "toggle-preview-mode": "プレビューパネルの切り替え",
+ "toggle-left-sidebar": "左サイドバーの切り替え",
+ "toggle-left-main-panel": "チャットパネルの切り替え",
+ "create-issue": "課題を作成",
+ "change-issue-status": "ステータスを変更",
+ "change-issue-priority": "優先度を変更",
+ "change-assignees": "担当者を変更",
+ "make-sub-issue-of": "サブ課題にする",
+ "add-sub-issue": "サブ課題を追加",
+ "remove-parent-issue": "親課題を解除",
+ "link-workspace": "ワークスペースをリンク",
+ "duplicate-issue": "課題を複製",
+ "delete-issue": "課題を削除",
+ "git-create-pr": "Pull Requestを作成",
+ "git-merge": "ブランチをマージ",
+ "git-rebase": "ブランチをリベース",
+ "git-push": "変更をプッシュ",
+ "copy-path": "パスをコピー",
+ "copy-raw-logs": "生ログをコピー",
+ "toggle-dev-server": "開発サーバーの切り替え",
+ "toggle-wrap-lines": "行折り返しの切り替え",
+ "run-setup-script": "セットアップスクリプトを実行",
+ "run-cleanup-script": "クリーンアップスクリプトを実行",
+ "run-archive-script": "アーカイブスクリプトを実行"
+ }
},
"typeahead": {
"tags": "タグ",
diff --git a/frontend/src/i18n/locales/ko/common.json b/frontend/src/i18n/locales/ko/common.json
index bb4b52c698..59c6459068 100644
--- a/frontend/src/i18n/locales/ko/common.json
+++ b/frontend/src/i18n/locales/ko/common.json
@@ -350,7 +350,66 @@
"title": "키보드 단축키",
"inWorkspace": "(워크스페이스 내)",
"sequentialHint": "순차 단축키: 첫 번째 키를 누른 후 500ms 이내에 두 번째 키를 누르세요.",
- "configurableHint": "설정 → 일반 → 메시지 입력에서 설정 가능"
+ "configurableHint": "설정 → 일반 → 메시지 입력에서 설정 가능",
+ "groups": {
+ "quickActions": "빠른 작업",
+ "navigation": "탐색",
+ "modifiers": "수정 키",
+ "goTo": "이동 (G ...)",
+ "workspace": "워크스페이스 (W ...)",
+ "view": "보기 (V ...)",
+ "issues": "이슈 (I ...)",
+ "git": "Git (X ...)",
+ "yank": "복사 (Y ...)",
+ "toggle": "전환 (T ...)",
+ "run": "실행 (R ...)"
+ },
+ "actions": {
+ "showHelp": "도움말 표시",
+ "closeCancel": "닫기/취소",
+ "createNewTask": "새 작업 만들기",
+ "deleteSelected": "선택 항목 삭제",
+ "focusSearch": "검색에 포커스",
+ "moveDown": "아래로 이동",
+ "moveUp": "위로 이동",
+ "moveLeft": "왼쪽으로 이동",
+ "moveRight": "오른쪽으로 이동",
+ "openCommandBar": "명령 바 열기",
+ "sendMessage": "메시지 보내기",
+ "settings": "설정으로 이동",
+ "new-workspace": "새 워크스페이스로 이동",
+ "duplicate-workspace": "워크스페이스 복제",
+ "rename-workspace": "워크스페이스 이름 변경",
+ "pin-workspace": "워크스페이스 고정/해제",
+ "archive-workspace": "워크스페이스 보관",
+ "delete-workspace": "워크스페이스 삭제",
+ "toggle-changes-mode": "변경사항 패널 전환",
+ "toggle-logs-mode": "로그 패널 전환",
+ "toggle-preview-mode": "미리보기 패널 전환",
+ "toggle-left-sidebar": "왼쪽 사이드바 전환",
+ "toggle-left-main-panel": "채팅 패널 전환",
+ "create-issue": "이슈 만들기",
+ "change-issue-status": "상태 변경",
+ "change-issue-priority": "우선순위 변경",
+ "change-assignees": "담당자 변경",
+ "make-sub-issue-of": "하위 이슈로 설정",
+ "add-sub-issue": "하위 이슈 추가",
+ "remove-parent-issue": "상위 이슈 해제",
+ "link-workspace": "워크스페이스 연결",
+ "duplicate-issue": "이슈 복제",
+ "delete-issue": "이슈 삭제",
+ "git-create-pr": "Pull Request 만들기",
+ "git-merge": "브랜치 병합",
+ "git-rebase": "브랜치 리베이스",
+ "git-push": "변경사항 푸시",
+ "copy-path": "경로 복사",
+ "copy-raw-logs": "원본 로그 복사",
+ "toggle-dev-server": "개발 서버 전환",
+ "toggle-wrap-lines": "줄 바꿈 전환",
+ "run-setup-script": "설정 스크립트 실행",
+ "run-cleanup-script": "정리 스크립트 실행",
+ "run-archive-script": "보관 스크립트 실행"
+ }
},
"typeahead": {
"tags": "태그",
diff --git a/frontend/src/i18n/locales/zh-Hans/common.json b/frontend/src/i18n/locales/zh-Hans/common.json
index 5601555dc9..1ef20df38a 100644
--- a/frontend/src/i18n/locales/zh-Hans/common.json
+++ b/frontend/src/i18n/locales/zh-Hans/common.json
@@ -350,7 +350,66 @@
"title": "键盘快捷键",
"inWorkspace": "(在工作区中)",
"sequentialHint": "组合快捷键:先按第一个键,然后在500毫秒内按第二个键。",
- "configurableHint": "可在 设置 → 常规 → 消息输入 中配置"
+ "configurableHint": "可在 设置 → 常规 → 消息输入 中配置",
+ "groups": {
+ "quickActions": "快捷操作",
+ "navigation": "导航",
+ "modifiers": "修饰键",
+ "goTo": "前往 (G ...)",
+ "workspace": "工作区 (W ...)",
+ "view": "视图 (V ...)",
+ "issues": "问题 (I ...)",
+ "git": "Git (X ...)",
+ "yank": "复制 (Y ...)",
+ "toggle": "切换 (T ...)",
+ "run": "运行 (R ...)"
+ },
+ "actions": {
+ "showHelp": "显示帮助",
+ "closeCancel": "关闭/取消",
+ "createNewTask": "创建新任务",
+ "deleteSelected": "删除所选",
+ "focusSearch": "聚焦搜索",
+ "moveDown": "向下移动",
+ "moveUp": "向上移动",
+ "moveLeft": "向左移动",
+ "moveRight": "向右移动",
+ "openCommandBar": "打开命令栏",
+ "sendMessage": "发送消息",
+ "settings": "前往设置",
+ "new-workspace": "前往新工作区",
+ "duplicate-workspace": "复制工作区",
+ "rename-workspace": "重命名工作区",
+ "pin-workspace": "置顶/取消置顶工作区",
+ "archive-workspace": "归档工作区",
+ "delete-workspace": "删除工作区",
+ "toggle-changes-mode": "切换更改面板",
+ "toggle-logs-mode": "切换日志面板",
+ "toggle-preview-mode": "切换预览面板",
+ "toggle-left-sidebar": "切换左侧边栏",
+ "toggle-left-main-panel": "切换聊天面板",
+ "create-issue": "创建问题",
+ "change-issue-status": "更改状态",
+ "change-issue-priority": "更改优先级",
+ "change-assignees": "更改负责人",
+ "make-sub-issue-of": "设为子问题",
+ "add-sub-issue": "添加子问题",
+ "remove-parent-issue": "移除父级",
+ "link-workspace": "关联工作区",
+ "duplicate-issue": "复制问题",
+ "delete-issue": "删除问题",
+ "git-create-pr": "创建 Pull Request",
+ "git-merge": "合并分支",
+ "git-rebase": "变基分支",
+ "git-push": "推送更改",
+ "copy-path": "复制路径",
+ "copy-raw-logs": "复制原始日志",
+ "toggle-dev-server": "切换开发服务器",
+ "toggle-wrap-lines": "切换自动换行",
+ "run-setup-script": "运行设置脚本",
+ "run-cleanup-script": "运行清理脚本",
+ "run-archive-script": "运行归档脚本"
+ }
},
"typeahead": {
"tags": "标签",
diff --git a/frontend/src/i18n/locales/zh-Hant/common.json b/frontend/src/i18n/locales/zh-Hant/common.json
index 780685d0a7..37c00ec282 100644
--- a/frontend/src/i18n/locales/zh-Hant/common.json
+++ b/frontend/src/i18n/locales/zh-Hant/common.json
@@ -350,7 +350,66 @@
"title": "鍵盤快捷鍵",
"inWorkspace": "(在工作區中)",
"sequentialHint": "組合快捷鍵:先按第一個鍵,然後在500毫秒內按第二個鍵。",
- "configurableHint": "可在 設定 → 一般 → 訊息輸入 中設定"
+ "configurableHint": "可在 設定 → 一般 → 訊息輸入 中設定",
+ "groups": {
+ "quickActions": "快捷操作",
+ "navigation": "導覽",
+ "modifiers": "修飾鍵",
+ "goTo": "前往 (G ...)",
+ "workspace": "工作區 (W ...)",
+ "view": "檢視 (V ...)",
+ "issues": "問題 (I ...)",
+ "git": "Git (X ...)",
+ "yank": "複製 (Y ...)",
+ "toggle": "切換 (T ...)",
+ "run": "執行 (R ...)"
+ },
+ "actions": {
+ "showHelp": "顯示說明",
+ "closeCancel": "關閉/取消",
+ "createNewTask": "建立新任務",
+ "deleteSelected": "刪除所選",
+ "focusSearch": "聚焦搜尋",
+ "moveDown": "向下移動",
+ "moveUp": "向上移動",
+ "moveLeft": "向左移動",
+ "moveRight": "向右移動",
+ "openCommandBar": "開啟命令列",
+ "sendMessage": "傳送訊息",
+ "settings": "前往設定",
+ "new-workspace": "前往新工作區",
+ "duplicate-workspace": "複製工作區",
+ "rename-workspace": "重新命名工作區",
+ "pin-workspace": "釘選/取消釘選工作區",
+ "archive-workspace": "封存工作區",
+ "delete-workspace": "刪除工作區",
+ "toggle-changes-mode": "切換變更面板",
+ "toggle-logs-mode": "切換日誌面板",
+ "toggle-preview-mode": "切換預覽面板",
+ "toggle-left-sidebar": "切換左側邊欄",
+ "toggle-left-main-panel": "切換聊天面板",
+ "create-issue": "建立問題",
+ "change-issue-status": "變更狀態",
+ "change-issue-priority": "變更優先順序",
+ "change-assignees": "變更負責人",
+ "make-sub-issue-of": "設為子問題",
+ "add-sub-issue": "新增子問題",
+ "remove-parent-issue": "移除父級",
+ "link-workspace": "關聯工作區",
+ "duplicate-issue": "複製問題",
+ "delete-issue": "刪除問題",
+ "git-create-pr": "建立 Pull Request",
+ "git-merge": "合併分支",
+ "git-rebase": "變基分支",
+ "git-push": "推送變更",
+ "copy-path": "複製路徑",
+ "copy-raw-logs": "複製原始日誌",
+ "toggle-dev-server": "切換開發伺服器",
+ "toggle-wrap-lines": "切換自動換行",
+ "run-setup-script": "執行設定腳本",
+ "run-cleanup-script": "執行清理腳本",
+ "run-archive-script": "執行封存腳本"
+ }
},
"typeahead": {
"tags": "標籤",
diff --git a/frontend/src/keyboard/index.ts b/frontend/src/keyboard/index.ts
index 31101a0126..a5a73c1549 100644
--- a/frontend/src/keyboard/index.ts
+++ b/frontend/src/keyboard/index.ts
@@ -3,3 +3,4 @@ export * from './registry';
export * from './SequenceTracker';
export * from './SequenceIndicator';
export * from './useWorkspaceShortcuts';
+export * from './useIssueShortcuts';
diff --git a/frontend/src/keyboard/registry.ts b/frontend/src/keyboard/registry.ts
index 1628ddd3fa..6966fdca8d 100644
--- a/frontend/src/keyboard/registry.ts
+++ b/frontend/src/keyboard/registry.ts
@@ -65,6 +65,7 @@ export const SEQUENCE_FIRST_KEYS = new Set([
'y', // Yank/Copy
't', // Toggle
'r', // Run
+ 'i', // Issue
]);
/**
@@ -256,6 +257,78 @@ export const sequentialBindings: SequentialBinding[] = [
group: 'Run',
actionId: 'run-archive-script',
},
+
+ // Issue (I)
+ {
+ id: 'seq-issue-create',
+ keys: ['i', 'c'],
+ description: 'Create Issue',
+ group: 'Issue',
+ actionId: 'create-issue',
+ },
+ {
+ id: 'seq-issue-status',
+ keys: ['i', 's'],
+ description: 'Change Status',
+ group: 'Issue',
+ actionId: 'change-issue-status',
+ },
+ {
+ id: 'seq-issue-priority',
+ keys: ['i', 'p'],
+ description: 'Change Priority',
+ group: 'Issue',
+ actionId: 'change-issue-priority',
+ },
+ {
+ id: 'seq-issue-assignees',
+ keys: ['i', 'a'],
+ description: 'Change Assignees',
+ group: 'Issue',
+ actionId: 'change-assignees',
+ },
+ {
+ id: 'seq-issue-make-sub-issue',
+ keys: ['i', 'm'],
+ description: 'Make Sub-issue of',
+ group: 'Issue',
+ actionId: 'make-sub-issue-of',
+ },
+ {
+ id: 'seq-issue-add-sub-issue',
+ keys: ['i', 'b'],
+ description: 'Add Sub-issue',
+ group: 'Issue',
+ actionId: 'add-sub-issue',
+ },
+ {
+ id: 'seq-issue-remove-parent',
+ keys: ['i', 'u'],
+ description: 'Remove Parent',
+ group: 'Issue',
+ actionId: 'remove-parent-issue',
+ },
+ {
+ id: 'seq-issue-link-workspace',
+ keys: ['i', 'w'],
+ description: 'Link Workspace',
+ group: 'Issue',
+ actionId: 'link-workspace',
+ },
+ {
+ id: 'seq-issue-duplicate',
+ keys: ['i', 'd'],
+ description: 'Duplicate Issue',
+ group: 'Issue',
+ actionId: 'duplicate-issue',
+ },
+ {
+ id: 'seq-issue-delete',
+ keys: ['i', 'x'],
+ description: 'Delete Issue',
+ group: 'Issue',
+ actionId: 'delete-issue',
+ },
];
export const keyBindings: KeyBinding[] = [
diff --git a/frontend/src/keyboard/useIssueShortcuts.ts b/frontend/src/keyboard/useIssueShortcuts.ts
new file mode 100644
index 0000000000..df9b758a6f
--- /dev/null
+++ b/frontend/src/keyboard/useIssueShortcuts.ts
@@ -0,0 +1,141 @@
+import { useCallback, useRef, useEffect, useMemo } from 'react';
+import { useParams, useLocation } from 'react-router-dom';
+import { useHotkeys } from 'react-hotkeys-hook';
+import { useActions } from '@/contexts/ActionsContext';
+import {
+ Actions,
+ type ActionDefinition,
+ ActionTargetType,
+} from '@/components/ui-new/actions';
+import { Scope } from './registry';
+
+const SEQUENCE_TIMEOUT_MS = 1500;
+
+const OPTIONS = {
+ scopes: [Scope.KANBAN],
+ sequenceTimeout: SEQUENCE_TIMEOUT_MS,
+} as const;
+
+export function useIssueShortcuts() {
+ const { executeAction } = useActions();
+ const { projectId, issueId } = useParams<{
+ projectId?: string;
+ issueId?: string;
+ }>();
+ const location = useLocation();
+
+ const isKanban = location.pathname.startsWith('/projects');
+ // Detect create mode from the URL path (e.g. /projects/:id/issues/new)
+ // NOT from ?mode=create searchParam which is a legacy format
+ const isCreatingIssue = location.pathname.endsWith('/issues/new');
+
+ const executeActionRef = useRef(executeAction);
+ const projectIdRef = useRef(projectId);
+ const issueIdRef = useRef(issueId);
+ const isKanbanRef = useRef(isKanban);
+ const isCreatingIssueRef = useRef(isCreatingIssue);
+
+ useEffect(() => {
+ executeActionRef.current = executeAction;
+ projectIdRef.current = projectId;
+ issueIdRef.current = issueId;
+ isKanbanRef.current = isKanban;
+ isCreatingIssueRef.current = isCreatingIssue;
+ });
+
+ const issueIds = useMemo(() => (issueId ? [issueId] : []), [issueId]);
+ const issueIdsRef = useRef(issueIds);
+ useEffect(() => {
+ issueIdsRef.current = issueIds;
+ });
+
+ const executeIssueAction = useCallback(
+ (action: ActionDefinition, e?: KeyboardEvent) => {
+ if (!isKanbanRef.current) return;
+ // react-hotkeys-hook does not call preventDefault for sequence hotkeys,
+ // so we must do it manually to stop the second keystroke from being typed
+ // into any focused input (e.g. the title field after i>c opens create mode).
+ e?.preventDefault();
+
+ const currentProjectId = projectIdRef.current;
+ const currentIssueIds = issueIdsRef.current;
+
+ if (action.requiresTarget === ActionTargetType.ISSUE) {
+ if (!currentProjectId || currentIssueIds.length === 0) return;
+ executeActionRef.current(
+ action,
+ undefined,
+ currentProjectId,
+ currentIssueIds
+ );
+ } else if (action.requiresTarget === ActionTargetType.NONE) {
+ executeActionRef.current(action);
+ }
+ },
+ []
+ );
+
+ const enabled = isKanban;
+
+ useHotkeys('i>c', (e) => executeIssueAction(Actions.CreateIssue, e), {
+ ...OPTIONS,
+ enabled,
+ });
+ useHotkeys(
+ 'i>s',
+ (e) => {
+ if (isCreatingIssueRef.current) {
+ executeIssueAction(Actions.ChangeNewIssueStatus, e);
+ } else {
+ executeIssueAction(Actions.ChangeIssueStatus, e);
+ }
+ },
+ { ...OPTIONS, enabled }
+ );
+ useHotkeys(
+ 'i>p',
+ (e) => {
+ if (isCreatingIssueRef.current) {
+ executeIssueAction(Actions.ChangeNewIssuePriority, e);
+ } else {
+ executeIssueAction(Actions.ChangePriority, e);
+ }
+ },
+ { ...OPTIONS, enabled }
+ );
+ useHotkeys(
+ 'i>a',
+ (e) => {
+ if (isCreatingIssueRef.current) {
+ executeIssueAction(Actions.ChangeNewIssueAssignees, e);
+ } else {
+ executeIssueAction(Actions.ChangeAssignees, e);
+ }
+ },
+ { ...OPTIONS, enabled }
+ );
+ useHotkeys('i>m', (e) => executeIssueAction(Actions.MakeSubIssueOf, e), {
+ ...OPTIONS,
+ enabled,
+ });
+ useHotkeys('i>b', (e) => executeIssueAction(Actions.AddSubIssue, e), {
+ ...OPTIONS,
+ enabled,
+ });
+ useHotkeys('i>u', (e) => executeIssueAction(Actions.RemoveParentIssue, e), {
+ ...OPTIONS,
+ enabled,
+ });
+ useHotkeys('i>w', (e) => executeIssueAction(Actions.LinkWorkspace, e), {
+ ...OPTIONS,
+ enabled,
+ });
+ useHotkeys('i>d', (e) => executeIssueAction(Actions.DuplicateIssue, e), {
+ ...OPTIONS,
+ enabled,
+ });
+ useHotkeys('i>x', (e) => executeIssueAction(Actions.DeleteIssue, e), {
+ ...OPTIONS,
+ enabled,
+ });
+}