Skip to content

fix(currency): respect system currencyDisplay setting in UI#717

Merged
ding113 merged 1 commit intodevfrom
fix/issue-678-currency-display
Feb 3, 2026
Merged

fix(currency): respect system currencyDisplay setting in UI#717
ding113 merged 1 commit intodevfrom
fix/issue-678-currency-display

Conversation

@ding113
Copy link
Owner

@ding113 ding113 commented Feb 3, 2026

Summary

Root Cause

  • users-page-client.tsx hardcoded currencyCode="USD" instead of reading from system settings
  • UserLimitBadge and LimitStatusIndicator had hardcoded unit="$" default values
  • big-screen/page.tsx used hardcoded "$" symbols in multiple places

Changes

File Change
currency.ts Add getCurrencySymbol() helper function
users-page-client.tsx Fetch system settings, pass currencyDisplay to table
user-key-table-row.tsx Convert currency code to symbol, pass to child badges
user-limit-badge.tsx Remove hardcoded "$" default from unit prop
limit-status-indicator.tsx Remove hardcoded "$" default from unit prop
big-screen/page.tsx Fetch system settings, use dynamic currency symbol
currency.test.ts Add unit tests for getCurrencySymbol

Test Plan

  • Unit tests for getCurrencySymbol pass
  • Manual: Set currencyDisplay to "CNY" in system settings
  • Manual: Verify user management limit badges show correct symbol
  • Manual: Verify big screen page shows correct currency symbol

Generated with Claude Code

Greptile Overview

Greptile Summary

Fixed issue #678 where currency display configuration (currencyDisplay system setting) was not applied to the UI. The fix correctly propagates the configured currency symbol throughout the application.

Key Changes

  • Added getCurrencySymbol() helper function to convert currency codes to symbols
  • Removed hardcoded "$" defaults from user-limit-badge.tsx and limit-status-indicator.tsx
  • Updated users-page-client.tsx to fetch system settings and pass currencyDisplay to the table
  • Updated user-key-table-row.tsx to convert currency code to symbol for all limit badges
  • Updated big-screen/page.tsx to fetch system settings and use dynamic currency symbols throughout

Implementation Quality

The implementation is clean and follows best practices:

  • Proper fallback to USD when settings unavailable
  • Comprehensive unit tests for the new helper function
  • Consistent pattern across both pages (users and big screen)
  • Uses existing /api/system-settings endpoint with appropriate caching (staleTime: 30_000)

Testing

Unit tests verify correct symbol mapping for all supported currencies (USD, CNY, EUR, JPY, GBP, HKD, TWD, KRW, SGD) and proper fallback behavior.

Confidence Score: 5/5

  • Safe to merge - well-tested, focused fix with proper fallbacks
  • Clean implementation with unit tests, proper error handling, and no breaking changes. The changes are focused and address the issue comprehensively.
  • No files require special attention

Important Files Changed

Filename Overview
src/lib/utils/currency.ts Added getCurrencySymbol helper function with proper fallback to USD
src/app/[locale]/dashboard/users/users-page-client.tsx Fetches system settings and passes currencyDisplay to table instead of hardcoded USD
src/app/[locale]/internal/dashboard/big-screen/page.tsx Fetches system settings, uses dynamic currency symbol throughout dashboard

Sequence Diagram

sequenceDiagram
    participant User
    participant UsersPage as users-page-client.tsx
    participant BigScreen as big-screen/page.tsx
    participant API as /api/system-settings
    participant DB as Database
    participant TableRow as user-key-table-row.tsx
    participant Badge as user-limit-badge.tsx
    participant Currency as currency.ts

    Note over User,Currency: Currency Display Configuration Flow

    User->>UsersPage: Load users page
    UsersPage->>API: GET /api/system-settings
    API->>DB: getSystemSettings()
    DB-->>API: { currencyDisplay: "CNY" }
    API-->>UsersPage: { currencyDisplay: "CNY" }
    UsersPage->>TableRow: Pass currencyCode="CNY"
    TableRow->>Currency: getCurrencySymbol("CNY")
    Currency-->>TableRow: "¥"
    TableRow->>Badge: Pass unit="¥"
    Badge-->>User: Display "¥100.50"

    User->>BigScreen: Load big screen dashboard
    BigScreen->>API: GET /api/system-settings
    API->>DB: getSystemSettings()
    DB-->>API: { currencyDisplay: "CNY" }
    API-->>BigScreen: { currencyDisplay: "CNY" }
    BigScreen->>Currency: CURRENCY_CONFIG["CNY"].symbol
    Currency-->>BigScreen: "¥"
    BigScreen-->>User: Display metrics with "¥" symbol
Loading

Fixes #678 - Currency display unit configuration was not applied.

Root cause:
- `users-page-client.tsx` hardcoded `currencyCode="USD"`
- `UserLimitBadge` and `LimitStatusIndicator` had hardcoded `unit="$"` default
- `big-screen/page.tsx` used hardcoded "$" in multiple places

Changes:
- Add `getCurrencySymbol()` helper function to currency.ts
- Fetch system settings in `users-page-client.tsx` and pass to table
- Pass `currencySymbol` from `user-key-table-row.tsx` to limit badges
- Remove hardcoded "$" defaults from badge components
- Update big-screen page to fetch settings and use dynamic symbol
- Add unit tests for `getCurrencySymbol`

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

coderabbitai bot commented Feb 3, 2026

📝 Walkthrough

Walkthrough

该变更将系统的货币显示从硬编码的"$"改为动态系统设置值。在仪表板和大屏页面中集成货币符号解析,新增getCurrencySymbol工具函数以支持多种货币代码的符号映射,并添加了相应的单元测试。

Changes

Cohort / File(s) Summary
货币显示组件默认值更新
src/app/[locale]/dashboard/_components/user/limit-status-indicator.tsx, src/app/[locale]/dashboard/_components/user/user-limit-badge.tsx
将默认单位参数从"$"改为空字符串"",使货币符号显示由调用方控制。
用户管理表格中的货币集成
src/app/[locale]/dashboard/_components/user/user-key-table-row.tsx
导入getCurrencySymbol函数,从currencyCode计算currencySymbol,并将其传递给5个UserLimitBadge实例(5h、daily、weekly、monthly、total限制)。
用户页面客户端的系统设置集成
src/app/[locale]/dashboard/users/users-page-client.tsx
新增system-settings查询获取currencyDisplay配置(staleTime为30秒),将硬编码的"USD"替换为systemSettings?.currencyDisplay ?? "USD",在两处传递给UserManagementTable。
大屏页面的货币感知渲染
src/app/[locale]/internal/dashboard/big-screen/page.tsx
添加system-settings数据查询获取currencyDisplay,使用CURRENCY_CONFIG计算currencySymbol,向UserRankings和ProviderRanking组件添加currencySymbol prop,替换多处硬编码的货币前缀。
货币工具函数与测试
src/lib/utils/currency.ts, tests/unit/lib/utils/currency.test.ts
新增getCurrencySymbol()助手函数,根据货币代码从CURRENCY_CONFIG返回对应符号,默认返回USD符号;添加9个测试覆盖有效货币代码、无效输入、undefined值及配置完整性验证。

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 57.14% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed 标题准确反映了主要变更内容:修复系统货币显示设置未被UI应用的问题,与所有文件变更紧密相关。
Description check ✅ Passed 描述详细列举了问题根因、所有变更文件及其目的,清晰说明了与变更集的关联。
Linked Issues check ✅ Passed 代码变更完整解决了#678要求:移除硬编码的USD和$符号,实现系统currencyDisplay设置的动态应用。
Out of Scope Changes check ✅ Passed 所有变更都在#678范围内:添加货币符号工具函数、更新UI组件以读取系统货币设置、添加相关单元测试。

✏️ 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 fix/issue-678-currency-display

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 implements a crucial fix to ensure that the application's user interface consistently respects the global currencyDisplay system setting. Previously, various parts of the dashboard, particularly those related to user limits and usage costs, displayed hardcoded currency symbols, leading to an inconsistent and potentially incorrect user experience for different locales. The changes centralize currency symbol retrieval and propagate it throughout the affected UI components, enhancing the application's internationalization capabilities.

Highlights

  • Dynamic Currency Display: The UI now dynamically displays currency symbols based on the currencyDisplay system setting, resolving an issue where usage displays were hardcoded to USD.
  • New Currency Utility: A getCurrencySymbol helper function has been introduced to convert currency codes (e.g., "USD", "CNY") into their corresponding symbols (e.g., "$", "¥").
  • UI Component Updates: Several UI components, including LimitStatusIndicator, UserLimitBadge, UserKeyTableRow, and components within big-screen/page.tsx, have been updated to utilize the dynamic currency symbol instead of hardcoded values.
  • Unit Test Coverage: Comprehensive unit tests have been added for the new getCurrencySymbol utility to ensure its correctness and reliability.
