From fab5159cd4e16739d858ff3a94703acd8e878a92 Mon Sep 17 00:00:00 2001 From: lzt404 <2596933790@qq.com> Date: Thu, 8 Oct 2026 17:17:36 +0800 Subject: [PATCH] fix(ui): compact credential cards and stop overlapping content Replace the wide credential table with a quota-style card grid so account, plan, status, requests, usage, and quota stay readable without colliding. --- src/components/AuthFileHealthStatus.css | 4 +- src/components/AuthFileQuotaPanel.tsx | 4 +- src/pages/AuthFileManagementPage.css | 85 +++++++++++-------------- src/pages/AuthFileManagementPage.tsx | 2 +- tests/auth-file-cooldown-ui.cjs | 8 +-- tests/auth-file-list-ui.cjs | 17 +++-- 6 files changed, 54 insertions(+), 66 deletions(-) diff --git a/src/components/AuthFileHealthStatus.css b/src/components/AuthFileHealthStatus.css index 7ec1472..e42b0b6 100644 --- a/src/components/AuthFileHealthStatus.css +++ b/src/components/AuthFileHealthStatus.css @@ -130,7 +130,8 @@ .auth-file-health-compact .auth-health-countdown { margin: 0 0 0 auto; color: var(--text-tertiary); font-size: 11px; } .auth-file-health-compact .auth-health-disclosure { margin: 0; } .auth-file-health-compact .auth-health-body { padding: 0 0 12px; } -.auth-file-health-compact .auth-health-records { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 0 20px; } +.auth-file-health-compact .auth-health-records { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(190px, 100%), 1fr)); gap: 0 16px; } +.auth-file-health-compact .auth-health-record-heading strong { overflow-wrap: anywhere; } .auth-file-health-compact .auth-health-records li { min-width: 0; padding: 10px 0; } .auth-file-health-compact .auth-health-record-heading { gap: 4px 10px; } .auth-file-health-compact .auth-health-record-heading strong { font-size: 12px; font-weight: 600; } @@ -151,7 +152,6 @@ @container credential-files (max-width: 700px) { .auth-file-health.auth-file-health-compact { gap: 0 8px; } - .auth-file-health-compact .auth-health-records { grid-template-columns: minmax(0, 1fr); } .auth-file-health-compact .auth-health-summary { position: relative; padding-right: 18px; } .auth-file-health-compact .auth-health-summary-content { flex-basis: calc(100% - 24px); } .auth-file-health-compact .auth-health-countdown { margin: 0 0 0 22px; } diff --git a/src/components/AuthFileQuotaPanel.tsx b/src/components/AuthFileQuotaPanel.tsx index 3bc01aa..40fe493 100644 --- a/src/components/AuthFileQuotaPanel.tsx +++ b/src/components/AuthFileQuotaPanel.tsx @@ -2,7 +2,6 @@ import { ResetCreditExpiries } from './ResetCreditExpiries'; import { LoaderCircle, RefreshCw } from 'lucide-react'; import { useI18n } from '../i18n'; import { MessageNotice } from '../appNotice'; -import { readBoolean } from '../services/managementApi'; import { fileName, formatQuotaTimestamp, type AuthFile, type QuotaState } from '../services/quotaService'; import { canResetQuota, hasPendingClaudeReset } from '../services/quotaActions'; import { formatQuotaReset, useQuotaClock } from '../services/quotaTime'; @@ -22,7 +21,6 @@ export function AuthFileQuotaPanel({ quota, file, disabled, onRefresh, onReset, const loading = quota.status === 'loading'; const name = fileName(file); const compactLayout = compact || dense; - const fileDisabled = readBoolean(file, 'disabled'); const showReset = Boolean(onReset && ((quota.resetCredits ?? 0) > 0 || hasPendingClaudeReset(file))); const accountCreditsLabel = quota.creditsUnlimited ? t('quota.creditUnlimited') @@ -62,7 +60,7 @@ export function AuthFileQuotaPanel({ quota, file, disabled, onRefresh, onReset, : null} : null} {loading ?
{t(quota.pendingAction === 'reset' ? 'quota.resetting' : 'authFiles.quota.loading')}
: null} - {quota.status === 'idle' ?

{t(dense ? (fileDisabled ? 'quota.disabled' : 'quota.notFetched') : disabled ? 'quota.fileDisabled' : 'authFiles.settings.quotaIdle')}

: null} + {quota.status === 'idle' ?

{t(dense ? 'quota.notFetched' : disabled ? 'quota.fileDisabled' : 'authFiles.settings.quotaIdle')}

: null} {quota.status === 'error' ?
{dense && quota.error ?
{t('authFiles.quota.failed')}{quota.error}
: <>{t('authFiles.quota.failed')}{quota.error ? {quota.error} : null}} diff --git a/src/pages/AuthFileManagementPage.css b/src/pages/AuthFileManagementPage.css index 1c1ebea..aef049e 100644 --- a/src/pages/AuthFileManagementPage.css +++ b/src/pages/AuthFileManagementPage.css @@ -13,16 +13,15 @@ --auth-border: #293241; --success-color: #34d399; } -.auth-files-heading { display: flex; align-items: center; justify-content: space-between; gap: 12px; min-height: 36px; } +.auth-files-heading { display: flex; align-items: flex-start; justify-content: space-between; flex-wrap: wrap; gap: 8px 12px; min-height: 36px; } .auth-files-heading > div:first-child { display: flex; align-items: baseline; flex-wrap: wrap; gap: 6px 12px; } .auth-files-heading h2 { margin: 0; color: var(--auth-text); font-size: 16px; font-weight: 700; } .auth-files-heading p { margin: 0; color: var(--auth-muted); font-size: 12px; } -.auth-files-heading-actions { display: flex; align-items: center; flex-wrap: wrap; gap: 6px; } +.auth-files-heading-actions { display: flex; align-items: center; justify-content: flex-end; flex: 1 1 280px; flex-wrap: wrap; gap: 6px; } .auth-files-heading-actions > button { flex: none; width: auto; margin: 0; min-height: 32px; padding: 5px 10px; gap: 6px; border-radius: 7px; box-shadow: none; font-size: 12px; } .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(160px, 1fr) 72px 90px 112px 148px minmax(220px, 1.35fr) minmax(300px, 1.55fr); padding: 0; min-height: 0; border: 0; @@ -39,16 +38,20 @@ .auth-files-filters { display: flex; align-items: center; gap: 8px; } .auth-files-page .auth-files-toolbar .auth-files-filters select { width: auto; min-width: 0; max-width: 100%; height: 32px; padding: 0 8px; border: 1px solid var(--auth-border); border-radius: 7px; background: var(--bg-card); color: var(--auth-muted); font: inherit; font-size: 12px; } .auth-files-page .auth-files-toolbar select:focus-visible { outline: 2px solid var(--auth-accent); outline-offset: 1px; } -.auth-file-table-scroll { min-width: 0; overflow-x: auto; padding: 0 0 2px; } -.auth-file-list-head, .auth-credential-row { display: grid; grid-template-columns: var(--auth-list-columns); align-items: center; column-gap: 10px; } -.auth-file-list-head { min-width: 1280px; min-height: 38px; padding: 4px 14px 0; color: var(--auth-muted); font-size: 13px; font-weight: 700; } -.auth-file-list-head > span { min-width: 0; line-height: 1.25; text-align: center; } -.auth-file-list-head > :first-child, .auth-file-list-head > :nth-child(6) { text-align: left; } -.auth-file-card-grid { display: grid; grid-template-columns: minmax(0, 1fr); min-width: 1280px; gap: 10px; } -.auth-file-card { min-width: 0; padding: 0 14px; border: 1px solid var(--auth-border); border-radius: 10px; background: var(--bg-card); } +.auth-file-table-scroll { min-width: 0; overflow: visible; padding: 0 0 2px; } +.auth-file-list-head { display: none; } +.auth-file-card-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(420px, 100%), 1fr)); gap: 12px; align-items: stretch; min-width: 0; } +.auth-file-card { display: flex; flex-direction: column; min-width: 0; padding: 12px 14px 10px; border: 1px solid var(--auth-border); border-radius: 10px; background: var(--bg-card); } .auth-file-card:hover { border-color: color-mix(in srgb, var(--auth-accent) 24%, var(--auth-border)); } -.auth-credential-row { min-height: 108px; padding: 12px 0; } +.auth-credential-row { display: grid; grid-template-columns: minmax(0, 1fr) auto; grid-template-areas: "identity status" "plan plan" "recent recent" "usage usage" "quota quota" "actions actions"; align-items: start; gap: 8px 12px; min-height: 0; padding: 0; } .auth-list-cell { min-width: 0; } +.auth-card-header { grid-area: identity; } +.auth-list-plan { grid-area: plan; } +.auth-list-status { grid-area: status; } +.auth-list-recent { grid-area: recent; } +.auth-list-usage { grid-area: usage; } +.auth-list-quota { grid-area: quota; } +.auth-card-actions { grid-area: actions; } .auth-card-header { display: flex; gap: 10px; align-items: center; min-width: 0; } .auth-card-header .provider-logo { width: 35px; height: 35px; object-fit: contain; flex-shrink: 0; } [data-theme="dark"] .auth-card-header .provider-logo[data-provider="xai"] { filter: invert(1); } @@ -56,6 +59,9 @@ .auth-card-identity strong { color: var(--auth-text); font-size: 14px; font-weight: 750; line-height: 1.3; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .auth-card-filename, .auth-card-provider { color: var(--auth-muted); font-size: 12px; line-height: 1.35; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .auth-list-plan, .auth-list-status { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 8px; text-align: center; } +.auth-list-plan { flex-direction: row; align-items: baseline; justify-content: flex-start; gap: 8px; text-align: left; } +.auth-list-plan:not(:has(span, small)) { display: none; } +.auth-list-status { align-items: flex-end; justify-self: end; text-align: right; gap: 2px; } .auth-list-plan > span { max-width: 100%; color: var(--auth-text); font-size: 13px; font-weight: 600; text-transform: capitalize; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .auth-list-plan small, .auth-list-status small { color: var(--auth-muted); font-size: 12px; line-height: 1.3; } .auth-list-status small { font-weight: 600; } @@ -66,29 +72,34 @@ .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; } +.auth-list-recent, .auth-list-usage, .auth-list-quota { display: grid; gap: 4px; } +.auth-list-recent::before, .auth-list-usage::before, .auth-list-quota::before { content: attr(data-label); color: var(--auth-muted); font-size: 11px; font-weight: 650; line-height: 1.2; } +.auth-list-recent .auth-file-requests { display: grid; grid-template-columns: max-content minmax(0, 1fr); grid-template-areas: "time bars" "empty empty"; align-items: center; gap: 4px 10px; } +.auth-list-recent .auth-file-requests-latest { grid-area: time; color: var(--auth-muted); font-size: 12px; font-weight: 650; text-align: left; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } +.auth-list-recent .auth-file-requests-timeline { display: contents; } +.auth-list-recent .auth-file-requests-chart { grid-area: bars; max-width: none; width: auto; min-width: 0; } +.auth-list-recent .auth-file-requests-timeline > span { grid-area: empty; justify-self: start; color: var(--auth-muted); font-size: 12px; line-height: 1.4; } .auth-list-recent .auth-file-requests-blocks { gap: 2px; padding: 0; } -.auth-list-recent .auth-file-request-block { height: 16px; border-radius: 2px; } -.auth-list-recent .auth-file-requests-timeline { justify-content: center; gap: 5px; font-size: 11px; } -.auth-list-recent .auth-file-requests-unavailable { font-size: 12px; line-height: 1.4; text-align: center; color: var(--auth-muted); } +.auth-list-recent .auth-file-request-block { height: 12px; border-radius: 2px; } +.auth-list-recent .auth-file-requests-timeline { gap: 5px; font-size: 11px; } +.auth-list-recent .auth-file-requests-unavailable { grid-area: empty; justify-self: start; font-size: 12px; line-height: 1.4; text-align: left; color: var(--auth-muted); } .auth-list-usage .auth-file-usage { color: var(--auth-text); } -.auth-list-usage .auth-file-usage-metrics { grid-template-columns: max-content max-content; grid-template-rows: repeat(2, 20px); gap: 0 18px; justify-content: center; align-content: center; } +.auth-list-usage .auth-file-usage-metrics { grid-template-columns: repeat(auto-fit, minmax(92px, max-content)); grid-template-rows: none; gap: 4px 14px; justify-content: start; align-content: center; } .auth-list-usage .auth-file-usage-metric { gap: 5px; } .auth-list-usage .auth-file-usage-metric { min-height: 0; height: 20px; line-height: 1; } .auth-list-usage .auth-file-usage-metric svg { width: 12px; height: 12px; padding: 2px; } .auth-list-usage .auth-file-usage-metric svg { width: 12px; height: 12px; padding: 2px; border-radius: 4px; } .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; flex-wrap: wrap; gap: 8px; min-width: 0; } +.auth-list-usage .auth-file-usage-scope { display: none; } +.auth-card-actions { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 8px; min-width: 0; padding-top: 8px; border-top: 1px solid var(--auth-border); } .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; flex: none; 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; margin-left: auto; padding-left: 0; border-left: 0; } .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); } @@ -154,36 +165,12 @@ @media (prefers-reduced-motion: reduce) { .credential-quota-track.indeterminate i { animation: none; width: 100%; opacity: .3; } } -@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: 1500px) { .auth-file-table-scroll { overflow: visible; } .auth-file-list-head { display: none; } - .auth-file-card-grid { min-width: 0; margin-top: 10px; } - .auth-credential-row { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 14px 10px; padding: 14px 0; } - .auth-card-header { grid-column: 1 / 3; } - .auth-list-plan { grid-column: 3; } - .auth-list-status { grid-column: 1; } - .auth-list-recent { grid-column: 2; } - .auth-list-usage { grid-column: 3; } - .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; 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; } - .auth-list-usage .auth-file-usage-metric svg { width: 10px; height: 10px; padding: 1px; } - .auth-list-cell[data-label]::before { content: attr(data-label); display: block; margin-bottom: 7px; color: var(--auth-muted); font-size: 10px; font-weight: 500; } - .auth-list-plan[data-label]::before, .auth-list-status[data-label]::before { margin: 0; } - .auth-card-actions[data-label]::before { flex: none; margin: 0 auto 0 0; white-space: nowrap; } + .auth-file-card-grid { min-width: 0; margin-top: 0; } + .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; } } @container credential-files (max-width: 400px) { .auth-files-search { flex-basis: 100%; max-width: none; } @@ -201,7 +188,7 @@ /* Keep the quota column airy without the heavy framed inset used previously. */ .auth-files-page .auth-list-quota { min-width: 0; - padding: 0 10px; + padding: 2px 0 0; border: 0 !important; background: transparent !important; box-shadow: none !important; @@ -240,7 +227,7 @@ overflow: hidden; white-space: normal; -webkit-box-orient: vertical; - -webkit-line-clamp: 2; + -webkit-line-clamp: 3; } .auth-files-page .auth-file-card { border-color: #edf0f4; diff --git a/src/pages/AuthFileManagementPage.tsx b/src/pages/AuthFileManagementPage.tsx index aeddb07..41667fc 100644 --- a/src/pages/AuthFileManagementPage.tsx +++ b/src/pages/AuthFileManagementPage.tsx @@ -558,7 +558,7 @@ export function AuthFileManagementPage() {
- {plan || '—'} + {plan ? {plan} : null} {expiryMs !== undefined ? {expiryMs <= now ? t('authFiles.list.expired') : t('authFiles.list.daysRemaining', { count: Math.ceil((expiryMs - now) / 86_400_000) })} : null}
diff --git a/tests/auth-file-cooldown-ui.cjs b/tests/auth-file-cooldown-ui.cjs index e8ed701..13f2f1b 100644 --- a/tests/auth-file-cooldown-ui.cjs +++ b/tests/auth-file-cooldown-ui.cjs @@ -37,7 +37,7 @@ const fs = require('node:fs/promises'); }; const assertMainRowHeight = async label => { const heights = await page.locator('.auth-credential-row').evaluateAll(nodes => nodes.map(node => node.getBoundingClientRect().height)); - assert.ok(heights.every(height => height >= 88 && height <= 120), `${label}: cooldown content does not stretch the seven-column main row: ${JSON.stringify(heights)}`); + assert.ok(heights.every(height => height >= 140 && height <= 420), `${label}: cooldown content does not stretch the credential card: ${JSON.stringify(heights)}`); }; const open = async query => { await page.goto(`${base}/tests/fixtures/auth-file-cooldown.html?${query || ''}`); @@ -58,7 +58,7 @@ const fs = require('node:fs/promises'); assert.equal(await primary().locator('.auth-file-health-compact').isVisible(), true, 'Cooldown summary is visible before opening any details'); assert.equal(await primary().locator('.auth-health-body').isVisible(), false, 'Individual cooldown records start collapsed'); assert.ok((await primary().locator('.auth-health-heading').innerText()).includes('凭证及 2 个模型冷却')); - assert.equal(await primary().locator('.auth-credential-row > .auth-list-cell').count(), 7, 'Cooldown rows retain the complete seven-column overview'); + assert.equal(await primary().locator('.auth-credential-row > .auth-list-cell').count(), 7, 'Cooldown cards retain the complete seven-section overview'); assert.equal(await primary().locator('.auth-list-recent').isVisible(), true, 'Recent requests stay visible while diagnostics are collapsed'); assert.equal(await primary().locator('.auth-list-usage').isVisible(), true, 'Runtime counters stay visible while diagnostics are collapsed'); assert.equal(await primary().locator('.auth-list-icon-actions > button:visible').count(), 5, 'Credential actions stay directly available'); @@ -151,7 +151,7 @@ const fs = require('node:fs/promises'); await open(); await confirmReset(primary()); await page.evaluate(() => { window.cooldownFixture.holdNextRead = true; }); - await page.getByRole('button', { name: '刷新', exact: true }).click(); + await page.locator('.auth-files-heading').getByRole('button', { name: '刷新列表', exact: true }).click(); await page.waitForFunction(() => Boolean(window.cooldownFixture.releaseRead)); await releaseReset(); await page.waitForFunction(() => window.cooldownFixture.reads === 3 && window.cooldownFixture.completedReads === 2); @@ -209,7 +209,7 @@ const fs = require('node:fs/promises'); } } assert.deepEqual(errors, []); - console.log('PASS: directly visible cooldown summary/reset, independent keyboard disclosure, seven-column main-row height, cooldown models/reasons/countdowns, request summary counts/rate and keyboard tooltips, confirmation and cancel, single auth_index POST, pending duplicate protection, isolated success refresh, failure preservation, stale response protection, unknown/empty/missing-index guards, elapsed records, runtime/disabled state, light/dark desktop/mobile layout.'); + console.log('PASS: directly visible cooldown summary/reset, independent keyboard disclosure, compact card height, cooldown models/reasons/countdowns, request summary counts/rate and keyboard tooltips, confirmation and cancel, single auth_index POST, pending duplicate protection, isolated success refresh, failure preservation, stale response protection, unknown/empty/missing-index guards, elapsed records, runtime/disabled state, light/dark desktop/mobile layout.'); console.log(`Screenshots: ${screenshotDir}`); } finally { await browser?.close(); await server.close(); } })().catch(error => { console.error(error); process.exitCode = 1; }); diff --git a/tests/auth-file-list-ui.cjs b/tests/auth-file-list-ui.cjs index b3b0d12..e8e3cc2 100644 --- a/tests/auth-file-list-ui.cjs +++ b/tests/auth-file-list-ui.cjs @@ -45,7 +45,7 @@ const fs = require('node:fs/promises'); }))); assert.ok(dimensions.every(size => size.scroll <= size.width + 1 || ( size.className === 'auth-file-table-scroll' && size.width > 700 && size.width < 980 && size.overflowX === 'auto' - )), `${label}: only intermediate widths may scroll inside the table; cards must fit ${JSON.stringify(dimensions)}`); + )), `${label}: cards and the credential list must fit ${JSON.stringify(dimensions)}`); assert.equal(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth + 1), true, `${label}: page overflow`); }; const assertMainInformation = async (label, width) => { @@ -58,14 +58,17 @@ const fs = require('node:fs/promises'); assert.equal(await card(1).getByRole('switch').isVisible(), true); assert.equal(await card(1).locator('.auth-list-details-button').isVisible(), true); assert.equal(await card(1).locator('.auth-list-details').isVisible(), false, `${label}: file metadata and the expanded request summary start collapsed`); - if (width >= 1800) { + if (width >= 1024) { const heights = await page.locator('.auth-credential-row').evaluateAll(nodes => nodes.map(node => node.getBoundingClientRect().height)); - assert.ok(heights.every(height => height >= 88 && height <= 190), `${label}: desktop rows stay compact, allowing labeled actions to wrap: ${JSON.stringify(heights)}`); + assert.ok(heights.every(height => height >= 140 && height <= 420), `${label}: credential cards stay compact: ${JSON.stringify(heights)}`); const cells = await card(1).locator('.auth-credential-row > .auth-list-cell').evaluateAll(nodes => nodes.map(node => { - const bounds = node.getBoundingClientRect(); return { left: bounds.left, right: bounds.right }; + const bounds = node.getBoundingClientRect(); return { top: bounds.top, bottom: bounds.bottom, left: bounds.left, right: bounds.right, hidden: bounds.width === 0 }; })); - assert.equal(cells.length, 7, `${label}: desktop has seven information columns`); - assert.ok(cells.slice(1).every((cell, index) => cell.left >= cells[index].right - 1), `${label}: all seven columns remain in one row`); + assert.equal(cells.length, 7, `${label}: each card keeps seven information sections`); + assert.equal(cells[1].hidden, false, `${label}: a known plan remains visible`); + assert.ok(cells[2].left >= cells[0].right - 1, `${label}: status stays beside the account`); + assert.ok(cells[1].top >= cells[0].bottom - 8, `${label}: plan stays on its own row`); + assert.ok(cells.slice(3).every(cell => cell.top >= cells[1].top), `${label}: activity, quota and actions follow the account`); } }; await fs.mkdir(screenshotDir, { recursive: true }); @@ -273,7 +276,7 @@ const fs = require('node:fs/promises'); await page.screenshot({ path: path.join(screenshotDir, `display-cases-${width}.png`), fullPage: true }); } - console.log('PASS: offline seven-column dense credential list with visible request/runtime/quota/action data, compact row height, exact quota percentages/reset timestamps, unknown quota, ten/two pagination, search/provider/status reset, page size twenty, additional quota disclosure, main-row and detail-summary request keyboard tooltips, directly visible cooldown controls, exact status PATCH, direct delete confirmation/cancel, small idle accounts without pagination, light/dark Chinese/English 1800/1280/1024/390 without page or card overflow, contained table scrolling only at intermediate widths.'); + console.log('PASS: offline compact credential card grid with visible request/runtime/quota/action data, compact row height, exact quota percentages/reset timestamps, unknown quota, ten/two pagination, search/provider/status reset, page size twenty, additional quota disclosure, main-row and detail-summary request keyboard tooltips, directly visible cooldown controls, exact status PATCH, direct delete confirmation/cancel, small idle accounts without pagination, light/dark Chinese/English 1800/1280/1024/390 without page or card overflow, contained table scrolling only at intermediate widths.'); console.log(`Screenshots: ${screenshotDir}`); } finally { await browser?.close(); await server.close(); } })().catch(error => { console.error(error); process.exitCode = 1; });