From b04672d7763dd178e3b9e30be5c4f6f6213ea4b8 Mon Sep 17 00:00:00 2001 From: Louis Knight-Webb Date: Tue, 18 Nov 2025 11:17:10 +0000 Subject: [PATCH] CMD+shift+enter should create task without start (vibe-kanban) (#1317) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Done! CMD+Shift+Enter will now create tasks without starting them. **Changes made:** 1. Added `useKeySubmitTaskAlt` import 2. Added `forceCreateOnlyRef` to track create-only mode 3. Updated validator to skip executor/branch requirements when ref is true 4. Updated submit logic to check `autoStart && !forceCreateOnlyRef.current` 5. Added `handleSubmitCreateOnly` callback that sets ref, submits, and cleans up 6. Bound CMD+Shift+Enter with simpler validation (just needs title) All type checks pass ✓ * Cleanup script changes for task attempt 2eb2be96-3b6c-4471-9964-3cff2dc9feef --- .../dialogs/tasks/TaskFormDialog.tsx | 37 +++++++++++++++++-- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/frontend/src/components/dialogs/tasks/TaskFormDialog.tsx b/frontend/src/components/dialogs/tasks/TaskFormDialog.tsx index 3078ae0e..c145ff53 100644 --- a/frontend/src/components/dialogs/tasks/TaskFormDialog.tsx +++ b/frontend/src/components/dialogs/tasks/TaskFormDialog.tsx @@ -38,7 +38,12 @@ import { useImageUpload, useTaskMutations, } from '@/hooks'; -import { useKeySubmitTask, useKeyExit, Scope } from '@/keyboard'; +import { + useKeySubmitTask, + useKeySubmitTaskAlt, + useKeyExit, + Scope, +} from '@/keyboard'; import { useHotkeysContext } from 'react-hotkeys-hook'; import { cn } from '@/lib/utils'; import type { @@ -96,6 +101,7 @@ const TaskFormDialogImpl = NiceModal.create((props) => { const [showDiscardWarning, setShowDiscardWarning] = useState(false); const imageUploadRef = useRef(null); const [pendingFiles, setPendingFiles] = useState(null); + const forceCreateOnlyRef = useRef(false); const { data: branches, isLoading: branchesLoading } = useProjectBranches(projectId); @@ -184,7 +190,8 @@ const TaskFormDialogImpl = NiceModal.create((props) => { image_ids: imageIds, shared_task_id: null, }; - if (value.autoStart) { + const shouldAutoStart = value.autoStart && !forceCreateOnlyRef.current; + if (shouldAutoStart) { await createAndStart.mutateAsync( { task, @@ -201,7 +208,11 @@ const TaskFormDialogImpl = NiceModal.create((props) => { const validator = (value: TaskFormValues): string | undefined => { if (!value.title.trim().length) return 'need title'; - if (value.autoStart && (!value.executorProfileId || !value.branch)) { + if ( + value.autoStart && + !forceCreateOnlyRef.current && + (!value.executorProfileId || !value.branch) + ) { return 'need executor profile or branch;'; } }; @@ -309,6 +320,26 @@ const TaskFormDialogImpl = NiceModal.create((props) => { preventDefault: true, }); + const canSubmitAlt = useStore( + form.store, + (state) => state.values.title.trim().length > 0 && !state.isSubmitting + ); + + const handleSubmitCreateOnly = useCallback(() => { + forceCreateOnlyRef.current = true; + const promise = form.handleSubmit(); + Promise.resolve(promise).finally(() => { + forceCreateOnlyRef.current = false; + }); + }, [form]); + + useKeySubmitTaskAlt(handleSubmitCreateOnly, { + enabled: modal.visible && canSubmitAlt && !showDiscardWarning, + scope: Scope.DIALOG, + enableOnFormTags: ['input', 'INPUT', 'textarea', 'TEXTAREA'], + preventDefault: true, + }); + // Dialog close handling const handleDialogClose = (open: boolean) => { if (open) return;