detached f4caba4

fix(omnibox): debounce search with min 2-char threshold to prevent freeze

commit
f4caba401e38234c7decb64dd18ff87c4160ab02
author
bnema · · 2025-11-29 12:34 UTC
committer
bnema · · 2025-11-29 13:24 UTC
parent
99c61d9
signature
unsigned
3 files · +53 -10
M CHANGELOG.md +1 -0
@@ -15,6 +15,7 @@ All notable changes to this project will be documented in this file.
1515 - **URL normalization**: Consistent URL storage across browser, CLI, and favicon services (strips trailing slashes, normalizes schemes).
1616
1717 ### Fixed
18+- **Omnibox search performance**: Debounce search with 2-character minimum threshold to prevent UI freeze on first keystroke. Applies to both URL/history search and find-in-page modes.
1819 - **Omnibox flash on load**: Load the injected GUI bundle at document-start and initialize the omnibox as soon as DOM is ready, ensuring it is ready without briefly flashing during page startup or new pane creation.
1920 - **Zoom toast suppression**: Suppress zoom toast notifications during browser initialization to prevent UI flash.
2021 - **Cosmetic filter injection**: Inject rules immediately and fix selector normalization for better ad blocking.
@@ -15,6 +15,7 @@ All notable changes to this project will be documented in this file.
15 - **URL normalization**: Consistent URL storage across browser, CLI, and favicon services (strips trailing slashes, normalizes schemes).15 - **URL normalization**: Consistent URL storage across browser, CLI, and favicon services (strips trailing slashes, normalizes schemes).
16 16
17 ### Fixed17 ### Fixed
18+- **Omnibox search performance**: Debounce search with 2-character minimum threshold to prevent UI freeze on first keystroke. Applies to both URL/history search and find-in-page modes.
18 - **Omnibox flash on load**: Load the injected GUI bundle at document-start and initialize the omnibox as soon as DOM is ready, ensuring it is ready without briefly flashing during page startup or new pane creation.19 - **Omnibox flash on load**: Load the injected GUI bundle at document-start and initialize the omnibox as soon as DOM is ready, ensuring it is ready without briefly flashing during page startup or new pane creation.
19 - **Zoom toast suppression**: Suppress zoom toast notifications during browser initialization to prevent UI flash.20 - **Zoom toast suppression**: Suppress zoom toast notifications during browser initialization to prevent UI flash.
20 - **Cosmetic filter injection**: Inject rules immediately and fix selector normalization for better ad blocking.21 - **Cosmetic filter injection**: Inject rules immediately and fix selector normalization for better ad blocking.
M gui/src/components/omnibox/OmniboxInput.svelte +3 -3
@@ -5,7 +5,7 @@
55 -->
66 <script lang="ts">
77 import { omniboxStore } from './stores.svelte.ts';
8- import { debouncedQuery, omniboxBridge } from './messaging';
8+ import { debouncedQuery, debouncedFind, omniboxBridge } from './messaging';
99 import { findInPage } from './find';
1010
1111 // Reactive state
@@ -131,8 +131,8 @@
131131 }
132132 }
133133 } else if (mode === 'find') {
134- // Immediate find for search
135- findInPage(value);
134+ // Debounced find with minimum length to avoid freeze on first letter
135+ debouncedFind(value, findInPage);
136136 }
137137 }
138138
@@ -5,7 +5,7 @@
5 -->5 -->
6 <script lang="ts">6 <script lang="ts">
7 import { omniboxStore } from './stores.svelte.ts';7 import { omniboxStore } from './stores.svelte.ts';
8- import { debouncedQuery, omniboxBridge } from './messaging';8+ import { debouncedQuery, debouncedFind, omniboxBridge } from './messaging';
9 import { findInPage } from './find';9 import { findInPage } from './find';
10 10
11 // Reactive state11 // Reactive state
@@ -131,8 +131,8 @@
131 }131 }
132 }132 }
133 } else if (mode === 'find') {133 } else if (mode === 'find') {
134- // Immediate find for search134+ // Debounced find with minimum length to avoid freeze on first letter
135- findInPage(value);135+ debouncedFind(value, findInPage);
136 }136 }
137 }137 }
138 138
M gui/src/components/omnibox/messaging.ts +49 -7
@@ -191,17 +191,59 @@ export class OmniboxBridge implements OmniboxMessageBridge {
191191 // Singleton instance
192192 export const omniboxBridge = new OmniboxBridge();
193193
194+// Minimum characters required before triggering search operations
195+const MIN_SEARCH_LENGTH = 2;
196+
197+// Debounce timers for different operations
198+const debounceTimers: Record<string, number> = {};
199+
194200 /**
195- * Debounced query function for search input
201+ * Creates a debounced search function with minimum length threshold
202+ * Prevents freezing on first letter and rapid input
196203 */
197-export function debouncedQuery(searchTerm: string): void {
198- omniboxStore.clearDebounceTimer();
204+function createDebouncedSearch(
205+ key: string,
206+ action: (query: string) => void,
207+ onClear?: () => void
208+): (query: string) => void {
209+ return (query: string) => {
210+ // Clear previous timer
211+ if (debounceTimers[key]) {
212+ clearTimeout(debounceTimers[key]);
213+ debounceTimers[key] = 0;
214+ }
199215
200- const timerId = window.setTimeout(() => {
201- omniboxBridge.query(searchTerm);
202- }, omniboxStore.config.debounceDelay);
216+ const trimmed = (query || "").trim();
203217
204- omniboxStore.setDebounceTimer(timerId);
218+ // Clear/reset immediately if query is empty or too short
219+ if (trimmed.length < MIN_SEARCH_LENGTH) {
220+ if (onClear) {
221+ onClear();
222+ }
223+ return;
224+ }
225+
226+ // Debounce the actual search operation
227+ debounceTimers[key] = window.setTimeout(() => {
228+ action(query);
229+ }, omniboxStore.config.debounceDelay);
230+ };
231+}
232+
233+/**
234+ * Debounced query function for omnibox search input
235+ */
236+export const debouncedQuery = createDebouncedSearch(
237+ "query",
238+ (searchTerm) => omniboxBridge.query(searchTerm),
239+ () => omniboxStore.updateSuggestions([])
240+);
241+
242+/**
243+ * Debounced find function for find-in-page
244+ */
245+export function debouncedFind(query: string, findFn: (q: string) => void): void {
246+ createDebouncedSearch("find", findFn, () => findFn(""))(query);
205247 }
206248
207249 // Extend global window interface for Go bridge compatibility
@@ -191,17 +191,59 @@ export class OmniboxBridge implements OmniboxMessageBridge {
191 // Singleton instance191 // Singleton instance
192 export const omniboxBridge = new OmniboxBridge();192 export const omniboxBridge = new OmniboxBridge();
193 193
194+// Minimum characters required before triggering search operations
195+const MIN_SEARCH_LENGTH = 2;
196+
197+// Debounce timers for different operations
198+const debounceTimers: Record<string, number> = {};
199+
194 /**200 /**
195- * Debounced query function for search input201+ * Creates a debounced search function with minimum length threshold
202+ * Prevents freezing on first letter and rapid input
196 */203 */
197-export function debouncedQuery(searchTerm: string): void {204+function createDebouncedSearch(
198- omniboxStore.clearDebounceTimer();205+ key: string,
206+ action: (query: string) => void,
207+ onClear?: () => void
208+): (query: string) => void {
209+ return (query: string) => {
210+ // Clear previous timer
211+ if (debounceTimers[key]) {
212+ clearTimeout(debounceTimers[key]);
213+ debounceTimers[key] = 0;
214+ }
199 215
200- const timerId = window.setTimeout(() => {216+ const trimmed = (query || "").trim();
201- omniboxBridge.query(searchTerm);
202- }, omniboxStore.config.debounceDelay);
203 217
204- omniboxStore.setDebounceTimer(timerId);218+ // Clear/reset immediately if query is empty or too short
219+ if (trimmed.length < MIN_SEARCH_LENGTH) {
220+ if (onClear) {
221+ onClear();
222+ }
223+ return;
224+ }
225+
226+ // Debounce the actual search operation
227+ debounceTimers[key] = window.setTimeout(() => {
228+ action(query);
229+ }, omniboxStore.config.debounceDelay);
230+ };
231+}
232+
233+/**
234+ * Debounced query function for omnibox search input
235+ */
236+export const debouncedQuery = createDebouncedSearch(
237+ "query",
238+ (searchTerm) => omniboxBridge.query(searchTerm),
239+ () => omniboxStore.updateSuggestions([])
240+);
241+
242+/**
243+ * Debounced find function for find-in-page
244+ */
245+export function debouncedFind(query: string, findFn: (q: string) => void): void {
246+ createDebouncedSearch("find", findFn, () => findFn(""))(query);
205 }247 }
206 248
207 // Extend global window interface for Go bridge compatibility249 // Extend global window interface for Go bridge compatibility

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 8

Tags 42

↑↓ moveenter openesc close