Changelog
  • src/app/[locale]/dashboard/_components/user/limit-status-indicator.tsx
    • Removed the default hardcoded "$" from the unit prop, allowing for dynamic currency symbols.
  • src/app/[locale]/dashboard/_components/user/user-key-table-row.tsx
    • Integrated the new getCurrencySymbol utility to convert the currencyCode prop into a symbol, which is then passed down to LimitStatusIndicator components.
  • src/app/[locale]/dashboard/_components/user/user-limit-badge.tsx
    • Updated the unit prop to no longer default to "$", enabling it to receive dynamic currency symbols.
  • src/app/[locale]/dashboard/users/users-page-client.tsx
    • Added logic to fetch the currencyDisplay from system settings and passed this dynamic currency code to the user table.
  • src/app/[locale]/internal/dashboard/big-screen/page.tsx
    • Modified to fetch the system's currencyDisplay setting, convert it to a symbol using CURRENCY_CONFIG, and then pass this symbol to UserRankings and ProviderRanking components, replacing all hardcoded "$" instances.
  • src/lib/utils/currency.ts
    • Introduced getCurrencySymbol function to map CurrencyCode to its corresponding symbol, with a fallback to USD.
  • tests/unit/lib/utils/currency.test.ts
    • Added new unit tests to validate the functionality of the getCurrencySymbol utility, covering various valid and invalid currency codes.
Activity
  • The pull request was initiated by ding113.
  • The author provided a detailed summary, identified the root cause of the issue, listed specific file changes, and outlined a comprehensive test plan including both automated unit tests and manual verification steps.
  • The PR aims to fix issue 配置中的货币显示单位对使用显示中的成本不生效 #678, related to currency display unit configuration not being applied.
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.

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 effectively addresses the issue of hardcoded currency symbols by introducing a centralized getCurrencySymbol helper and fetching the currencyDisplay setting from the backend. The changes are applied consistently across the user management and big screen pages, removing hardcoded '$' and 'USD' values. The inclusion of unit tests for the new currency utility is a great addition. My review includes a few suggestions to improve code consistency, reduce duplication, and enhance robustness.


// Fetch system settings for currency display
const { data: systemSettings } = useSWR(
"system-settings",
Copy link
Contributor

Choose a reason for hiding this comment

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

high

The cache key for system settings is inconsistent between useQuery and useSWR. In users-page-client.tsx, the key ['system-settings'] is used. To ensure data is fetched once and cached consistently across different data-fetching hooks for the same resource, it's best to use the same key format. useSWR supports array keys, so aligning it with the useQuery implementation is recommended.

Suggested change
"system-settings",
["system-settings"],

{ revalidateOnFocus: false }
);

const currencySymbol = CURRENCY_CONFIG[systemSettings?.currencyDisplay ?? "USD"]?.symbol ?? "$";
Copy link
Contributor

Choose a reason for hiding this comment

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

high

This line duplicates the logic that is now available in the new getCurrencySymbol helper function. To improve maintainability and avoid code duplication, you should use the helper function here.

You will also need to update the import from @/lib/utils/currency to include getCurrencySymbol and you can remove the CURRENCY_CONFIG import as it's no longer directly used.

Suggested change
const currencySymbol = CURRENCY_CONFIG[systemSettings?.currencyDisplay ?? "USD"]?.symbol ?? "$";
const currencySymbol = getCurrencySymbol(systemSettings?.currencyDisplay);

Comment on lines +141 to +144
if (!currencyCode || !(currencyCode in CURRENCY_CONFIG)) {
return CURRENCY_CONFIG.USD.symbol;
}
return CURRENCY_CONFIG[currencyCode as CurrencyCode].symbol;
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 current implementation for checking the currency code is case-sensitive. For example, getCurrencySymbol('usd') would fall back to the default '$' instead of returning '$' for USD. To make the function more robust, consider converting the input currencyCode to uppercase before checking its existence in CURRENCY_CONFIG.

After making this change, please ensure the unit test in tests/unit/lib/utils/currency.test.ts is updated to reflect the case-insensitive behavior (e.g., expect(getCurrencySymbol('cny')).toBe('¥')).

  const code = currencyCode?.toUpperCase();
  if (code && code in CURRENCY_CONFIG) {
    return CURRENCY_CONFIG[code as CurrencyCode].symbol;
  }
  return CURRENCY_CONFIG.USD.symbol;

@github-actions github-actions bot added bug Something isn't working area:UI labels Feb 3, 2026
@github-actions
Copy link
Contributor

github-actions bot commented Feb 3, 2026

🧪 测试结果

测试类型 状态
代码质量
单元测试
集成测试
API 测试

总体结果: ✅ 所有测试通过

@ding113 ding113 merged commit 277c02f into dev Feb 3, 2026
20 checks passed
@github-project-automation github-project-automation bot moved this from Backlog to Done in Claude Code Hub Roadmap Feb 3, 2026
@github-actions github-actions bot added the size/S Small PR (< 200 lines) label Feb 3, 2026
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

This PR successfully addresses issue #678 by implementing dynamic currency display throughout the UI. The implementation is clean and well-tested, but there is one high-priority issue that should be addressed.

PR Size: S

  • Lines changed: 110 (102 additions, 8 deletions)
  • Files changed: 7

Issues Found

Category Critical High Medium Low
Logic/Bugs 0 0 0 0
Security 0 0 0 0
Error Handling 0 1 0 0
Types 0 0 0 0
Comments/Docs 0 0 0 0
Tests 0 0 0 0
Simplification 0 0 0 0

High Priority Issue (Should Fix)

1. [ERROR-SILENT] Silent error handling in system settings fetch

Location:

  • src/app/[locale]/dashboard/users/users-page-client.tsx:195-203
  • src/app/[locale]/internal/dashboard/big-screen/page.tsx:751-759

Problem: Both fetch operations for system settings throw errors that React Query/SWR will silently swallow. When the API call fails, users see no indication that currency settings couldn't be loaded, and the fallback to USD happens silently.

Why this matters: This violates CLAUDE.md Critical Rule #1: "No Silent Failures - Any error caught without logging or user feedback is a CRITICAL defect."

Real-world impact: If /api/system-settings is unavailable, users who configured CNY/EUR will see USD symbols without any error feedback, causing confusion about whether their settings are being respected.

Current code:

const { data: systemSettings } = useQuery({
  queryFn: async () => {
    const response = await fetch("/api/system-settings");
    if (\!response.ok) throw new Error("Failed to fetch settings"); // ← Error thrown but not logged
    return response.json() as Promise<{ currencyDisplay: CurrencyCode }>;
  },
  staleTime: 30_000,
});

Suggested fix (Option 1 - Explicit error handling):

const { data: systemSettings } = useQuery({
  queryKey: ["system-settings"],
  queryFn: async () => {
    try {
      const response = await fetch("/api/system-settings");
      if (\!response.ok) {
        logger.warn("Failed to fetch system settings", { status: response.status });
        return { currencyDisplay: "USD" as CurrencyCode }; // Explicit fallback
      }
      return response.json() as Promise<{ currencyDisplay: CurrencyCode }>;
    } catch (error) {
      logger.error("System settings fetch error", { error });
      return { currencyDisplay: "USD" as CurrencyCode }; // Explicit fallback
    }
  },
  staleTime: 30_000,
});

Suggested fix (Option 2 - onError callback):

const { data: systemSettings } = useQuery({
  queryKey: ["system-settings"],
  queryFn: async () => {
    const response = await fetch("/api/system-settings");
    if (\!response.ok) throw new Error("Failed to fetch settings");
    return response.json() as Promise<{ currencyDisplay: CurrencyCode }>;
  },
  staleTime: 30_000,
  onError: (error) => {
    logger.warn("Failed to load currency settings, using USD fallback", { error });
  },
});

Apply the same fix to both files.


Review Coverage

  • Logic and correctness - Clean
  • Security (OWASP Top 10) - Clean
  • Error handling - 1 issue found
  • Type safety - Clean
  • Documentation accuracy - Clean
  • Test coverage - Good (comprehensive unit tests for getCurrencySymbol)
  • Code clarity - Good

Positive Observations (Not Issues)

  • Proper fallback to USD when settings unavailable
  • Comprehensive unit tests covering all currency codes
  • Consistent pattern across both pages
  • Clean separation of concerns with getCurrencySymbol helper
  • Appropriate caching strategy (30s staleTime)

Automated review by Claude AI

*/
export function getCurrencySymbol(currencyCode?: CurrencyCode | string): string {
if (!currencyCode || !(currencyCode in CURRENCY_CONFIG)) {
return CURRENCY_CONFIG.USD.symbol;
Copy link
Contributor

Choose a reason for hiding this comment

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

[HIGH] [LOGIC-BUG] getCurrencySymbol uses in which accepts prototype keys, so some invalid inputs bypass the fallback and can return a non-string.

Evidence (src/lib/utils/currency.ts):

if (!currencyCode || !(currencyCode in CURRENCY_CONFIG)) {
  return CURRENCY_CONFIG.USD.symbol;
}
return CURRENCY_CONFIG[currencyCode as CurrencyCode].symbol;

Why this is a problem: currencyCode in CURRENCY_CONFIG is also true for inherited properties (e.g. "toString", "constructor"). In those cases, CURRENCY_CONFIG[currencyCode as CurrencyCode] is not a currency config entry, so .symbol becomes undefined, violating the declared return type (: string) and dropping the symbol in the UI.

Suggested fix:

export function getCurrencySymbol(currencyCode?: CurrencyCode | string): string {
  if (!currencyCode || !Object.hasOwn(CURRENCY_CONFIG, currencyCode)) {
    return CURRENCY_CONFIG.USD.symbol;
  }
  return CURRENCY_CONFIG[currencyCode as CurrencyCode].symbol;
}

(Optional) Add an edge-case test to lock this down:

expect(getCurrencySymbol("toString")).toBe("$");

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

This PR wires currencyDisplay into several UI surfaces, but the new getCurrencySymbol helper has a membership check bug that can return undefined for certain invalid inputs (prototype keys). That breaks the function contract (: string) and can drop the currency symbol in the UI.

PR Size: S

  • Lines changed: 110
  • Files changed: 7

Issues Found

Category Critical High Medium Low
Logic/Bugs 0 1 0 0
Security 0 0 0 0
Error Handling 0 0 0 0
Types 0 0 0 0
Comments/Docs 0 0 0 0
Tests 0 0 0 0
Simplification 0 0 0 0

Critical Issues (Must Fix)

None

High Priority Issues (Should Fix)

  1. src/lib/utils/currency.ts:141 - getCurrencySymbol uses in for membership checks; prototype keys can bypass the fallback and return undefined.

Review Coverage

  • Logic and correctness
  • Security (OWASP Top 10)
  • Error handling
  • Type safety
  • Documentation accuracy
  • Test coverage
  • Code clarity

Automated review by Codex AI

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.

  • Identified PR #717 (fix(currency): respect system currencyDisplay setting in UI) and generated the full diff for review.
  • Applied size label: size/S (110 lines changed across 7 files).
  • Posted 1 inline review comment (HIGH / LOGIC-BUG) on src/lib/utils/currency.ts for getCurrencySymbol using in (prototype keys can bypass fallback and yield undefined); suggested switching to Object.hasOwn(...) and adding an edge-case test.
  • Submitted the required PR review summary via gh pr review --comment.

@github-actions github-actions bot mentioned this pull request Feb 7, 2026
10 tasks
ding113 added a commit that referenced this pull request Feb 7, 2026
* fix(proxy): add 'cannot be modified' error detection to thinking signature rectifier

Extend the thinking signature rectifier to detect and handle the
Anthropic API error when thinking/redacted_thinking blocks have been
modified from their original response. This error occurs when clients
inadvertently modify these blocks in multi-turn conversations.

The rectifier will now remove these blocks and retry the request,
similar to how it handles other thinking-related signature errors.

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

* chore(deps): bump jspdf in the npm_and_yarn group across 1 directory

Bumps the npm_and_yarn group with 1 update in the / directory: [jspdf](https://github.com/parallax/jsPDF).


Updates `jspdf` from 3.0.4 to 4.1.0
- [Release notes](https://github.com/parallax/jsPDF/releases)
- [Changelog](https://github.com/parallax/jsPDF/blob/master/RELEASE.md)
- [Commits](parallax/jsPDF@v3.0.4...v4.1.0)

---
updated-dependencies:
- dependency-name: jspdf
  dependency-version: 4.1.0
  dependency-type: direct:production
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>

* fix: Hot-reload cache invalidation for Request Filters and Sensitive Words (#710)

* fix: hot-reload request filters via globalThis singleton pattern

EventEmitter and RequestFilterEngine now use globalThis caching to ensure
the same instance is shared across different Next.js worker contexts.
This fixes the issue where filter changes required Docker restart.

Added diagnostic logging for event subscription and propagation.

* fix(redis): wait for subscriber connection ready before subscribe

- ensureSubscriber now returns Promise<Redis>, waits for 'ready' event
- subscribeCacheInvalidation returns null on failure instead of noop
- RequestFilterEngine checks cleanup !== null before logging success
- Fixes false "Subscribed" log when Redis connection fails

* feat(sensitive-words): add hot-reload via Redis pub/sub

Enable real-time cache invalidation for sensitive words detector,
matching the pattern used by request-filter-engine and error-rule-detector.

* fix(redis): harden cache invalidation subscriptions

Ensure sensitive-words CRUD emits update events so hot-reload propagates across workers. Roll back failed pub/sub subscriptions, add retry/timeout coverage, and avoid sticky provider-cache subscription state.

* fix(codex): bump default User-Agent fallback

Update the hardcoded Codex UA used when requests lack an effective user-agent (e.g. filtered out). Keep unit tests in sync with the new default.

* fix(redis): resubscribe cache invalidation after reconnect

Clear cached subscription state on disconnect and resubscribe on ready so cross-worker cache invalidation survives transient Redis reconnects. Add unit coverage, avoid misleading publish logs, track detector cleanup handlers, and translate leftover Russian comments to English.

* fix(sensitive-words): use globalThis singleton detector

Align SensitiveWordDetector with existing __CCH_* singleton pattern to avoid duplicate instances across module reloads. Extend singleton unit tests to cover the detector.

* chore: format code (req-fix-dda97fd)

* fix: address PR review comments

- pubsub.ts: use `once` instead of `on` for ready event to prevent
  duplicate resubscription handlers on reconnect
- forwarder.ts: extract DEFAULT_CODEX_USER_AGENT constant
- provider-cache.ts: wrap subscribeCacheInvalidation in try/catch
- tests: use exported constant instead of hardcoded UA string

* fix(redis): resubscribe across repeated reconnects

Ensure pub/sub resubscribe runs on every reconnect, extend unit coverage, and keep emitRequestFiltersUpdated resilient when logger import fails.

---------

Co-authored-by: John Doe <johndoe@example.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>

* feat(logs): make cost column toggleable with improved type safety (#715)

close #713

* fix(proxy): add OpenAI chat completion format support in usage extraction (#705) (#716)

The `extractUsageMetrics` function was missing support for OpenAI chat
completion format fields (`prompt_tokens`/`completion_tokens`), causing
token statistics to not be recorded for OpenAI-compatible providers.

Changes:
- Add `prompt_tokens` -> `input_tokens` mapping
- Add `completion_tokens` -> `output_tokens` mapping
- Preserve priority: Claude > Gemini > OpenAI format
- Add 5 unit tests for OpenAI format handling

Closes #705

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>

* fix(currency): respect system currencyDisplay setting in UI (#717)

Fixes #678 - Currency display unit configuration was not applied.

Root cause:
- `users-page-client.tsx` hardcoded `currencyCode="USD"`
- `UserLimitBadge` and `LimitStatusIndicator` had hardcoded `unit="$"` default
- `big-screen/page.tsx` used hardcoded "$" in multiple places

Changes:
- Add `getCurrencySymbol()` helper function to currency.ts
- Fetch system settings in `users-page-client.tsx` and pass to table
- Pass `currencySymbol` from `user-key-table-row.tsx` to limit badges
- Remove hardcoded "$" defaults from badge components
- Update big-screen page to fetch settings and use dynamic symbol
- Add unit tests for `getCurrencySymbol`

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>

* feat(gemini): add Google Search web access preference for Gemini providers (#721)

* feat(gemini): add Google Search web access preference for Gemini providers

Add provider-level preference for Gemini API type providers to control
Google Search (web access) tool injection:

- inherit: Follow client request (default)
- enabled: Force inject googleSearch tool into request
- disabled: Force remove googleSearch tool from request

Changes:
- Add geminiGoogleSearchPreference field to provider schema
- Add GeminiGoogleSearchPreference type and validation
- Implement applyGeminiGoogleSearchOverride with audit trail
- Add UI controls in provider form (Gemini Overrides section)
- Add i18n translations for 5 languages (en, zh-CN, zh-TW, ja, ru)
- Integrate override logic in proxy forwarder for Gemini requests
- Add 22 unit tests for the override logic

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

* fix(gemini): address code review feedback

- Use explicit else-if for disabled preference check (gemini-code-assist)
- Use i18n for SelectValue placeholder instead of hardcoded string (coderabbitai)
- Sync overridden body back to session.request.message for log consistency (coderabbitai)
- Persist Gemini special settings immediately, matching Anthropic pattern (coderabbitai)

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

* fix(gemini): use strict types for provider config and audit

- Narrow preference type to "enabled" | "disabled" (exclude unreachable "inherit")
- Use ProviderType and GeminiGoogleSearchPreference types instead of string

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

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>

* fix(api): 透传 /api/actions 认证会话以避免 getUsers 返回空数据 (#720)

* fix(api): 透传 /api/actions 认证会话以避免 getUsers 返回空数据

* fix(auth): 让 scoped session 继承 allowReadOnlyAccess 语义并支持内部降权校验

* chore: format code (dev-93585fa)

* fix: bound SessionTracker active_sessions zsets by env TTL (#718)

* fix(session): bound active_sessions zsets by env ttl

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>

* fix(rate-limit): pass session ttl to lua cleanup

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>

* fix(session): validate SESSION_TTL env and prevent ZSET leak on invalid values

- Add input validation for SESSION_TTL (reject NaN, 0, negative; default 300)
- Guard against invalid TTL in Lua script to prevent clearing all sessions
- Use dynamic EXPIRE based on SESSION_TTL instead of hardcoded 3600s
- Add unit tests for TTL validation and dynamic expiry behavior

---------

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>

* fix(provider): stop standard-path fallback to legacy provider url

* feat(providers): expose vendor endpoint pools in settings UI (#719)

* feat(providers): add endpoint status mapping

* feat(providers): add endpoint pool hover

* feat(providers): show vendor endpoints in list rows

* feat(providers): extract vendor endpoint CRUD table

* chore(i18n): add provider endpoint UI strings

* fix(providers): integrate endpoint pool into provider form

* fix(provider): wrap endpoint sync in transactions to prevent race conditions (#730)

* fix(provider): wrap provider create/update endpoint sync in transactions

Provider create and update operations now run vendor resolution and
endpoint sync inside database transactions to prevent race conditions
that could leave orphaned or inconsistent endpoint rows.

Key changes:
- createProvider: wrap vendor + insert + endpoint seed in a single tx
- updateProvider: wrap vendor + update + endpoint sync in a single tx
- Add syncProviderEndpointOnProviderEdit for atomic URL/type/vendor
  migration with in-place update, soft-delete, and conflict handling
- Vendor cleanup failures degrade to warnings instead of propagating
- Add comprehensive unit and integration tests for sync edge cases

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(provider): defer endpoint circuit reset until transaction commit

Avoid running endpoint circuit reset side effects inside DB transactions to prevent rollback inconsistency. Run resets only after commit and add regression tests for deferred reset behavior in helper and provider update flows.

* fix(provider): distinguish noop from created-next in endpoint sync action label

When ensureNextEndpointActive() returns "noop" (concurrent transaction
already created an active next endpoint), the action was incorrectly
labelled "kept-previous-and-created-next". Add a new
"kept-previous-and-kept-next" action to ProviderEndpointSyncAction and
use a three-way branch so callers and logs reflect the true outcome.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* fix: address review comments from PR #731

- fix(auth): prevent scoped session access widening via ?? -> && guard
- fix(i18n): standardize zh-CN provider terminology to "服务商"
- fix(i18n): use consistent Russian translations for circuit status
- fix(i18n): replace raw formatDistanceToNow with locale-aware RelativeTime
- fix(gemini): log warning for unknown google search preference values
- fix(error-rules): check subscribeCacheInvalidation return value
- fix(test): correct endpoint hover sort test to assert URLs not labels

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: export auth session storage and fix test mock types

- Export authSessionStorage from auth-session-storage.node.ts to prevent
  undefined on named imports; remove duplicate declare global block
- Fix mockEndpoints in provider-endpoint-hover test: remove nonexistent
  lastOk/lastLatencyMs fields, add missing lastProbe* fields, use Date
  objects for createdAt/updatedAt

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: miraserver <20286838+miraserver@users.noreply.github.com>
Co-authored-by: John Doe <johndoe@example.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: 泠音 <im@ling.plus>
Co-authored-by: Longlone <41830147+WAY29@users.noreply.github.com>
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:UI bug Something isn't working size/S Small PR (< 200 lines)

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant