Summary
Expand the Purchase modal (the "Execute Purchase" confirmation that appears after selecting recommendations on the Recommendations page) to show full per-purchase detail — costs, savings, configuration, count — with per-row checkboxes so the user can opt out of individual purchases without leaving the modal.
Why
Today the modal confirms the selected purchases at a fairly summarized level. When a user has selected, say, 12 RIs across 4 cells (instance type × region × payment), they typically want to:
- Verify the breakdown by configuration (instance type, term, payment, count, upfront, monthly cost, savings) before clicking through.
- Drop one or two specific purchases that on second look they're not ready to commit to — without going back to the table to re-select.
Currently the modal flow forces an all-or-nothing decision: cancel → re-select → reopen. That's slow and error-prone, and users have asked for a finer-grained gate.
Scope
The modal renders a per-purchase table with these columns (all already available on LocalRecommendation and the fan-out bucket structure in frontend/src/recommendations.ts):
| Column |
Source |
| ☑ Include |
new — per-row checkbox, all checked by default |
| Account |
account_name / account_id |
| Region |
region |
| Service / Type |
provider-specific (instance type, RDS class, OpenSearch, etc.) |
| Term |
term |
| Payment |
payment |
| Count |
scaled count from the fan-out bucket |
| Upfront |
upfront_cost * count |
| Monthly cost |
monthly_cost * count |
| Effective monthly savings |
effectiveMonthlySavings(rec) * count |
| Effective % |
effectiveSavingsPct(rec) |
Plus a totals row that updates live as checkboxes toggle:
- Total selected purchases (count of checked rows × per-row count)
- Total upfront
- Total monthly cost
- Total effective monthly savings
- Average effective % (weighted by on-demand monthly)
UX requirements:
- "Select all / Deselect all" checkbox in the header.
- Disabling all rows disables the Execute Purchase primary button (with a tooltip "Select at least one purchase").
- Modal is scrollable when the list is long (current modal grows past the viewport on big batches — fix as part of this).
- Closing the modal still clears state via
clearPurchaseModalRecommendations() (recommendations.ts L77).
- The submitted set respects the unchecked exclusions — only checked rows are sent to the existing execute-purchase code path.
Implementation pointers
- Modal markup is hand-rolled in
frontend/src/recommendations.ts (search 'purchase-modal', ~L2177 / ~L2674) and the close path is in frontend/src/plans.ts (~L981).
- The existing
recommendations array already holds all the data needed; just render an additional layer.
- Live totals: derive from the same
effectiveMonthlySavings / effectiveSavingsPct helpers (L382 / L402) that the table uses, so the modal can't drift from the table's semantics.
- The fan-out bucket structure (
getFanOutBuckets) already groups purchases by configuration and carries count — reuse it for the per-config grouping rather than re-grouping from scratch.
Out of scope
- Inline editing of count / payment / term in the modal (that's a bigger UX change; file separately if wanted).
- Provider-specific deep-link previews (already covered by
purchases-deeplink.ts).
- Saved selections across sessions.
Acceptance criteria
Summary
Expand the Purchase modal (the "Execute Purchase" confirmation that appears after selecting recommendations on the Recommendations page) to show full per-purchase detail — costs, savings, configuration, count — with per-row checkboxes so the user can opt out of individual purchases without leaving the modal.
Why
Today the modal confirms the selected purchases at a fairly summarized level. When a user has selected, say, 12 RIs across 4 cells (instance type × region × payment), they typically want to:
Currently the modal flow forces an all-or-nothing decision: cancel → re-select → reopen. That's slow and error-prone, and users have asked for a finer-grained gate.
Scope
The modal renders a per-purchase table with these columns (all already available on
LocalRecommendationand the fan-out bucket structure infrontend/src/recommendations.ts):account_name/account_idregiontermpaymentcountfrom the fan-out bucketupfront_cost * countmonthly_cost * counteffectiveMonthlySavings(rec) * counteffectiveSavingsPct(rec)Plus a totals row that updates live as checkboxes toggle:
UX requirements:
clearPurchaseModalRecommendations()(recommendations.tsL77).Implementation pointers
frontend/src/recommendations.ts(search'purchase-modal', ~L2177 / ~L2674) and the close path is infrontend/src/plans.ts(~L981).recommendationsarray already holds all the data needed; just render an additional layer.effectiveMonthlySavings/effectiveSavingsPcthelpers (L382 / L402) that the table uses, so the modal can't drift from the table's semantics.getFanOutBuckets) already groups purchases by configuration and carriescount— reuse it for the per-config grouping rather than re-grouping from scratch.Out of scope
purchases-deeplink.ts).Acceptance criteria