From 0c7392ec7a92d9d82942bf58d86a6b017d3d2be7 Mon Sep 17 00:00:00 2001 From: "shay.hen" Date: Tue, 10 Mar 2026 13:24:39 +0200 Subject: [PATCH 1/2] fix: skip copy-on-select when search bar has focus Navigating search results in the terminal causes xterm.js to change the selection programmatically, which was triggering copy-on-select and clobbering the clipboard. Skip the clipboard write when an element inside .search-container is the active element. --- frontend/app/view/term/termwrap.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/frontend/app/view/term/termwrap.ts b/frontend/app/view/term/termwrap.ts index f692765868..1cd167c800 100644 --- a/frontend/app/view/term/termwrap.ts +++ b/frontend/app/view/term/termwrap.ts @@ -343,6 +343,12 @@ export class TermWrap { if (!globalStore.get(copyOnSelectAtom)) { return; } + // Don't copy-on-select when the search bar has focus — navigating + // search results changes the terminal selection programmatically. + const active = document.activeElement; + if (active != null && active.closest(".search-container") != null) { + return; + } const selectedText = this.terminal.getSelection(); if (selectedText.length > 0) { navigator.clipboard.writeText(selectedText); From 1c106fb8d95cd9d0a864fe09deb1d6aa74587932 Mon Sep 17 00:00:00 2001 From: "shay.hen" Date: Tue, 10 Mar 2026 13:45:01 +0200 Subject: [PATCH 2/2] fix: refocus search input on repeated Cmd+F MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the search bar was already open, Cmd+F was a no-op because setting the isOpen atom to true again had no effect. Fix uses a per-instance focusInput atom (a monotonic counter) on SearchAtoms: activateSearch increments it for the focused block's search instance, and SearchComponent watches it to call focus()+select() on its own inputRef — avoiding a global DOM query that would target the wrong block when multiple searches are open simultaneously. The counter is reset to 0 on close so re-opening doesn't re-trigger the focus effect. --- frontend/app/element/search.tsx | 15 +++++++++++++++ frontend/app/store/keymodel.ts | 10 +++++++++- frontend/types/custom.d.ts | 3 ++- 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/frontend/app/element/search.tsx b/frontend/app/element/search.tsx index 031bcd97d8..e09e8a1078 100644 --- a/frontend/app/element/search.tsx +++ b/frontend/app/element/search.tsx @@ -26,6 +26,7 @@ const SearchComponent = ({ caseSensitive: caseSensitiveAtom, wholeWord: wholeWordAtom, isOpen: isOpenAtom, + focusInput: focusInputAtom, anchorRef, offsetX = 10, offsetY = 10, @@ -37,6 +38,8 @@ const SearchComponent = ({ const [search, setSearch] = useAtom(searchAtom); const [index, setIndex] = useAtom(indexAtom); const [numResults, setNumResults] = useAtom(numResultsAtom); + const [focusInputCounter, setFocusInputCounter] = useAtom(focusInputAtom); + const inputRef = useRef(null); const handleOpenChange = useCallback((open: boolean) => { setIsOpen(open); @@ -47,6 +50,7 @@ const SearchComponent = ({ setSearch(""); setIndex(0); setNumResults(0); + setFocusInputCounter(0); } }, [isOpen]); @@ -56,6 +60,15 @@ const SearchComponent = ({ onSearch?.(search); }, [search]); + // When activateSearch fires while already open, it increments focusInputCounter + // to signal this specific instance to grab focus (avoids global DOM queries). + useEffect(() => { + if (focusInputCounter > 0 && isOpen) { + inputRef.current?.focus(); + inputRef.current?.select(); + } + }, [focusInputCounter]); + const middleware: Middleware[] = []; const offsetCallback = useCallback( ({ rects }) => { @@ -146,6 +159,7 @@ const SearchComponent = ({
; resultsCount: PrimitiveAtom; isOpen: PrimitiveAtom; + focusInput: PrimitiveAtom; regex?: PrimitiveAtom; caseSensitive?: PrimitiveAtom; wholeWord?: PrimitiveAtom;