Skip to content

Commit 60e7517

Browse files
Merge branch 'terminal-keyboard-shortcuts-ws0o'
2 parents 555a8ae + d23f510 commit 60e7517

4 files changed

Lines changed: 55 additions & 3 deletions

File tree

frontend/components/kanban/create-task-modal.tsx

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
import { useState, useEffect, useMemo } from 'react'
1+
import { useState, useEffect, useMemo, useRef } from 'react'
22
import { useNavigate } from '@tanstack/react-router'
33
import { useTranslation } from 'react-i18next'
4+
import { useHotkeys } from '@/hooks/use-hotkeys'
45
import {
56
Dialog,
67
DialogContent,
@@ -88,6 +89,7 @@ export function CreateTaskModal({ open: controlledOpen, onOpenChange, defaultRep
8889
const navigate = useNavigate()
8990
const createTask = useCreateTask()
9091
const createRepository = useCreateRepository()
92+
const formRef = useRef<HTMLFormElement>(null)
9193
const { data: worktreeBasePath } = useWorktreeBasePath()
9294
const { data: defaultGitReposDir } = useDefaultGitReposDir()
9395
const { data: repositories } = useRepositories()
@@ -237,6 +239,19 @@ export function CreateTaskModal({ open: controlledOpen, onOpenChange, defaultRep
237239
)
238240
}
239241

242+
// Cmd+Enter to submit form when modal is open
243+
const canSubmit = !createTask.isPending && !!title.trim() && !!repoPath
244+
useHotkeys('meta+enter', () => {
245+
if (formRef.current) {
246+
formRef.current.requestSubmit()
247+
}
248+
}, {
249+
enabled: open && canSubmit,
250+
allowInInput: true,
251+
ignoreContext: true,
252+
deps: [open, canSubmit],
253+
})
254+
240255
const effectiveBranch = branch.trim() || autoGeneratedBranch
241256
const displayWorktreePath = effectiveBranch
242257
? `${worktreeBasePath}/${effectiveBranch}`
@@ -252,7 +267,7 @@ export function CreateTaskModal({ open: controlledOpen, onOpenChange, defaultRep
252267
</DialogTrigger>
253268
)}
254269
<DialogContent className="sm:max-w-md max-h-[80dvh] flex flex-col overflow-hidden">
255-
<form onSubmit={handleSubmit} className="flex flex-col min-h-0 flex-1">
270+
<form ref={formRef} onSubmit={handleSubmit} className="flex flex-col min-h-0 flex-1">
256271
<DialogHeader className="shrink-0">
257272
<DialogTitle>{t('createModal.title')}</DialogTitle>
258273
<DialogDescription>

frontend/components/keyboard-shortcuts-help.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,18 @@ const shortcutGroups: ShortcutGroup[] = [
4242
{ labelKey: 'keyboardShortcuts', shortcut: 'meta+/' },
4343
],
4444
},
45+
{
46+
titleKey: 'terminals',
47+
items: [
48+
{ labelKey: 'newTerminal', shortcut: 'meta+d' },
49+
{ labelKey: 'closeTerminal', shortcut: 'meta+w' },
50+
],
51+
},
4552
{
4653
titleKey: 'general',
4754
items: [
4855
{ labelKey: 'closeModal', shortcut: 'escape' },
56+
{ labelKey: 'submitForm', shortcut: 'meta+enter' },
4957
],
5058
},
5159
]

frontend/i18n/locales/en/navigation.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
"groups": {
3333
"navigation": "Navigation",
3434
"actions": "Actions",
35+
"terminals": "Terminals (Desktop)",
3536
"general": "General"
3637
},
3738
"labels": {
@@ -44,7 +45,10 @@
4445
"commandPalette": "Command Palette",
4546
"newTask": "New Task",
4647
"keyboardShortcuts": "Keyboard Shortcuts",
47-
"closeModal": "Close Modal"
48+
"closeModal": "Close Modal",
49+
"newTerminal": "New Terminal",
50+
"closeTerminal": "Close Terminal",
51+
"submitForm": "Submit Form"
4852
}
4953
}
5054
}

frontend/routes/terminals/index.tsx

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ import type { ITerminal, ITab } from '@/stores'
2020
import { useTasks } from '@/hooks/use-tasks'
2121
import { useRepositories } from '@/hooks/use-repositories'
2222
import { useWorktreeBasePath } from '@/hooks/use-config'
23+
import { useTerminalViewState } from '@/hooks/use-terminal-view-state'
24+
import { useHotkeys } from '@/hooks/use-hotkeys'
2325
import { cn } from '@/lib/utils'
2426
import type { Terminal as XTerm } from '@xterm/xterm'
2527
import type { TerminalTab, TaskStatus } from '@/types'
@@ -95,6 +97,9 @@ const TerminalsView = observer(function TerminalsView() {
9597
// State for tab edit dialog
9698
const [editingTab, setEditingTab] = useState<TerminalTab | null>(null)
9799

100+
// View state for tracking focused terminals
101+
const { getFocusedTerminal } = useTerminalViewState()
102+
98103
// URL is the source of truth for active tab
99104
// Fall back to first tab if URL doesn't specify a valid tab
100105
const tabIds = useMemo(() => tabs.map((t) => t.id), [tabs])
@@ -538,6 +543,26 @@ const TerminalsView = observer(function TerminalsView() {
538543
[updateTab]
539544
)
540545

546+
// Keyboard shortcuts (Cmd+D/W only work on desktop - browser intercepts on web)
547+
useHotkeys('meta+d', handleTerminalAdd, {
548+
enabled: activeTabId !== ALL_TASKS_TAB_ID && connected,
549+
allowInTerminal: true,
550+
deps: [handleTerminalAdd, activeTabId, connected],
551+
})
552+
553+
useHotkeys('meta+w', () => {
554+
if (activeTabId && activeTabId !== ALL_TASKS_TAB_ID) {
555+
const focusedId = getFocusedTerminal(activeTabId)
556+
if (focusedId) {
557+
handleTerminalClose(focusedId)
558+
}
559+
}
560+
}, {
561+
enabled: activeTabId !== ALL_TASKS_TAB_ID,
562+
allowInTerminal: true,
563+
deps: [activeTabId, getFocusedTerminal, handleTerminalClose],
564+
})
565+
541566
return (
542567
<div className="flex h-full max-w-full flex-col overflow-hidden">
543568
{/* Tab Bar + Actions */}

0 commit comments

Comments
 (0)