fix(#2313): guard the display formatters that could render literal NaN - #2529
Conversation
The mechanism is quiet: `(NaN).toFixed(2)` is the STRING "NaN", and every magnitude comparison against NaN is false — so NaN falls through each `n >= 1e6` branch and lands on the final toFixed, producing "$NaN" in the DOM with no error raised anywhere. SCOPE CORRECTION. The issue says "zero Number.isFinite guards anywhere in the Earn display pipeline". That is no longer true, and the real exposure is narrower. I checked each claim: lib/formatters.ts formatCompact — ALREADY guarded OiCapMeter utilPct — guarded at source (`if (maxOI <= 0) return 0`) LpPositionDashboard userSharePct — guarded at source (`lpSupply > 0n ? ... : 0`) VaultDepositRail / VaultRow fees — safe (`?? 10` / plain field) Three real gaps remained, and they are fixed here: 1. Watchlist.tsx carried a PRIVATE copy of formatCompact with no guard at all. 2. MarketInfoBar.tsx carried a second private copy that guarded `null` but not NaN — so the two copies had already diverged in exactly the way duplication invites. 3. DepositWithdrawPanel.tsx computed a share preview inline as `Number(previewShares) / Number(lpSupply + previewShares)`, which is 0/0 — NaN — for the first deposit into an empty pool. That is the one a user could actually hit, and it renders "NaN%" on the deposit screen. Both private copies are deleted and replaced by a shared, guarded `formatCompactUsd`; the inline division goes through a new guarded `formatPercent`. Consolidating is the durable half — two copies of a formatter will drift again, and here they already had. The shared version keeps the STRICTER of the two behaviours (null-guarded, like MarketInfoBar's) rather than the laxer one. Negative control: removing the two guards fails 7 of the 12 new tests. Five more pin that real values still format across every magnitude branch, so this cannot be satisfied by returning a placeholder for everything. Launch suite: 3123 passed / 16 skipped / 0 failed. Refs: #2313 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NgoNgagkvw7i5SSRC3FJ8D
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Team Run ID: 📒 Files selected for processing (5)
Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review. 📝 WalkthroughWalkthroughThe change adds shared guarded formatters for USD and percentage values. Watchlist, MarketInfoBar, and DepositWithdrawPanel use them. Tests cover non-finite inputs, nullish values, magnitude branches, precision options, and zero-over-zero calculations. ChangesFormatter guard consolidation
Estimated code review effort: 3 (Moderate) | ~20 minutes Merge Risk: ⚪ Minimal · up to The change prevents invalid numeric values from rendering as NaN and consolidates duplicated display formatting without altering security, data, deployment, or runtime behavior. No actionable merge-blocking risk remains. 🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
Full details: Linked Issues checkExplanation The changes address several NaN display gaps in Watchlist, MarketInfoBar, and DepositWithdrawPanel [ Resolution Update all affected Earn components and formatter copies required by issue
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
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. Comment |
Closes #2313.
(NaN).toFixed(2)is the string"NaN", and every magnitude comparison against NaN is false — so NaN falls through eachn >= 1e6branch and lands on the finaltoFixed, producing$NaNin the DOM with no error raised anywhere.Scope correction
The issue says "zero
Number.isFinite()guards anywhere in the Earn display pipeline". That is no longer true, and the real exposure is narrower. I checked each claim rather than taking the count:lib/formatters.tsformatCompactOiCapMeterutilPctif (maxOI <= 0) return 0LpPositionDashboarduserSharePctlpSupply > 0n ? … : 0VaultDepositRail/VaultRowfees?? 10, plain field)Three real gaps, all fixed
Watchlist.tsxcarried a private copy offormatCompactwith no guard at all.MarketInfoBar.tsxcarried a second private copy that guardednullbut not NaN — the two copies had already diverged in exactly the way duplication invites.DepositWithdrawPanel.tsxcomputed a share preview inline asNumber(previewShares) / Number(lpSupply + previewShares)— which is 0/0 = NaN for the first deposit into an empty pool. That is the one a user can actually hit, and it rendersNaN%on the deposit screen.Both private copies are deleted in favour of a shared, guarded
formatCompactUsd; the inline division goes through a new guardedformatPercent.Consolidating is the durable half. Two copies of a formatter will drift — and here they already had.
The shared version keeps the stricter of the two behaviours (null-guarded, like MarketInfoBar's), not the laxer one.
Verification
🤖 Generated with Claude Code
https://claude.ai/code/session_01NgoNgagkvw7i5SSRC3FJ8D
Summary by CodeRabbit
New Features
NaNor infinity.Bug Fixes