Skip to content

feat(gui): make the add-provider search reach every tab at once - #4331

Merged
lidge-jun merged 4 commits into
devfrom
codex/provider-catalog-unified-search
Sep 12, 2026
Merged

feat(gui): make the add-provider search reach every tab at once#4331
lidge-jun merged 4 commits into
devfrom
codex/provider-catalog-unified-search

Conversation

@lidge-jun

@lidge-jun lidge-jun commented Sep 12, 2026

Copy link
Copy Markdown
Owner

Summary

  • The search box sat under the tab strip and filtered only the selected tab, and a tab click wiped whatever had been typed. Someone looking for Claude while the Free tab was open saw nothing, with no hint that it was one tab over. The input now sits above the tabs and reaches all four at once.

  • Search mode replaces browse mode rather than filtering inside it. While a query is live every group renders - Accounts, Free, Local, Paid - each under a small-caps heading with its count, and the selected tab is frozen rather than moved.

    Moving it would not merely change which rows are listed: it changes the kind of row, because a preset-select button becomes a login row with Log in and Add account buttons. That is the VS Code Extensions-view failure (Marketplace vs Installed vs Updates), and it is why there is no auto-switch here. Scroll position could not be restored honestly across such a jump either, because the destination is a different dataset.

  • The strip drops role="tablist" for the duration and becomes jump chips with counts, disabled at zero, that scroll to a group without changing the tab. Keeping aria-selected on a tab whose rows are no longer on screen would have a screen reader announce "Free, selected" over a Paid row. Clearing the query restores the tab the user chose and returns the list to the top.

  • Account login rows are searchable, and the row with a login in flight is pinned into the results regardless of the query: it owns the authorization URL and the paste field, so unmounting it mid-login throws away what the user is in the middle of doing. A preset sharing an id with a matched login row is dropped so the same provider cannot appear twice.

  • The haystack stays label + id. Matching adapters would return Ollama, vLLM, LM Studio, Groq, Cerebras and PackyCode for the query openai, and matching base URLs would return every local row for localhost. It widens in exactly two controlled ways instead: an equality match on the adapter id, and a small alias set (localhost, lmstudio, self-hosted, ...) resolved through the local classifier rather than by substring.

  • Ranking is per group only - exact, then prefix, then the caller order that already carries the sponsor pin and usage rank. A paid sponsor sorted above free NVIDIA on the query nim reads as an ad slot.

  • The search text is owned by AddProviderModal because its Escape listener is on window and registers before a child's, so Escape unwinds one layer at a time: note popup, then a live query, then the dialog.

Two things worth a reviewer's eye. The chip looks its group up by a data-catalog-group attribute rather than by id, because group ids come from useId (which emits colons) and selecting on one needs CSS.escape - which does not exist in the happy-dom environment the GUI tests run in, so a chip click would have thrown ReferenceError on CI. And the jump sets scrollTop directly and focuses with preventScroll, because .modal-card is also a scroll container and either default would drag the search field out of view.

This is a gui change. Grouped results across two tabs, a match that lives on a different tab from the selected one, and browse mode unchanged after clearing:

Search for opencode showing Free and Paid groups with counts while the tab strip is chips

Search for claude surfacing the Anthropic account login row while Free stays the selected tab

Browse mode restored after clearing the query

Verification

  • bun x tsc --noEmit - passed.
  • cd gui && bun x tsc --noEmit -p tsconfig.json - passed (includes gui/tests).
  • bun run structure:check - passed.
  • Manual, in a dev build of this branch: a query typed on the Free tab surfaces an Accounts login row without moving the tab; chip counts match the groups; localhost resolves to Local only; a chip click scrolls to its group, keeps the query, and does not drag the card; Escape closes the note popup, then clears the query, then closes the dialog.
  • New coverage: matchesCatalogQuery, sortCatalogMatches, filterAccountRows and dropPresetsCoveredByAccounts in tests/gui/provider-workspace-data.test.ts, plus gui/tests/provider-catalog-search.test.tsx for the frozen tab, the query surviving a strip click, and the pinned in-flight login.
  • The test suite was NOT RUN locally by instruction, so remote CI on this head is these tests' first execution. Two behaviours are verified by hand only and have no test: Escape clearing the query, and ArrowDown moving from the input to the first result.
  • Docs: structure/gui-and-management-api.md and the Add provider row in five docs-site locales now describe the four tabs, the unified search and the note clamp.

Based on #4328. Retarget to dev once that lands.

Checklist

  • Scope stays focused and avoids unrelated cleanup.
  • Docs or release notes were updated when needed.
  • Security-sensitive changes were reviewed for secrets, auth, and unsafe defaults.

Integration review

