From f7e9bd98846d7ecc2c0b6fec65be60c2083da2f2 Mon Sep 17 00:00:00 2001 From: lzt404 <2596933790@qq.com> Date: Thu, 8 Oct 2026 15:46:27 +0800 Subject: [PATCH] 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 --- .../claude-code/ClaudeCodeModelInputs.css | 2 +- .../claude-code/ClaudeCodeModelInputs.tsx | 1 - src/i18n/authFileList.ts | 12 ++++ src/pages/AgentsPage.tsx | 36 ++++------ src/pages/AuthFileManagementPage.css | 47 ++++++++---- src/pages/AuthFileManagementPage.tsx | 13 ++-- src/styles/feature-pages.css | 5 +- src/styles/ui-refinements.css | 19 +++-- tests/auth-file-list-ui.cjs | 35 ++++++++- tests/claude-code-model-inputs-ui.cjs | 2 +- tests/claude-code-model-layout-ui.cjs | 71 +++++++++++++++++++ tests/fixtures/agent-backups.tsx | 7 ++ tests/fixtures/auth-file-list.tsx | 23 +++--- 13 files changed, 208 insertions(+), 65 deletions(-) create mode 100644 tests/claude-code-model-layout-ui.cjs diff --git a/src/components/claude-code/ClaudeCodeModelInputs.css b/src/components/claude-code/ClaudeCodeModelInputs.css index 664cfff..9181129 100644 --- a/src/components/claude-code/ClaudeCodeModelInputs.css +++ b/src/components/claude-code/ClaudeCodeModelInputs.css @@ -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; diff --git a/src/components/claude-code/ClaudeCodeModelInputs.tsx b/src/components/claude-code/ClaudeCodeModelInputs.tsx index f977551..3f81782 100644 --- a/src/components/claude-code/ClaudeCodeModelInputs.tsx +++ b/src/components/claude-code/ClaudeCodeModelInputs.tsx @@ -50,7 +50,6 @@ export function ClaudeCodeModelInputs({ startupModel, subagentModel, models, rou 1M - {preview.role && {preview.role} → {preview.model}} {!loading && !isKnownClaudeCodeModel(value, models) ? ( {t('agents.claudeCodeRuntime.unverifiedModel')} ) : null} diff --git a/src/i18n/authFileList.ts b/src/i18n/authFileList.ts index 82e6fbf..7e84051 100644 --- a/src/i18n/authFileList.ts +++ b/src/i18n/authFileList.ts @@ -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 = { @@ -52,6 +56,10 @@ export const authFileListEn: Record = { '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 = { @@ -80,4 +88,8 @@ export const authFileListJa: Record = { 'authFiles.usage.title': '累計使用量', 'authFiles.toolbar.refreshList': '一覧を更新', 'authFiles.toolbar.refreshQuota': 'クォータを更新', + 'authFiles.action.refreshQuota': '更新', + 'authFiles.action.settings': '設定', + 'authFiles.action.models': 'モデル', + 'authFiles.action.copy': 'コピー', }; diff --git a/src/pages/AgentsPage.tsx b/src/pages/AgentsPage.tsx index 2b61b6c..eacd8c9 100644 --- a/src/pages/AgentsPage.tsx +++ b/src/pages/AgentsPage.tsx @@ -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 => ({ @@ -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; diff --git a/src/pages/AuthFileManagementPage.css b/src/pages/AuthFileManagementPage.css index da931a1..1c1ebea 100644 --- a/src/pages/AuthFileManagementPage.css +++ b/src/pages/AuthFileManagementPage.css @@ -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%); diff --git a/src/pages/AuthFileManagementPage.tsx b/src/pages/AuthFileManagementPage.tsx index e4c00d5..aeddb07 100644 --- a/src/pages/AuthFileManagementPage.tsx +++ b/src/pages/AuthFileManagementPage.tsx @@ -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() {