From 56b0dc7d08c99a37178a3f23f398fbf5d8296e97 Mon Sep 17 00:00:00 2001 From: Brooklyn Nicholson Date: Mon, 27 Apr 2026 16:49:49 -0500 Subject: [PATCH] fix(tui): clear selections from blank composer space Clicking blank space in the transcript or composer now clears active TUI/input selections like a normal text surface. TextInput clicks stop bubbling so cursor placement and selection gestures keep their local behavior. Made-with: Cursor --- ui-tui/src/app/interfaces.ts | 1 + ui-tui/src/app/useMainApp.ts | 10 +++++++++- ui-tui/src/components/appLayout.tsx | 25 +++++++++++++++++++++++-- ui-tui/src/components/textInput.tsx | 3 ++- 4 files changed, 35 insertions(+), 4 deletions(-) diff --git a/ui-tui/src/app/interfaces.ts b/ui-tui/src/app/interfaces.ts index 1d7cdaead0..9b987f87d3 100644 --- a/ui-tui/src/app/interfaces.ts +++ b/ui-tui/src/app/interfaces.ts @@ -285,6 +285,7 @@ export interface AppLayoutActions { answerClarify: (answer: string) => void answerSecret: (value: string) => void answerSudo: (pw: string) => void + clearSelection: () => void onModelSelect: (value: string) => void resumeById: (id: string) => void setStickyPrompt: (value: string) => void diff --git a/ui-tui/src/app/useMainApp.ts b/ui-tui/src/app/useMainApp.ts index 9f2cae78d6..168454f8ba 100644 --- a/ui-tui/src/app/useMainApp.ts +++ b/ui-tui/src/app/useMainApp.ts @@ -25,6 +25,7 @@ import type { Msg, PanelSection, SlashCatalog } from '../types.js' import { createGatewayEventHandler } from './createGatewayEventHandler.js' import { createSlashHandler } from './createSlashHandler.js' +import { getInputSelection } from './inputSelectionStore.js' import { type GatewayRpc, type TranscriptRow } from './interfaces.js' import { $overlayState, patchOverlayState } from './overlayStore.js' import { scrollWithSelectionBy } from './scroll.js' @@ -147,6 +148,11 @@ export function useMainApp(gw: GatewayClient) { selection.setSelectionBgColor(ui.theme.color.selectionBg) }, [selection, ui.theme.color.selectionBg]) + const clearSelection = useCallback(() => { + selection.clearSelection() + getInputSelection()?.clear() + }, [selection]) + const composer = useComposerState({ gw, onClipboardPaste: quiet => clipboardPasteRef.current(quiet), @@ -519,6 +525,7 @@ export function useMainApp(gw: GatewayClient) { [ appendMessage, bellOnComplete, + clearSelection, composerActions.setInput, gateway, panel, @@ -691,11 +698,12 @@ export function useMainApp(gw: GatewayClient) { answerClarify, answerSecret, answerSudo, + clearSelection, onModelSelect, resumeById: session.resumeById, setStickyPrompt }), - [answerApproval, answerClarify, answerSecret, answerSudo, onModelSelect, session.resumeById] + [answerApproval, answerClarify, answerSecret, answerSudo, clearSelection, onModelSelect, session.resumeById] ) const appComposer = useMemo( diff --git a/ui-tui/src/components/appLayout.tsx b/ui-tui/src/components/appLayout.tsx index e4e9608365..3c7989a93e 100644 --- a/ui-tui/src/components/appLayout.tsx +++ b/ui-tui/src/components/appLayout.tsx @@ -47,7 +47,18 @@ const TranscriptPane = memo(function TranscriptPane({ return ( <> - + { + if (e.cellIsBlank) { + actions.clearSelection() + } + }} + ref={transcript.scrollRef} + stickyScroll + > {transcript.virtualHistory.topSpacer > 0 ? : null} @@ -118,7 +129,17 @@ const ComposerPane = memo(function ComposerPane({ const inputHeight = inputVisualHeight(composer.input, inputColumns) return ( - + { + if (e.cellIsBlank) { + actions.clearSelection() + } + }} + paddingX={1} + > { + onClick={(e: { localCol?: number; localRow?: number; stopImmediatePropagation?: () => void }) => { if (!focus) { return } + e.stopImmediatePropagation?.() clearSel() const pos = mouseOffset(e) const next = offsetFromPosition(display, pos.row, pos.col, columns)