From 863ca94c9f0be4ce73abfc9d55116d7af80d24ca Mon Sep 17 00:00:00 2001 From: Bob Lee Date: Tue, 1 Sep 2026 20:40:08 +0800 Subject: [PATCH 1/2] fix(ui): stretch combobox search to popover width --- .../packages/ui/src/components/Combobox/Combobox.module.css | 4 ++++ .../packages/ui/src/components/Combobox/Combobox.tsx | 1 + design-system/packages/ui/tests/combobox.test.mjs | 6 ++++++ 3 files changed, 11 insertions(+) diff --git a/design-system/packages/ui/src/components/Combobox/Combobox.module.css b/design-system/packages/ui/src/components/Combobox/Combobox.module.css index c3bd86b49e..6d0ccfcfbc 100644 --- a/design-system/packages/ui/src/components/Combobox/Combobox.module.css +++ b/design-system/packages/ui/src/components/Combobox/Combobox.module.css @@ -273,6 +273,10 @@ padding: var(--bf-space-1); } + .searchField { + inline-size: 100%; + } + .listbox { flex: 1 1 auto; min-block-size: 0; diff --git a/design-system/packages/ui/src/components/Combobox/Combobox.tsx b/design-system/packages/ui/src/components/Combobox/Combobox.tsx index 05179f3db9..b59310ede9 100644 --- a/design-system/packages/ui/src/components/Combobox/Combobox.tsx +++ b/design-system/packages/ui/src/components/Combobox/Combobox.tsx @@ -502,6 +502,7 @@ const CollectionPicker = forwardRef(function Collec aria-expanded={resolvedOpen} aria-label={designSystem.messages.searchOptions} autoComplete="off" + className={styles.searchField} clearLabel={designSystem.messages.clearSelection} onClear={() => { setQuery(""); diff --git a/design-system/packages/ui/tests/combobox.test.mjs b/design-system/packages/ui/tests/combobox.test.mjs index 42326cf49a..518cf23cd6 100644 --- a/design-system/packages/ui/tests/combobox.test.mjs +++ b/design-system/packages/ui/tests/combobox.test.mjs @@ -63,6 +63,10 @@ test("Combobox owns keyboard selection, IME safety, filtering, and custom values }); test("Combobox styling uses public field, overlay, action, and motion tokens", async () => { + const source = await readFile( + new URL("../src/components/Combobox/Combobox.tsx", import.meta.url), + "utf8", + ); const styles = await readFile( new URL("../src/components/Combobox/Combobox.module.css", import.meta.url), "utf8", @@ -74,6 +78,8 @@ test("Combobox styling uses public field, overlay, action, and motion tokens", a assert.match(styles, /--bf-shadow-menu/); assert.match(styles, /position:\s*fixed/); assert.match(styles, /z-index:\s*var\(--bf-layer-popover\)/); + assert.match(source, /className=\{styles\.searchField\}/); + assert.match(styles, /\.searchField\s*\{[^}]*inline-size:\s*100%/); assert.doesNotMatch(styles, /data-popover-mode/); assert.doesNotMatch(styles, /#[0-9a-f]{3,8}/i); }); From 126b35fc1901bc17d93b69f97126d4e2f11bc6e7 Mon Sep 17 00:00:00 2001 From: Bob Lee Date: Tue, 1 Sep 2026 20:40:19 +0800 Subject: [PATCH 2/2] fix(settings): replace corrupted model metadata separators --- .../ModelSettingsPage.presentation.test.ts | 15 +++++++++++++++ .../config/components/ModelSettingsPage.tsx | 14 +++++++------- 2 files changed, 22 insertions(+), 7 deletions(-) create mode 100644 src/web-ui/src/infrastructure/config/components/ModelSettingsPage.presentation.test.ts diff --git a/src/web-ui/src/infrastructure/config/components/ModelSettingsPage.presentation.test.ts b/src/web-ui/src/infrastructure/config/components/ModelSettingsPage.presentation.test.ts new file mode 100644 index 0000000000..b1ea8472a0 --- /dev/null +++ b/src/web-ui/src/infrastructure/config/components/ModelSettingsPage.presentation.test.ts @@ -0,0 +1,15 @@ +import { readFileSync } from 'node:fs'; +import { fileURLToPath } from 'node:url'; +import { describe, expect, it } from 'vitest'; + +const source = readFileSync( + fileURLToPath(new URL('./ModelSettingsPage.tsx', import.meta.url)), + 'utf8', +); + +describe('ModelSettingsPage presentation', () => { + it('uses intentional separators instead of Unicode replacement characters', () => { + expect(source).not.toContain('\uFFFD'); + expect(source.match(/\{' · '\}/g)).toHaveLength(2); + }); +}); diff --git a/src/web-ui/src/infrastructure/config/components/ModelSettingsPage.tsx b/src/web-ui/src/infrastructure/config/components/ModelSettingsPage.tsx index 0949e8acbd..3e2f4e4112 100644 --- a/src/web-ui/src/infrastructure/config/components/ModelSettingsPage.tsx +++ b/src/web-ui/src/infrastructure/config/components/ModelSettingsPage.tsx @@ -255,7 +255,7 @@ function dedupeSelectedModelDraftsByModelName(drafts: SelectedModelDraft[]): Sel /** * Compute the stored request URL from a base URL and provider format. - * For gemini, stores the bare base (no /v1beta/models/... suffix) � + * For Gemini, stores the bare base without the /v1beta/models/... suffix; * the backend dynamically appends /v1beta/models/{model}:streamGenerateContent?alt=sse. */ function resolveRequestUrl(baseUrl: string, provider: string, _modelName = ''): string { @@ -2276,9 +2276,9 @@ const ModelSettingsPage: React.FC = () => {
{categoryLabel} - {' � '} + {' · '} {formatTokenCountShort(draft.contextWindow)} ctx - {' � '} + {' · '} {formatReasoningSummary(draft, reasoningProjection)}
@@ -2496,7 +2496,7 @@ const ModelSettingsPage: React.FC = () => { })); }} placeholder={t('form.baseUrl')} - options={currentTemplate.baseUrlOptions.map(opt => ({ label: opt.url, value: opt.url, description: `${opt.format.toUpperCase()} � ${opt.note}` }))} + options={currentTemplate.baseUrlOptions.map(opt => ({ label: opt.url, value: opt.url, description: `${opt.format.toUpperCase()} · ${opt.note}` }))} size="sm" /> )} @@ -3609,7 +3609,7 @@ const ModelSettingsPage: React.FC = () => {
- {modelsDevStatus?.cache_path || '�'} + {modelsDevStatus?.cache_path || '—'} { - {modelsDevStatus?.revision ? `${modelsDevStatus.revision.slice(0, 12)}�` : '�'} + {modelsDevStatus?.revision ? `${modelsDevStatus.revision.slice(0, 12)}…` : '—'}
@@ -3691,7 +3691,7 @@ const ModelSettingsPage: React.FC = () => {
    {subscriptionLogoutRequest.affectedModels.map((model) => ( -
  • {model.name} � {model.model_name}
  • +
  • {model.name} · {model.model_name}
  • ))}