Add multi-engine search settings, onboarding, and custom search support#278
Add multi-engine search settings, onboarding, and custom search support#278JaggedGem wants to merge 9 commits into
Conversation
- add node scripts to start the browser with omnibox devtools - improve typing to better handle multiple search engines
…th a variable that is replaced at runtime with the user's query - add support to choose either none or any of the available search providers for completion (when custom mode is enabled) - add the custom search engine configuration options to both settings and the onboarding screen
- Introduced a new search settings snapshot mechanism to manage search engine preferences and custom search URL templates. - Updated context menu to utilize dynamic search engine settings for search actions. - Added IPC handlers for retrieving search settings snapshots asynchronously and synchronously. - Refactored basic settings to include search engine options and custom search URL templates. - Implemented validation for custom search URL templates to ensure they meet required formats. - Enhanced onboarding and settings components to support new search engine configurations. - Updated omnibox providers to build search URLs based on selected search engine settings. - Improved overall search experience by integrating custom search capabilities with existing providers.
WalkthroughRefactors search from Google-only to a settings-driven multi-provider system: adds provider abstractions (Google/DuckDuckGo/Yandex/Custom), custom-template validation/builders, provider registry, settings snapshot/IPC/preload accessors, onboarding and settings UI, and context-menu/omnibox integration. ChangesConfigurable Multi-Provider Search
Sequence Diagram(s)sequenceDiagram
participant User
participant OnboardingUI as Onboarding UI
participant FlowSettings as Flow Settings (preload/API)
participant SettingsStore as Settings Store
participant Omnibox
participant ProviderFactory as Search Provider Factory
participant CurrentProvider
User->>OnboardingUI: select provider / edit template
OnboardingUI->>FlowSettings: flow.settings.setSetting(searchEngine, ...)
FlowSettings->>SettingsStore: persist change
User->>Omnibox: type query
Omnibox->>ProviderFactory: getSearchProvider()
ProviderFactory->>FlowSettings: getSearchSettingsSnapshotSync()
FlowSettings-->>ProviderFactory: SearchSettingsSnapshot
ProviderFactory->>CurrentProvider: return matching provider
Omnibox->>CurrentProvider: buildSearchUrl(query) / getSuggestions({ input, signal })
CurrentProvider-->>Omnibox: URL or completions
Omnibox-->>User: show suggestions / navigate
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested labels
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
ESLint install failed. For unrecoverable errors, disable the tool in CodeRabbit configuration. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Greptile SummaryThis PR replaces the hard-coded Google search setup with a fully configurable search system supporting Google, DuckDuckGo, Yandex, and custom URL templates, wiring the selection through onboarding, settings UI, context menus, omnibox suggestions, and the shared URL builder.
Confidence Score: 3/5Mostly safe to merge, but the startup repair path can leave the renderer's displayed engine out of sync with what the main process actually uses. The src/main/saving/settings.ts — the repair function needs to emit a settings-changed notification; src/renderer/src/lib/omnibox-new/search-providers/index.ts — the onSettingsChanged handler issues a synchronous IPC on every settings write. Important Files Changed
Sequence DiagramsequenceDiagram
participant R as Renderer (index.ts)
participant P as Preload
participant M as Main (settings.ts)
participant DS as DataStore
M->>DS: load all settings (parallel)
DS-->>M: raw values
M->>M: repairInvalidActiveSearchConfiguration()
Note over M: mutates in-memory + saves to DS<br/>but does NOT fire onSettingsChanged
M->>M: resolve settingsCachedPromise
R->>P: getSearchSettingsSnapshotSync (sendSync)
P->>M: settings:get-search-settings-snapshot-sync
M-->>P: SearchSettingsSnapshot
P-->>R: snapshot → currentSearchSettings
Note over R: If repair ran before this call ✅ correct<br/>If repair runs after ❌ renderer gets stale value
R->>R: onSettingsChanged → refreshSelectedSearchProvider()
R->>P: getSearchSettingsSnapshotSync (sendSync)
Note over R: fires on ANY setting change, not just search
Reviews (1): Last reviewed commit: "feat: enhance search settings management..." | Re-trigger Greptile |
| function repairInvalidActiveSearchConfiguration() { | ||
| const searchSettings = getSearchSettingsSnapshotFromValues(basicSettingsCurrentValues); | ||
| if (validateActiveSearchSettings(searchSettings).valid) { | ||
| return; | ||
| } | ||
|
|
||
| basicSettingsCurrentValues.searchEngine = DEFAULT_SEARCH_SETTINGS_SNAPSHOT.searchEngine; | ||
| void SettingsDataStore.set("searchEngine", DEFAULT_SEARCH_SETTINGS_SNAPSHOT.searchEngine).catch(() => undefined); | ||
| } |
There was a problem hiding this comment.
Missing listener notification after startup repair
repairInvalidActiveSearchConfiguration mutates basicSettingsCurrentValues and persists the corrected engine to disk, but never calls fireOnSettingsChanged() or settingsEmitter.emit("settings-changed"). The repair runs after settingsCachedPromise resolves, which can happen after the renderer has already called getSearchSettingsSnapshotSync during module initialization. In that sequence the renderer initializes currentSearchSettings to the invalid (pre-repair) value and is never told to refresh it — so the settings UI continues to show the stale engine selection while the main process has silently moved to the default.
| function buildDuckDuckGoSearchUrl(query: string, aiEnabled: boolean = true): string { | ||
| const url = new URL("https://www.duckduckgo.com"); | ||
| url.searchParams.set("q", query); | ||
| if (!aiEnabled) { | ||
| url.searchParams.set("ia", "web"); | ||
| url.searchParams.set("assist", "false"); | ||
| } | ||
| return url.toString(); | ||
| } |
There was a problem hiding this comment.
The
aiEnabled parameter defaults to true and buildSearchUrlFromProviderId always calls the function without a second argument, making the if (!aiEnabled) branch permanently unreachable dead code. Either expose the parameter publicly or remove it.
| function buildDuckDuckGoSearchUrl(query: string, aiEnabled: boolean = true): string { | |
| const url = new URL("https://www.duckduckgo.com"); | |
| url.searchParams.set("q", query); | |
| if (!aiEnabled) { | |
| url.searchParams.set("ia", "web"); | |
| url.searchParams.set("assist", "false"); | |
| } | |
| return url.toString(); | |
| } | |
| function buildDuckDuckGoSearchUrl(query: string): string { | |
| const url = new URL("https://duckduckgo.com"); | |
| url.searchParams.set("q", query); | |
| return url.toString(); | |
| } |
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
| flow.settings.onSettingsChanged(() => { | ||
| refreshSelectedSearchProvider(); | ||
| }); |
There was a problem hiding this comment.
Synchronous IPC on every settings change
The onSettingsChanged callback calls refreshSelectedSearchProvider() → flow.settings.getSearchSettingsSnapshotSync() (a blocking ipcRenderer.sendSync) on every settings mutation, regardless of whether it's search-related. Changing an unrelated setting (e.g., sidebar opacity) will block the renderer's main thread for a round-trip to the main process. Consider reading only when a search-related key changes, or switching to the async getSearchSettingsSnapshot and updating state when the promise resolves.
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/renderer/src/components/onboarding/main.tsx (1)
29-32:⚠️ Potential issue | 🔴 Critical | ⚡ Quick winMissing return statement causes crash at onboarding completion.
When
stageexceeds the bounds of thestagesarray,Stagebecomesundefined. The code callsflow.onboarding.finish()but doesn't return early, so execution continues to line 37 where<Stage key={stage} advance={advance} />attempts to renderundefinedas a component, throwing a runtime error: "Element type is invalid".🐛 Proposed fix
const Stage = stages[stage]; if (!Stage) { flow.onboarding.finish(); + return null; }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/renderer/src/components/onboarding/main.tsx` around lines 29 - 32, When Stage is undefined because stage is out of bounds, call flow.onboarding.finish() and then return early to avoid rendering an invalid component; update the component that uses Stage (the local constant Stage and JSX <Stage key={stage} advance={advance} />) to either return null or a safe fallback immediately after invoking flow.onboarding.finish() so execution does not continue to the JSX that tries to render undefined.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/renderer/src/lib/omnibox-new/search-providers/yandex.ts`:
- Around line 28-30: The mapSuggestionRelevance function in yandex.ts is
duplicate logic also used in duckduckgo.ts; extract the shared calculation into
a new exported utility (e.g., mapSuggestionRelevanceByIndex) and replace the
local mapSuggestionRelevance usage in both yandex.ts and duckduckgo.ts with an
import of that utility; ensure the new function signature matches (index:
number) and returns Math.max(100, 400 - index * 40) so callers like
mapSuggestionRelevance(...) keep the same behavior.
- Around line 32-57: Extract the duplicated URL helpers into a shared module
(e.g., create src/renderer/src/lib/omnibox-new/search-providers/url-utils.ts)
and move normalizeNavigationUrl, isAllowedProtocol and normalizeAndValidateUrl
into it; update yandex.ts and google.ts to import those functions instead of
defining them locally, and standardize the default scheme inside
normalizeNavigationUrl to use https:// (so both providers behave consistently
and securely).
In `@src/renderer/src/lib/search.ts`:
- Around line 9-22: The getSearchSuggestions function currently creates a new
AbortController when signal is undefined, which prevents callers from cancelling
requests and leaks unused controllers; update getSearchSuggestions so it passes
the incoming signal directly to searchProvider.getSuggestions (allowing
undefined) instead of creating new AbortController().signal, or if the provider
interface requires a signal, change the provider type
(SearchProviderRequest.signal) to be optional or document/require callers to
supply a signal—adjust the call site to use getSearchProvider().getSuggestions({
input: query, limit: 10, signal }) and ensure any type changes are made on the
SearchProviderRequest/getSuggestions signatures.
---
Outside diff comments:
In `@src/renderer/src/components/onboarding/main.tsx`:
- Around line 29-32: When Stage is undefined because stage is out of bounds,
call flow.onboarding.finish() and then return early to avoid rendering an
invalid component; update the component that uses Stage (the local constant
Stage and JSX <Stage key={stage} advance={advance} />) to either return null or
a safe fallback immediately after invoking flow.onboarding.finish() so execution
does not continue to the JSX that tries to render undefined.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 8d0ac2fc-73f4-4768-b43b-ed5adbdfbb20
📒 Files selected for processing (24)
package.jsonsrc/main/controllers/tabs-controller/context-menu.tssrc/main/controllers/windows-controller/utils/browser/omnibox.tssrc/main/ipc/window/settings.tssrc/main/modules/basic-settings.tssrc/main/saving/settings.tssrc/preload/index.tssrc/renderer/src/components/onboarding/main.tsxsrc/renderer/src/components/onboarding/stages/search-provider.tsxsrc/renderer/src/components/search/custom-search-engine-fields.tsxsrc/renderer/src/components/settings/sections/general/basic-settings-cards.tsxsrc/renderer/src/lib/omnibox-new/search-providers/custom-utils.tssrc/renderer/src/lib/omnibox-new/search-providers/duckduckgo.tssrc/renderer/src/lib/omnibox-new/search-providers/google.tssrc/renderer/src/lib/omnibox-new/search-providers/index.tssrc/renderer/src/lib/omnibox-new/search-providers/types.tssrc/renderer/src/lib/omnibox-new/search-providers/yandex.tssrc/renderer/src/lib/omnibox-v1.tssrc/renderer/src/lib/omnibox-v2.tssrc/renderer/src/lib/search.tssrc/shared/flow/interfaces/settings/settings.tssrc/shared/search/custom-search.tssrc/shared/search/search-settings.tssrc/shared/types/settings.ts
…ilities for search providers
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
src/shared/search/search-settings.ts (2)
24-28: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick winAdd optional
optionsparameter for signature consistency.Same issue as
buildGoogleSearchUrl— add the optionaloptionsparameter to match the signature expected bybuildSearchUrlFromProviderId.♻️ Suggested fix to add optional parameter
-function buildYandexSearchUrl(query: string): string { +function buildYandexSearchUrl(query: string, options?: SearchUrlBuildOptions): string { const url = new URL("https://yandex.com/search/"); url.searchParams.set("text", query); return url.toString(); }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/shared/search/search-settings.ts` around lines 24 - 28, The buildYandexSearchUrl function lacks the optional options parameter used by buildSearchUrlFromProviderId; update the signature of buildYandexSearchUrl to accept a second optional parameter (e.g., options?: Record<string, any> or matching the type used by buildGoogleSearchUrl) and ensure the implementation ignores or accepts it without breaking behavior (keep query handling and URL construction the same), so the function signature matches buildGoogleSearchUrl and can be called uniformly by buildSearchUrlFromProviderId.
13-22:⚠️ Potential issue | 🟠 MajorDuckDuckGo “classic/non-AI” disabling: use
noai.duckduckgo.cominstead ofia=web/assist=false.In
src/shared/search/search-settings.ts(buildDuckDuckGoSearchUrl), whenduckduckgoAiEnabledis false, DuckDuckGo’s documented AI-off experience is thenoai.duckduckgo.comsubdomain (e.g.,https://noai.duckduckgo.com/?q=...). Theia=webandassist=falseURL parameters aren’t officially documented as a reliable “disable AI” switch.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/shared/search/search-settings.ts` around lines 13 - 22, The buildDuckDuckGoSearchUrl function currently toggles AI off by adding ia=web and assist=false; instead, when options?.duckduckgoAiEnabled is false, switch the URL base to use the noai.duckduckgo.com subdomain (e.g., construct the URL with "https://noai.duckduckgo.com") and remove the ia/assist parameter logic so the query parameter is preserved but the request targets the non-AI domain; update buildDuckDuckGoSearchUrl and references to duckduckgoAiEnabled to use the alternate base URL only when AI is disabled.src/main/saving/settings.ts (1)
30-47: 🧹 Nitpick | 🔵 Trivial | ⚖️ Poor tradeoffConsider reusing
normalizeSearchSettingsSnapshotto reduce duplication.The logic in
getSearchSettingsSnapshotFromValuesclosely mirrorsnormalizeSearchSettingsSnapshotfromsrc/shared/search/search-settings.ts. While the input types differ slightly, the core normalization logic is identical. You could potentially cast or adapt the input to reuse the shared function.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/main/saving/settings.ts` around lines 30 - 47, getSearchSettingsSnapshotFromValues duplicates normalization logic found in normalizeSearchSettingsSnapshot; instead adapt the incoming values into a Partial<SearchSettingsSnapshot> (or cast the relevant fields from SettingType["defaultValue"] to the snapshot shape), call normalizeSearchSettingsSnapshot(...) and return its result so defaults and validation stay centralized. Concretely: in getSearchSettingsSnapshotFromValues build a minimal object with keys searchEngine, customSearchUrlTemplate, customSearchSuggestionsProvider, duckduckgoAiEnabled (or cast values as Partial<SearchSettingsSnapshot>), import normalizeSearchSettingsSnapshot, call normalizeSearchSettingsSnapshot(adaptedValues, getDefaultSearchSettingsSnapshot()) and return that. Ensure types compile (narrow/cast where necessary) and remove the duplicated per-field checks.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/shared/search/search-settings.ts`:
- Around line 7-11: The buildGoogleSearchUrl function lacks an optional options
parameter causing a signature mismatch with buildSearchUrlFromProviderId which
forwards options; update buildGoogleSearchUrl to accept a second optional
parameter (e.g., options?: Record<string, any>) and ignore or use it as needed
so its signature matches other provider builders, keeping the existing behavior
of constructing the URL from the query; reference function names
buildGoogleSearchUrl and buildSearchUrlFromProviderId when making the change.
---
Outside diff comments:
In `@src/main/saving/settings.ts`:
- Around line 30-47: getSearchSettingsSnapshotFromValues duplicates
normalization logic found in normalizeSearchSettingsSnapshot; instead adapt the
incoming values into a Partial<SearchSettingsSnapshot> (or cast the relevant
fields from SettingType["defaultValue"] to the snapshot shape), call
normalizeSearchSettingsSnapshot(...) and return its result so defaults and
validation stay centralized. Concretely: in getSearchSettingsSnapshotFromValues
build a minimal object with keys searchEngine, customSearchUrlTemplate,
customSearchSuggestionsProvider, duckduckgoAiEnabled (or cast values as
Partial<SearchSettingsSnapshot>), import normalizeSearchSettingsSnapshot, call
normalizeSearchSettingsSnapshot(adaptedValues,
getDefaultSearchSettingsSnapshot()) and return that. Ensure types compile
(narrow/cast where necessary) and remove the duplicated per-field checks.
In `@src/shared/search/search-settings.ts`:
- Around line 24-28: The buildYandexSearchUrl function lacks the optional
options parameter used by buildSearchUrlFromProviderId; update the signature of
buildYandexSearchUrl to accept a second optional parameter (e.g., options?:
Record<string, any> or matching the type used by buildGoogleSearchUrl) and
ensure the implementation ignores or accepts it without breaking behavior (keep
query handling and URL construction the same), so the function signature matches
buildGoogleSearchUrl and can be called uniformly by
buildSearchUrlFromProviderId.
- Around line 13-22: The buildDuckDuckGoSearchUrl function currently toggles AI
off by adding ia=web and assist=false; instead, when
options?.duckduckgoAiEnabled is false, switch the URL base to use the
noai.duckduckgo.com subdomain (e.g., construct the URL with
"https://noai.duckduckgo.com") and remove the ia/assist parameter logic so the
query parameter is preserved but the request targets the non-AI domain; update
buildDuckDuckGoSearchUrl and references to duckduckgoAiEnabled to use the
alternate base URL only when AI is disabled.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: e3fdfbbc-14cd-4a87-bc6d-2b2859bdd83a
📒 Files selected for processing (11)
src/main/ipc/window/settings.tssrc/main/modules/basic-settings.tssrc/main/saving/settings.tssrc/preload/index.tssrc/renderer/src/components/onboarding/stages/search-provider.tsxsrc/renderer/src/components/providers/settings-provider.tsxsrc/renderer/src/components/search/duckduckgo-ai-toggle.tsxsrc/renderer/src/components/settings/sections/general/basic-settings-cards.tsxsrc/renderer/src/lib/omnibox-new/search-providers/index.tssrc/shared/flow/interfaces/settings/settings.tssrc/shared/search/search-settings.ts
| function buildGoogleSearchUrl(query: string): string { | ||
| const url = new URL("https://www.google.com/search"); | ||
| url.searchParams.set("q", query); | ||
| return url.toString(); | ||
| } |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial | ⚡ Quick win
Add optional options parameter for signature consistency.
The buildGoogleSearchUrl function doesn't accept an options parameter, but buildSearchUrlFromProviderId (line 121) passes options to all provider build functions. While JavaScript ignores extra arguments, this creates a type inconsistency that may cause TypeScript errors depending on strictness settings.
♻️ Suggested fix to add optional parameter
-function buildGoogleSearchUrl(query: string): string {
+function buildGoogleSearchUrl(query: string, options?: SearchUrlBuildOptions): string {
const url = new URL("https://www.google.com/search");
url.searchParams.set("q", query);
return url.toString();
}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/shared/search/search-settings.ts` around lines 7 - 11, The
buildGoogleSearchUrl function lacks an optional options parameter causing a
signature mismatch with buildSearchUrlFromProviderId which forwards options;
update buildGoogleSearchUrl to accept a second optional parameter (e.g.,
options?: Record<string, any>) and ignore or use it as needed so its signature
matches other provider builders, keeping the existing behavior of constructing
the URL from the query; reference function names buildGoogleSearchUrl and
buildSearchUrlFromProviderId when making the change.
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/renderer/src/components/onboarding/main.tsx (1)
25-27:⚠️ Potential issue | 🟡 MinorUse a functional state update for stage advancement.
advancecurrently doessetStage(stage + 1), which can drop increments ifadvanceis triggered multiple times within the same React batch/event. Use the functional updater instead (Line 25-27).Suggested fix
const advance = () => { - setStage(stage + 1); + setStage((prev) => prev + 1); };🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/renderer/src/components/onboarding/main.tsx` around lines 25 - 27, The advance function uses setStage(stage + 1) which can lose updates when called multiple times in one batch; change it to use the functional updater form — call setStage(prev => prev + 1) inside advance so state increments are applied reliably (locate the advance function and the setStage call).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@src/renderer/src/components/onboarding/main.tsx`:
- Around line 25-27: The advance function uses setStage(stage + 1) which can
lose updates when called multiple times in one batch; change it to use the
functional updater form — call setStage(prev => prev + 1) inside advance so
state increments are applied reliably (locate the advance function and the
setStage call).
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: a53f1e79-cc9f-40f7-b42b-e7149f173d3d
📒 Files selected for processing (8)
src/renderer/src/components/onboarding/main.tsxsrc/renderer/src/lib/omnibox-new/search-providers/duckduckgo.tssrc/renderer/src/lib/omnibox-new/search-providers/google.tssrc/renderer/src/lib/omnibox-new/search-providers/suggestion-utils.tssrc/renderer/src/lib/omnibox-new/search-providers/types.tssrc/renderer/src/lib/omnibox-new/search-providers/url-utils.tssrc/renderer/src/lib/omnibox-new/search-providers/yandex.tssrc/renderer/src/lib/search.ts
Summary
This PR expands Flow's search engine support from a Google-only setup to a fully configurable search system.
Users can now choose between Google, DuckDuckGo, Yandex, or a custom search engine URL. The new search configuration is available both during onboarding and later in settings, and the selected engine is respected across search URL generation, autocomplete suggestions, and related browser UI flows.
What Changed
{{query}}Implementation Details
Validation and Safety
Supporting Changes
Validation
bun run lintbun run typecheckSummary by CodeRabbit
New Features
Bug Fixes
Chores