现象
价格类提醒(price_above / price_below)的目标价在列表里完全没有币种:
(fail) states the currency of a Hong Kong price alert
Expected to contain: "Above HK$350.00"
Received: "0700.HKActivePrice Above·Above 350×Created: 1970/1/1"
(fail) states the currency of a mainland price alert
Expected to contain: "Above CN¥350.00"
Received: "600519.SHActivePrice Above·Above 350×Created: 1970/1/1"
(fail) keeps a US price alert in USD
Expected to contain: "Above $350.00"
Received: "AAPL.USActivePrice Above·Above 350×Created: 1970/1/1"
注意第三条:连美股提醒也只显示 Above 350 —— 不是币种错了,而是币种信息整个丢了。
同时存在一个自相矛盾的孪生实现:packages/ui/src/atoms/alertAtoms.ts:97-99 的纯函数 ruleSummary() 走另一个极端,把 $ 硬编码进字符串:
(fail) ruleSummary > states the price threshold in the rule symbol currency
Expected: "Above HK$100.00"
Received: "Above $100.00"
同一个数据在两条渲染路径上得到两种都不正确的表示。
根因
两个原因,方向相反:
- 实时渲染路径(用户可见):
packages/ui/src/components/alert/AlertCard.tsx:52-55 调用 formatCurrency(rule.targetPrice) —— 不传币种。而 packages/ui/src/i18n 的 formatCurrency 在 currency 为空时会退化成「分组后的裸数字 + 无代码」(packages/i18n/src/format.ts:64-82),于是渲染出 Above 350。文案本身是 above: 'Above {{price}}' / 高于 {{price}},币种只能由 {{price}} 承载,所以这里丢失后无法从别处补回来。
- 纯函数孪生体:
ruleSummary() 用模板字符串拼 `Above $${...}`,违反 packages/i18n/src/format.ts 与 packages/ui/src/lib/money.ts 的「NEVER hardcode $」约定。(该函数目前仅被自身单测引用,但它是 AlertCard.summary() 注释中明确声明的「mirrors the atoms ruleSummary logic」孪生体,两者必须一致。)
AlertRule 本身没有 currency 字段,所以币种只能由 rule.symbol 的市场后缀推导 —— 这与行情展示层用的是同一条规则。
复现
新增 packages/ui/src/components/alert/AlertCard.test.tsx(happy-dom + TestI18n,直接渲染 AlertCard)并在 packages/ui/src/atoms/alertAtoms.test.ts 补充带 .HK / .SH 后缀的用例:
# 修复前
10 pass / 4 fail
# 修复后
14 pass / 0 fail
修复前的失败信息即问题本身(见上方原文)。
修复方案
两条路径统一改用 formatMoney(value, currencyForSymbol(rule.symbol))(packages/ui/src/lib/money.ts),即行情展示层同一个格式入口:
AlertCard.tsx:新增 alertPrice(rule) 辅助函数,price_above / price_below 分支共用;不再直接依赖 @finagent/i18n 的 formatCurrency。
alertAtoms.ts ruleSummary():同样改为 formatMoney(...),去掉硬编码 $。
不新增 i18n key、不改动任何文案;展示形态(en-US HK$100.00 / zh-CN HK$100.00、US$100.00)交给既有 locale 策略。
与 #259 的关系(前置改动)
本修复依赖 lib/money.ts 中的 currencyForSymbol(),该函数由 #259 引入(同一条约定:UI 包不能 import @finagent/shared,故把符号→币种映射镜像到渲染进程)。本 PR 的 lib/money.ts 改动与 #259 逐字节一致,两个 PR 可独立合并:先合并者生效,后合并者的该 hunk 自动变为空。
验证
TDD 顺序:先写测试 → 在未修复代码上确认失败 → 修复 → 全部通过。
| 文件 |
覆盖内容 |
components/alert/AlertCard.test.tsx |
港股 HK$350.00 / A 股 CN¥350.00 / 美股 $350.00 三条 |
atoms/alertAtoms.test.ts(新增用例) |
ruleSummary 对 .HK、.SH 规则的输出;原有 US 用例保持不变(防过度修正) |
回归对照:
| 命令 |
修复前 |
修复后 |
bun test packages/ui --isolate |
332 pass / 0 fail |
336 pass / 0 fail |
bun test apps/electron --isolate |
32 pass / 0 fail |
32 pass / 0 fail |
bun run typecheck |
5/5 exit 0 |
5/5 exit 0 |
现象
价格类提醒(
price_above/price_below)的目标价在列表里完全没有币种:注意第三条:连美股提醒也只显示
Above 350—— 不是币种错了,而是币种信息整个丢了。同时存在一个自相矛盾的孪生实现:
packages/ui/src/atoms/alertAtoms.ts:97-99的纯函数ruleSummary()走另一个极端,把$硬编码进字符串:同一个数据在两条渲染路径上得到两种都不正确的表示。
根因
两个原因,方向相反:
packages/ui/src/components/alert/AlertCard.tsx:52-55调用formatCurrency(rule.targetPrice)—— 不传币种。而packages/ui/src/i18n的formatCurrency在currency为空时会退化成「分组后的裸数字 + 无代码」(packages/i18n/src/format.ts:64-82),于是渲染出Above 350。文案本身是above: 'Above {{price}}'/高于 {{price}},币种只能由{{price}}承载,所以这里丢失后无法从别处补回来。ruleSummary()用模板字符串拼`Above $${...}`,违反packages/i18n/src/format.ts与packages/ui/src/lib/money.ts的「NEVER hardcode$」约定。(该函数目前仅被自身单测引用,但它是AlertCard.summary()注释中明确声明的「mirrors the atoms ruleSummary logic」孪生体,两者必须一致。)AlertRule本身没有currency字段,所以币种只能由rule.symbol的市场后缀推导 —— 这与行情展示层用的是同一条规则。复现
新增
packages/ui/src/components/alert/AlertCard.test.tsx(happy-dom +TestI18n,直接渲染AlertCard)并在packages/ui/src/atoms/alertAtoms.test.ts补充带.HK/.SH后缀的用例:修复前的失败信息即问题本身(见上方原文)。
修复方案
两条路径统一改用
formatMoney(value, currencyForSymbol(rule.symbol))(packages/ui/src/lib/money.ts),即行情展示层同一个格式入口:AlertCard.tsx:新增alertPrice(rule)辅助函数,price_above/price_below分支共用;不再直接依赖@finagent/i18n的formatCurrency。alertAtoms.tsruleSummary():同样改为formatMoney(...),去掉硬编码$。不新增 i18n key、不改动任何文案;展示形态(en-US
HK$100.00/ zh-CNHK$100.00、US$100.00)交给既有 locale 策略。与 #259 的关系(前置改动)
本修复依赖
lib/money.ts中的currencyForSymbol(),该函数由 #259 引入(同一条约定:UI 包不能 import@finagent/shared,故把符号→币种映射镜像到渲染进程)。本 PR 的lib/money.ts改动与 #259 逐字节一致,两个 PR 可独立合并:先合并者生效,后合并者的该 hunk 自动变为空。验证
TDD 顺序:先写测试 → 在未修复代码上确认失败 → 修复 → 全部通过。
components/alert/AlertCard.test.tsxHK$350.00/ A 股CN¥350.00/ 美股$350.00三条atoms/alertAtoms.test.ts(新增用例)ruleSummary对.HK、.SH规则的输出;原有 US 用例保持不变(防过度修正)回归对照:
bun test packages/ui --isolatebun test apps/electron --isolatebun run typecheck