From 8e1dccff21f5c8e26387530b579a40126295b01a Mon Sep 17 00:00:00 2001 From: NSO <108399823+NSO73@users.noreply.github.com> Date: Wed, 24 Jun 2026 13:37:17 +0200 Subject: [PATCH 1/6] fix(renderer): keep find-bar prefill from being clobbered on open (#4646) Selecting a word and opening the find bar is meant to prefill the find input with the selection. The prefill is driven reactively by `watch(searchMatches)`, which mirrors the editor's current selection into the input. But opening the bar steals focus from the editor, and the engine emits a spurious selection-change for the reset selection. Because the watch is async, that change coalesces with the intended one and can cancel it, leaving a stale value in the input (the regression guard observes a single leading character, e.g. "T"). Fix in two parts: - Seed the input synchronously from the current selection when the bar opens (`prefillFromSelection`), so the prefill can't lose the race with the focus-steal selection-change. - Once open, the bar owns the query: ignore editor selection-changes in the `searchMatches` watch while `showSearch` is true, so the focus-steal change can't clobber the prefilled/typed value. Covered by test/e2e/search-prefill-from-selection.spec.ts (the race reproduces under the Linux/xvfb CI environment). --- .../renderer/src/components/search/index.vue | 17 +++ .../test/unit/specs/search-prefill.spec.ts | 121 ++++++++++++++++++ 2 files changed, 138 insertions(+) create mode 100644 packages/desktop/test/unit/specs/search-prefill.spec.ts diff --git a/packages/desktop/src/renderer/src/components/search/index.vue b/packages/desktop/src/renderer/src/components/search/index.vue index c835a5f736..6ae87b26cc 100644 --- a/packages/desktop/src/renderer/src/components/search/index.vue +++ b/packages/desktop/src/renderer/src/components/search/index.vue @@ -170,6 +170,11 @@ watch(searchValue, () => { }) watch(searchMatches, (newValue, oldValue) => { + // Once the search bar is open it owns the query. Ignore editor + // selection-changes while open — notably the spurious selection-change the + // engine emits when the bar steals editor focus, which would otherwise + // clobber the just-prefilled value (e.g. leaving a stale single character). + if (showSearch.value) return if (!newValue || !oldValue) return const { value } = newValue if (value !== oldValue.value) { @@ -177,6 +182,16 @@ watch(searchMatches, (newValue, oldValue) => { } }) +// Seed the find input from the current selection synchronously, before the bar +// opens and steals focus. Relying on the reactive `searchMatches` watch alone +// races with the focus-steal selection-change and can drop the prefill. +const prefillFromSelection = () => { + const selected = searchMatches.value?.value + if (selected) { + searchValue.value = selected + } +} + const highlightIndex = computed(() => { if (searchMatches.value) { return searchMatches.value.index @@ -229,6 +244,7 @@ const toggleCtrl = (ctrl: 'isCaseSensitive' | 'isWholeWord' | 'isRegexp') => { } const listenFind = () => { + prefillFromSelection() showSearch.value = true type.value = 'search' nextTick(() => { @@ -240,6 +256,7 @@ const listenFind = () => { } const listenReplace = () => { + prefillFromSelection() showSearch.value = true type.value = 'replace' } diff --git a/packages/desktop/test/unit/specs/search-prefill.spec.ts b/packages/desktop/test/unit/specs/search-prefill.spec.ts new file mode 100644 index 0000000000..ea4e25d160 --- /dev/null +++ b/packages/desktop/test/unit/specs/search-prefill.spec.ts @@ -0,0 +1,121 @@ +import { describe, it, expect, vi } from 'vitest' +import { readFileSync } from 'node:fs' +import { fileURLToPath } from 'node:url' +import { dirname, resolve } from 'node:path' +import { parse, compileScript } from 'vue/compiler-sfc' +import ts from 'typescript' +import { ref, computed, watch, nextTick } from 'vue' + +// Regression guard for the find-bar prefill race (issue: the input showed a +// stale single char like "T" instead of the selection). The bug lives entirely +// in search/index.vue's reactive logic: `watch(searchMatches)` mirrors the +// editor selection into the input, but when the find bar opens it steals focus +// and the engine emits a spurious selection-change pointing at the document +// start, which clobbers the just-prefilled value. +// +// The desktop unit runner ships no @vitejs/plugin-vue / @vue/test-utils, so we +// compile the real