The owner requested merging this chain into dev. Integrating as lidge-jun under MAINTAINERS.md after exact-head remote CI passes; no self-approval is claimed. Local tests remain NOT RUN.
Contains the two corrected parent layers, merged without rewriting their published commits. Production GUI build passed; root source contract remains unchanged.

Verification target for maintainer integration: a154645d76e98af199fd79aff8c8d393afaf30ab. Cross-platform CI: https://github.com/lidge-jun/opencodex/actions/runs/34672274572. Merge is gated on that exact-head run succeeding. Review repairs were verified with a production GUI build and browser checks; the local suite remains NOT RUN.

@lidge-jun
lidge-jun requested a review from Ingwannu as a code owner September 12, 2026 02:45
@github-actions

Copy link
Copy Markdown
Contributor

Deterministic PR hygiene checks passed.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 12, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-12T02:51:12.583914Z f39b5ad PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@coderabbitai

coderabbitai Bot commented Sep 12, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

🗂️ Base branches to auto review (2)
  • ^dev$
  • ^preview$

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Advanced

Run ID: 0df579b4-ad46-4d77-9cf1-7601502ba694

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added the enhancement New feature or request label Sep 12, 2026
@lidge-jun

Copy link
Copy Markdown
Owner Author

리뷰 · 우선순위 63 / 80

지금 dev HEAD(dbad475a5, #4330 Grok reset-coupon GUI 직후)의 추가 프로바이더 카탈로그는 아직 Accounts / Free / Paid 세 탭이고, 검색창이 탭 아래에 있으며 지금 고른 탭만 걸러 줍니다. 탭을 바꾸면 타이핑한 검색어도 같이 지워집니다. 그래서 Free 탭에 있는 채로 Claude를 찾으면 결과가 비고, 그게 Accounts 쪽에 있다는 힌트도 없습니다.

이 PR(#4331)은 그 불편을 고칩니다. 검색창을 탭 로 올리고, 글자를 치는 동안에는 탭을 바꾸지 않은 채 Accounts·Free·Local·Paid 네 그룹을 한 화면에 묶어서 보여 줍니다. 탭 줄은 잠깐 role="tablist"가 아니라 개수가 적힌 점프 칩이 됩니다. 칩을 눌러도 선택 탭은 그대로 두고, 해당 그룹 제목으로만 스크롤합니다. 탭을 옮기면 행의 종류가 바뀌기 때문입니다(프리셋 선택 버튼이 로그인/계정 추가 행으로 바뀜). 그걸 막은 설계는 설명과 코드가 잘 맞습니다.

검색 문자열은 AddProviderModal이 들고, Escape는 노트 팝업 → 검색어 비우기 → 모달 닫기 순으로 한 겹씩 풀리게 했습니다. 매칭은 라벨·id 위주이고, 어댑터는 완전 일치만, localhost/self-hosted 같은 별칭은 로컬 분류기로만 넓힙니다. 그룹 안 정렬만 exact→prefix→기존 순서(스폰서 핀·사용량)이고 그룹끼리 섞어 광고처럼 보이게 하지 않습니다. 로그인 중인 Accounts 행은 쿼리와 안 맞아도 고정하고, 같은 id 프리셋은 깎아 중복을 막습니다. CatalogAccountRow 분리, data-catalog-group으로 happy-dom에 없는 CSS.escape를 피한 점, scrollTop+preventScroll.modal-card가 같이 끌려가는 문제를 막은 점도 설득력 있습니다. 단위 테스트(matchesCatalogQuery 등)와 gui/tests/provider-catalog-search.test.tsx가 핵심 행동을 잡습니다.

다만 이 브랜치 베이스는 dev가 아니라 codex/provider-catalog-note-popup(#4328)입니다. 그 아래는 Local 탭(#4325) → 계획 문서(#4324dev) 스택입니다. PR 본문도 “#4328이 올라간 뒤 dev로 리타깃”이라고 적혀 있습니다. 지금 HEAD에 바로 머지할 수 있는 조각이 아닙니다. 또한 작성자가 로컬에서 테스트 스위트를 돌리지 않았다고 했고, Escape로 검색어 지우기·ArrowDown으로 첫 결과 포커스는 수동만 확인했습니다. CI가 이 헤드의 첫 자동 검증입니다.

gui/src/components/provider-catalog/provider-presets.ts - LOCAL_QUERY_ALIASESollama/vllm/lmstudio가 들어 있어서, ollama만 쳐도 로컬 분류에 걸리는 다른 로컬 런타임 전부가 같이 나옵니다. localhost류 별칭과 제품명 별칭을 같은 Set에 둔 부작용입니다.

gui/src/components/AddProviderModal.tsx Escape 핸들러 - 주석은 “노트 팝업 → 검색어 → 다이얼로그”인데, 이 핸들러는 notePreset이 있으면 바로 return만 하고 노트를 직접 닫지 않습니다. 실제 닫기는 ProviderNoteModalonClose에 맡긴 구조라서, 노트 쪽이 Escape를 안 먹으면 검색어 지우기가 영원히 안 됩니다. #4328 팝업과 한 세트로 동작하는지 CI/수동으로 한 번 더 확인하는 게 좋습니다.

gui/tests/provider-catalog-search.test.tsx / Escape·ArrowDown - PR이 인정한 대로 Escape로 쿼리 클리어, ArrowDown으로 첫 결과 이동은 자동 테스트가 없습니다. 회귀가 나기 쉬운 UX 축입니다.

provider-presets.tsfilterPresets - 카탈로그 UI는 matchesCatalogQuery로 넘어갔지만 헬퍼·기존 테스트는 남아 있습니다. 의도된 호환이면 괜찮고, 아니면 정리 시점을 정하면 됩니다.

문서(structure/gui-and-management-api.md, docs-site 5로케일 Add provider 행)와 i18n modal.searchResults는 네 탭·통합 검색·노트 클램프를 같이 반영해 두었습니다.

메인테이너의 판단이 필요한 지점

너의 추천

  • 지금 dev에 머지하지 말고, feat(gui): clamp long provider notes to two lines and open the full text in a popup #4328(및 그 아래 Local/plan)이 먼저 안착한 뒤 이 PR을 dev로 리타깃하세요.
  • CI에서 gui/tests/provider-catalog-search.test.tsxprovider-workspace-data 신규 케이스가 초록인지 확인한 뒤 들이세요.
  • 여력이 있으면 ollama 별칭 범위와 Escape/ArrowDown 테스트 한두 개를 같은 스택의 작은 후속(또는 이 PR 보정)으로 넣는 쪽을 추천합니다.

이 댓글은 grok-bot이 작성했습니다

The search box sat under the tab strip and filtered only the selected tab, and a tab click wiped whatever had been typed. Someone looking for Claude on the Free tab saw nothing and had no way to find out it was one tab over. The input now sits above the tabs and matches across all four.

Search mode replaces browse mode rather than filtering inside it. While a query is live every group renders - Accounts, Free, Local, Paid - each under a small-caps heading with its count, and the selected tab is frozen rather than moved. Moving it would not merely change which rows are listed: it changes the kind of row, because a preset-select button becomes a login row with Log in and Add account buttons. The strip drops role=tablist for that duration and becomes jump chips with counts, disabled at zero, that scroll to a group without changing the tab. Clearing the query restores the tab the user chose and returns the list to the top.

Account login rows are searchable too, and the row with a login in flight is pinned into the results regardless of the query: it owns the authorization URL and the paste field, and unmounting it mid-login throws away what the user is doing. A preset sharing an id with a matched login row is dropped so the same provider cannot appear twice.

The haystack stays label and id. Matching adapters would return Ollama, vLLM, LM Studio, Groq, Cerebras and PackyCode for the query openai, and matching base URLs would return every local row for localhost. It widens in exactly two controlled ways instead: an equality match on the adapter id, and a small alias set resolved through the local classifier. Ranking is per group only - exact, then prefix, then the caller order that already carries the sponsor pin and usage rank - because a paid sponsor sorted above free NVIDIA on the query nim reads as an ad slot.

The search text is owned by AddProviderModal because its Escape listener is on window and registers before a child's, so Escape unwinds one layer at a time: the note popup, then a live query, then the dialog.
@lidge-jun
lidge-jun force-pushed the codex/provider-catalog-note-popup branch from 414ec2d to e3fdf8f Compare September 12, 2026 02:48
@lidge-jun
lidge-jun force-pushed the codex/provider-catalog-unified-search branch from f39b5ad to 68e6b02 Compare September 12, 2026 02:48
…vidence

Four dependent PRs with their exact heads and live CI run ids, plus the two traps that make a green head look red: a cancelled duplicate run leaves a FAILURE ci row attached to the same SHA as the live green run, and a cancelled required check is not a passing one - all three enforce-target attempts on #4328 were cancelled by concurrency, which left the PR UNSTABLE until one was re-run.
…catalog-unified-search

# Conflicts:
#	gui/src/components/provider-catalog/provider-presets.ts
#	structure/gui-and-management-api.md
#	tests/gui/provider-workspace-data.test.ts
…catalog-unified-search

# Conflicts:
#	gui/src/components/provider-catalog/provider-presets.ts
Base automatically changed from codex/provider-catalog-note-popup to dev September 12, 2026 04:27
@lidge-jun
lidge-jun merged commit 9a37813 into dev Sep 12, 2026
35 checks passed
@lidge-jun
lidge-jun deleted the codex/provider-catalog-unified-search branch September 12, 2026 04:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant