Repository navigation
fix(frontend): low money-path display and SP coverage input fixes - #1345
Conversation
The four per-Savings-Plan coverage inputs sit outside the settings form, so native min/max validation never runs for them and the inline hint only paints a message. Save accepted any finite number, so a fractional value like 12.7 passed the backend's 0..100 range check and was stored. Apply the global coverage field's whole-number 0..100 rule to every edited SP card before any PUT is sent, and abort the save with a toast naming the offending card. Untouched and empty cards keep their existing fallback behavior. Refs LeanerCloud#318 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The marketplace listing dialog rounded the list price, AWS fee and net proceeds to whole dollars independently, so the lines did not add up: a $100.50 listing showed $101 = $12 + $88. Render all three with cents and derive the fee from the rounded list and net totals, so fee + net always equals the list price shown. Refs LeanerCloud#318 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With the amortize toggle on, Purchase History and the Approval Queue showed the full upfront next to a Monthly Cost that already includes it, and only the Monthly Cost header disclosed this. Upfront plus 12 times Monthly then double-counted the upfront. When the upfront is spread into the monthly figure, mute the Upfront cell, add an "(in monthly)" note and an explanatory title. Also read the amortize flag once per render instead of once per row. Refs LeanerCloud#318 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Warning Review limit reached
This review includes 7 billable files and costs up to $1.75. Or wait 59 minutes for your next included review. View limit details
Comment |
|
Independent review (IAmJSD's agent, fresh context, given only the diff, the issue and the PR text): APPROVE Blocking:
|
|
Local verification at
|
|
Marked ready and merged by IAmJSD's agent, at IAmJSD's request (IAmJSD reviewed the before/after screenshots; cristim is traveling), after the independent review and local verification above, all at |
Conflicts: - frontend/src/history.ts (Purchase History row and Approval queue row): kept LeanerCloud#181's no-commitment guards (dashOr/MUTED_DASH, NO_RECS_HTML in the Type/Service cell, termCell(p) dash for a 0 term) and main's changes (count escaped with a null fallback from LeanerCloud#1318, upfrontCell(p, amortize) with the "(in monthly)" note from LeanerCloud#1345, amortize hoisted out of the row loop). A no-commitment row renders a muted dash <td> instead of upfrontCell; real rows use upfrontCell unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The 2026-09-02 audit grouped four low-severity money-path findings in this issue; three were still live on main. The Marketplace listing dialog rounded list price, AWS fee and net proceeds to whole dollars separately so they did not add up, the history tables showed the full upfront next to an amortized Monthly Cost that already includes it, and the per-Savings-Plan coverage inputs saved any number (for example 12.7). This PR addresses those three; the fourth (A12-044, the "Run now" dialog with no amount) is already addressed on main by #781 (
6248db25).Closes #318
What changed
943422b4(A11-006),frontend/src/settings.ts: a newinvalidEditedCardCoverages()helper runs insidesaveGlobalSettingsnext to the other numeric guards, before any PUT. Each edited SP coverage card must be a whole number from 0 to 100. If one is not, the save stops with the global field's toast text plus the card's heading, e.g. "(Compute Savings Plans)".f67dc499(A12-058),frontend/src/history.tsMarketplace dialog: list price and net proceeds are rounded to cents, the fee is list minus net, and all three (and the per-unit values) are shown with 2 digits.ec847dfc(A12-059),frontend/src/history.ts: a newupfrontCell()helper, used by both Purchase History and the Approval Queue, renders the Upfront cell muted with a small "(in monthly)" line and the title "Included in the amortized monthly cost" when amortize is on and the upfront was actually folded in (monthly cost, upfront > 0 and term are known). The amortize flag is read once per render instead of once per row. New.upfront-in-monthlyand.upfront-in-monthly-noteclasses instyles/components.css.plans.tsrunNowBodyon main already shows the plan, count, term, payment and upfront.Why this approach
digits: 2on all three rows, but three separately rounded numbers can still be a cent apart (e.g. 100.505). Deriving the fee from the two rounded totals guarantees they reconcile, and the fee is the least important of the three.Screenshots
Before is main (
a14dc8f2), after is this branch. Both are headless Chromium at 1440x900 against the repo's mocked e2e API.Verification
Run on this branch's head
ec847dfcon top of maina14dc8f2, fromfrontend/:settings.test.ts+5 (12.7, -40 and 500 abort with no PUT and an error toast; 0 and 100 are saved),history.test.ts+2 (amortize on/off: Upfront cell class, title and text in both tables; monthly $200 vs $100),history-marketplace-sell-button.test.ts+1 (real formatter: $142.50 / $17.10 / $125.40, no $143). The new tests pass under--randomize. Some existing tests insettings.test.tsandhistory.test.tsare order-dependent and fail with some seeds; the same failures occur ona14dc8f2in a clean worktree.e2e-local.sh): 158 passed, including the newtests-e2e/amortized-upfront.spec.ts(toggles amortize and checks the Upfront cell's class, title and note).scripts/test-git-secrets-allowlist.sh: 137 PASS, 0 FAIL.Notes for review
settings.ts(3,800+ lines) andhistory.ts(1,900+) are already far over the 500-line guideline; this adds about 30 lines to each rather than starting a split.Overlaps
<td>lines inrenderHistoryListandrenderApprovalQueue(dashOrwrapping /class="num"). These will conflict textually; resolve by moving that wrapping or class intoupfrontCell().settings.ts(related, probably no textual conflict).history.ts(RBAC predicates and_fourEyesMode), in different functions.Note for the UI owner
This changes UI code that @cristim owns. IAmJSD reviewed the before/after screenshots and asked for it to be merged; any design point you disagree with can be a follow-up.
🤖 Generated with Claude Code