Skip to content

fix(frontend): floor the marketplace consent dialog's remaining term - #383

Merged
cristim merged 1 commit into
mainfrom
fix/240-marketplace-consent-floor
Sep 28, 2026
Merged

cristim merged 1 commit into
mainfrom
fix/240-marketplace-consent-floor

Conversation

@cristim

@cristim cristim commented Sep 28, 2026 •

Copy link
Copy Markdown
Member

Summary

The marketplace consent dialog computed the list price it shows by rounding the remaining months with a floor of zero, while the backend recomputes the price by flooring with a floor of one. For any fractional remainder of half a month or more, the user authorized one amount and a different, lower amount was actually submitted.

Root cause

frontend/src/history.ts computed remainingMonths = Math.max(0, Math.round(termMonths - elapsedMonths)), while the backend's computeRemainingMonths (internal/api/handler_marketplace.go:447) computes int(math.Floor(remaining)) clamped to a minimum of 1. A comment on the dialog claimed the two calculations mirror each other "EXACTLY", but they used different rounding functions and different floors.

Worked example from the issue: a 1-year, $1,200-upfront, count-1 RI purchased 5.4 months ago. Remaining is 6.6. Pre-fix the dialog rounds to 7 months and shows a list price of $665 (net $585.20); the backend floors to 6 and actually lists at $570 (net $501.60).

Fix

Changed to Math.max(1, Math.floor(termMonths - elapsedMonths)), matching computeRemainingMonths exactly.

Regression test

Added frontend/src/__tests__/history-marketplace-sell-button.test.ts: "issue #240: floors the remaining term like the backend, not rounds it", reproducing the issue's exact worked example (1yr, $1,200 upfront, count 1, purchased 5.4 months ago) and asserting the dialog shows 6 months / $570 / $501.6, not the pre-fix 7 months / $665 / $585.

Two pre-existing tests in the same file ("shows the correctly-prorated residual for a 3yr RI ~6 months in" and "shows the backend-matching per-unit price...") pinned their fixture timestamps at exact month boundaries, which is only stable under round semantics -- under floor, real wall-clock drift between building the timestamp and the click handler re-computing elapsed time could tip the result either side of the boundary. Moved both to a half-month margin off the boundary (same expected remaining-months/price values for the first; updated values for the second, which was previously sitting exactly on a 12-month boundary) so they stay deterministic.

Proved pre-fix failure: git stash push -- frontend/src/history.ts then ran the new test -- failed with Expected substring: "6 months" / actual "...Remaining term7 months...$665...$585.2..." (matching the issue's stated pre-fix figures exactly). Restored via git stash pop and reran: passes.

Verification

  • npm test -- history -- 233 passed (13 suites)
  • npx eslint src/history.ts src/__tests__/history-marketplace-sell-button.test.ts -- no issues
  • npm run build -- webpack production build succeeds (pre-existing entrypoint-size warning only)

Closes #240

The marketplace consent dialog computed the remaining months with
Math.max(0, Math.round(...)), while the backend's
computeRemainingMonths (internal/api/handler_marketplace.go) floors
and clamps to a minimum of 1. For any fractional remainder of half a
month or more, the two diverged: the dialog would show and the user
would authorize one price, while a different, lower price was
actually submitted server-side. A comment on the dialog claimed the
two calculations mirrored each other exactly.

Change to Math.max(1, Math.floor(...)) to match the backend exactly.
Two existing tests pinned timestamps at exact month boundaries
(assuming round semantics); moved them off the boundary with a
half-month margin so they stay deterministic under floor and updated
their expected figures.

Co-Authored-By: claude-flow <ruv@ruv.net>
@coderabbitai

coderabbitai Bot commented Sep 28, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

  • Run on-demand review

This review includes 2 billable files and costs up to $0.50.

  • Ask an admin to make reviews automatic

Open in CodeRabbit

Reviews can continue after your included limit without a manual trigger. An admin must approve usage-based billing.

Or wait 5 minutes for your next included review.

Check out review usage here.

View limit details

Limit details: You’ve used all 4 included reviews currently available. Your 32 included PR review attempts over the past 7 days set your current allowance at 4 reviews per hour.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Repository: LeanerCloud/cloud-commitments-platform/.coderabbit.yaml

Review profile: CHILL

Plan: Essentials

Run ID: 9d851454-927f-4718-9065-c1b64817064d

📥 Commits

Reviewing files that changed from the base of the PR and between f8d2c64 and 677c842.

📒 Files selected for processing (2)
  • frontend/src/__tests__/history-marketplace-sell-button.test.ts
  • frontend/src/history.ts

Comment @coderabbitai help to get the list of available commands.

@cristim cristim added urgency/this-sprint Within the current sprint priority/p1 Next up; this sprint severity/high Significant harm impact/many Affects most users effort/xs Trivial / one-liner type/bug Defect triaged Item has been triaged labels Sep 28, 2026
@cristim

cristim commented Sep 28, 2026

Copy link
Copy Markdown
Member Author

Independent adversarial review + local verification: MERGE at 677c842. The consent dialog's remaining term, residual per unit, 0.95 buyer discount and 12% fee now match computeRemainingMonths / marketplaceResidualPerUnit in handler_marketplace.go line by line (floor, min 1). Pre-fix test reproduces the issue's 7 months / $665 / $585.2; passes after; lint/build OK.

@cristim
cristim merged commit 92ffecc into main Sep 28, 2026
27 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

effort/xs Trivial / one-liner impact/many Affects most users priority/p1 Next up; this sprint severity/high Significant harm triaged Item has been triaged type/bug Defect urgency/this-sprint Within the current sprint

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(frontend): marketplace consent dialog rounds the list price where the backend floors

1 participant