diff --git a/devlog/_plan/260912_provider_catalog_unified_search/040_r1_unified_search.md b/devlog/_plan/260912_provider_catalog_unified_search/040_r1_unified_search.md
index e0d0f11b45..45a7f084dc 100644
--- a/devlog/_plan/260912_provider_catalog_unified_search/040_r1_unified_search.md
+++ b/devlog/_plan/260912_provider_catalog_unified_search/040_r1_unified_search.md
@@ -70,3 +70,28 @@ comparator, and the alias rules all live in `provider-presets.ts` and are unit-t
without React. The component test covers: a query with zero hits in the active tab
leaves `tier` untouched; tab click with a non-empty query does not clear the query; an
in-flight Accounts login keeps its `LoginHint` and paste field across a chip click.
+
+## What changed against this doc during the build
+
+Five things the plan did not anticipate, each found by an independent reviewer or by
+driving the surface:
+
+- **The group heading is not sticky.** A sticky bar needs an opaque background, and
+ `.modal-card` is a translucent glass panel, so it seamed against the card behind it —
+ and it read as a grey slab. It is now a small-caps `
` with a hairline rule that
+ scrolls with the content; the chips above are already the index.
+- **The chip lookup cannot use `CSS.escape`.** Group ids come from `useId`, which emits
+ colons, so selecting on one needs `CSS.escape` — and `CSS` does not exist in the
+ happy-dom environment the GUI tests run in, so a chip click would have thrown
+ `ReferenceError` on CI rather than merely being untested. The heading carries a
+ `data-catalog-group` attribute instead.
+- **The jump scrolls the list itself**, and focuses with `preventScroll`. `.modal-card`
+ is also a scroll container, so both `scrollIntoView` and a plain `focus()` could drag
+ the search field out of view while jumping between groups.
+- **Loading and empty states key off what is actually on screen.** Using the total match
+ count meant the unfiltered Accounts bucket — which almost always holds an OpenAI login
+ row — kept a still-loading Free tab from ever saying it was loading. `visibleCount` is
+ the selected tab's own row count while browsing.
+- **The login/preset dedupe is a named helper**, `dropPresetsCoveredByAccounts`, rather
+ than a `Set` inside the memo, so the rule that a login row outranks a preset of the
+ same id has its own unit test.
diff --git a/devlog/_plan/260912_provider_catalog_unified_search/060_delivery_record.md b/devlog/_plan/260912_provider_catalog_unified_search/060_delivery_record.md
new file mode 100644
index 0000000000..3a8528892b
--- /dev/null
+++ b/devlog/_plan/260912_provider_catalog_unified_search/060_delivery_record.md
@@ -0,0 +1,52 @@
+# 060 — Delivery record
+
+Four dependent pull requests onto `dev`, one per work phase, every push `--no-verify`,
+no local test suite run at any point. Verified 2026-09-12.
+
+| PR | branch | base | head | live CI run |
+|---|---|---|---|---|
+| #4324 | `codex/provider-catalog-plan` | `dev` | `67657b9655` | `34666254983` success |
+| #4325 | `codex/provider-catalog-local-tab` | #4324 head | `7ae08971c7` | `34668670304` success |
+| #4328 | `codex/provider-catalog-note-popup` | #4325 head | `e3fdf8fd26` | `34668670844` success |
+| #4331 | `codex/provider-catalog-unified-search` | #4328 head | `68e6b028d7` | `34668669980` success |
+
+Ancestry is a real chain, checked with `git merge-base --is-ancestor`:
+`67657b9655` ⊂ `7ae08971c7` ⊂ `e3fdf8fd26` ⊂ `68e6b028d7`. Each PR's diff against its
+own base carries only that phase's work; no parent commit is replayed.
+
+## Two CI facts that are easy to misread
+
+**Cancelled duplicate runs leave FAILURE rows on a live head.** Pushing the rebased
+chain started overlapping workflow runs, and the concurrency group cancelled the older
+ones. A cancelled run's `ci` aggregator concludes *failure* with
+`needed job(s) did not pass: changes=cancelled`, and that row stays attached to the same
+head SHA as the live green run. On `e3fdf8fd26` the failing `ci` is job `103485691739`
+on cancelled run `34668670665`; the live aggregator `103488063420` on `34668670844`
+succeeded. Read the run, not the rollup.
+
+**A cancelled required check is not a passing one.** All three `enforce-target`
+attempts on `e3fdf8fd26` were cancelled the same way, which left #4328 `UNSTABLE` even
+though its product CI was green — `gh pr checks` maps a cancelled required check to
+fail. Re-running `34668680065` produced a success on that exact head and the PR went
+`CLEAN`. This was found by an independent auditor, not by reading the rollup.
+
+#4324 reports `BLOCKED` because `dev` requires a pull-request review; that is branch
+protection, not a check failure.
+
+## What CI proved that local runs did not
+
+The test suite was never run in this worktree, by instruction. Two defects were caught
+that a local run would have caught instantly, and both were found by static review
+instead:
+
+- `CSS.escape` in the chip jump would have thrown `ReferenceError` under bun/happy-dom,
+ taking the new chip test red. Replaced with a `data-catalog-group` attribute lookup
+ before it ever reached CI.
+- A third defect did reach CI: `gui/tests/fr-localization.test.ts` rejects a French
+ value identical to its English source, and `modal.tab.local` is `"Local"` in both.
+ "Local" is genuinely the same word in French and the Local *badge* was already on that
+ allowlist, so the tab joined it. Fixed at the root of the stack and the two children
+ were rebased onto it, which is why #4328 and #4331 were force-pushed once.
+
+Local checks that were run: `bun x tsc --noEmit`, `cd gui && bun x tsc --noEmit -p
+tsconfig.json`, and `bun run structure:check`. Everything else is **NOT RUN** locally.
diff --git a/devlog/_plan/260912_provider_catalog_unified_search/evidence/wp4-browse-mode.png b/devlog/_plan/260912_provider_catalog_unified_search/evidence/wp4-browse-mode.png
new file mode 100644
index 0000000000..d311c5d86f
Binary files /dev/null and b/devlog/_plan/260912_provider_catalog_unified_search/evidence/wp4-browse-mode.png differ
diff --git a/devlog/_plan/260912_provider_catalog_unified_search/evidence/wp4-search-accounts.png b/devlog/_plan/260912_provider_catalog_unified_search/evidence/wp4-search-accounts.png
new file mode 100644
index 0000000000..8b4d963812
Binary files /dev/null and b/devlog/_plan/260912_provider_catalog_unified_search/evidence/wp4-search-accounts.png differ
diff --git a/devlog/_plan/260912_provider_catalog_unified_search/evidence/wp4-search-groups.png b/devlog/_plan/260912_provider_catalog_unified_search/evidence/wp4-search-groups.png
new file mode 100644
index 0000000000..cf77ac92b8
Binary files /dev/null and b/devlog/_plan/260912_provider_catalog_unified_search/evidence/wp4-search-groups.png differ
diff --git a/docs-site/src/content/docs/guides/web-dashboard.md b/docs-site/src/content/docs/guides/web-dashboard.md
index 7e86901233..a077b39f28 100644
--- a/docs-site/src/content/docs/guides/web-dashboard.md
+++ b/docs-site/src/content/docs/guides/web-dashboard.md
@@ -87,7 +87,7 @@ badge or the version value to read the full value.
| **Windows tray** | Install a per-user login tray for one-click proxy start, stop, restart, dashboard access, and status. The tray is a controller, not a proxy restart service. |
| **Codex autostart** | Allow an already-installed Codex launcher shim to run `ocx ensure`. This toggle does not install a shim or background service. |
| **Providers** | Add, edit, set the default (enabled providers only), enable/disable, and remove providers; manage OAuth account pools and API-key pools where supported. Removing the current default switches to the first remaining enabled provider when one exists; otherwise deletion is refused and the current default is kept. Provider Settings can disable live model discovery for endpoints with missing, slow, or oversized `/models` catalogs. For Claude (Anthropic) OAuth pools, each logged-in account shows its own 5-hour and weekly rate-limit bars (usage is per credential); a failed probe keeps the last-known bars and marks them unavailable until the next successful refresh. The Provider Overview shown when no provider is selected carries a **Refresh all quotas** control that forces one server-side re-read of every configured provider; a provider whose upstream probe fails keeps its last-good row, so the status line reports that the check completed rather than claiming every value is fresh, and each row's own age stays the per-provider freshness signal. |
-| **Add provider** | Search registry-backed presets for account login, API-key services, local servers, or a custom endpoint. |
+| **Add provider** | One search above the tabs reaches all four at once — Accounts, Free, Local, Paid. While a query is live the results are grouped by tab with a count each, and the selected tab stays put rather than jumping. Local runtimes (Ollama, vLLM, LM Studio, LiteLLM) have their own tab, and a long provider note clamps to two lines with the full text one click away. |
| **Codex Auth** | Add ChatGPT/Codex pool accounts, select the next-session account, refresh 5h / weekly / 30d quotas, enable or disable quota auto-switch, set its 1–100% threshold, and configure transient-failure failover. |
| **Subagents** | Feature up to five bare native or namespaced routed models in the `spawn_agent` override list. |
| **Models** | Toggle native GPT and routed models, set provider allowlists and context caps, choose v1/base/v2, and configure the v2 thread limit. Configured providers stay visible as zero-model groups when discovery is off or returns no rows. |
diff --git a/docs-site/src/content/docs/ko/guides/web-dashboard.md b/docs-site/src/content/docs/ko/guides/web-dashboard.md
index efdd80179f..4ae2762b1a 100644
--- a/docs-site/src/content/docs/ko/guides/web-dashboard.md
+++ b/docs-site/src/content/docs/ko/guides/web-dashboard.md
@@ -39,7 +39,7 @@ bun run dev:gui
| **Windows 트레이** | 로그인할 때 사용자 전용 트레이를 시작하고 프록시 시작·중지·재시작·대시보드·상태를 클릭으로 제어합니다. 트레이는 재시작 서비스가 아닙니다. |
| **Codex 자동 시작** | 이미 설치된 Codex launcher shim이 `ocx ensure`를 실행하도록 허용합니다. 이 토글은 shim이나 백그라운드 서비스를 설치하지 않습니다. |
| **Providers** | 프로바이더를 추가, 편집, 기본으로 설정(활성만), 활성화/비활성화, 제거하고, 지원되는 OAuth 계정 풀과 API key 풀을 관리합니다. 현재 기본 프로바이더를 제거하면 남아 있는 첫 번째 활성 프로바이더로 전환됩니다(있는 경우); 없으면 삭제가 거부되고 현재 기본이 유지됩니다. Claude(Anthropic) OAuth 풀에서는 로그인한 계정마다 자체 5시간·주간 한도 막대가 표시되며(사용량은 자격 증명 단위), 조회 실패 시 마지막 값을 유지하고 일시 불가 상태로 표시합니다. 프로바이더를 선택하지 않았을 때 보이는 Provider Overview에는 **전체 할당량 갱신** 버튼이 있어 설정된 모든 프로바이더를 서버에서 한 번에 다시 읽습니다. 조회에 실패한 프로바이더는 마지막 값을 유지하므로, 상태 문구는 모든 값이 새로 왔다고 말하지 않고 조회가 끝났다고만 알리며, 각 행의 확인 시각이 프로바이더별 신선도를 보여 줍니다. |
-| **Add provider** | 레지스트리 기반 프리셋에서 계정 로그인, API key 서비스, 로컬 서버, custom endpoint를 검색합니다. |
+| **Add provider** | 탭 위의 검색창 하나가 계정·무료·로컬·유료 네 탭을 한 번에 찾습니다. 검색 중에는 선택한 탭이 움직이지 않고, 결과를 탭별로 묶어 개수와 함께 보여 줍니다. 로컬 런타임(Ollama, vLLM, LM Studio, LiteLLM)은 별도 탭을 쓰며, 긴 설명은 두 줄로 줄고 클릭하면 전체가 열립니다. |
| **Codex Auth** | ChatGPT/Codex 풀 계정을 추가하고, 다음 세션 계정을 선택하고, 5시간 / 주간 / 30일 할당량을 갱신하며, 할당량 자동 전환을 켜거나 끄고 1~100% 임계값과 일시적 실패 failover를 설정합니다. |
| **Subagents** | `spawn_agent` override 목록에 네이티브 또는 라우팅 모델을 최대 5개까지 우선 노출합니다. |
| **Models** | 네이티브 GPT와 라우팅 모델을 켜고 끄고, 프로바이더 allowlist와 컨텍스트 상한, v1/base/v2, v2 thread 수를 설정합니다. |
diff --git a/docs-site/src/content/docs/ru/guides/web-dashboard.md b/docs-site/src/content/docs/ru/guides/web-dashboard.md
index 4600b51ace..05b11a8780 100644
--- a/docs-site/src/content/docs/ru/guides/web-dashboard.md
+++ b/docs-site/src/content/docs/ru/guides/web-dashboard.md
@@ -39,7 +39,7 @@ bun run dev:gui
| **Трей Windows** | Устанавливает пользовательский значок входа для запуска, остановки, перезапуска, панели и состояния прокси одним щелчком. Трей не является службой перезапуска. |
| **Автозапуск Codex** | Разрешает уже установленному launcher shim Codex выполнять `ocx ensure`. Переключатель не устанавливает shim или фоновую службу. |
| **Providers** | Добавление, редактирование, назначение провайдера по умолчанию (только включённые), включение/отключение и удаление провайдеров; управление пулами OAuth-аккаунтов и пулами API-ключей там, где они поддерживаются. При удалении текущего провайдера по умолчанию выбирается первый оставшийся включённый провайдер, если он есть; иначе удаление отклоняется и текущий default сохраняется. Для пулов Claude (Anthropic) OAuth у каждого вошедшего аккаунта свои полосы 5-часового и недельного лимита (использование по учётным данным); при сбое опроса сохраняются последние известные значения с пометкой недоступности. Обзор Providers, который показывается, когда провайдер не выбран, содержит кнопку **Обновить все квоты**: она принудительно перечитывает на сервере все настроенные провайдеры за один раз. Провайдер с неудачным опросом сохраняет последнее известное значение, поэтому статус сообщает лишь о завершении проверки, а не о том, что все значения свежие; время проверки в каждой строке остаётся признаком актуальности для конкретного провайдера. |
-| **Add provider** | Поиск по пресетам из реестра: вход по аккаунту, сервисы с API-ключом, локальные серверы или пользовательская конечная точка. |
+| **Add provider** | Одно поле поиска над вкладками ищет сразу по всем четырём — Аккаунты, Бесплатные, Локальные, Платные. Во время поиска выбранная вкладка не переключается, а результаты группируются по вкладкам с количеством. У локальных сред выполнения (Ollama, vLLM, LM Studio, LiteLLM) своя вкладка, а длинное описание сворачивается до двух строк и открывается целиком по клику. |
| **Codex Auth** | Добавление аккаунтов пула ChatGPT/Codex, выбор аккаунта для следующей сессии, обновление квот 5 ч / недельных / 30-дневных, включение или отключение автопереключения, настройка его порога 1–100% и failover при временных сбоях. |
| **Subagents** | Выделение до пяти «голых» нативных или маршрутизируемых моделей с пространством имён в списке переопределений `spawn_agent`. |
| **Models** | Включение и отключение нативных GPT и маршрутизируемых моделей, настройка списков разрешённых провайдеров и лимитов контекста, выбор v1/base/v2 и настройка лимита потоков v2. |
diff --git a/docs-site/src/content/docs/zh-cn/guides/web-dashboard.md b/docs-site/src/content/docs/zh-cn/guides/web-dashboard.md
index dbb6383120..62eaad0bc2 100644
--- a/docs-site/src/content/docs/zh-cn/guides/web-dashboard.md
+++ b/docs-site/src/content/docs/zh-cn/guides/web-dashboard.md
@@ -38,7 +38,7 @@ bun run dev:gui
| **Windows 托盘** | 安装用户登录托盘,一键控制代理启动、停止、重启、面板和状态。托盘不是代理重启服务。 |
| **Codex 自动启动** | 允许已安装的 Codex launcher shim 运行 `ocx ensure`。此开关不会安装 shim 或后台服务。 |
| **Providers** | 添加、编辑、设为默认(仅已启用)、启用/禁用、删除 provider,并在支持时管理 OAuth 账号池和 API key 池。删除当前默认时,会切换到剩余的第一个已启用 provider(若存在);否则拒绝删除并保留当前默认。Claude(Anthropic)OAuth 池中,每个已登录账号显示各自的 5 小时与周限额条(用量按凭证计);探测失败时保留上次已知数值并标记为暂时不可用。 未选中任何 provider 时显示的 Providers 概览带有**刷新全部额度**按钮,会在服务端一次性重新读取所有已配置的 provider;上游探测失败的 provider 会保留上次已知数值,因此状态文案只表示检查已完成,而不声称每个数值都是最新的,各行自身的检查时间仍是该 provider 的新鲜度信号。 |
-| **Add provider** | 搜索 registry preset,选择账号登录、API key 服务、本地服务器或自定义 endpoint。 |
+| **Add provider** | 标签页上方的单个搜索框可同时搜索账号、免费、本地、付费四个标签页。搜索时选中的标签页不会跳转,结果按标签页分组并显示数量。本地运行时(Ollama、vLLM、LM Studio、LiteLLM)拥有独立标签页,过长的说明会截断为两行,点击即可查看全文。 |
| **Codex Auth** | 添加 ChatGPT/Codex 池账号,选择下一 session 的账号,刷新 5h / 每周 / 30d 配额,启用或停用配额自动切换,设置其 1–100% 阈值和临时故障 failover。 |
| **Subagents** | 在 `spawn_agent` override 列表中置顶最多五个原生或路由模型。 |
| **Models** | 开关原生 GPT 与路由模型,配置 provider allowlist、上下文上限、v1/base/v2 以及 v2 thread 数量。 |
diff --git a/docs-site/src/content/docs/zh-tw/guides/web-dashboard.md b/docs-site/src/content/docs/zh-tw/guides/web-dashboard.md
index 358624f2f9..3b7005fd76 100644
--- a/docs-site/src/content/docs/zh-tw/guides/web-dashboard.md
+++ b/docs-site/src/content/docs/zh-tw/guides/web-dashboard.md
@@ -43,7 +43,7 @@ GUI session 簽發到服務的頁面中,並在到期或代理重啟時靜默
| **Windows 托盤** | 安裝使用者登入托盤,一鍵控制代理啟動、停止、重啟、面板和狀態。托盤不是代理重啟服務。 |
| **Codex 自動啟動** | 允許已安裝的 Codex launcher shim 執行 `ocx ensure`。此開關不會安裝 shim 或後臺服務。 |
| **Providers** | 新增、編輯、啟用/停用、刪除 provider,並在支援時管理 OAuth 帳號池和 API key 池。 |
-| **Add provider** | 搜尋 registry preset,選擇帳號登入、API key 服務、本機伺服器或自訂 endpoint。 |
+| **Add provider** | 分頁上方的單一搜尋框可同時搜尋帳號、免費、本機、付費四個分頁。搜尋時選取的分頁不會跳轉,結果依分頁分組並顯示數量。本機執行環境(Ollama、vLLM、LM Studio、LiteLLM)有專屬分頁,過長的說明會截斷為兩行,點擊即可查看全文。 |
| **Codex Auth** | 新增 ChatGPT/Codex 池帳號,選擇下一 session 的帳號,重新整理 5h / 每週 / 30d 配額,啟用或停用配額自動切換,設定其 1–100% 閾值和臨時故障 failover。 |
| **Subagents** | 在 `spawn_agent` override 列表中置頂最多五個原生或路由模型。 |
| **Models** | 開關原生 GPT 與路由模型,設定 provider allowlist、上下文上限、v1/base/v2 以及 v2 thread 數量。 |
diff --git a/gui/src/components/AddProviderModal.tsx b/gui/src/components/AddProviderModal.tsx
index 8a55273e20..f19abbb836 100644
--- a/gui/src/components/AddProviderModal.tsx
+++ b/gui/src/components/AddProviderModal.tsx
@@ -66,6 +66,10 @@ export default function AddProviderModal({
// The full-note popup is owned here, not in the catalog: it has to render as a sibling
// of this overlay, and its open state has to be visible to the Escape handler below.
const [notePreset, setNotePreset] = useState(null);
+ // The unified search text is owned here for the same reason: Escape has to clear a
+ // non-empty query instead of closing the dialog, and the handler that decides is this
+ // component's.
+ const [catalogQuery, setCatalogQuery] = useState("");
const oauthPoll = useKeyedClientResource(
`add-provider-oauth:${apiBase}`,
@@ -132,11 +136,20 @@ export default function AddProviderModal({
// This listener is on `window` and does not read `defaultPrevented`, so a native
//