From 6c76c6b34547ccc6e161910a26b353e4a91b4763 Mon Sep 17 00:00:00 2001 From: Charles Vien Date: Fri, 6 Feb 2026 09:29:02 -0800 Subject: [PATCH] Allow composing messages while offline --- .../components/MessageEditor.tsx | 21 ++++++++++++------- .../message-editor/tiptap/useTiptapEditor.ts | 11 +++++++--- .../components/TaskInputEditor.tsx | 19 ++++++++++------- 3 files changed, 33 insertions(+), 18 deletions(-) diff --git a/apps/twig/src/renderer/features/message-editor/components/MessageEditor.tsx b/apps/twig/src/renderer/features/message-editor/components/MessageEditor.tsx index 887b45654..92cd3b942 100644 --- a/apps/twig/src/renderer/features/message-editor/components/MessageEditor.tsx +++ b/apps/twig/src/renderer/features/message-editor/components/MessageEditor.tsx @@ -61,7 +61,7 @@ export const MessageEditor = forwardRef( const disabled = context?.disabled ?? false; const isLoading = context?.isLoading ?? false; const repoPath = context?.repoPath; - const isDisabled = disabled || !isOnline; + const isSubmitDisabled = disabled || !isOnline; const { editor, @@ -80,7 +80,8 @@ export const MessageEditor = forwardRef( sessionId, taskId, placeholder, - disabled: isDisabled, + disabled, + submitDisabled: !isOnline, isLoading, autoFocus, context: { taskId, repoPath }, @@ -157,7 +158,7 @@ export const MessageEditor = forwardRef( ( ( e.stopPropagation(); submit(); }} - disabled={isDisabled || isEmpty} + disabled={isSubmitDisabled || isEmpty} loading={isLoading} style={{ backgroundColor: - isDisabled || isEmpty ? "var(--accent-a4)" : undefined, + isSubmitDisabled || isEmpty + ? "var(--accent-a4)" + : undefined, color: - isDisabled || isEmpty ? "var(--accent-8)" : undefined, + isSubmitDisabled || isEmpty + ? "var(--accent-8)" + : undefined, }} > diff --git a/apps/twig/src/renderer/features/message-editor/tiptap/useTiptapEditor.ts b/apps/twig/src/renderer/features/message-editor/tiptap/useTiptapEditor.ts index d0c9dafc4..1ef455a61 100644 --- a/apps/twig/src/renderer/features/message-editor/tiptap/useTiptapEditor.ts +++ b/apps/twig/src/renderer/features/message-editor/tiptap/useTiptapEditor.ts @@ -15,6 +15,7 @@ export interface UseTiptapEditorOptions { taskId?: string; placeholder?: string; disabled?: boolean; + submitDisabled?: boolean; isLoading?: boolean; autoFocus?: boolean; context?: DraftContext; @@ -41,6 +42,7 @@ export function useTiptapEditor(options: UseTiptapEditorOptions) { taskId, placeholder = "", disabled = false, + submitDisabled = false, isLoading = false, autoFocus = false, context, @@ -77,6 +79,9 @@ export function useTiptapEditor(options: UseTiptapEditorOptions) { onBlur, }; + const submitDisabledRef = useRef(submitDisabled); + submitDisabledRef.current = submitDisabled; + const prevBashModeRef = useRef(false); const prevIsEmptyRef = useRef(true); const submitRef = useRef<() => void>(() => {}); @@ -128,7 +133,7 @@ export function useTiptapEditor(options: UseTiptapEditorOptions) { : !event.shiftKey; if (isSubmitKey) { - if (!view.editable) return false; + if (!view.editable || submitDisabledRef.current) return false; const suggestionPopup = document.querySelector("[data-tippy-root]"); if (suggestionPopup) return false; @@ -341,7 +346,7 @@ export function useTiptapEditor(options: UseTiptapEditorOptions) { const submit = useCallback(() => { if (!editor) return; - if (disabled) return; + if (disabled || submitDisabled) return; const content = draft.getContent(); if (isContentEmpty(content)) return; @@ -366,7 +371,7 @@ export function useTiptapEditor(options: UseTiptapEditorOptions) { prevBashModeRef.current = false; draft.clearDraft(); } - }, [editor, disabled, isLoading, draft, clearOnSubmit]); + }, [editor, disabled, submitDisabled, isLoading, draft, clearOnSubmit]); submitRef.current = submit; 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 a0daf5d2b..dfffdf6da 100644 --- a/apps/twig/src/renderer/features/task-detail/components/TaskInputEditor.tsx +++ b/apps/twig/src/renderer/features/task-detail/components/TaskInputEditor.tsx @@ -40,7 +40,7 @@ export const TaskInputEditor = forwardRef< ref, ) => { const { isOnline } = useConnectivity(); - const isDisabled = isCreatingTask || !isOnline; + const isSubmitDisabled = isCreatingTask || !isOnline; const { editor, @@ -55,7 +55,8 @@ export const TaskInputEditor = forwardRef< } = useTiptapEditor({ sessionId, placeholder: "What do you want to work on? - @ to add context", - disabled: isDisabled, + disabled: isCreatingTask, + submitDisabled: !isOnline, isLoading: isCreatingTask, autoFocus: true, context: { repoPath }, @@ -95,7 +96,7 @@ export const TaskInputEditor = forwardRef< const getSubmitTooltip = () => { if (isCreatingTask) return "Creating task..."; - if (!isOnline) return "You're offline"; + if (!isOnline) return "You're offline — send when reconnected"; if (isEmpty) return "Enter a task description"; if (!hasDirectory) return "Select a folder first"; if (!canSubmit) return "Missing required fields"; @@ -181,7 +182,7 @@ export const TaskInputEditor = forwardRef<