Skip to content

fix(webui): catalog display names must keep the checkpoint's real name #1912

Closed

Description

@inureyes

Part of #1910.

Problem / Background

display_name is the label every WebUI surface prints for a model, and it is produced by rewriting the checkpoint name: src/server/webui/catalog_metadata.rs:465-470 takes the last path segment and replaces every - and _ with a space, so qwen3-0.6b-4bit renders as qwen3 0.6b 4bit and Meta-Llama-3.1-8B-Instruct-4bit as Meta Llama 3.1 8B Instruct 4bit. Observed on 2026-09-17 against 212 local checkpoints: the user never sees the directory or inference id they pass to -m and the API; two directories that differ only by - versus _ collapse to one label; typing the real name qwen3-0.6b-4bit into the library search finds nothing, because inventory() at webui/src/features/models/policy.ts:83-92 matches display_name, inference_id and architecture case-insensitively but the hyphenated form only matches inference_id, which no surface shows; and Playwright specs have to target the transformed name (webui/tests/models.spec.ts:178).

Current Behavior

The helper is called at catalog_metadata.rs:61 (router entries; model.name is the directory name for models-dir entries and owner/name such as mlx-community/retry-once for cache entries, see src/server/router_models_tests.rs:856) and at catalog_metadata.rs:177 (single-model mode). Consumers: the library row webui/src/features/models/screen.tsx:194-197, the inspector heading webui/src/features/models/inspector.tsx:56, the toolbar pill webui/src/provider-surfaces.tsx:25, the Chat picker webui/src/features/chat/chat.tsx:132, the Activity selector webui/src/features/activity/index.tsx:25, operation titles webui/src/features/models/operations.tsx:121, the settings selector webui/src/features/settings/server-settings.tsx:27, and the dialogs at webui/src/features/models/dialogs.tsx:34,88,100. Sorting uses it at policy.ts:104 and webui/src/state/reducer.ts:151; server-side q search concatenates it with inference_id at src/server/webui/catalog.rs:358-370. The delete dialog (dialogs.tsx:62-78) prints the opaque identity.id in a <code> block and only accepts that id as the token, while the body string models.library.delete_body (webui/src/features/models/strings.ts:245) says "Type the model name to confirm" and the field label models.library.confirm_name (strings.ts:250-253) says "Exact cache model ID to confirm", so the two strings disagree about what to type.

Proposed Solution

Make display_name the inference id with no character rewriting: models-dir and single-model entries get the last path segment of the directory (qwen3-0.6b-4bit), cache entries get the full owner/name (mlx-community/Qwen3-4B-4bit). Concretely, in catalog_metadata.rs:465-470 drop the .replace(['-', '_'], " "), and for RouterModelSource::Cache return model.name unchanged instead of the rsplit('/') segment. Do that by giving the helper the source (fn display_name(name: &str, source: RouterModelSource) -> String) or by branching at the call site at :61; the :177 single-model path keeps the last-segment rule. Do not add a humanized variant in this issue; if one is ever wanted it goes in as a separate optional field, never as the primary label. Rejected alternative: keep the space-rewritten label and print inference_id next to it, because that leaves two names for one thing and fixes neither search nor the -/_ collision.

Schema: this is a semantic change to an existing string field, so schema_version stays webui.ui-api.v1 (src/server/router_lifecycle.rs:29). The only docs/webui/api.yaml edit is widening ModelIdentity.display_name.maxLength at docs/webui/api.yaml:1518-1522 from 128 to 256 to match inference_id at api.yaml:1513-1517, because a cache id can reach 193 characters (validRepo at policy.ts:110 allows 96 per part) and a directory name can be 255 bytes; leave the root-level display_name at api.yaml:1340-1344 alone. Add a one-line description on the field stating it is the inference id verbatim, then regenerate docs/webui/generated/ui-api.d.ts with python3 scripts/ci/check_webui_contract.py --fix (the TS type stays string, so expect no diff there).

Delete dialog: keep identity.id as the token, because the reviewed identity fence at webui/src/features/models/screen.test.tsx:312-322 exists precisely so a duplicate display name cannot authorize a delete (tests/fixtures/webui/scenarios/identity-collision.json:9,97). Fix the wording instead: models.library.delete_body (en and ko at strings.ts:245-246) must say to type the ID shown below, and models.library.confirm_name must name it as the opaque model ID printed in the dialog, so the label tells the user exactly what to type and the visible name is no longer implied.

Scope

In scope: src/server/webui/catalog_metadata.rs (helper and both call sites); src/server/webui/catalog_contract_tests.rs (new test: a models-dir name containing both - and _ and a cache owner/name both come back verbatim); docs/webui/api.yaml plus regenerated docs/webui/generated/ui-api.d.ts; docs/webui/catalog.md:23 (state that display_name is the inference id verbatim, not a humanized form); webui/src/features/models/strings.ts delete strings (en and ko); tests/fixtures/webui/scenarios/identity-collision.json:97 (alpha models dir becomes alpha; the scenario still collides on inference_id, which is its purpose); and the frontend tests and fixtures that encode space-separated names, rewritten to hyphenated ids: webui/src/state/reducer.test.ts:10, webui/src/features/models/screen.test.tsx:195,216,221, webui/src/features/models/load-profile-integration.test.tsx:19, webui/tests/performance-fixtures.ts:41, webui/tests/models.spec.ts:235. Keep the CJK long-name string models.long_name (webui/src/i18n/catalog.ts:222, tests/fixtures/webui/strings.json:262-265) exercising wrapping, but as a long hyphenated id such as Qwen3-Very-Long-Local-Checkpoint-Name-With-Mixed-English-and-한국어-모델-이름, and keep the models-wrap treatment on the row, inspector heading and dialog so a 200-character id wraps instead of overflowing.

Out of scope: any other inspector content change; the model library redesign is a separate child of #1910 that depends on this issue.

Implementation Notes

  • Reuse: model.name and inference_id are already the verbatim ids at :61 and :177; no new derivation is needed. Leave matches() at catalog.rs:358-370 and inventory() as they are; once display_name == inference_id they match the real name by construction.
  • Edge cases: a name ending in / must still fall back to the whole name via the existing unwrap_or(name); a name that already contains spaces is kept verbatim; a - directory and a _ directory now produce distinct labels and distinct sort positions (localeCompare at policy.ts:105).
  • Error handling: nothing new. The server never truncates; a name over 256 characters is a schema violation that scripts/ci/check_webui_contract.py reports on fixtures.

Acceptance Criteria

  • With models/mlx/qwen3-0.6b-4bit in the store, GET /ui-api/v1/catalog returns identity.display_name == "qwen3-0.6b-4bit", and the library row, inspector heading and toolbar pill print exactly that.
  • A cache entry prints mlx-community/Qwen3-4B-4bit in those three places and in the Chat and Activity selectors.
  • Typing qwen3-0.6b-4bit in the library search box returns that row; the underscore spelling does not match it.
  • The delete confirmation body and label both say to type the opaque model ID printed in the dialog, and the fence test at screen.test.tsx:312-322 passes unchanged.
  • catalog_contract_tests.rs asserts verbatim display_name for a models-dir name with - and _ and for a cache owner/name; the test fails on main and passes with the fix.
  • schema_version is still webui.ui-api.v1, and python3 scripts/ci/check_webui_contract.py passes with the widened maxLength.
  • Verified against the running bundled binary with a store that holds both hyphenated and underscored names, not only fixtures.

Verification

cargo test --workspace --profile test-fast --features metal,accelerate
cargo clippy --workspace --all-targets -- -D warnings && cargo fmt --all -- --check
python3 scripts/ci/check_webui_contract.py
pnpm --dir webui run unit
pnpm --dir webui run browser
pnpm --dir webui run verify-generated

Manual: cargo build --release --features metal,accelerate, then ./target/release/mlxcel-server --webui --models-dir models/mlx --api-key-file <file> --no-models-autoload (--models-dir is the router-mode discovery root that yields models_dir entries; --model-store-root is the managed cache root and yields cache entries, so test both kinds), open the WebUI, and confirm the three surfaces above, the search-by-real-name result, and the delete dialog wording. A pass is the unmodified directory name on every surface.

Activity

  1. added
    type:bugBug fixes, error corrections, or issue resolutions
    area:architectureArchitecture and code structure changes
    and removed on Sep 18, 2026
  2. added a commit that references this issue on Sep 19, 2026
    31c0459
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:architectureArchitecture and code structure changespriority:mediumMedium prioritystatus:doneCompletedtype:bugBug fixes, error corrections, or issue resolutions

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions