Skip to content

Feat/dashboard UI improvements#657

Merged
ding113 merged 12 commits intodevfrom
feat/dashboard-ui-improvements
Jan 27, 2026
Merged

Feat/dashboard UI improvements#657
ding113 merged 12 commits intodevfrom
feat/dashboard-ui-improvements

Conversation

@ding113
Copy link
Owner

@ding113 ding113 commented Jan 26, 2026

Summary

This PR enhances the dashboard UI with multiple improvements including server timezone support for logs filtering, provider chain display enhancements, TagInput dropdown positioning fixes inside dialogs, and provider vendor view UX improvements.

Changes

Core Changes

Server Timezone Support for Logs Filtering

  • Added getServerTimeZone() server action to expose the server's TZ environment variable
  • Updated time range utilities to support timezone-aware date/time conversions using date-fns-tz
  • Propagated serverTimeZone prop through logs filter components (TimeFilters, LogsDateRangePicker, UsageLogsFilters)
  • Ensures date filtering in logs dashboard uses server timezone for accurate "today", "yesterday", etc. calculations

Provider Chain Popover Improvements

  • Added 5 new i18n strings for provider exclusion reasons: excluded, format_type_mismatch, type_mismatch, model_not_allowed, context_1m_disabled
  • Improved group tag badge display: now parses comma-separated tags, deduplicates them, and renders each as a separate badge with tooltip
  • Added vendorAggregationRule i18n string explaining vendor grouping logic

TagInput Dropdown Positioning Fix

  • Fixed dropdown positioning when TagInput is rendered inside a Dialog component
  • Detects if TagInput is inside a [data-slot="dialog-content"] container
  • Uses absolute positioning relative to dialog content instead of fixed viewport positioning
  • Properly handles scroll events within the dialog container

Provider Vendor View Enhancements

  • Added inline Switch toggle for enabling/disabling endpoints directly in the table row
  • Removed endpoint label field from add/edit forms (simplified UX)
  • Added info tooltip explaining vendor aggregation rule ("Grouped by website domain")
  • Hide vendor cards that have zero providers associated
  • Fixed deriveDisplayNameFromDomain to properly extract display name from domains like co.yes.vg

Cache Badge Alignment Fix

  • Moved cache TTL badge (1h, 5m) to the left side of the cell
  • Token numbers remain right-aligned with ml-auto class
  • Applied consistently across UsageLogsTable, VirtualizedLogsTable, and my-usage table

Availability Dashboard Cleanup

  • Removed FloatingProbeButton component from availability dashboard

Supporting Changes

i18n Updates (5 languages: en, ja, ru, zh-CN, zh-TW)

  • Added provider chain exclusion reason translations
  • Added vendorAggregationRule translation for provider settings

Test Coverage

  • provider-chain-popover.test.tsx: Added test for multiple deduped group badges
  • tag-input-dialog.test.tsx: New test file for TagInput inside Dialog scenarios
  • dashboard-logs-time-range-utils.test.ts: Added test for timezone-aware quick date ranges
  • dashboard-logs-virtualized-special-settings-ui.test.tsx: Added cache badge alignment test
  • dashboard-logs-warmup-ui.test.tsx: Added cache badge alignment test
  • availability-dashboard-ui.test.tsx: New test verifying probe button removal
  • provider-endpoints-display-name.test.ts: New test for domain name derivation
  • provider-vendor-view-circuit-ui.test.tsx: Added tests for vendor list filtering and endpoint toggle

Breaking Changes

None. All changes are backward compatible.

Testing

Automated Tests

  • Unit tests added for all new functionality
  • Existing tests updated where needed

Manual Testing

  1. Timezone filtering: Set server TZ to a different timezone, verify "Today" filter shows correct date
  2. TagInput in Dialog: Open a dialog with TagInput, verify dropdown appears correctly positioned
  3. Provider endpoint toggle: Toggle endpoint enabled/disabled via inline switch
  4. Cache badge alignment: Verify TTL badge appears on left, numbers on right in logs tables

Checklist

  • Code follows project conventions
  • Self-review completed
  • Tests pass locally
  • i18n strings added for all 5 supported languages

Description enhanced by Claude AI

Greptile Overview

Greptile Summary

This PR enhances the dashboard UI with multiple well-implemented improvements focused on internationalization, timezone handling, and UX refinements.

Key Changes

  • Server Timezone Support: Added timezone-aware date/time conversion using date-fns-tz for accurate logs filtering. The implementation properly uses toZonedTime and fromZonedTime to handle server timezone differences, ensuring "Today" and "Yesterday" filters work correctly regardless of server location.

  • TagInput Dialog Positioning Fix: Solved dropdown positioning issues when TagInput is rendered inside Dialog components by detecting the dialog container and switching from fixed to absolute positioning with proper scroll event handling.

  • Domain Name Derivation Improvement: Enhanced deriveDisplayNameFromDomain to handle complex TLDs (like co.yes.vg) by parsing from right-to-left and supporting multiple API prefix patterns (api, v1, v2, v3, www). Test coverage confirms backward compatibility with existing domains.

  • Token Overflow Prevention: Changed token count columns from integer to bigint to prevent overflow for large token values, addressing a real production concern for high-volume usage.

  • Provider Vendor UX: Added inline endpoint toggle switches, removed unnecessary label fields, added vendor aggregation tooltips, and hide empty vendor cards for cleaner UI.

  • Cache Badge Alignment: Improved visual consistency by moving TTL badges to the left with token numbers right-aligned.

  • i18n Coverage: Added translations for 5 new provider exclusion reasons across all 5 supported languages.

Test Coverage

Excellent test coverage with new test files for all major changes:

  • tag-input-dialog.test.tsx - Dialog positioning scenarios
  • provider-endpoints-display-name.test.ts - Domain derivation logic
  • dashboard-logs-time-range-utils.test.ts - Timezone-aware date ranges
  • Token aggregation and overflow tests
  • Cache badge alignment tests

All changes are backward compatible with no breaking changes.

Confidence Score: 5/5

  • This PR is safe to merge with minimal risk
  • Score reflects comprehensive test coverage, backward-compatible changes, and well-thought-out implementations. All major changes (timezone support, TagInput positioning, domain derivation, token overflow prevention) have corresponding test coverage. The schema change for providerVendorId.notNull() matches existing database state. Only minor style improvement suggested.
  • No files require special attention

Important Files Changed

Filename Overview
src/repository/provider-endpoints.ts Improved domain name derivation logic to handle complex TLDs like co.yes.vg by checking from right to left and handling multiple API prefixes
src/components/ui/tag-input.tsx Fixed dropdown positioning when TagInput is inside Dialog by detecting dialog container and using absolute positioning with proper scroll handling
src/app/[locale]/dashboard/logs/_utils/time-range.ts Added timezone-aware date/time conversion utilities using date-fns-tz for accurate server timezone handling in logs filtering
src/app/[locale]/settings/providers/_components/provider-vendor-view.tsx Added inline endpoint toggle switch, removed label field from forms, added vendor aggregation tooltip, and hide empty vendor cards
src/drizzle/schema.ts Changed token columns to bigint to prevent overflow, added explicit notNull constraint to providerVendorId (matches existing DB state)
drizzle/0057_conscious_quicksilver.sql Migration to convert token count columns from integer to bigint to prevent overflow for large token values

Sequence Diagram

sequenceDiagram
    participant Client as Browser Client
    participant Server as Next.js Server
    participant Action as System Config Action
    participant TimeUtils as Time Range Utils
    participant DB as PostgreSQL DB
    
    Note over Client,DB: Server Timezone Support Flow
    Client->>Server: Load Dashboard Page
    Server->>Action: getServerTimeZone()
    Action->>Action: Read TZ env variable
    Action-->>Server: Return timezone string
    Server-->>Client: Render with serverTimeZone prop
    
    Client->>Client: User selects "Today" filter
    Client->>TimeUtils: getQuickDateRange("today", serverTimeZone)
    TimeUtils->>TimeUtils: toZonedTime(now, timeZone)
    TimeUtils->>TimeUtils: formatInTimeZone(date, timeZone)
    TimeUtils-->>Client: Return {startDate, endDate}
    
    Client->>TimeUtils: dateStringWithClockToTimestamp(date, clock, timezone)
    TimeUtils->>TimeUtils: fromZonedTime(date, timeZone)
    TimeUtils-->>Client: Return timestamp
    
    Client->>DB: Query logs with timestamp range
    DB-->>Client: Return filtered logs
    
    Note over Client,DB: Tag Input in Dialog Flow
    Client->>Client: Open Dialog with TagInput
    Client->>Client: User focuses input
    Client->>Client: Detect [data-slot="dialog-content"]
    Client->>Client: Calculate dropdown position
    alt Inside Dialog
        Client->>Client: Use absolute positioning
        Client->>Client: Listen to dialog scroll events
    else Outside Dialog
        Client->>Client: Use fixed positioning
        Client->>Client: Listen to window scroll events
    end
    Client->>Client: Render dropdown suggestions
    
    Note over Client,DB: Provider Vendor Display Flow
    Client->>DB: Fetch provider endpoints
    DB-->>Client: Return endpoints with vendorId
    
    alt Vendor has providers
        Client->>Client: Show vendor card
        Client->>Client: Render endpoint rows with inline toggle
    else Vendor has no providers
        Client->>Client: Hide vendor card
    end
    
    Client->>Client: User toggles endpoint switch
    Client->>Server: editProviderEndpoint(endpointId, isEnabled)
    Server->>DB: UPDATE provider_endpoints
    DB-->>Server: Success
    Server-->>Client: Invalidate cache & refresh
Loading

ding113 and others added 5 commits January 26, 2026 20:15
- Add time range filter improvements with preset options
- Enhance provider chain popover with better error display
- Improve provider vendor view circuit breaker UI
- Add availability dashboard and provider endpoints display name tests
- Update i18n messages for provider chain and settings

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…ibility

The file has "use server" directive, which requires all exported functions
to be async. This fixes the Next.js Turbopack build error:
"Server Actions must be async functions."

CI Run: https://github.com/ding113/claude-code-hub/actions/runs/21357337034

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Detect when TagInput is inside a Dialog and portal suggestions to dialog-content
- Use absolute positioning relative to dialog container instead of fixed viewport positioning
- Fix scroll/resize event listeners to target the correct scroll container
- Add unit tests for TagInput behavior inside Dialog

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@coderabbitai
Copy link

coderabbitai bot commented Jan 26, 2026

📝 Walkthrough

Walkthrough

新增多个本地化键与提示;引入并传递 serverTimeZone 到日志/使用界面,添加时区感知的时间范围工具;provider-chain 支持解析/去重并渲染多徽章;端点支持启用切换;将若干 message_request token 列改为 bigint,SQL 聚合改为 double precision;并新增/调整大量测试与布局对齐。

Changes

Cohort / File(s) 变更说明
i18n — provider-chain 本地化
messages/en/provider-chain.json, messages/ja/provider-chain.json, messages/ru/provider-chain.json, messages/zh-CN/provider-chain.json, messages/zh-TW/provider-chain.json
filterReasons 下新增键:excludedformat_type_mismatchtype_mismatchmodel_not_allowedcontext_1m_disabled(各语言翻译)
i18n — providers 设置字符串
messages/en/settings/providers/strings.json, messages/ja/settings/providers/strings.json, messages/ru/settings/providers/strings.json, messages/zh-CN/settings/providers/strings.json, messages/zh-TW/settings/providers/strings.json
新增 vendorAggregationRule 翻译键(各语言对应翻译)
服务动作:时区接口
src/actions/system-config.ts
新增导出函数 getServerTimeZone(),鉴权后返回环境时区,含错误处理
时区感知时间工具
src/app/[locale]/dashboard/logs/_utils/time-range.ts, tests/unit/dashboard-logs-time-range-utils.test.ts
新增导出类型 QuickPeriodgetQuickDateRange(period, timeZone?);多处函数接受可选 timeZone,支持时区感知的日期/时钟格式化与解析并新增测试
日志组件:serverTimeZone 传播
src/app/[locale]/dashboard/logs/_components/*, src/app/[locale]/my-usage/*, src/app/[locale]/dashboard/logs/_components/usage-logs-view-virtualized.tsx, src/app/[locale]/dashboard/logs/_components/usage-logs-view.tsx
多个组件新增可选 serverTimeZone?: string prop 并向下传递(TimeFilters、LogsDateRangePicker、UsageLogsView 等),并在依赖数组/计算中加入该参数
LogsDateRangePicker 与 TimeFilters 改动
src/app/[locale]/dashboard/logs/_components/logs-date-range-picker.tsx, src/app/[locale]/dashboard/logs/_components/filters/time-filters.tsx
支持 serverTimeZone,重构 quick-period 检测(含 PickerQuickPeriod)、新增 shiftDateRange,更新依赖与日期/时钟计算
provider-chain UI 与测试
src/app/[locale]/dashboard/logs/_components/provider-chain-popover.tsx, src/app/[locale]/dashboard/logs/_components/provider-chain-popover.test.tsx
新增 parseGroupTags() 用于解析/去重逗号分隔的 groupTag;渲染多个带 Tooltip 的徽章;新增测试(注意测试块在 diff 中重复)
提供商设置视图变更
src/app/[locale]/settings/providers/_components/provider-vendor-view.tsx
添加 InfoIcon/Tooltip 显示 vendorAggregationRule;为端点添加启用/禁用 Switch(含 toggle mutation、乐观更新、回滚);移除 endpoint label 的显示/表单字段
TagInput 下拉在 Dialog 中支持
src/components/ui/tag-input.tsx, src/components/ui/__tests__/tag-input-dialog.test.tsx
下拉改为可在 Dialog 内通过 Portal 渲染并依据 portal 容器计算位置;新增对话框内交互测试
表格/布局对齐调整(缓存/令牌显示)
src/app/[locale]/dashboard/logs/_components/usage-logs-table.tsx, src/app/[locale]/dashboard/logs/_components/virtualized-logs-table.tsx, src/app/[locale]/my-usage/_components/usage-logs-table.tsx
调整容器与类名,将令牌数量右移(使用 ml-auto)、修改 gap/宽度以改善对齐与布局;相关测试新增断言(ml-auto 类存在)
域名派生显示名异步化
src/repository/provider-endpoints.ts, tests/unit/repository/provider-endpoints-display-name.test.ts
deriveDisplayNameFromDomain(domain) 改为异步 Promise<string>,增强域名段解析与 api/www 前缀处理;更新调用处为 await 并补充测试
数据库迁移与模式更改
drizzle/0057_conscious_quicksilver.sql, drizzle/meta/0057_snapshot.json, drizzle/meta/_journal.json, src/drizzle/schema.ts
message_request 的若干 token 列从 integer 改为 bigint(mode: number);新增 SQL migration、schema 更改与 meta 快照/journal 条目;导入 bigint 类型
Token 聚合类型调整(SQL)
src/actions/my-usage.ts, src/repository/key.ts, src/repository/leaderboard.ts, tests/unit/*
多处 SUM 聚合改为先 cast 为 double precision::double precision)以避免整型溢出;新增/调整单元测试以断言生成 SQL 不包含 ::int/::int4
ActiveSessions / SessionList 改动
src/components/customs/active-sessions-list.tsx, src/components/customs/session-list-item.tsx, src/app/[locale]/dashboard/logs/_components/active-sessions-skeleton.tsx
ActiveSessionsList 增加 showTokensCost?: booleanSessionListItem 提供导出接口并支持 showTokensCost;ActiveSessionsSkeleton 导出并调整骨架布局
Dashboard 布局调整
src/app/[locale]/dashboard/_components/dashboard-bento.tsx, src/app/[locale]/dashboard/_components/dashboard-main.tsx
调整 Dashboard 布局(合并排行榜与侧栏区域)、规范化 pathname(移除尾斜杠)
MyUsage 页面与统计卡
src/app/[locale]/my-usage/page.tsx, src/app/[locale]/my-usage/_components/statistics-summary-card.tsx, src/app/[locale]/my-usage/_components/usage-logs-section.tsx
在 MyUsage 页面获取 serverTimeZone 并将其传入相关组件;组件签名新增可选 serverTimeZone 并向下传递
测试覆盖扩展与调整
tests/unit/*(多个新增/更新测试文件,如 session-list-item、availability-dashboard、token-aggregation 等)
新增和更新大量单元测试,覆盖时区逻辑、provider-chain 徽章、TagInput dialog、token 聚合 SQL、防止整型溢出、表格对齐、AvailabilityDashboard UI 等行为

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 10.53% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed 标题简洁清晰,准确反映了此PR的主要改进内容,涵盖了仪表盘UI增强的核心主题。
Description check ✅ Passed 描述详细完整,清晰说明了所有主要更改、测试覆盖范围和向后兼容性,与变更集内容完全相关。

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/dashboard-ui-improvements

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @ding113, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request delivers a suite of UI improvements primarily focused on the dashboard's logs and provider settings. It introduces crucial timezone handling for log data, enhances the display of provider chain details, and refines the user experience for managing provider endpoints. Additionally, it includes general UI tweaks for better readability and ensures component compatibility within complex layouts.

Highlights

  • Internationalization Updates: Added new translation keys across multiple languages (English, Japanese, Russian, Simplified Chinese, Traditional Chinese) for various provider chain statuses (e.g., 'Excluded', 'Format Type Mismatch', 'Model Not Allowed') and a new 'Vendor Aggregation Rule' message.
  • Timezone Support for Logs: Implemented server timezone awareness in the usage logs dashboard. Date and time filters, as well as quick date range selections, now correctly reflect the server's configured timezone, improving accuracy for distributed teams.
  • Provider Chain Popover Enhancements: The provider chain popover now displays multiple deduped group tags as individual badges, each with a tooltip for better clarity and information density.
  • Provider Endpoint Management Refinements: Streamlined the provider endpoint management in the settings. Endpoint labels have been removed, and a new toggle switch allows for easy enabling/disabling of individual endpoints directly within the table. A tooltip has also been added to vendor cards to explain the aggregation rule.
  • UI Alignment for Usage Logs Tables: Adjusted the layout of cache badges and token amounts in both the main and virtualized usage logs tables, ensuring cache badges are left-aligned while token numbers remain right-aligned for improved readability.
  • TagInput Component Portal Fix: The TagInput component has been updated to correctly render its suggestions dropdown when used inside a Dialog component, utilizing React Portals to prevent clipping and ensure proper positioning.
  • Domain Display Name Logic Improvement: The deriveDisplayNameFromDomain function has been made more robust, improving its ability to parse and generate display names from various domain formats, including handling api prefixes and multi-part domains more accurately.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@github-actions github-actions bot added enhancement New feature or request area:UI area:i18n size/XL Extra Large PR (> 1000 lines) labels Jan 26, 2026
Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces several new translation keys across multiple languages for provider chain reasons and a vendor aggregation rule. It enhances date and time handling in log and usage dashboards by implementing server-side timezone awareness, fetching the server's timezone, and passing it down to various date/time filter components for accurate display and calculation. The ProviderChainPopover now displays individual, deduped group tags as badges with tooltips, and the cache token display in log tables has been restyled for better alignment. Additionally, the 'Probe All' floating button has been removed from the availability dashboard, and the provider endpoint management has been refined by removing the 'label' field, adding an enable/disable switch for endpoints, and improving the logic for displaying vendor cards. A new unit test for TagInput within a Dialog was added, and the deriveDisplayNameFromDomain utility was updated for more robust domain name parsing.

try {
const session = await getSession();
if (!session) {
return { ok: false, error: "未授权" };
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The error message "未授权" is hardcoded in Chinese. For better internationalization, consider using a translation key from the next-intl library.

return { ok: true, data: { timeZone: TZ } };
} catch (error) {
logger.error("获取时区失败:", error);
return { ok: false, error: "获取时区失败" };
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The error message "获取时区失败" is hardcoded in Chinese. For better internationalization, consider using a translation key from the next-intl library.

Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 file reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

Comment on lines 245 to 248
let name = parts[parts.length - 2];
if (name === "api" && parts.length >= 3) {
name = parts[parts.length - 3];
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic for detecting api prefix changed from checking first part to checking second-to-last part - breaks for domains like api.openai.com where original expected parts[0] === "api" but new checks parts[parts.length - 2] which is "openai" not "api"

Suggested change
let name = parts[parts.length - 2];
if (name === "api" && parts.length >= 3) {
name = parts[parts.length - 3];
}
let name = parts[0];
if (name === "api" && parts.length >= 2) {
name = parts[1];
} else if (parts.length > 1) {
name = parts[parts.length - 2];
}
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/repository/provider-endpoints.ts
Line: 245:248

Comment:
logic for detecting `api` prefix changed from checking first part to checking second-to-last part - breaks for domains like `api.openai.com` where original expected `parts[0] === "api"` but new checks `parts[parts.length - 2]` which is "openai" not "api"

```suggestion
  let name = parts[0];
  if (name === "api" && parts.length >= 2) {
    name = parts[1];
  } else if (parts.length > 1) {
    name = parts[parts.length - 2];
  }
```

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review Summary

No significant issues identified in this PR. The changes implement dashboard UI improvements including server timezone support, cache badge alignment fixes, group tag badge enhancements, and tag input dialog positioning fixes.

PR Size: XL

  • Lines changed: 980 (858 additions, 122 deletions)
  • Files changed: 37

Split Suggestion: This XL PR touches multiple independent features. Consider splitting future similar PRs into:

  1. Time zone support changes (time-range utils, filters, date pickers)
  2. UI layout fixes (cache badge alignment, group tag badges)
  3. Provider vendor view enhancements (toggle switch, info tooltip)
  4. Tag input dialog fix (separate component change)

Changes Overview

Category Description
i18n Added 5 new translation keys across all 5 languages for provider chain reasons
Time Zone Server timezone now passed through to date/time filters for accurate date calculations
UI/UX Cache TTL badge moved to left side, group tags now display as separate badges with tooltips
Components TagInput dropdown now correctly positions inside Dialog components
Provider View Added inline toggle switch for endpoint enable/disable, info tooltip for vendor aggregation
Tests Added comprehensive tests for new functionality

Review Coverage

  • Logic and correctness - Clean
  • Security (OWASP Top 10) - Clean
  • Error handling - Clean
  • Type safety - Clean
  • Documentation accuracy - Clean
  • Test coverage - Adequate (new tests added for key changes)
  • Code clarity - Good

Notes

  • All i18n strings properly added to all 5 supported languages (zh-CN, zh-TW, en, ja, ru)
  • The deriveDisplayNameFromDomain function was changed to async but contains no async operations - this is a minor design choice that doesn't affect functionality
  • Removed FloatingProbeButton from availability dashboard (intentional UI simplification)
  • Removed endpoint label field from add/edit dialogs (intentional simplification)

Automated review by Claude AI

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
src/app/[locale]/my-usage/_components/statistics-summary-card.tsx (1)

31-45: 默认日期应与 serverTimeZone 对齐。
目前初始 dateRange 仍使用客户端本地日期;当 serverTimeZone 与本地不一致时,首次加载统计范围可能偏移一天,且与 LogsDateRangePicker 显示不一致。建议初始化时基于 serverTimeZone 计算 today(可复用日志时间范围工具或与 picker 同源逻辑)。

Also applies to: 129-134

src/app/[locale]/dashboard/logs/_components/usage-logs-filters.tsx (1)

46-68: Quick Filters 仍使用本地时区,可能与 serverTimeZone 不一致。
虽然已将 serverTimeZone 透传到 TimeFilters,但 "today/this-week" 预设仍使用本地 startOfDay/startOfWeek。serverTimeZone 与客户端时区不同会导致预设范围错误,并与日期选择器产生不一致。建议改用时区感知的 helper(例如 getQuickDateRange(period, serverTimeZone))统一计算。

Also applies to: 264-268

src/app/[locale]/dashboard/logs/_components/filters/time-filters.tsx (1)

50-54: displayEndDate 未使用 serverTimeZone 进行时区转换。

displayEndDate 直接使用 format(new Date(inclusiveEndTime), "yyyy-MM-dd"),而 displayStartDate(Line 41)使用了 timestampToDateString 函数(该函数会考虑 serverTimeZone)。这种不一致可能导致在非本地时区下,开始日期和结束日期的显示格式不同。

🐛 建议修复:统一使用 timestampToDateString
   // Memoized endDate calculation: endTime is exclusive, use endTime-1s to infer inclusive display end date
   const displayEndDate = useMemo(() => {
     if (!filters.endTime) return undefined;
     const inclusiveEndTime = inclusiveEndTimestampFromExclusive(filters.endTime);
-    return format(new Date(inclusiveEndTime), "yyyy-MM-dd");
-  }, [filters.endTime]);
+    return timestampToDateString(inclusiveEndTime);
+  }, [filters.endTime, timestampToDateString]);
🤖 Fix all issues with AI agents
In `@messages/en/settings/providers/strings.json`:
- Line 68: Remove the trailing period from the string value for the key
"vendorAggregationRule" in messages/en/settings/providers/strings.json so it
matches the punctuation style of sibling short labels; update the value "Grouped
by website domain." to "Grouped by website domain" without changing the key
name.

In `@src/app/`[locale]/dashboard/logs/_components/usage-logs-sections.tsx:
- Line 3: 当前缺少对 TZ 环境变量的 IANA 时区验证,导致无效 TZ 值会传到 date-fns-tz(例如
toZonedTime、formatInTimeZone)并抛出 RangeError,从而在 time-range.ts 和 time-filters.tsx
中未被捕获而使页面崩溃;请在 env.schema.ts 中为 TZ(当前由 getEnvConfig() 提供)添加 IANA
白名单或正则校验并设默认值,或者在所有调用 date-fns-tz 的位置(例如在 time-range.ts 的相关函数和 time-filters.tsx
中使用 formatInTimeZone/toZonedTime 的代码段)用 try-catch 包裹并在捕获异常时回退到安全值(undefined 或
"UTC")并记录错误。

In `@src/components/ui/__tests__/tag-input-dialog.test.tsx`:
- Around line 50-59: 将测试中对用户可见文本的硬编码替换为 i18n 资源:把 DialogTitle、DialogDescription
以及 TagInput 的 suggestions 中的 label 和 value 从硬编码字符串改为从项目 i18n 翻译函数或资源对象读取(例如通过
t('...') 或导入的 locales),并更新断言以基于翻译后的输出或更稳健地使用 data-testid 查询(例如 为
DialogTitle、DialogDescription 或 suggestion 项添加 data-testid 并在测试中使用这些 id
进行断言);修改点集中在组件使用处(DialogTitle、DialogDescription、TagInput 的 suggestions
属性)和相应的测试断言。

In `@tests/unit/dashboard-logs-virtualized-special-settings-ui.test.tsx`:
- Around line 44-49: Mock's totalTokens value is inconsistent with the cache
fields; update the test fixture in
tests/unit/dashboard-logs-virtualized-special-settings-ui.test.tsx so
totalTokens equals the sum of cacheCreationInputTokens and cacheReadInputTokens
(and also include cacheCreation5mInputTokens and cacheCreation1hInputTokens if
those represent additional token counts) instead of the current hardcoded 2;
locate the object containing totalTokens, cacheCreationInputTokens,
cacheReadInputTokens, cacheCreation5mInputTokens and cacheCreation1hInputTokens
and set totalTokens to the correct summed value.

In `@tests/unit/dashboard-logs-warmup-ui.test.tsx`:
- Around line 108-128: The test fixture cacheLog (UsageLogRow) has totalTokens
set to 15 but that omits cache-related tokens; update the cacheLog object
(identifier: cacheLog) so totalTokens includes cacheCreationInputTokens and
cacheReadInputTokens in addition to inputTokens and outputTokens (i.e., compute
totalTokens = inputTokens + outputTokens + cacheCreationInputTokens +
cacheReadInputTokens) so the test matches the expected statistical counting for
cache creation/read.

In `@tests/unit/dashboard/availability/availability-dashboard-ui.test.tsx`:
- Around line 61-77: The test for AvailabilityDashboard is incomplete and races
with the component's async fetchData in useEffect and leaves global stubs in
place; update tests that exercise key behaviors (tab switching, time-range
selection, loading state, error handling, auto-refresh and the "Probe All"
button visibility) by adding additional test cases for those flows and in each
async test await the component updates (use waitFor or findBy* against the
rendered AvailabilityDashboard) so assertions run after fetchData completes, and
ensure any global mocks created with vi.stubGlobal or fetch are restored after
each test (use vi.restoreAllMocks or vi.unstubGlobal) to avoid cross-test
pollution; reference the AvailabilityDashboard component and its
fetchData/useEffect behavior when implementing these changes.
🧹 Nitpick comments (7)
tests/unit/dashboard/availability/availability-dashboard-ui.test.tsx (1)

22-59: renderWithIntl 辅助函数实现良好,但建议小幅改进。

此辅助函数正确设置了 NextIntlClientProvider 和清理逻辑。两点建议:

  1. 可以考虑将此辅助函数提取到共享的测试工具文件中,以便其他测试复用。
  2. 建议添加 act 的异步版本以更好地处理组件内的异步操作。
src/components/ui/__tests__/tag-input-dialog.test.tsx (1)

1-3: 建议将测试文件移动到源文件同级目录
当前文件位于 src/components/ui/__tests__/,但规范要求 source-adjacent 测试放在 src/**/*.test.ts(x) 与源文件同级。建议移动至例如 src/components/ui/tag-input-dialog.test.tsx。As per coding guidelines, ...

tests/unit/dashboard-logs-virtualized-special-settings-ui.test.tsx (1)

164-175: 断言建议避免依赖 innerHTML 字符串。

可直接查询带有 class 的元素,降低误匹配与脆弱性。

修改建议
-    expect(container.innerHTML).toContain("ml-auto");
+    expect(container.querySelector(".ml-auto")).not.toBeNull();
tests/unit/dashboard-logs-warmup-ui.test.tsx (1)

152-154: 建议使用 DOM 查询替代 innerHTML 字符串断言。

这样更稳健、也更精准。

修改建议
-    expect(container.innerHTML).toContain("ml-auto");
+    expect(container.querySelector(".ml-auto")).not.toBeNull();
tests/unit/dashboard-logs-time-range-utils.test.ts (1)

48-60: 时区边界测试用例设计精妙!

测试巧妙地选择了 UTC 时间 2024-01-02T02:00:00Z,在 America/Los_Angeles 时区(UTC-8)仍是 2024-01-01,有效验证了跨日期边界的时区处理。

建议考虑补充 last7dayslast30days 的测试用例,以提高覆盖率:

📝 可选:补充测试用例
test("getQuickDateRange calculates last7days/last30days with timezone", () => {
  const now = new Date("2024-01-10T02:00:00Z");
  const tz = "America/Los_Angeles";

  expect(getQuickDateRange("last7days", tz, now)).toEqual({
    startDate: "2024-01-03",
    endDate: "2024-01-09",
  });
  expect(getQuickDateRange("last30days", tz, now)).toEqual({
    startDate: "2023-12-11",
    endDate: "2024-01-09",
  });
});
src/app/[locale]/my-usage/page.tsx (1)

