现象
行情价格展示层仍有 4 处 $ 硬编码,任何非美元标的都会把本币金额当成美元显示:
| 文件 |
硬编码位置 |
影响 |
packages/ui/src/components/workspace/SecurityHeader.tsx |
formatPrice = (v) => \$${v.toFixed(2)}`` |
顶栏最新价 + 开/高/低/昨收 5 个数字 |
packages/ui/src/components/workspace/OverviewView.tsx |
同名 fmtPrice |
行情块 + 表现块 5 个数字 |
packages/ui/src/components/stock/Watchlist.tsx |
formatPrice |
自选列表每一行的最新价 |
packages/ui/src/components/stock/StockCard.tsx |
3 处内联模板 |
卡片最新价 + H:/L: 行 |
这些都是错误断言:
0700.HK(港股)显示 $350.00,正确应为 HK$350.00;
600519.SH / 000001.SZ(A 股)显示 $350.00,正确应为 CN¥350.00;
D05.SG 显示 $350.00,正确应为 SGD 350.00。
并且在 zh-CN 下即使标的是美股也会错:Intl 的 zh-CN 货币格式是 US$1,234.56,硬编码 $ 与该 locale 约定不符。
这直接违反仓库自身的约定 —— packages/i18n/src/format.ts 与 packages/ui/src/lib/money.ts 的 docstring 都写明「NEVER hardcode $」,lib/money.ts 更明确要求「一个视图不能同时出现 US$1,234.56 与 $1,234.56」。
OverviewView 是这条约束被破坏的最直观证据:它已经用 formatMoney(holding.marketValue, holding.currency) 渲染持仓块,却在同一个视图的行情块里用硬编码 $。
复现
新增 5 个测试文件(详见下方验证段),在未修复的代码上运行:
(fail) prices a Hong Kong instrument in HKD, never a bare $ Expected: 1 Received: 0
(fail) falls back to the routed market when the provider reports no currency
Expected: 5 Received: 0
(fail) formats every quote cell in the instrument currency Expected: 5 Received: 0
(fail) prices a Hong Kong tile in HKD instead of a hardcoded $ Expected: 3 Received: 1
(fail) prices each row in that symbol currency
Received: "0700.HKTencent Holdings$350.00+0.43%"
1 pass / 6 fail + SyntaxError: Export named 'currencyForSymbol' not found
Watchlist 那条失败信息把问题完整暴露出来:Tencent Holdings 的最新价被渲染成 $350.00。
根因
已关闭的 issue #86「Unify currency formatting onto the i18n locale policy (lib/money vs @finagent/i18n)」把货币格式化统一到了 lib/money.ts → @finagent/i18n,但迁移时漏掉了这 4 个只显示行情价格、不显示持仓金额的调用点。它们各自保留了设计刷新时期的本地 formatPrice/fmtPrice 辅助函数。
SecurityHeader / OverviewView 其实已经通过 market.getStaticInfo() 拿到了 StaticInfo.currency(packages/core/src/index.ts:81),只是没有用它格式化价格。
修复方案
- 在
packages/ui/src/lib/money.ts 补一个 currencyForSymbol(symbol),作为 @finagent/shared/src/agent/answer-block-emitter.ts 中同名函数的渲染进程镜像(UI 包因会把 node/executor 代码带进 renderer 而不能 import @finagent/shared,仓库中已有多个同类镜像先例)。映射规则与上游完全一致:.HK→HKD、.SH/.SZ→CNY、.SG→SGD、其余→USD。
- 4 个调用点改用
formatMoney(value, currency):
SecurityHeader / OverviewView 优先使用数据源上报的货币(info?.currency / staticInfo?.currency),缺失时回落到符号路由推断;
Watchlist / StockCard 只持有 symbol,直接使用符号路由推断。
- 删除这 4 处本地硬编码辅助函数。
不改动任何 i18n 文案、不新增 key,展示形态完全由既有 @finagent/i18n 的 locale 策略决定。
影响面
仅涉及「行情价格」的展示;formatNumber(quote.volume) 等非货币数字不变。本次未纳入提醒规则的 ruleSummary(packages/ui/src/atoms/alertAtoms.ts:97-99 同样硬编码 $,而本地化的 AlertCard 走的是 formatCurrency(value) 无币种参数、渲染成不带币种的裸数字)——那是另一条数据通路(告警规则描述,且该 helper 尚未 i18n),如需处理建议单独开 issue,避免把这个 PR 扩大成两类改动。
验证
TDD 顺序:先写测试 → 在未修复代码上确认失败 → 修复 → 全部通过。
# 修复前
1 pass / 6 fail (+ SyntaxError: Export named 'currencyForSymbol' not found)
# 修复后
9 pass / 0 fail
新增测试覆盖:
| 文件 |
覆盖内容 |
packages/ui/src/lib/money.test.ts |
currencyForSymbol 的 5 种后缀映射、大小写、未知/空值回落,以及与 formatMoney 的组合输出 |
packages/ui/src/components/workspace/SecurityHeader.test.tsx |
0700.HK + provider 上报 HKD → 5 处 HK$;600519.SH 且 provider 未上报币种 → 5 处 CN¥(验证回落分支) |
packages/ui/src/components/workspace/OverviewView.test.tsx |
行情块 5 个数字全部使用 HK$ |
packages/ui/src/components/stock/Watchlist.test.tsx |
自选行按该行 symbol 的币种渲染 |
packages/ui/src/components/stock/StockCard.test.tsx |
港股卡片 3 处 HK$;美股卡片仍为 $(防止过度修正) |
回归对照:
| 命令 |
修复前 |
修复后 |
bun test packages/ui --isolate |
321 pass / 0 fail |
332 pass / 0 fail |
bun test apps/electron --isolate |
32 pass / 0 fail |
32 pass / 0 fail |
bun test packages/core --isolate |
53 pass / 0 fail |
53 pass / 0 fail |
bun run typecheck |
5/5 exit 0 |
5/5 exit 0 |
现象
行情价格展示层仍有 4 处
$硬编码,任何非美元标的都会把本币金额当成美元显示:packages/ui/src/components/workspace/SecurityHeader.tsxformatPrice = (v) => \$${v.toFixed(2)}``packages/ui/src/components/workspace/OverviewView.tsxfmtPricepackages/ui/src/components/stock/Watchlist.tsxformatPricepackages/ui/src/components/stock/StockCard.tsxH:/L:行这些都是错误断言:
0700.HK(港股)显示$350.00,正确应为HK$350.00;600519.SH/000001.SZ(A 股)显示$350.00,正确应为CN¥350.00;D05.SG显示$350.00,正确应为SGD 350.00。并且在 zh-CN 下即使标的是美股也会错:
Intl的 zh-CN 货币格式是US$1,234.56,硬编码$与该 locale 约定不符。这直接违反仓库自身的约定 ——
packages/i18n/src/format.ts与packages/ui/src/lib/money.ts的 docstring 都写明「NEVER hardcode$」,lib/money.ts更明确要求「一个视图不能同时出现US$1,234.56与$1,234.56」。OverviewView是这条约束被破坏的最直观证据:它已经用formatMoney(holding.marketValue, holding.currency)渲染持仓块,却在同一个视图的行情块里用硬编码$。复现
新增 5 个测试文件(详见下方验证段),在未修复的代码上运行:
Watchlist那条失败信息把问题完整暴露出来:Tencent Holdings 的最新价被渲染成$350.00。根因
已关闭的 issue #86「Unify currency formatting onto the i18n locale policy (lib/money vs @finagent/i18n)」把货币格式化统一到了
lib/money.ts→@finagent/i18n,但迁移时漏掉了这 4 个只显示行情价格、不显示持仓金额的调用点。它们各自保留了设计刷新时期的本地formatPrice/fmtPrice辅助函数。SecurityHeader/OverviewView其实已经通过market.getStaticInfo()拿到了StaticInfo.currency(packages/core/src/index.ts:81),只是没有用它格式化价格。修复方案
packages/ui/src/lib/money.ts补一个currencyForSymbol(symbol),作为@finagent/shared/src/agent/answer-block-emitter.ts中同名函数的渲染进程镜像(UI 包因会把 node/executor 代码带进 renderer 而不能 import@finagent/shared,仓库中已有多个同类镜像先例)。映射规则与上游完全一致:.HK→HKD、.SH/.SZ→CNY、.SG→SGD、其余→USD。formatMoney(value, currency):SecurityHeader/OverviewView优先使用数据源上报的货币(info?.currency/staticInfo?.currency),缺失时回落到符号路由推断;Watchlist/StockCard只持有 symbol,直接使用符号路由推断。不改动任何 i18n 文案、不新增 key,展示形态完全由既有
@finagent/i18n的 locale 策略决定。影响面
仅涉及「行情价格」的展示;
formatNumber(quote.volume)等非货币数字不变。本次未纳入提醒规则的ruleSummary(packages/ui/src/atoms/alertAtoms.ts:97-99同样硬编码$,而本地化的AlertCard走的是formatCurrency(value)无币种参数、渲染成不带币种的裸数字)——那是另一条数据通路(告警规则描述,且该 helper 尚未 i18n),如需处理建议单独开 issue,避免把这个 PR 扩大成两类改动。验证
TDD 顺序:先写测试 → 在未修复代码上确认失败 → 修复 → 全部通过。
新增测试覆盖:
packages/ui/src/lib/money.test.tscurrencyForSymbol的 5 种后缀映射、大小写、未知/空值回落,以及与formatMoney的组合输出packages/ui/src/components/workspace/SecurityHeader.test.tsx0700.HK+ provider 上报HKD→ 5 处HK$;600519.SH且 provider 未上报币种 → 5 处CN¥(验证回落分支)packages/ui/src/components/workspace/OverviewView.test.tsxHK$packages/ui/src/components/stock/Watchlist.test.tsxpackages/ui/src/components/stock/StockCard.test.tsxHK$;美股卡片仍为$(防止过度修正)回归对照:
bun test packages/ui --isolatebun test apps/electron --isolatebun test packages/core --isolatebun run typecheck