From 386882113179734f5b66597ef7f418e8be8b94db Mon Sep 17 00:00:00 2001 From: Charles Vien Date: Thu, 15 Jan 2026 18:45:26 -0800 Subject: [PATCH 1/5] Implement ability to choose key combination to send messages --- .../message-editor/tiptap/useTiptapEditor.ts | 26 ++++-- .../settings/components/SettingsView.tsx | 87 ++++++++++++++++--- .../features/settings/stores/settingsStore.ts | 10 +++ 3 files changed, 101 insertions(+), 22 deletions(-) diff --git a/apps/array/src/renderer/features/message-editor/tiptap/useTiptapEditor.ts b/apps/array/src/renderer/features/message-editor/tiptap/useTiptapEditor.ts index c57ba7566..765cfe8fd 100644 --- a/apps/array/src/renderer/features/message-editor/tiptap/useTiptapEditor.ts +++ b/apps/array/src/renderer/features/message-editor/tiptap/useTiptapEditor.ts @@ -98,14 +98,24 @@ export function useTiptapEditor(options: UseTiptapEditorOptions) { editorProps: { attributes: { class: EDITOR_CLASS }, handleKeyDown: (view, event) => { - if (event.key === "Enter" && !event.shiftKey) { - if (!view.editable) return false; - const suggestionPopup = document.querySelector("[data-tippy-root]"); - if (suggestionPopup) return false; - event.preventDefault(); - historyActions.reset(); - submitRef.current(); - return true; + if (event.key === "Enter") { + const sendMessagesWith = + useSettingsStore.getState().sendMessagesWith; + const isCmdEnterMode = sendMessagesWith === "cmd+enter"; + const isSubmitKey = isCmdEnterMode + ? event.metaKey || event.ctrlKey + : !event.shiftKey; + + if (isSubmitKey) { + if (!view.editable) return false; + const suggestionPopup = + document.querySelector("[data-tippy-root]"); + if (suggestionPopup) return false; + event.preventDefault(); + historyActions.reset(); + submitRef.current(); + return true; + } } if ( diff --git a/apps/array/src/renderer/features/settings/components/SettingsView.tsx b/apps/array/src/renderer/features/settings/components/SettingsView.tsx index ccec761aa..eed59bfdf 100644 --- a/apps/array/src/renderer/features/settings/components/SettingsView.tsx +++ b/apps/array/src/renderer/features/settings/components/SettingsView.tsx @@ -1,6 +1,9 @@ import { useAuthStore } from "@features/auth/stores/authStore"; import { FolderPicker } from "@features/folder-picker/components/FolderPicker"; -import { useSettingsStore } from "@features/settings/stores/settingsStore"; +import { + type SendMessagesWith, + useSettingsStore, +} from "@features/settings/stores/settingsStore"; import { useMeQuery } from "@hooks/useMeQuery"; import { useProjectQuery } from "@hooks/useProjectQuery"; import { useSetHeaderContent } from "@hooks/useSetHeaderContent"; @@ -56,10 +59,14 @@ export function SettingsView() { createPR, cursorGlow, desktopNotifications, + autoConvertLongText, + sendMessagesWith, setAutoRunTasks, setCreatePR, setCursorGlow, setDesktopNotifications, + setAutoConvertLongText, + setSendMessagesWith, } = useSettingsStore(); const terminalLayoutMode = useTerminalLayoutStore( (state) => state.terminalLayoutMode, @@ -160,6 +167,30 @@ export function SettingsView() { [terminalLayoutMode, setTerminalLayout], ); + const handleAutoConvertLongTextChange = useCallback( + (checked: boolean) => { + track(ANALYTICS_EVENTS.SETTING_CHANGED, { + setting_name: "auto_convert_long_text", + new_value: checked, + old_value: autoConvertLongText, + }); + setAutoConvertLongText(checked); + }, + [autoConvertLongText, setAutoConvertLongText], + ); + + const handleSendMessagesWithChange = useCallback( + (value: SendMessagesWith) => { + track(ANALYTICS_EVENTS.SETTING_CHANGED, { + setting_name: "send_messages_with", + new_value: value, + old_value: sendMessagesWith, + }); + setSendMessagesWith(value); + }, + [sendMessagesWith, setSendMessagesWith], + ); + const handleWorktreeLocationChange = async (newLocation: string) => { setLocalWorktreeLocation(newLocation); try { @@ -341,20 +372,48 @@ export function SettingsView() { Chat - - - - Desktop notifications - - - Show notifications when the agent finishes working on a task - + + + + + Desktop notifications + + + Show notifications when the agent finishes working on a + task + + + + + + + + + Send messages with + + + Choose which key combination sends messages. Use + Shift+Enter for new lines. + + + + handleSendMessagesWithChange(value as SendMessagesWith) + } + size="1" + > + + + Enter + ⌘ Enter + + - diff --git a/apps/array/src/renderer/features/settings/stores/settingsStore.ts b/apps/array/src/renderer/features/settings/stores/settingsStore.ts index 7051ae7ba..bbfa2553c 100644 --- a/apps/array/src/renderer/features/settings/stores/settingsStore.ts +++ b/apps/array/src/renderer/features/settings/stores/settingsStore.ts @@ -5,6 +5,7 @@ import { persist } from "zustand/middleware"; export type DefaultRunMode = "local" | "cloud" | "last_used"; export type LocalWorkspaceMode = "worktree" | "root"; +export type SendMessagesWith = "enter" | "cmd+enter"; interface SettingsStore { autoRunTasks: boolean; @@ -16,6 +17,8 @@ interface SettingsStore { defaultModel: string; desktopNotifications: boolean; cursorGlow: boolean; + autoConvertLongText: boolean; + sendMessagesWith: SendMessagesWith; setAutoRunTasks: (autoRun: boolean) => void; setDefaultRunMode: (mode: DefaultRunMode) => void; @@ -26,6 +29,8 @@ interface SettingsStore { setDefaultModel: (model: string) => void; setDesktopNotifications: (enabled: boolean) => void; setCursorGlow: (enabled: boolean) => void; + setAutoConvertLongText: (enabled: boolean) => void; + setSendMessagesWith: (mode: SendMessagesWith) => void; } export const useSettingsStore = create()( @@ -40,6 +45,8 @@ export const useSettingsStore = create()( defaultModel: DEFAULT_MODEL, desktopNotifications: true, cursorGlow: false, + autoConvertLongText: true, + sendMessagesWith: "enter", setAutoRunTasks: (autoRun) => set({ autoRunTasks: autoRun }), setDefaultRunMode: (mode) => set({ defaultRunMode: mode }), @@ -52,6 +59,9 @@ export const useSettingsStore = create()( setDesktopNotifications: (enabled) => set({ desktopNotifications: enabled }), setCursorGlow: (enabled) => set({ cursorGlow: enabled }), + setAutoConvertLongText: (enabled) => + set({ autoConvertLongText: enabled }), + setSendMessagesWith: (mode) => set({ sendMessagesWith: mode }), }), { name: "settings-storage", From 7a2f5afb979485c41899cc10a06cb93630f40a02 Mon Sep 17 00:00:00 2001 From: Charles Vien Date: Fri, 16 Jan 2026 09:41:23 -0800 Subject: [PATCH 2/5] upd --- .../features/settings/components/SettingsView.tsx | 14 -------------- .../features/settings/stores/settingsStore.ts | 5 ----- 2 files changed, 19 deletions(-) diff --git a/apps/array/src/renderer/features/settings/components/SettingsView.tsx b/apps/array/src/renderer/features/settings/components/SettingsView.tsx index eed59bfdf..7872eb07b 100644 --- a/apps/array/src/renderer/features/settings/components/SettingsView.tsx +++ b/apps/array/src/renderer/features/settings/components/SettingsView.tsx @@ -59,13 +59,11 @@ export function SettingsView() { createPR, cursorGlow, desktopNotifications, - autoConvertLongText, sendMessagesWith, setAutoRunTasks, setCreatePR, setCursorGlow, setDesktopNotifications, - setAutoConvertLongText, setSendMessagesWith, } = useSettingsStore(); const terminalLayoutMode = useTerminalLayoutStore( @@ -167,18 +165,6 @@ export function SettingsView() { [terminalLayoutMode, setTerminalLayout], ); - const handleAutoConvertLongTextChange = useCallback( - (checked: boolean) => { - track(ANALYTICS_EVENTS.SETTING_CHANGED, { - setting_name: "auto_convert_long_text", - new_value: checked, - old_value: autoConvertLongText, - }); - setAutoConvertLongText(checked); - }, - [autoConvertLongText, setAutoConvertLongText], - ); - const handleSendMessagesWithChange = useCallback( (value: SendMessagesWith) => { track(ANALYTICS_EVENTS.SETTING_CHANGED, { diff --git a/apps/array/src/renderer/features/settings/stores/settingsStore.ts b/apps/array/src/renderer/features/settings/stores/settingsStore.ts index bbfa2553c..59f05807b 100644 --- a/apps/array/src/renderer/features/settings/stores/settingsStore.ts +++ b/apps/array/src/renderer/features/settings/stores/settingsStore.ts @@ -17,7 +17,6 @@ interface SettingsStore { defaultModel: string; desktopNotifications: boolean; cursorGlow: boolean; - autoConvertLongText: boolean; sendMessagesWith: SendMessagesWith; setAutoRunTasks: (autoRun: boolean) => void; @@ -29,7 +28,6 @@ interface SettingsStore { setDefaultModel: (model: string) => void; setDesktopNotifications: (enabled: boolean) => void; setCursorGlow: (enabled: boolean) => void; - setAutoConvertLongText: (enabled: boolean) => void; setSendMessagesWith: (mode: SendMessagesWith) => void; } @@ -45,7 +43,6 @@ export const useSettingsStore = create()( defaultModel: DEFAULT_MODEL, desktopNotifications: true, cursorGlow: false, - autoConvertLongText: true, sendMessagesWith: "enter", setAutoRunTasks: (autoRun) => set({ autoRunTasks: autoRun }), @@ -59,8 +56,6 @@ export const useSettingsStore = create()( setDesktopNotifications: (enabled) => set({ desktopNotifications: enabled }), setCursorGlow: (enabled) => set({ cursorGlow: enabled }), - setAutoConvertLongText: (enabled) => - set({ autoConvertLongText: enabled }), setSendMessagesWith: (mode) => set({ sendMessagesWith: mode }), }), { From 8c7933978c374f57fb4ae37841ed53860484f947 Mon Sep 17 00:00:00 2001 From: Charles Vien Date: Fri, 16 Jan 2026 09:45:32 -0800 Subject: [PATCH 3/5] typecheck --- .../renderer/features/message-editor/tiptap/useTiptapEditor.ts | 1 + .../features/sessions/components/raw-logs/RawLogsHeader.tsx | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/array/src/renderer/features/message-editor/tiptap/useTiptapEditor.ts b/apps/array/src/renderer/features/message-editor/tiptap/useTiptapEditor.ts index 765cfe8fd..0d34f07a6 100644 --- a/apps/array/src/renderer/features/message-editor/tiptap/useTiptapEditor.ts +++ b/apps/array/src/renderer/features/message-editor/tiptap/useTiptapEditor.ts @@ -1,4 +1,5 @@ import { toast } from "@renderer/utils/toast"; +import { useSettingsStore } from "@stores/settingsStore"; import { useEditor } from "@tiptap/react"; import { useCallback, useRef, useState } from "react"; import { usePromptHistoryStore } from "../stores/promptHistoryStore"; diff --git a/apps/array/src/renderer/features/sessions/components/raw-logs/RawLogsHeader.tsx b/apps/array/src/renderer/features/sessions/components/raw-logs/RawLogsHeader.tsx index 835ae0892..f1ebf0084 100644 --- a/apps/array/src/renderer/features/sessions/components/raw-logs/RawLogsHeader.tsx +++ b/apps/array/src/renderer/features/sessions/components/raw-logs/RawLogsHeader.tsx @@ -10,7 +10,7 @@ interface RawLogsHeaderProps { onToggleSearch: () => void; onCopyAll: () => void; onSearchChange: (query: string) => void; - searchInputRef: RefObject; + searchInputRef: RefObject; } export function RawLogsHeader({ From 453111fd5b5e066662b9cb70021fee4abfe28805 Mon Sep 17 00:00:00 2001 From: Charles Vien Date: Fri, 16 Jan 2026 09:51:52 -0800 Subject: [PATCH 4/5] Update settingsStore.ts --- .../src/renderer/stores/settingsStore.ts | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/apps/array/src/renderer/stores/settingsStore.ts b/apps/array/src/renderer/stores/settingsStore.ts index 27340803c..a8b894f09 100644 --- a/apps/array/src/renderer/stores/settingsStore.ts +++ b/apps/array/src/renderer/stores/settingsStore.ts @@ -2,16 +2,21 @@ import { create } from "zustand"; import { trpcVanilla } from "../trpc"; export type TerminalLayoutMode = "split" | "tabbed"; +export type SendMessagesWith = "enter" | "cmd+enter"; interface SettingsState { terminalLayoutMode: TerminalLayoutMode; + sendMessagesWith: SendMessagesWith; isLoading: boolean; loadTerminalLayout: () => Promise; setTerminalLayout: (mode: TerminalLayoutMode) => Promise; + loadSendMessagesWith: () => Promise; + setSendMessagesWith: (mode: SendMessagesWith) => Promise; } export const useSettingsStore = create()((set) => ({ terminalLayoutMode: "split", + sendMessagesWith: "enter", isLoading: true, loadTerminalLayout: async () => { @@ -37,4 +42,27 @@ export const useSettingsStore = create()((set) => ({ set({ terminalLayoutMode: mode }); } catch (_error) {} }, + + loadSendMessagesWith: async () => { + try { + const mode = await trpcVanilla.secureStore.getItem.query({ + key: "sendMessagesWith", + }); + if (mode === "enter" || mode === "cmd+enter") { + set({ sendMessagesWith: mode }); + } + } catch (_error) { + // Keep default value + } + }, + + setSendMessagesWith: async (mode: SendMessagesWith) => { + try { + await trpcVanilla.secureStore.setItem.query({ + key: "sendMessagesWith", + value: mode, + }); + set({ sendMessagesWith: mode }); + } catch (_error) {} + }, })); From 8c7ffb94a2eda3bba5cea588346c1a26341bf5fb Mon Sep 17 00:00:00 2001 From: Charles Vien Date: Fri, 16 Jan 2026 09:59:26 -0800 Subject: [PATCH 5/5] Update RawLogsHeader.tsx --- .../features/sessions/components/raw-logs/RawLogsHeader.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/array/src/renderer/features/sessions/components/raw-logs/RawLogsHeader.tsx b/apps/array/src/renderer/features/sessions/components/raw-logs/RawLogsHeader.tsx index f1ebf0084..835ae0892 100644 --- a/apps/array/src/renderer/features/sessions/components/raw-logs/RawLogsHeader.tsx +++ b/apps/array/src/renderer/features/sessions/components/raw-logs/RawLogsHeader.tsx @@ -10,7 +10,7 @@ interface RawLogsHeaderProps { onToggleSearch: () => void; onCopyAll: () => void; onSearchChange: (query: string) => void; - searchInputRef: RefObject; + searchInputRef: RefObject; } export function RawLogsHeader({