44-46: 时区获取缺少错误处理反馈。

当前实现在 getServerTimeZone 失败时静默忽略错误。虽然时区是可选功能,但建议考虑在调试模式下记录错误,以便排查问题。

♻️ 可选改进:添加错误日志
     void getServerTimeZone().then((tzResult) => {
-      if (tzResult.ok) setServerTimeZone(tzResult.data.timeZone);
+      if (tzResult.ok) {
+        setServerTimeZone(tzResult.data.timeZone);
+      } else if (process.env.NODE_ENV === "development") {
+        console.warn("Failed to fetch server timezone:", tzResult.error);
+      }
     });
src/app/[locale]/dashboard/logs/_components/logs-date-range-picker.tsx (1)

230-238: 考虑:导航禁用条件可能与服务器时区不一致

第 234 行的 formatDate(new Date()) 使用客户端本地时间判断是否可以导航到"下一个"周期。如果 serverTimeZone 与客户端时区不同,可能会出现边界情况:服务器时区的"今天"可能与客户端的"今天"不一致。

对于导航 UI 来说,这是一个次要的边界情况,当前行为是可接受的。如果需要严格一致性,可以考虑使用 serverTimeZone 来计算禁用条件。

@@ -1,5 +1,6 @@
import { cache } from "react";
import { ActiveSessionsCards } from "@/components/customs/active-sessions-cards";
import { getEnvConfig } from "@/lib/config";
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# First, let's locate and examine the getEnvConfig implementation
fd -t f -e ts -e tsx "config" | grep -E "(lib|config)" | head -20

Repository: ding113/claude-code-hub

Length of output: 795


🏁 Script executed:

# Check the specific file mentioned in the review
cat -n src/app/[locale]/dashboard/logs/_components/usage-logs-sections.tsx | head -50

Repository: ding113/claude-code-hub

Length of output: 1394


🏁 Script executed:

# Search for getEnvConfig definition and TZ usage
rg "getEnvConfig\s*=" -g '*.ts*' -A 5 -B 2

Repository: ding113/claude-code-hub

Length of output: 49


🏁 Script executed:

# Search for TZ validation/normalization
rg "TZ|timezone" -g '*.ts*' -i | head -30

Repository: ding113/claude-code-hub

Length of output: 3449


🏁 Script executed:

# Check getEnvConfig implementation
cat src/lib/config/config.ts

Repository: ding113/claude-code-hub

Length of output: 276


🏁 Script executed:

# Find and check UsageLogsViewVirtualized
fd -t f "usage-logs-view-virtualized"

Repository: ding113/claude-code-hub

Length of output: 142


🏁 Script executed:

# Check how time zone functions handle invalid values
rg "toZonedTime|formatInTimeZone|fromZonedTime" src/app/[locale]/dashboard/logs/_utils/time-range.ts -B 2 -A 2

Repository: ding113/claude-code-hub

Length of output: 1336


🏁 Script executed:

# Check if there's any validation or error handling in time-range.ts
cat src/app/[locale]/dashboard/logs/_utils/time-range.ts

Repository: ding113/claude-code-hub

Length of output: 3426


🏁 Script executed:

# Find env.schema.ts
fd -t f "env.schema"

Repository: ding113/claude-code-hub

Length of output: 95


🏁 Script executed:

# Check UsageLogsViewVirtualized implementation
cat src/app/[locale]/dashboard/logs/_components/usage-logs-view-virtualized.tsx | head -80

Repository: ding113/claude-code-hub

Length of output: 3057


🏁 Script executed:

# Also check if there are any environment variable definitions
rg "TZ" -g '*.env*' -g '*.ts*' | grep -E "(env|ENV)" | head -20

Repository: ding113/claude-code-hub

Length of output: 138


🏁 Script executed:

# Look for any zod schema or validation
rg "z\..*timezone|z\..*TZ" -g '*.ts*'

Repository: ding113/claude-code-hub

Length of output: 49


🏁 Script executed:

# Check full env.schema.ts file
cat src/lib/config/env.schema.ts

Repository: ding113/claude-code-hub

Length of output: 4914


🏁 Script executed:

# Check if there's any error handling or try-catch around date-fns-tz usage
rg "try|catch" src/app/[locale]/dashboard/logs/_utils/time-range.ts

Repository: ding113/claude-code-hub

Length of output: 49


🏁 Script executed:

# Search for how serverTimeZone is used in the component
rg "serverTimeZone" src/app/[locale]/dashboard/logs/_components/usage-logs-view-virtualized.tsx -A 2 -B 2

Repository: ding113/claude-code-hub

Length of output: 515


🏁 Script executed:

# Check if date-fns-tz has any documentation or tests about timezone validation
fd -t f "*.test.ts*" | xargs rg "toZonedTime|formatInTimeZone" 2>/dev/null | head -20

Repository: ding113/claude-code-hub

Length of output: 2328


确认 TZ 环境变量的 IANA 时区验证缺失。

当前 getEnvConfig() 中 TZ 字段仅定义为 z.string().default("Asia/Shanghai"),无任何 IANA 时区格式验证。若环境变量显式设置为无效值(如 TZ=InvalidTZ),会直接透传到 date-fns-tztoZonedTimeformatInTimeZone 等函数,这些函数会抛出 RangeError。time-range.ts 和 time-filters.tsx 中对这些函数的调用均无 try-catch 保护,将导致日志页面崩溃。

建议:

  • 在 env.schema.ts 中为 TZ 添加 IANA 时区白名单或正则验证;或
  • 在调用 date-fns-tz 函数处增加 try-catch,并在捕获异常时回退到 undefined"UTC"
🤖 Prompt for AI Agents
In `@src/app/`[locale]/dashboard/logs/_components/usage-logs-sections.tsx at line
3, 当前缺少对 TZ 环境变量的 IANA 时区验证,导致无效 TZ 值会传到 date-fns-tz(例如
toZonedTime、formatInTimeZone)并抛出 RangeError,从而在 time-range.ts 和 time-filters.tsx
中未被捕获而使页面崩溃;请在 env.schema.ts 中为 TZ(当前由 getEnvConfig() 提供)添加 IANA
白名单或正则校验并设默认值,或者在所有调用 date-fns-tz 的位置(例如在 time-range.ts 的相关函数和 time-filters.tsx
中使用 formatInTimeZone/toZonedTime 的代码段)用 try-catch 包裹并在捕获异常时回退到安全值(undefined 或
"UTC")并记录错误。

Comment on lines +50 to +59
<DialogTitle>Tag Input</DialogTitle>
<DialogDescription>Tag input dialog test</DialogDescription>
</DialogHeader>
<TagInput
value={value}
onChange={setValue}
suggestions={[
{ value: "tag1", label: "Tag 1" },
{ value: "tag2", label: "Tag 2" },
]}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

对话框与建议项的展示文案请改为 i18n 资源
DialogTitleDialogDescription 以及建议项的 label/value 都是用户可见文本,需从 i18n 资源中读取,避免硬编码。断言可改为基于 i18n 输出或使用 data-testid。As per coding guidelines, ...

🤖 Prompt for AI Agents
In `@src/components/ui/__tests__/tag-input-dialog.test.tsx` around lines 50 - 59,
将测试中对用户可见文本的硬编码替换为 i18n 资源:把 DialogTitle、DialogDescription 以及 TagInput 的
suggestions 中的 label 和 value 从硬编码字符串改为从项目 i18n 翻译函数或资源对象读取(例如通过 t('...') 或导入的
locales),并更新断言以基于翻译后的输出或更稳健地使用 data-testid 查询(例如 为 DialogTitle、DialogDescription
或 suggestion 项添加 data-testid 并在测试中使用这些 id
进行断言);修改点集中在组件使用处(DialogTitle、DialogDescription、TagInput 的 suggestions
属性)和相应的测试断言。

Comment on lines +44 to 49
cacheCreationInputTokens: 10,
cacheReadInputTokens: 5,
cacheCreation5mInputTokens: 10,
cacheCreation1hInputTokens: 0,
cacheTtlApplied: null,
cacheTtlApplied: "1h",
totalTokens: 2,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

mock 中 totalTokens 与 cache 相关字段不一致。

现在 cacheCreationInputTokens=10、cacheReadInputTokens=5,totalTokens 应包含这些值;否则与真实统计口径不符。

修改建议
-          totalTokens: 2,
+          totalTokens: 17,

Based on learnings, totalTokens 需要包含 cacheCreationInputTokens 与 cacheReadInputTokens。

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
cacheCreationInputTokens: 10,
cacheReadInputTokens: 5,
cacheCreation5mInputTokens: 10,
cacheCreation1hInputTokens: 0,
cacheTtlApplied: null,
cacheTtlApplied: "1h",
totalTokens: 2,
cacheCreationInputTokens: 10,
cacheReadInputTokens: 5,
cacheCreation5mInputTokens: 10,
cacheCreation1hInputTokens: 0,
cacheTtlApplied: "1h",
totalTokens: 17,
🤖 Prompt for AI Agents
In `@tests/unit/dashboard-logs-virtualized-special-settings-ui.test.tsx` around
lines 44 - 49, Mock's totalTokens value is inconsistent with the cache fields;
update the test fixture in
tests/unit/dashboard-logs-virtualized-special-settings-ui.test.tsx so
totalTokens equals the sum of cacheCreationInputTokens and cacheReadInputTokens
(and also include cacheCreation5mInputTokens and cacheCreation1hInputTokens if
those represent additional token counts) instead of the current hardcoded 2;
locate the object containing totalTokens, cacheCreationInputTokens,
cacheReadInputTokens, cacheCreation5mInputTokens and cacheCreation1hInputTokens
and set totalTokens to the correct summed value.

Comment on lines +108 to +128
const cacheLog: UsageLogRow = {
id: 2,
createdAt: new Date(),
sessionId: "session_cache",
requestSequence: 1,
userName: "user",
keyName: "key",
providerName: "provider",
model: "claude-sonnet-4-5-20250929",
originalModel: "claude-sonnet-4-5-20250929",
endpoint: "/v1/messages",
statusCode: 200,
inputTokens: 10,
outputTokens: 5,
cacheCreationInputTokens: 10,
cacheReadInputTokens: 5,
cacheCreation5mInputTokens: 10,
cacheCreation1hInputTokens: 0,
cacheTtlApplied: "1h",
totalTokens: 15,
costUsd: "0.000001",
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

cacheLog 的 totalTokens 需要包含缓存读写 token。

当前 totalTokens=15 只反映 input/output,建议按统计口径补上 cacheCreation/cacheRead。

修改建议
-      totalTokens: 15,
+      totalTokens: 30,

Based on learnings, totalTokens 需要包含 cacheCreationInputTokens 与 cacheReadInputTokens。

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const cacheLog: UsageLogRow = {
id: 2,
createdAt: new Date(),
sessionId: "session_cache",
requestSequence: 1,
userName: "user",
keyName: "key",
providerName: "provider",
model: "claude-sonnet-4-5-20250929",
originalModel: "claude-sonnet-4-5-20250929",
endpoint: "/v1/messages",
statusCode: 200,
inputTokens: 10,
outputTokens: 5,
cacheCreationInputTokens: 10,
cacheReadInputTokens: 5,
cacheCreation5mInputTokens: 10,
cacheCreation1hInputTokens: 0,
cacheTtlApplied: "1h",
totalTokens: 15,
costUsd: "0.000001",
const cacheLog: UsageLogRow = {
id: 2,
createdAt: new Date(),
sessionId: "session_cache",
requestSequence: 1,
userName: "user",
keyName: "key",
providerName: "provider",
model: "claude-sonnet-4-5-20250929",
originalModel: "claude-sonnet-4-5-20250929",
endpoint: "/v1/messages",
statusCode: 200,
inputTokens: 10,
outputTokens: 5,
cacheCreationInputTokens: 10,
cacheReadInputTokens: 5,
cacheCreation5mInputTokens: 10,
cacheCreation1hInputTokens: 0,
cacheTtlApplied: "1h",
totalTokens: 30,
costUsd: "0.000001",
🤖 Prompt for AI Agents
In `@tests/unit/dashboard-logs-warmup-ui.test.tsx` around lines 108 - 128, The
test fixture cacheLog (UsageLogRow) has totalTokens set to 15 but that omits
cache-related tokens; update the cacheLog object (identifier: cacheLog) so
totalTokens includes cacheCreationInputTokens and cacheReadInputTokens in
addition to inputTokens and outputTokens (i.e., compute totalTokens =
inputTokens + outputTokens + cacheCreationInputTokens + cacheReadInputTokens) so
the test matches the expected statistical counting for cache creation/read.

Comment on lines +61 to +77
describe("AvailabilityDashboard UI", () => {
test("does not render Probe All floating button", async () => {
vi.stubGlobal(
"fetch",
vi.fn(async () => ({
ok: true,
json: async () => ({ providers: [], systemAvailability: 0 }),
}))
);

const { container, unmount } = renderWithIntl(<AvailabilityDashboard />);

expect(container.textContent).not.toContain("Probe All");

unmount();
});
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

测试覆盖率不足,且异步操作处理存在问题。

  1. 测试覆盖率:当前只有一个测试用例,AvailabilityDashboard 组件包含多个功能(标签切换、时间范围选择、加载状态、错误处理、自动刷新等),这可能无法满足 80% 覆盖率要求。

  2. 异步操作未正确等待:组件在 useEffect 中调用 fetchData,但测试在渲染后立即检查 DOM,没有等待异步操作完成。建议使用 waitForfindBy 等待状态更新。

  3. 缺少测试清理vi.stubGlobal 在测试后未恢复,可能污染其他测试。

建议的修复方案
+import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
-import { describe, expect, test, vi } from "vitest";

 describe("AvailabilityDashboard UI", () => {
+  beforeEach(() => {
+    vi.stubGlobal(
+      "fetch",
+      vi.fn(async () => ({
+        ok: true,
+        json: async () => ({ providers: [], systemAvailability: 0 }),
+      }))
+    );
+  });
+
+  afterEach(() => {
+    vi.unstubAllGlobals();
+  });
+
   test("does not render Probe All floating button", async () => {
-    vi.stubGlobal(
-      "fetch",
-      vi.fn(async () => ({
-        ok: true,
-        json: async () => ({ providers: [], systemAvailability: 0 }),
-      }))
-    );
-
     const { container, unmount } = renderWithIntl(<AvailabilityDashboard />);

+    // 等待异步 fetch 完成
+    await act(async () => {
+      await new Promise((resolve) => setTimeout(resolve, 0));
+    });
+
     expect(container.textContent).not.toContain("Probe All");

     unmount();
   });
+
+  // 建议添加更多测试用例以提高覆盖率
+  test("renders provider and endpoint tabs", async () => {
+    const { container, unmount } = renderWithIntl(<AvailabilityDashboard />);
+    
+    await act(async () => {
+      await new Promise((resolve) => setTimeout(resolve, 0));
+    });
+
+    expect(container.querySelector('[data-testid="overview-section"]')).toBeTruthy();
+    expect(container.textContent).toContain("Provider");
+    expect(container.textContent).toContain("Endpoint");
+
+    unmount();
+  });
 });

根据 coding guidelines,所有新功能必须有至少 80% 的单元测试覆盖率。

🤖 Prompt for AI Agents
In `@tests/unit/dashboard/availability/availability-dashboard-ui.test.tsx` around
lines 61 - 77, The test for AvailabilityDashboard is incomplete and races with
the component's async fetchData in useEffect and leaves global stubs in place;
update tests that exercise key behaviors (tab switching, time-range selection,
loading state, error handling, auto-refresh and the "Probe All" button
visibility) by adding additional test cases for those flows and in each async
test await the component updates (use waitFor or findBy* against the rendered
AvailabilityDashboard) so assertions run after fetchData completes, and ensure
any global mocks created with vi.stubGlobal or fetch are restored after each
test (use vi.restoreAllMocks or vi.unstubGlobal) to avoid cross-test pollution;
reference the AvailabilityDashboard component and its fetchData/useEffect
behavior when implementing these changes.

ding113 and others added 2 commits January 27, 2026 02:00
Change token-related columns in message_request table from integer to
bigint to prevent overflow when storing large token counts. Also update
all SQL aggregation queries to use double precision instead of ::int
casting to avoid overflow during SUM operations.

Changes:
- Schema: inputTokens, outputTokens, cacheCreationInputTokens,
  cacheReadInputTokens, cacheCreation5mInputTokens,
  cacheCreation1hInputTokens now use bigint
- Schema: providerVendorId is now NOT NULL
- Queries: All token SUM operations use ::double precision
- Tests: Add unit tests to verify no ::int casting in token aggregations

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@src/drizzle/schema.ts`:
- Around line 155-159: The schema adds a NOT NULL constraint to providerVendorId
(references providerVendors.id) but the migration 0057_conscious_quicksilver.sql
doesn't apply that change; update that migration to make the DB match the code
by (a) handling existing NULLs in the providers table (e.g., UPDATE providers
SET provider_vendor_id = <valid_id> OR delete/abort if business rules require)
and then (b) executing ALTER TABLE providers ALTER COLUMN provider_vendor_id SET
NOT NULL (and ensure the foreign key exists with ON DELETE RESTRICT), or
alternatively remove .notNull() from providerVendorId in the schema if you
intend to allow NULLs—modify either the migration or the schema accordingly so
providerVendorId and providerVendors.id stay consistent.

Comment on lines +155 to +159
providerVendorId: integer('provider_vendor_id')
.notNull()
.references(() => providerVendors.id, {
onDelete: 'restrict',
}),
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

fd -t f '0057_conscious_quicksilver.sql' --search-path .

Repository: ding113/claude-code-hub

Length of output: 107


🏁 Script executed:

find . -name '*0057*' -type f

Repository: ding113/claude-code-hub

Length of output: 141


🏁 Script executed:

find . -path '*/drizzle/*' -name '*.sql' -type f | sort

Repository: ding113/claude-code-hub

Length of output: 2248


🏁 Script executed:

cat ./drizzle/0057_conscious_quicksilver.sql

Repository: ding113/claude-code-hub

Length of output: 729


🏁 Script executed:

cat -n ./src/drizzle/schema.ts | head -200 | tail -60

Repository: ding113/claude-code-hub

Length of output: 2683


🏁 Script executed:

rg -A 5 -B 5 "providerVendorId" ./src/drizzle/schema.ts

Repository: ding113/claude-code-hub

Length of output: 1332


迁移文件缺少 providerVendorId NOT NULL 约束

providerVendorId.notNull() 约束是破坏性变更,但迁移文件 0057_conscious_quicksilver.sql 中仅包含 message_request 表的变更,未包含对 providers 表的任何修改。

需要更新迁移文件以包含 provider_vendor_id 列的 NOT NULL 约束,或者在约束生效前确保所有现有记录都已关联 vendor(无 NULL 值)。

🤖 Prompt for AI Agents
In `@src/drizzle/schema.ts` around lines 155 - 159, The schema adds a NOT NULL
constraint to providerVendorId (references providerVendors.id) but the migration
0057_conscious_quicksilver.sql doesn't apply that change; update that migration
to make the DB match the code by (a) handling existing NULLs in the providers
table (e.g., UPDATE providers SET provider_vendor_id = <valid_id> OR
delete/abort if business rules require) and then (b) executing ALTER TABLE
providers ALTER COLUMN provider_vendor_id SET NOT NULL (and ensure the foreign
key exists with ON DELETE RESTRICT), or alternatively remove .notNull() from
providerVendorId in the schema if you intend to allow NULLs—modify either the
migration or the schema accordingly so providerVendorId and providerVendors.id
stay consistent.

Extend deriveDisplayNameFromDomain to skip common API prefixes (api, v1,
v2, v3, www) when extracting display names from domains. This fixes
handling of multi-prefix domains like v1.api.anthropic.com.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 file reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

Comment on lines +246 to +249
let name = parts[parts.length - 2];
if (apiPrefixes.has(name) && parts.length >= 3) {
name = parts[parts.length - 3];
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed from checking first part to second-to-last part - this was noted in a previous thread as potentially breaking for api.openai.com where the original logic expected parts[0] === "api" but new logic checks parts[parts.length - 2] which would be "openai" not "api". Verify this is the intended behavior and all existing provider endpoints still derive correct display names after this change.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/repository/provider-endpoints.ts
Line: 246:249

Comment:
Changed from checking first part to second-to-last part - this was noted in a previous thread as potentially breaking for `api.openai.com` where the original logic expected `parts[0] === "api"` but new logic checks `parts[parts.length - 2]` which would be "openai" not "api". Verify this is the intended behavior and all existing provider endpoints still derive correct display names after this change.

How can I resolve this? If you propose a fix, please make it concise.

- Refactor DashboardBento to use two-column layout with fixed-width
  sidebar (300px) for LiveSessionsPanel on admin view
- Update DashboardMain to remove max-w-7xl constraint on dashboard home
  page for wider content area
- Simplify ActiveSessionsSkeleton to match compact list style
- Add showTokensCost prop to SessionListItem and ActiveSessionsList
  for conditional token/cost display
- Fix router import to use i18n routing in ActiveSessionsList
- Add unit tests for layout behavior and SessionListItem props

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 file reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

Comment on lines 154 to +157
key: varchar('key').notNull(),
providerVendorId: integer('provider_vendor_id').references(() => providerVendors.id, {
onDelete: 'restrict',
}),
providerVendorId: integer('provider_vendor_id')
.notNull()
.references(() => providerVendors.id, {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check that all existing providers have valid providerVendorId values before deploying - the new .notNull() constraint will fail if any NULL values exist in production.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/drizzle/schema.ts
Line: 154:157

Comment:
Check that all existing providers have valid `providerVendorId` values before deploying - the new `.notNull()` constraint will fail if any NULL values exist in production.

How can I resolve this? If you propose a fix, please make it concise.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@src/app/`[locale]/dashboard/_components/dashboard-main.tsx:
- Around line 22-23: The pathname check in isDashboardHomePage is overly
permissive; replace the endsWith check with a strict equality: use the
normalizedPathname exact match only (change the condition in the
isDashboardHomePage declaration that currently references
normalizedPathname.endsWith("/dashboard") so it only compares normalizedPathname
=== "/dashboard"). This simplifies the logic tied to usePathname() behavior and
the routing setup in src/i18n/routing.ts without altering other code paths.
🧹 Nitpick comments (5)
src/app/[locale]/dashboard/_components/bento/dashboard-bento.tsx (1)

260-313: 使用 cn() 工具函数来组合 className,保持项目一致性。

项目已在 src/lib/utils/cn.ts 中定义了 cn() 工具函数,并在整个代码库(UI 组件、设置页面等)中广泛使用。当前代码使用 .join(" ") 与项目约定不一致,建议导入并使用 cn() 函数。

♻️ 可选重构
+import { cn } from "@/lib/utils";
+
 export function DashboardBento({
-          <div
-            className={[
-              "grid gap-4 md:gap-6",
-              "grid-cols-1 sm:grid-cols-2 lg:grid-cols-3",
-              "auto-rows-[minmax(140px,auto)]",
-            ].join(" ")}
-          >
+          <div
+            className={cn(
+              "grid gap-4 md:gap-6",
+              "grid-cols-1 sm:grid-cols-2 lg:grid-cols-3",
+              "auto-rows-[minmax(140px,auto)]"
+            )}
+          >
tests/unit/dashboard/dashboard-home-layout.test.tsx (3)

138-146: findClosestWithClasses 函数存在空指针风险。

while 循环中,current 在条件判断后可能为 null,但 current.classList 的访问没有进行空值检查。虽然 while (current) 确保了循环体内 current 不为 null,但 TypeScript 的类型推断可能会导致问题。

♻️ 建议的改进以提高类型安全性
 function findClosestWithClasses(element: Element | null, classes: string[]) {
   let current = element?.parentElement ?? null;
   while (current) {
-    const hasAll = classes.every((cls) => current.classList.contains(cls));
+    const el = current;
+    const hasAll = classes.every((cls) => el.classList.contains(cls));
     if (hasAll) return current;
     current = current.parentElement;
   }
   return null;
 }

218-219: 测试选择器过于脆弱。

使用完整的 Tailwind 类名 lg:grid-cols-[minmax(0,1fr)_300px] 作为选择器会使测试对样式实现细节高度敏感。如果布局类名稍有变化,测试就会失败。

建议在被测组件中添加 data-testid 属性来提高测试稳定性。

♻️ 建议的改进

DashboardBento 组件中添加:

<div data-testid="dashboard-grid" className="lg:grid-cols-[minmax(0,1fr)_300px] ...">

然后在测试中使用:

-    const grid = findByClassToken(container, "lg:grid-cols-[minmax(0,1fr)_300px]");
+    const grid = container.querySelector('[data-testid="dashboard-grid"]');
     expect(grid).toBeTruthy();

173-204: 建议增加测试覆盖率。

当前测试覆盖了 /dashboard/dashboard/logs 路径,但缺少以下场景的测试:

  1. 带尾部斜杠的路径:验证 normalizedPathname 逻辑,如 /dashboard/
  2. session messages 页面:验证 isSessionMessagesPage 分支,如 /dashboard/sessions/123/messages
♻️ 建议添加的测试用例
test("pathname /dashboard/ with trailing slash removes max-w-7xl", () => {
  routingMocks.usePathname.mockReturnValue("/dashboard/");
  const { container, unmount } = renderSimple(
    <DashboardMain>
      <div data-testid="content" />
    </DashboardMain>
  );

  const main = container.querySelector("main");
  expect(main?.className).toContain("px-6");
  expect(main?.className).not.toContain("max-w-7xl");

  unmount();
});

test("pathname /dashboard/sessions/[id]/messages uses overflow-hidden layout", () => {
  routingMocks.usePathname.mockReturnValue("/dashboard/sessions/abc123/messages");
  const { container, unmount } = renderSimple(
    <DashboardMain>
      <div data-testid="content" />
    </DashboardMain>
  );

  const main = container.querySelector("main");
  expect(main?.className).toContain("overflow-hidden");
  expect(main?.className).not.toContain("max-w-7xl");

  unmount();
});
tests/unit/components/session-list-item.test.tsx (1)

41-49: 可移除重复类型与强转,降低类型漂移风险。

直接使用组件即可,无需本地 props 类型与强制类型转换。

Proposed refactor
-type SessionListItemProps = {
-  session: ActiveSessionInfo;
-  currencyCode?: CurrencyCode;
-  showTokensCost?: boolean;
-};
-
-const SessionListItemTest = SessionListItem as unknown as (
-  props: SessionListItemProps
-) => JSX.Element;
+const SessionListItemTest = SessionListItem;
@@
-  const html = renderToStaticMarkup(
-    <SessionListItemTest session={session} showTokensCost={options?.showTokensCost} />
-  );
+  const html = renderToStaticMarkup(
+    <SessionListItemTest session={session} showTokensCost={options?.showTokensCost} />
+  );

Also applies to: 74-75

Comment on lines 22 to 23
const isDashboardHomePage =
normalizedPathname === "/dashboard" || normalizedPathname.endsWith("/dashboard");
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's look at the dashboard-main.tsx file
cat -n src/app/[locale]/dashboard/_components/dashboard-main.tsx | head -40

Repository: ding113/claude-code-hub

Length of output: 1518


🏁 Script executed:

# Check how usePathname is used in the codebase and what it returns
rg "usePathname" --type=ts --type=tsx -B 2 -A 5 | head -80

Repository: ding113/claude-code-hub

Length of output: 94


🏁 Script executed:

# Look for the next-intl hook to understand what it returns
fd -e ts -e tsx | xargs grep -l "from.*next-intl" | head -10

Repository: ding113/claude-code-hub

Length of output: 590


🏁 Script executed:

# Let's find the routing configuration to understand the actual routes
cat -n src/i18n/routing.ts 2>/dev/null || cat -n src/i18n/routing.tsx 2>/dev/null || echo "File not found"

Repository: ding113/claude-code-hub

Length of output: 1622


🏁 Script executed:

# Check the app directory structure to see what dashboard routes exist
fd -path "*/dashboard/*" -type f | head -20

Repository: ding113/claude-code-hub

Length of output: 235


🏁 Script executed:

# Search for all files in the dashboard directory to understand the structure
find src/app/\[locale\]/dashboard -type f -name "*.ts*" | sort

Repository: ding113/claude-code-hub

Length of output: 12527


🏁 Script executed:

# Look for route segments that might match endsWith("/dashboard")
rg "dashboard" src/app --type=ts --type=tsx -l

Repository: ding113/claude-code-hub

Length of output: 94


🏁 Script executed:

# Let's check if there are any actual routes that end with /dashboard besides /dashboard itself
find src/app/\[locale\]/dashboard -type d | sed 's|src/app/\[locale\]||g' | grep -v "/_"

Repository: ding113/claude-code-hub

Length of output: 407


🏁 Script executed:

# Let's look at the exact route structure to confirm what paths usePathname would return
cat -n src/app/\[locale\]/dashboard/page.tsx | head -20

Repository: ding113/claude-code-hub

Length of output: 947


🏁 Script executed:

# Let's check if there are any other segment patterns that could produce paths ending in /dashboard
rg "/dashboard" src/app --type=ts -B 2 -A 2 | head -60

Repository: ding113/claude-code-hub

Length of output: 5210


路径匹配条件可以简化为精确匹配。

normalizedPathname.endsWith("/dashboard") 在当前的路由结构中是冗余的。根据 src/i18n/routing.ts 的配置和实际的路由结构,usePathname() 返回不含 locale 前缀的路径,且没有其他路由以 "/dashboard" 结尾(所有子页面形式为 /dashboard/availability/dashboard/sessions 等)。因此可以简化为仅使用精确匹配:

♻️ 建议的改进
  const isDashboardHomePage =
-    normalizedPathname === "/dashboard" || normalizedPathname.endsWith("/dashboard");
+    normalizedPathname === "/dashboard";
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const isDashboardHomePage =
normalizedPathname === "/dashboard" || normalizedPathname.endsWith("/dashboard");
const isDashboardHomePage =
normalizedPathname === "/dashboard";
🤖 Prompt for AI Agents
In `@src/app/`[locale]/dashboard/_components/dashboard-main.tsx around lines 22 -
23, The pathname check in isDashboardHomePage is overly permissive; replace the
endsWith check with a strict equality: use the normalizedPathname exact match
only (change the condition in the isDashboardHomePage declaration that currently
references normalizedPathname.endsWith("/dashboard") so it only compares
normalizedPathname === "/dashboard"). This simplifies the logic tied to
usePathname() behavior and the routing setup in src/i18n/routing.ts without
altering other code paths.

ding113 and others added 2 commits January 27, 2026 20:33
Add overflow constraints to LogicTraceTab and StepCard components:
- Add min-w-0 to grid containers to allow content shrinking
- Add break-all to font-mono text for long IDs and URLs
- Add flex-wrap and truncate for provider name badges
- Add overflow-hidden to StepCard details container

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add max-w-7xl constraint to dashboard home container for consistent alignment with header
- Restructure dashboard-bento into 3 independent sections: metrics, chart, leaderboards
- Remove nested grid structure that caused misalignment on desktop
- Change admin layout from 2-column (content + sidebar) to 4-column grid (3 leaderboards + live sessions)
- Remove colSpan/rowSpan from StatisticsChartCard for full-width independent rendering
- Add min-h-[280px] to LeaderboardCard for consistent card heights
- Update tests to match new layout structure

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/app/[locale]/dashboard/_components/bento/dashboard-bento.tsx (1)

260-309: data-testid 不会被转发到 DOM

LiveSessionsPanel 组件的 LiveSessionsPanelProps 接口(第 17-22 行)仅定义了 sessionsisLoadingmaxItemsclassName 这四个属性。组件在渲染 BentoCard 时(第 165-176 行)也只显式传入这些属性,未使用 spread 操作符转发其他 props。因此,从父组件传入的 data-testid="dashboard-home-sidebar" 会被忽略,不会出现在 DOM 中,导致该测试选择器失效。

建议:

  • LiveSessionsPanelProps 中添加 data-testid 属性并转发到 BentoCard;或
  • data-testid 放在调用 LiveSessionsPanel 的外层 div
🤖 Fix all issues with AI agents
In `@src/app/`[locale]/dashboard/_components/dashboard-main.tsx:
- Around line 29-33: The conditional using isDashboardHomePage is redundant
because both branches return identical JSX; either remove the conditional and
return a single <main> with the common classes/children (delete the
isDashboardHomePage check and its branch), or implement the intended homepage
variant by changing the true-branch to the proper JSX/styles (modify the code
where isDashboardHomePage is evaluated so the true branch renders the special
homepage layout/styling and the false branch renders the existing default
<main>); locate the isDashboardHomePage check in dashboard-main.tsx and update
the return(s) accordingly.
🧹 Nitpick comments (2)
tests/unit/dashboard/dashboard-home-layout.test.tsx (2)

78-130: 建议抽取共享挂载逻辑以减少重复。

renderSimplerenderWithProviders 里创建/卸载容器逻辑一致,可以提取到一个小 helper。

可选重构示例
+function createTestRoot() {
+  const container = document.createElement("div");
+  document.body.appendChild(container);
+  const root = createRoot(container);
+  const unmount = () => {
+    act(() => root.unmount());
+    container.remove();
+  };
+  return { container, root, unmount };
+}
+
 function renderSimple(node: ReactNode) {
-  const container = document.createElement("div");
-  document.body.appendChild(container);
-  const root = createRoot(container);
+  const { container, root, unmount } = createTestRoot();
 
   act(() => {
     root.render(node);
   });
 
   return {
     container,
-    unmount: () => {
-      act(() => root.unmount());
-      container.remove();
-    },
+    unmount,
   };
 }
 
 function renderWithProviders(node: ReactNode) {
-  const container = document.createElement("div");
-  document.body.appendChild(container);
-  const root = createRoot(container);
+  const { container, root, unmount } = createTestRoot();
   const queryClient = new QueryClient({
     defaultOptions: {
       queries: {
         retry: false,
         refetchOnWindowFocus: false,
@@
   return {
     container,
-    unmount: () => {
-      act(() => root.unmount());
-      container.remove();
-    },
+    unmount,
   };
 }

138-146: 未使用的 helper 可考虑移除。

findClosestWithClasses 目前未被使用,若无计划用途可删掉以保持测试文件简洁。

可选清理示例
-function findClosestWithClasses(element: Element | null, classes: string[]) {
-  let current = element?.parentElement ?? null;
-  while (current) {
-    const hasAll = classes.every((cls) => current.classList.contains(cls));
-    if (hasAll) return current;
-    current = current.parentElement;
-  }
-  return null;
-}

- Remove redundant isDashboardHomePage conditional branch (now same as default)
- Remove trailing period from vendorAggregationRule for style consistency

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@ding113 ding113 merged commit 090d5e4 into dev Jan 27, 2026
8 of 10 checks passed
@github-project-automation github-project-automation bot moved this from Backlog to Done in Claude Code Hub Roadmap Jan 27, 2026
Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 file reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

function deriveDisplayNameFromDomain(domain: string): string {
const parts = domain.split(".");
const name = parts[0] === "api" && parts[1] ? parts[1] : parts[0];
export async function deriveDisplayNameFromDomain(domain: string): Promise<string> {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

function changed to async but doesn't use any async operations - consider removing async/await unless planning future async operations

Suggested change
export async function deriveDisplayNameFromDomain(domain: string): Promise<string> {
export function deriveDisplayNameFromDomain(domain: string): string {

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/repository/provider-endpoints.ts
Line: 234:234

Comment:
function changed to `async` but doesn't use any async operations - consider removing async/await unless planning future async operations

```suggestion
export function deriveDisplayNameFromDomain(domain: string): string {
```

<sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub>

How can I resolve this? If you propose a fix, please make it concise.

This was referenced Jan 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:i18n area:UI enhancement New feature or request size/XL Extra Large PR (> 1000 lines)

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant