fix(ui): prevent text overlap in Claude Code model selectors

- Let selector height adapt to model names and aliases
- Add layout regression tests across languages, themes, and viewport sizes
- Verify model selection, keyboard search, 1M toggles, and config updates
This commit is contained in:
lzt404 committed 2026-10-08 18:36:58 +08:00
1 parent 97ba1051a2
commit f7e9bd9884
13 files changed
+208 -65

No files matched your search

@@ -52,7 +52,7 @@
.claude-code-model-inputs .agent-model-trigger,
.claude-role-table .agent-model-trigger {
min-height: 40px;
height: 40px;
height: auto;
padding: 7px 11px;
border: 1px solid var(--theme-d5d2cb);
border-radius: 6px;
@@ -50,7 +50,6 @@ export function ClaudeCodeModelInputs({ startupModel, subagentModel, models, rou
<span className="switch-track" aria-hidden="true" /></span><span>1M</span>
</label>
</div>
{preview.role && <small className="claude-code-model-summary" aria-live="polite">{preview.role} → {preview.model}</small>}
{!loading && !isKnownClaudeCodeModel(value, models) ? (
<small role="status">{t('agents.claudeCodeRuntime.unverifiedModel')}</small>
) : null}
+12
View File
@@ -24,6 +24,10 @@ export const authFileListZhCN = {
'authFiles.usage.title': '累计用量',
'authFiles.toolbar.refreshList': '刷新列表',
'authFiles.toolbar.refreshQuota': '刷新额度',
'authFiles.action.refreshQuota': '刷新',
'authFiles.action.settings': '设置',
'authFiles.action.models': '模型',
'authFiles.action.copy': '复制',
} as const;
export const authFileListEn: Record<keyof typeof authFileListZhCN, string> = {
@@ -52,6 +56,10 @@ export const authFileListEn: Record<keyof typeof authFileListZhCN, string> = {
'authFiles.usage.title': 'Cumulative usage',
'authFiles.toolbar.refreshList': 'Refresh list',
'authFiles.toolbar.refreshQuota': 'Refresh quota',
'authFiles.action.refreshQuota': 'Refresh',
'authFiles.action.settings': 'Settings',
'authFiles.action.models': 'Models',
'authFiles.action.copy': 'Copy',
};
export const authFileListJa: Record<keyof typeof authFileListZhCN, string> = {
@@ -80,4 +88,8 @@ export const authFileListJa: Record<keyof typeof authFileListZhCN, string> = {
'authFiles.usage.title': '累計使用量',
'authFiles.toolbar.refreshList': '一覧を更新',
'authFiles.toolbar.refreshQuota': 'クォータを更新',
'authFiles.action.refreshQuota': '更新',
'authFiles.action.settings': '設定',
'authFiles.action.models': 'モデル',
'authFiles.action.copy': 'コピー',
};
+15 -21
View File
@@ -185,11 +185,11 @@ const CODEX_OAUTH_LOGIN_REQUIRED_ERROR = 'CODEX_OAUTH_LOGIN_REQUIRED';
const DEFAULT_CLAUDE_CODE_MAX_CONTEXT_TOKENS = 200_000;
const DEFAULT_CLAUDE_AUTO_COMPACT_PCT = 90;
const createClaudeModelMappings = (model: string): ClaudeModelMappings => ({
opus: model,
sonnet: model,
haiku: model,
fable: model,
const createClaudeModelMappings = (): ClaudeModelMappings => ({
opus: '',
sonnet: '',
haiku: '',
fable: '',
fable1m: false,
opus1m: false,
sonnet1m: false,
@@ -207,8 +207,8 @@ const createClaudeModelMappingsByClient = (): Record<
ClaudeModelMappingClientId,
ClaudeModelMappings
> => ({
'claude-code': createClaudeModelMappings(''),
'claude-desktop': { ...createClaudeModelMappings(''), desktopModels: createDefaultDesktopModels() },
'claude-code': createClaudeModelMappings(),
'claude-desktop': { ...createClaudeModelMappings(), desktopModels: createDefaultDesktopModels() },
});
const createClaudeBooleanByClient = (): Record<ClaudeModelMappingClientId, boolean> => ({
@@ -830,7 +830,7 @@ export function AgentsPage({ embedded = false, onConfigurationApplied }: AgentsP
const isClaudeModelMappingClient = selected === 'claude-code' || selected === 'claude-desktop';
const claudeModelMappingsDraft = isClaudeModelMappingClient
? claudeModelMappingsDraftByClient[selected]
: createClaudeModelMappings('');
: createClaudeModelMappings();
const claudeCustomMapping = isClaudeModelMappingClient
? claudeCustomMappingByClient[selected]
: false;
@@ -896,19 +896,19 @@ export function AgentsPage({ embedded = false, onConfigurationApplied }: AgentsP
current,
selected,
appliedMappings,
createClaudeModelMappings(selected === 'claude-desktop' ? '' : selectedModel),
createClaudeModelMappings(),
dirty,
);
const next: ClaudeModelMappings = {
...(selected === 'claude-desktop' ? { desktopModels: dirty && source.desktopModels
? source.desktopModels : appliedMappings ? desktopModelEntries(source) : createDefaultDesktopModels() } : {}),
opus: findAgentModel(models, source.opus)?.name ?? (selected === 'claude-code' ? source.opus || selectedModel : ''),
sonnet: findAgentModel(models, source.sonnet)?.name ?? (selected === 'claude-code' ? source.sonnet || selectedModel : ''),
haiku: findAgentModel(models, source.haiku)?.name ?? (selected === 'claude-code' ? source.haiku || selectedModel : ''),
opus: findAgentModel(models, source.opus)?.name ?? '',
sonnet: findAgentModel(models, source.sonnet)?.name ?? '',
haiku: findAgentModel(models, source.haiku)?.name ?? '',
opus1m: Boolean(source.opus1m),
sonnet1m: Boolean(source.sonnet1m),
haiku1m: Boolean(source.haiku1m),
fable: source.fable || source.sonnet || selectedModel,
fable: findAgentModel(models, source.fable)?.name ?? '',
fable1m: Boolean(source.fable1m),
maxContextTokens: source.maxContextTokens ?? DEFAULT_CLAUDE_CODE_MAX_CONTEXT_TOKENS,
autoCompactPct: source.autoCompactPct ?? DEFAULT_CLAUDE_AUTO_COMPACT_PCT,
@@ -928,7 +928,6 @@ export function AgentsPage({ embedded = false, onConfigurationApplied }: AgentsP
isClaudeModelMappingClient,
models,
selected,
selectedModel,
]);
const connectionState = activeStatus?.connectionState ?? 'invalid';
@@ -1075,12 +1074,7 @@ export function AgentsPage({ embedded = false, onConfigurationApplied }: AgentsP
const model = findAgentModel(models, value);
if (!model) return;
if (isClaudeModelMappingClient) {
editClaudeModelMappings((current) => ({
...current,
opus: model.name,
sonnet: model.name,
haiku: model.name,
}));
editClaudeModelMappings((current) => ({ ...current, sonnet: model.name }));
}
selectModel(model.name);
};
@@ -1164,7 +1158,7 @@ export function AgentsPage({ embedded = false, onConfigurationApplied }: AgentsP
return null;
}
const selectedEntries = selectedDesktopModelEntries(desktopEntries);
return { ...createClaudeModelMappings(''), sonnet: selectedEntries[0].model.trim(),
return { ...createClaudeModelMappings(), sonnet: selectedEntries[0].model.trim(),
desktopModels: selectedEntries.map((entry) => ({ ...entry, model: entry.model.trim(), alias: entry.alias.trim() })) };
}
const resolved = {} as ClaudeModelMappings;
+34 -13
View File
@@ -22,7 +22,7 @@
.auth-files-heading-actions > .auth-toolbar-quiet { border-color: transparent; background: transparent; color: var(--auth-muted); }
.auth-files-heading-actions > .auth-toolbar-quiet:hover:not(:disabled) { background: var(--bg-hover); color: var(--auth-text); }
.auth-files-page .auth-files-panel {
--auth-list-columns: minmax(168px, 1.1fr) 72px 92px 116px 156px minmax(240px, 2.4fr) 248px;
--auth-list-columns: minmax(160px, 1fr) 72px 90px 112px 148px minmax(220px, 1.35fr) minmax(300px, 1.55fr);
padding: 0;
min-height: 0;
border: 0;
@@ -64,6 +64,7 @@
.auth-status-badge.warning { color: var(--amber-text); background: color-mix(in srgb, var(--amber-text) 8%, var(--bg-card)); }
.auth-status-badge.error { color: var(--failure-badge-text); background: color-mix(in srgb, var(--failure-badge-text) 7%, var(--bg-card)); }
.auth-status-badge.info { color: var(--auth-accent); background: color-mix(in srgb, var(--auth-accent) 8%, var(--bg-card)); }
.auth-status-badge.neutral { color: var(--auth-muted); background: color-mix(in srgb, var(--auth-muted) 10%, var(--bg-card)); }
.auth-list-muted { color: var(--auth-muted); font-size: 12px; }
.auth-list-recent .auth-file-requests { gap: 9px; }
.auth-list-recent .auth-file-requests-latest { color: var(--auth-muted); font-size: 12px; font-weight: 650; text-align: center; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
@@ -80,14 +81,14 @@
.auth-list-usage .auth-file-usage-metric.total svg { color: var(--auth-accent); background: color-mix(in srgb, var(--auth-accent) 9%, var(--bg-card)); }
.auth-list-usage .auth-file-usage-metric strong { font-size: 13px; line-height: 1.2; font-weight: 750; }
.auth-list-usage .auth-file-usage-scope { margin-top: 6px; color: var(--auth-muted); text-align: right; font-size: 10px; line-height: 1.2; }
.auth-card-actions { display: flex; align-items: center; justify-content: flex-end; gap: 10px; min-width: 0; }
.auth-list-icon-actions { display: flex; align-items: center; gap: 6px; }
.auth-card-actions .auth-list-action { display: inline-flex; align-items: center; justify-content: center; flex: none; width: 32px; min-width: 32px; height: 32px; padding: 0; border: 1px solid color-mix(in srgb, var(--auth-accent) 16%, var(--auth-border)); border-radius: 8px; color: var(--auth-accent); background: var(--bg-card); cursor: pointer; }
.auth-card-actions { display: flex; align-items: center; justify-content: flex-end; flex-wrap: wrap; gap: 8px; min-width: 0; }
.auth-list-icon-actions { display: flex; align-items: center; justify-content: flex-end; flex-wrap: wrap; gap: 6px; min-width: max-content; }
.auth-card-actions .auth-list-action { display: inline-flex; align-items: center; justify-content: center; flex: none; width: auto; min-width: 0; min-height: 30px; padding: 4px 8px; gap: 4px; border: 1px solid color-mix(in srgb, var(--auth-accent) 16%, var(--auth-border)); border-radius: 8px; color: var(--auth-accent); background: var(--bg-card); font: inherit; font-size: 12px; font-weight: 650; line-height: 1.2; white-space: nowrap; cursor: pointer; }
.auth-card-actions .auth-list-action.muted { color: var(--auth-text); border-color: var(--auth-border); background: var(--bg-card); }
.auth-card-actions .auth-list-action.danger { color: #e11d48; border-color: color-mix(in srgb, #e11d48 22%, var(--auth-border)); background: var(--bg-card); }
.auth-card-actions .auth-list-action:disabled { opacity: .38; cursor: default; }
.auth-card-actions .auth-list-action:not(:disabled):hover { background: color-mix(in srgb, currentColor 8%, var(--bg-card)); border-color: currentColor; }
.auth-list-main-actions { display: flex; align-items: center; justify-content: flex-end; gap: 8px; padding-left: 10px; border-left: 1px solid var(--auth-border); }
.auth-list-main-actions { display: flex; align-items: center; justify-content: flex-end; flex: none; gap: 8px; padding-left: 10px; border-left: 1px solid var(--auth-border); }
.auth-list-switch { display: flex; align-items: center; flex: none; width: 40px; height: 24px; padding: 2px; border: 1px solid color-mix(in srgb, var(--auth-muted) 22%, var(--bg-card)); border-radius: 20px; background: color-mix(in srgb, var(--auth-muted) 28%, var(--bg-card)); cursor: pointer; }
.auth-list-switch > span { display: block; width: 18px; height: 18px; flex-shrink: 0; border-radius: 50%; background: #fff; box-shadow: 0 1px 3px #0002; transition: transform .15s; }
.auth-list-switch[aria-checked="true"] { background: var(--auth-accent); border-color: var(--auth-accent); }
@@ -153,16 +154,16 @@
@media (prefers-reduced-motion: reduce) { .credential-quota-track.indeterminate i { animation: none; width: 100%; opacity: .3; } }
@container credential-files (min-width: 701px) and (max-width: 1360px) {
.auth-file-list-head, .auth-credential-row { grid-template-columns: minmax(132px, 1fr) 58px 74px 92px 128px minmax(168px, 1.6fr) 214px;; column-gap: 8px; }
.auth-file-card-grid, .auth-file-list-head { min-width: 980px; }
.auth-card-actions { gap: 8px; }
.auth-list-icon-actions { gap: 4px; }
.auth-card-actions .auth-list-action { width: 30px; min-width: 30px; height: 30px; }
@container credential-files (min-width: 1501px) and (max-width: 1500px) {
.auth-file-list-head, .auth-credential-row { grid-template-columns: minmax(136px, 1fr) 62px 78px 96px 128px minmax(170px, 1.25fr) minmax(220px, 1.15fr); column-gap: 8px; }
.auth-file-card-grid, .auth-file-list-head { min-width: 1120px; }
.auth-card-actions { display: grid; grid-template-columns: minmax(0, 1fr); justify-items: end; gap: 6px; }
.auth-list-icon-actions { display: grid; grid-template-columns: repeat(3, max-content); justify-content: end; gap: 4px; }
.auth-card-actions .auth-list-action { min-height: 28px; padding: 3px 7px; font-size: 12px; }
.auth-list-details-button { min-width: 48px; min-height: 30px; padding-inline: 8px; font-size: 12px; }
.auth-list-usage .auth-file-usage-metric strong { font-size: 12px; }
}
@container credential-files (max-width: 700px) {
@container credential-files (max-width: 1500px) {
.auth-file-table-scroll { overflow: visible; }
.auth-file-list-head { display: none; }
.auth-file-card-grid { min-width: 0; margin-top: 10px; }
@@ -175,7 +176,7 @@
.auth-list-quota { grid-column: 1 / -1; border-top: 1px solid var(--auth-border); padding-top: 12px; }
.auth-card-actions { grid-column: 1 / -1; display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 8px; border-top: 1px solid var(--auth-border); padding-top: 12px; }
.auth-list-main-actions { display: flex; gap: 8px; padding-left: 8px; }
.auth-list-icon-actions { display: flex; gap: 4px; }
.auth-list-icon-actions { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 6px; }
.auth-list-usage .auth-file-usage-metrics { gap: 5px; }
.auth-list-usage .auth-file-usage-metric { gap: 3px; }
.auth-list-usage .auth-file-usage-metric strong { font-size: 11px; }
@@ -231,10 +232,30 @@
text-overflow: ellipsis;
white-space: nowrap;
}
.auth-files-page .credential-quota-dense .credential-quota-row:has(.credential-quota-row-detail) {
grid-column: 1 / -1;
}
.auth-files-page .credential-quota-dense .credential-quota-row-detail {
display: -webkit-box;
overflow: hidden;
white-space: normal;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
}
.auth-files-page .auth-file-card {
border-color: #edf0f4;
box-shadow: 0 1px 2px rgb(35 48 65 / 3%);
}
.auth-files-page .auth-card-actions {
min-width: 0;
max-width: 100%;
overflow: hidden;
}
.auth-files-page .auth-list-icon-actions,
.auth-files-page .auth-list-main-actions {
max-width: 100%;
}
.auth-files-page .auth-file-card:hover {
border-color: color-mix(in srgb, var(--auth-accent) 26%, #edf0f4);
box-shadow: 0 4px 14px rgb(35 83 160 / 7%);
+5 -8
View File
@@ -15,16 +15,13 @@ import {
Check,
ChevronLeft,
ChevronRight,
Copy,
FileDown,
FolderOpen,
Import,
LoaderCircle,
Network,
RefreshCw,
Search,
Settings2,
Trash2,
X,
} from 'lucide-react';
import antigravityIcon from '../assets/icons/antigravity.svg';
@@ -576,11 +573,11 @@ export function AuthFileManagementPage() {
</div>
<footer className="auth-card-actions auth-list-cell" data-label={t('authFiles.list.actions')}>
<div className="auth-list-icon-actions">
<button type="button" className="auth-list-action" onClick={() => void refreshQuota(file)} disabled={busy || disabled || !quotaProvider || quota.status === 'loading'} title={t('authFiles.quota.refresh')} aria-label={t('authFiles.quota.refresh')}><RefreshCw size={15} className={quota.status === 'loading' ? 'spin' : ''} aria-hidden="true" /></button>
<button type="button" className="auth-list-action" onClick={() => setSettingsName(name)} disabled={busy || resettingCooldown || !isOAuthCredentialFile(file)} title={t(isOAuthCredentialFile(file) ? 'authFiles.settings.title' : 'authFiles.fileOnly')} aria-label={t('authFiles.settings.title')}><Settings2 size={15} aria-hidden="true" /></button>
<button type="button" className="auth-list-action" onClick={() => setModelViewName(name)} disabled={busy || !providerKey(file)} title={t('authFiles.models.viewTitle')} aria-label={t('authFiles.models.viewTitle')}><Network size={15} aria-hidden="true" /></button>
<button type="button" className="auth-list-action muted" onClick={() => void copyName(name)} disabled={busy} title={t('authFiles.copyName')} aria-label={t('authFiles.copyName')}>{copied === name ? <Check size={15} aria-hidden="true" /> : <Copy size={15} aria-hidden="true" />}</button>
<button type="button" className="auth-list-action danger" onClick={() => void deleteFile(file)} disabled={busy || resettingCooldown || isRuntimeOnly(file)} title={t('common.delete')} aria-label={t('common.delete')}><Trash2 size={15} aria-hidden="true" /></button>
<button type="button" className="auth-list-action" onClick={() => void refreshQuota(file)} disabled={busy || disabled || !quotaProvider || quota.status === 'loading'} title={t('authFiles.quota.refresh')}><RefreshCw size={14} className={quota.status === 'loading' ? 'spin' : ''} aria-hidden="true" />{t('authFiles.action.refreshQuota')}</button>
<button type="button" className="auth-list-action" onClick={() => setSettingsName(name)} disabled={busy || resettingCooldown || !isOAuthCredentialFile(file)} title={t(isOAuthCredentialFile(file) ? 'authFiles.settings.title' : 'authFiles.fileOnly')}>{t('authFiles.action.settings')}</button>
<button type="button" className="auth-list-action" onClick={() => setModelViewName(name)} disabled={busy || !providerKey(file)} title={t('authFiles.models.viewTitle')}>{t('authFiles.action.models')}</button>
<button type="button" className="auth-list-action muted" onClick={() => void copyName(name)} disabled={busy} title={t('authFiles.copyName')}>{copied === name ? <Check size={14} aria-hidden="true" /> : null}{t('authFiles.action.copy')}</button>
<button type="button" className="auth-list-action danger" onClick={() => void deleteFile(file)} disabled={busy || resettingCooldown || isRuntimeOnly(file)} title={t('common.delete')}>{t('common.delete')}</button>
</div>
<div className="auth-list-main-actions">
<button type="button" className="auth-list-switch" role="switch" aria-checked={!disabled} aria-label={`${t(disabled ? 'common.enable' : 'common.disable')} ${identity || name}`} onClick={() => void toggleStatus(file)} disabled={busy || resettingCooldown || !isOAuthCredentialFile(file)} title={t(isOAuthCredentialFile(file) ? disabled ? 'common.enable' : 'common.disable' : 'authFiles.fileOnly')}><span /></button>
+3 -2
View File
@@ -3498,8 +3498,9 @@
flex: 1 1 auto;
overflow-y: auto;
overscroll-behavior: contain;
padding-right: 4px;
scrollbar-gutter: stable;
padding-right: 0;
scrollbar-gutter: auto;
scrollbar-width: none;
}
@media (min-width: 761px) {
+13 -6
View File
@@ -863,19 +863,26 @@ body, html, #root {
}
/* Agent List & Config Scrollbar Polish */
.agent-list-items::-webkit-scrollbar,
.agent-config-scroll-region::-webkit-scrollbar {
.agent-list-items::-webkit-scrollbar {
width: 6px;
}
.agent-list-items::-webkit-scrollbar-thumb,
.agent-config-scroll-region::-webkit-scrollbar-thumb {
.agent-config-scroll-region {
scrollbar-width: none;
}
.agent-config-scroll-region::-webkit-scrollbar {
display: none;
width: 0;
height: 0;
}
.agent-list-items::-webkit-scrollbar-thumb {
background: var(--border-primary);
border-radius: 4px;
}
.agent-list-items::-webkit-scrollbar-track,
.agent-config-scroll-region::-webkit-scrollbar-track {
.agent-list-items::-webkit-scrollbar-track {
background: transparent;
}