Skip to content

fix(models): make switches reflect final visibility#389

Closed
csa906 wants to merge 3 commits into
lidge-jun:devfrom
csa906:codex/fix-models-selected-visibility
Closed

fix(models): make switches reflect final visibility#389
csa906 wants to merge 3 commits into
lidge-jun:devfrom
csa906:codex/fix-models-selected-visibility

Conversation

@csa906

@csa906 csa906 commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Summary

  • make the existing Models page ON/OFF switches represent final Codex visibility, including both selectedModels and disabledModels
  • add one atomic PUT /api/model-visibility management route so a single enable action can repair both filters without exposing a second selection editor
  • preserve the existing row layout, provider bulk controls, search, pagination, shadow-model picker, and provider discovery diagnostics

Problem

The Models page currently renders a model as ON whenever it is absent from disabledModels. The Codex catalog applies an additional per-provider selectedModels allowlist, so a model can appear ON in the dashboard while still being omitted from /v1/models and the Codex app.

This is especially visible after a provider discovers new models: an existing non-empty allowlist does not contain the new IDs, but the dashboard still presents them as enabled.

Implementation

  • derive row state, provider counts, global counts, sorting, and shadow-picker options from the final visibility rule
  • enable a model by adding its raw provider ID to a non-empty allowlist and removing its canonical blocklist entry in one config save/catalog refresh
  • disable a model through the blocklist while preserving the allowlist's original selection semantics
  • make provider-level Enable all enter future-proof All mode and clear provider blocklist entries; Disable all blocks only the current inventory
  • support native, routed, encoded, and combo model identifiers without changing the existing selected/disabled API contracts
  • serialize polling around model discovery, ignore stale responses, and reload authoritative server state after mutations or failures
  • keep the upstream empty-provider/discovery-failure UI intact
  • document the final ON/OFF meaning and the new route in English, Japanese, Korean, Russian, and Simplified Chinese

Tests

  • all local verification used the CI-pinned Bun 1.3.14
  • bun run typecheck
  • bun run lint:gui
  • cd gui && bun run test — 24 passed, 0 failed
  • focused management/catalog tests — 12 passed, 0 failed
  • bun run privacy:scan
  • bun run build:gui
  • docs-site build — 126 pages built
  • bun run doctor:gui:if-changed — completed with advisory-only existing recommendations
  • git diff --check

The CI-equivalent Windows root suite (bun test --isolate tests) was also executed: 4,017 passed, 4 skipped, and 7 failed. The new model-visibility tests passed inside that full run. Every non-zero case was then rerun successfully in isolation: two doctor tests passed after putting Bun on PATH, two unchanged Cursor shell tests passed after adding Git's usr/bin as on the hosted runner, and three timing-sensitive tests passed with a 15 s limit after taking 4.9–5.8 s against their default 5 s limit.

Scope

  • 11 files changed
  • 500 insertions, 65 deletions
  • no configuration migration
  • no dependency, authentication, workflow, or release-automation changes

Summary by CodeRabbit

  • New Features
    • Added “Model visibility” controls to the Models dashboard (individual, provider-wide, and bulk “all” toggles).
    • Added API support to read selected models and atomically change model visibility (GET /api/selected-models, PUT /api/model-visibility).
  • Bug Fixes
    • Updated dashboard visibility logic to reflect provider allowlists, disabled state, and correct combo/native handling.
    • Improved Models page loading/refresh sequencing and accurate active-model counts.
  • Documentation
    • Updated EN/JA/KO/RU/ZH-CN guides with the new “Model visibility” section and endpoint table entries.
  • Tests
    • Added coverage for visibility helper logic, API atomic semantics, and combo preservation behavior.

@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 7fcc6662-6e86-4f68-ab55-53070b72b9b1

📥 Commits

Reviewing files that changed from the base of the PR and between 792baab and 1e1fa59.

📒 Files selected for processing (15)
  • docs-site/src/content/docs/guides/web-dashboard.md
  • docs-site/src/content/docs/ja/guides/web-dashboard.md
  • docs-site/src/content/docs/ko/guides/web-dashboard.md
  • docs-site/src/content/docs/ru/guides/web-dashboard.md
  • docs-site/src/content/docs/zh-cn/guides/web-dashboard.md
  • gui/src/model-visibility.ts
  • gui/src/pages/Models.tsx
  • gui/tests/model-visibility.test.tsx
  • gui/tests/models-empty-provider.test.tsx
  • src/combos/index.ts
  • src/combos/types.ts
  • src/router.ts
  • src/server/management/model-routes.ts
  • tests/combos.test.ts
  • tests/model-visibility-management-api.test.ts

📝 Walkthrough

Walkthrough

Adds atomic model-visibility APIs, GUI visibility reconciliation, combo-provider routing support, coverage for model/provider/combo toggles, and localized dashboard documentation.

Changes

Model visibility

Layer / File(s) Summary
Visibility contracts and combo preservation
gui/src/model-visibility.ts, gui/tests/model-visibility.test.tsx, src/combos/*, src/router.ts, tests/combos.test.ts
Adds selected-model parsing, visibility evaluation, API request helpers, load-generation checks, and shared physical combo-provider preservation logic used by routing.
Atomic visibility management API
src/server/management/model-routes.ts, tests/model-visibility-management-api.test.ts
Adds and validates PUT /api/model-visibility, updates selected and disabled model configuration atomically, handles combo identifiers, persists state, refreshes the catalog, and tests model-, provider-, combo-, and invalid-request behavior.
Models page visibility orchestration
gui/src/pages/Models.tsx, gui/tests/models-empty-provider.test.tsx
Loads selected-model state, derives effective visibility and counts, serializes model/provider updates, guards polling generations, refreshes after saves, and tests successful and failed visibility actions.
Dashboard visibility documentation
docs-site/src/content/docs/guides/web-dashboard.md, docs-site/src/content/docs/{ja,ko,ru,zh-cn}/guides/web-dashboard.md
Documents final model visibility rules, atomic filter reconciliation, “All on” behavior, and the new visibility endpoints.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ModelsPage
  participant VisibilityAPI
  participant Config
  participant CodexCatalog
  ModelsPage->>VisibilityAPI: PUT /api/model-visibility
  VisibilityAPI->>Config: Validate and persist visibility state
  VisibilityAPI->>CodexCatalog: Refresh catalog cache
  VisibilityAPI-->>ModelsPage: Return visibility response
  ModelsPage->>VisibilityAPI: GET /api/selected-models
  VisibilityAPI-->>ModelsPage: Return selected model map
Loading

Suggested labels: enhancement

Suggested reviewers: ingwannu, lidge-jun

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: Models switches now reflect final visibility state.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

@github-actions github-actions Bot added the bug Something isn't working label Jul 24, 2026
@csa906
csa906 force-pushed the codex/fix-models-selected-visibility branch from 399947f to e8e5915 Compare July 24, 2026 06:40
@csa906
csa906 marked this pull request as ready for review July 24, 2026 06:43

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e8e5915bd4

ℹ️ 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".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/server/management/model-routes.ts Outdated
}

const providerConfig = hasOwnProvider(config.providers, provider) ? config.providers[provider] : undefined;
const comboProvider = provider === "combo";

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve physical combo providers in visibility toggles

When a valid physical provider named combo exists with no combos configured, routeModelInternal explicitly preserves that provider, but this classification forces every /api/model-visibility request for provider combo down the combo-namespace path below, where targets must exist in config.combos and the request returns 400. Since the Models page now uses this endpoint for row switches and All on/off, users of that supported provider name can no longer change visibility; treat combo as the combo namespace only when combos are configured or no physical provider is being preserved.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in b89c5399. The model-visibility route now shares preservesPhysicalComboProvider with routing, so a configured physical combo provider remains a normal routed provider whenever no virtual combos are configured. The regression covers row on/off and provider All on/off behavior.

Comment thread src/server/management/model-routes.ts Outdated
disabled = disabled.filter(stored => !allComboSlugs.has(stored));
} else {
const nativeIds = new Set(targets.filter(target => target.native).map(target => target.id));
disabled = disabled.filter(stored => !stored.startsWith(`${provider}/`) && !nativeIds.has(stored));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Limit provider all-on cleanup to provider-owned slugs

When a combo is disabled by a public alias that looks like a provider slug, e.g. anthropic/fast, clicking All on for provider anthropic removes that unrelated alias from disabledModels because this prefix filter matches any stored string beginning with anthropic/. Combo aliases are allowed to contain one slash and are persisted in the shared disabled list, so provider all-on can unexpectedly re-expose a disabled combo; exclude known combo public aliases or match only provider-owned model slugs.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in b89c5399. Provider All on now preserves every configured combo selector, including canonical IDs and public aliases, while clearing only provider-owned slugs. The regression specifically covers the anthropic/fast alias and verifies it remains disabled until the combo namespace is enabled.

@Ingwannu Ingwannu left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The dashboard truthfulness bug is real and the atomic route is the right shape, but two current cases make the mutation unsafe: a legitimate physical provider named combo is forced into the combo namespace, and provider-level All on can remove a disabled combo alias that merely shares the provider prefix. Please classify physical combo providers consistently with routeModelInternal, exclude known combo aliases from provider cleanup, add regressions for both cases, and re-request review.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 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/server/management/model-routes.ts`:
- Around line 192-210: The provider-scope enable logic in the native branch of
the model route only removes bare native IDs found in targets, leaving stale IDs
disabled. Update the non-virtual provider branch within the body.enabled
handling to remove all bare native IDs for that provider, while preserving known
combo selectors and prefixed IDs; keep the virtual namespace behavior unchanged.
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 7daec4f3-1470-4c65-b630-4b6f93dd6afd

📥 Commits

Reviewing files that changed from the base of the PR and between e8e5915 and b89c539.

📒 Files selected for processing (6)
  • src/combos/index.ts
  • src/combos/types.ts
  • src/router.ts
  • src/server/management/model-routes.ts
  • tests/combos.test.ts
  • tests/model-visibility-management-api.test.ts

Comment thread src/server/management/model-routes.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 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 `@tests/model-visibility-management-api.test.ts`:
- Around line 96-118: Update the test fixture in “all-on clears stale native ids
while preserving combo selectors” so the unrelated routed model uses a
provider-prefixed identifier instead of “fast-chat”, matching
disabledNativeSlugs classification. Keep the assertion aligned with the
configured preserved identifiers and retain the existing stale-native removal
behavior.
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 123ab41c-ba44-4209-806b-c9db94d9110c

📥 Commits

Reviewing files that changed from the base of the PR and between b89c539 and 792baab.

📒 Files selected for processing (2)
  • src/server/management/model-routes.ts
  • tests/model-visibility-management-api.test.ts

Comment thread tests/model-visibility-management-api.test.ts

csa906 commented Jul 24, 2026

Copy link
Copy Markdown
Contributor Author

@Ingwannu I addressed both requested changes in b89c5399 and the follow-up native All on finding in 792baab4. The focused regression suite passes 39/39, along with typecheck and the privacy scan. CodeRabbit re-reviewed the current head, confirmed the functional fix, and withdrew its remaining fixture comment after verifying the configured combo alias is intentionally preserved.

@lidge-jun

Copy link
Copy Markdown
Owner

Re-reviewed against @Ingwannu's CHANGES_REQUESTED — both concerns are resolved on the current head (792baab4), each with a regression test:

  1. Physical provider named combo — the route classifies it the same way routeModelInternal does via preservesPhysicalComboProvider(config) (src/server/management/model-routes.ts:146-149 == src/router.ts:237-240; helper in src/combos/types.ts:13-18). Regression: tests/model-visibility-management-api.test.ts:120-164.
  2. Provider "All on" removing a shared-prefix combo alias — known canonical combo selectors/aliases are collected (model-routes.ts:171-176) and explicitly preserved during physical-provider cleanup (model-routes.ts:198-204). Regression with alias anthropic/fast: test:166-202. bun test tests/model-visibility-management-api.test.ts → 8 pass.

ocx sync relationship (the specific thing I was asked to check): no conflict. #389 does not change the CLI sync path; visibility persists via saveConfig() and both the new route and ocx sync regenerate the catalog through the same refreshCodexModelCatalog(). Running ocx sync afterward reproduces the same visibility state — the config stays authoritative, and a concurrent CLI sync is only the pre-existing last-writer race, not something #389 introduces.

Two things before merge: (1) this head predates current dev (ac49d13d) and (2) the full CI matrix (macOS/Ubuntu/Windows) has not run on it — only CodeRabbit/enforce-target/label show. Could you rebase on dev and push to re-trigger the full matrix? Once it's green I'll merge. Content-wise this is MERGE_READY.

@csa906
csa906 force-pushed the codex/fix-models-selected-visibility branch from 792baab to 1e1fa59 Compare July 24, 2026 12:33
@lidge-jun

Copy link
Copy Markdown
Owner

🔒 Maintainer integration in progress — please hold off on merging

@lidge-jun (maintainer) is actively integrating this PR into dev as part of a coordinated
integration pass. Please do not merge, rebase, force-push, or close this PR until this
marker is removed.

What is happening

This PR is being integrated on the maintainer branch codex/260725-pr-rework, which is
based on dev. The pass applies the contributed change, adds any missing regression
coverage, and repairs review-identified defects before a single verified merge into dev.
Every integrated change goes through bun run typecheck, the full bun run test suite,
bun run privacy:scan, and bun run lint:gui, followed by exact-SHA hosted
Cross-platform CI and Service lifecycle runs.

What this means for you

  • Your authorship and commits are preserved. Nothing is being rewritten under your name.
  • No action is needed from you right now. If a defect repair changes your intended
    behavior, it will be described explicitly in a follow-up comment before the merge.
  • If you have work in flight on this branch, please comment here instead of pushing, so we
    do not race each other.

This PR will be closed with a merge receipt (integration commit SHA, verification output,
and hosted CI links) once the pass lands on dev. Thanks for the contribution.

Integration tracker: devlog/_plan/260725_pr_issue_rework · marker posted by the maintainer integration pass

lidge-jun added a commit that referenced this pull request Jul 25, 2026
PR #389 (head 1e1fa59, author csa906)를 통합한다. dev의 provider workspace
구조와 충돌하므로 3-way 병합 후 두 conflict를 수동 해소했다.

스위치는 disable 플래그만 읽어서 실제로 Codex에 노출되는 목록과 어긋났다.
이제 provider allowlist가 허용하고 disabled가 아닐 때만 visible로 판정하며,
provider 카드 카운트와 workspace rail 카운트가 같은 규칙을 쓴다.

충돌 해소: dev의 controls/Tooltip 구조와 renderGroup/visibleGroups 흐름을
보존하고, PR의 최종 가시성 로직만 renderGroup()으로 이식했다. 구형 page-head와
인라인 groups.map은 폐기했다.

빈 provider에서는 bulk 버튼을 비활성으로 유지한다. rows가 비어 있을 때
every()가 true를 반환해 두 버튼이 동시에 활성화되면 빈 targets PUT이 400으로
실패한다.

테스트 조정과 추가:
- 통합 테스트의 shadow Select 조회를 document 기준으로 고쳤다. dev의 Select는
  listbox를 document.body로 portal하므로 mount 노드 안에 없다
- poll이 forced refresh보다 늦게 도착하는 stale-generation 순서를 실제로
  재현하는 회귀를 추가했다. 기존 테스트는 single-flight만 검증해서
  shouldApplyLoadGeneration()이 무력화돼도 통과했다

Co-authored-by: csa906 <csa906@users.noreply.github.com>
@lidge-jun

Copy link
Copy Markdown
Owner

✅ Integrated into dev

Thank you for this fix, and for working through the earlier review rounds. It has been integrated into dev.

Integration commit: 323bb93ffix(models): make switches reflect final visibility
Merged into dev at: ebc62d1f
Your authorship is preserved via Co-authored-by.

Conflict resolution

dev had meanwhile moved the Models page to the provider-workspace structure, so this needed a manual merge rather than a straight apply — GitHub reported CONFLICTING, and the three-way merge left two conflict regions. Both were resolved by keeping the current dev structure and porting your visibility logic into it:

  • the controls region kept controlsBlock and the Tooltip-based Shadow Call UI; the older page-head was dropped and the count reflected in the workspace rail instead
  • the group region kept collapseControls / emptyStateBlock / visibleGroups.map, and renderGroup() gained the provider-local isVisible, active count, visible-first sort, bulk state, target construction, and row switch

Your backend changes — the shared preservesPhysicalComboProvider(), the combo-alias preservation, and the stale bare-native cleanup — went in unchanged.

One behavioral adjustment

On an empty provider, rows.every(...) returns true for both bulk predicates, so porting rows.length > 0 && ... directly would have left both buttons enabled and sent an empty target list, which the management API rejects with 400. Bulk controls are now inert when there are no rows.

Verification

  • GUI suite: 136 pass / 0 fail; build clean
  • The shadow-select assertion needed updating because dev's Select portals its listbox to document.body, so it is not inside the mount node
  • Added a stale-generation regression: the existing test only covered single-flight, so neutralizing shouldApplyLoadGeneration() still passed. The new one holds a poll open, lets a forced refresh land first, then resolves the stale poll — and it fails when the guard is neutralized
  • Rendered check at 1280×720 confirmed the actual symptom is gone: models outside the allowlist now render off and struck through even when not disabled, and all three counts agree
  • Full suite at merge: 4222 pass / 0 fail; Cross-platform CI green on all three OSes

@lidge-jun

Copy link
Copy Markdown
Owner

Closing: integrated into dev at ebc62d1f with authorship preserved. See the receipt comment above for the integration commit, the changes made on top, and the verification evidence.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants