diff --git a/.husky/pre-commit b/.husky/pre-commit old mode 100644 new mode 100755 diff --git a/apps/twig/src/renderer/features/sessions/components/SessionView.tsx b/apps/twig/src/renderer/features/sessions/components/SessionView.tsx index 3fcc1ddef..50b2eb747 100644 --- a/apps/twig/src/renderer/features/sessions/components/SessionView.tsx +++ b/apps/twig/src/renderer/features/sessions/components/SessionView.tsx @@ -13,6 +13,7 @@ import { } from "@features/sessions/stores/sessionStore"; import type { Plan } from "@features/sessions/types"; import { useSettingsStore } from "@features/settings/stores/settingsStore"; +import { useAutoFocusOnTyping } from "@hooks/useAutoFocusOnTyping"; import { Spinner, Warning } from "@phosphor-icons/react"; import { Box, Button, ContextMenu, Flex, Text } from "@radix-ui/themes"; import { @@ -336,6 +337,8 @@ export function SessionView({ editorRef.current?.focus(); }, []); + useAutoFocusOnTyping(editorRef); + return ( diff --git a/apps/twig/src/renderer/features/task-detail/components/TaskInput.tsx b/apps/twig/src/renderer/features/task-detail/components/TaskInput.tsx index 5bae7f970..641b53195 100644 --- a/apps/twig/src/renderer/features/task-detail/components/TaskInput.tsx +++ b/apps/twig/src/renderer/features/task-detail/components/TaskInput.tsx @@ -10,12 +10,14 @@ import { } from "@features/sessions/stores/sessionStore"; import type { AgentAdapter } from "@features/settings/stores/settingsStore"; import { useSettingsStore } from "@features/settings/stores/settingsStore"; +import { useAutoFocusOnTyping } from "@hooks/useAutoFocusOnTyping"; import { useRepositoryIntegration } from "@hooks/useIntegrations"; import { Flex } from "@radix-ui/themes"; import { useRegisteredFoldersStore } from "@renderer/stores/registeredFoldersStore"; import { useNavigationStore } from "@stores/navigationStore"; import { useTaskDirectoryStore } from "@stores/taskDirectoryStore"; import { useCallback, useEffect, useRef, useState } from "react"; +import { useHotkeys } from "react-hotkeys-hook"; import { usePreviewSession } from "../hooks/usePreviewSession"; import { useTaskCreation } from "../hooks/useTaskCreation"; import { TaskInputEditor } from "./TaskInputEditor"; @@ -107,6 +109,23 @@ export function TaskInput() { } }, [modeOption, allowBypassPermissions, previewTaskId]); + // Global shift+tab to cycle mode regardless of focus + useHotkeys( + "shift+tab", + (e) => { + e.preventDefault(); + handleCycleMode(); + }, + { + enableOnFormTags: true, + enableOnContentEditable: true, + enabled: !!modeOption, + }, + [handleCycleMode, modeOption], + ); + + useAutoFocusOnTyping(editorRef, isCreatingTask); + const handleDragEnter = useCallback((e: React.DragEvent) => { e.preventDefault(); e.stopPropagation(); @@ -253,7 +272,6 @@ export function TaskInput() { onEmptyChange={setEditorIsEmpty} adapter={adapter} previewTaskId={previewTaskId} - onCycleMode={handleCycleMode} onAdapterChange={setAdapter} isPreviewConnecting={isConnecting} /> diff --git a/apps/twig/src/renderer/features/task-detail/components/TaskInputEditor.tsx b/apps/twig/src/renderer/features/task-detail/components/TaskInputEditor.tsx index 25579da1e..3afcb36ae 100644 --- a/apps/twig/src/renderer/features/task-detail/components/TaskInputEditor.tsx +++ b/apps/twig/src/renderer/features/task-detail/components/TaskInputEditor.tsx @@ -25,7 +25,6 @@ interface TaskInputEditorProps { onEmptyChange?: (isEmpty: boolean) => void; adapter?: "claude" | "codex"; previewTaskId?: string; - onCycleMode?: () => void; onAdapterChange?: (adapter: AgentAdapter) => void; isPreviewConnecting?: boolean; } @@ -45,7 +44,6 @@ export const TaskInputEditor = forwardRef< onEmptyChange, adapter, previewTaskId, - onCycleMode, onAdapterChange, isPreviewConnecting, }, @@ -148,13 +146,6 @@ export const TaskInputEditor = forwardRef< focus(); } }} - onKeyDown={(e) => { - if (e.key === "Tab" && e.shiftKey && onCycleMode) { - e.preventDefault(); - e.stopPropagation(); - onCycleMode(); - } - }} > , + disabled = false, +) { + useEffect(() => { + const handleKeyDown = (e: KeyboardEvent) => { + if (disabled) return; + + const activeEl = document.activeElement; + const isInInput = + activeEl && + (activeEl.tagName === "INPUT" || + activeEl.tagName === "TEXTAREA" || + activeEl.tagName === "SELECT" || + activeEl.getAttribute("contenteditable") === "true"); + if (isInInput) return; + + if (e.key.length > 1 || e.metaKey || e.ctrlKey || e.altKey) return; + + editorRef.current?.focus(); + }; + + document.addEventListener("keydown", handleKeyDown); + return () => document.removeEventListener("keydown", handleKeyDown); + }, [editorRef, disabled]); +}