Skip to content

Release 0.80.14 - #3974

Merged
odlbot merged 6 commits into
releasefrom
release-candidate
Sep 23, 2026
Merged

odlbot merged 6 commits into
releasefrom
release-candidate

Conversation

@odlbot

@odlbot odlbot commented Sep 22, 2026 •

Copy link
Copy Markdown
Contributor

Shankar Ambady

renovate[bot]

Zaman Afzal

Chris Chudzicki

…own (#3943)

* refactor: price every caller from user_pricing

user_flexible_price only answers whether a learner's financial assistance
is approved. user_pricing answers what checkout will charge, carries the
old answer field for field, and is the only source for the per-user
discounts hq#11787 needs to display.

The InfoBox hooks keep their gates and their behaviour: the displayed
price still ignores an approved discount, which is surfaced as text. The
dashboard enrollment dialog stops recomputing a discounted price in the
browser from the aid record alone and shows the quote, so a learner
holding an automatic or user-tied discount now sees what they will pay.
Its query loses the financial-assistance-form condition for the same
reason; without that, the quote would be ignored for most courses.

The dialog does not gate on authentication: the quote endpoint rejects
anonymous requests, and the dialog only opens from the dashboard, which
is already behind an authenticated route.

getFlexiblePriceForProduct and priceWithDiscount computed that price and
go away with it. upgradeRunUrl goes too: it had no callers and was the
last reference to the deprecated serializer's type.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* feat: quote every purchasable program and derive the applied-savings breakdown

Widen the program pricing query so it fires for any signed-in learner on a
purchasable program, not just one whose CMS page configures a financial
assistance form. A program-child purchase credit exists independently of
financial aid, so the aid form was never the right gate; it also conflated the
form URL with the product's existence, hence the explicit product guard.

The quote now yields `breakdown`: the one discount checkout would apply, split
into rows that subtract, with the discount's kind resolved from the quote
(`paid-amount-off` names a prior purchase; an id matching the flexible price is
financial aid). Nothing renders it yet.

`showsRange` collapses the advertised range to a single price once a learner has
an approved flexible price. The range exists only to advertise the aid floor, so
it is meaningless to someone already quoted against it. The CMS list-price
comparison is untouched.

The query opts out of the error boundary: the browser client throws 400/401/403,
so a stale mitxonline session would otherwise replace the whole program page.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* feat: add the applied-savings card

The paid offering box for a learner whose quote carries a discount: the
program's price, what comes off it, and what checkout charges today. It replaces
the Certificate Track card rather than decorating it, so it has no title,
subtitle or feature bullets — a learner with a real number has no use for the
advertised one. Nothing renders it yet.

The rows are a description list so each label and amount are announced as a
pair, and the deduction's sign is text ("minus") rather than colour alone.

The discount's kind decides the sub-label and the info dialog: a credit names
the purchase it came from, aid names itself, and anything else gets neither —
a sale or a personal code has no rule behind it the learner could act on.

TrackCard's shell/body pair becomes an exported CardSurface so both cards share
one definition of the shaded surface and its fill behaviour; they stretch to a
shared row height in the side-by-side layout and cannot drift apart.

ProductSummary's LearnMoreDialog is exported and its href made optional, for a
dialog whose description is the whole explanation. Extracting it instead would
put UnderlinedLink in a cycle or break AboutSection's import of it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* feat: teach the enroll area to render an applied-savings breakdown

EnrollOfferingBoxes takes an optional breakdown and, when it has one, replaces
the Certificate Track card with AppliedSavingsCard under a heading row: the
heading, the financial-aid indicator right-aligned beside it, and -- for a
prior-purchase credit alone -- a line saying the purchase is applied to the full
program price. A credit is the one discount the learner did not ask for, so it
explains itself in the row; every other discount is left to the deduction row,
which already names it. Courses pass nothing and are unaffected.

The heading is "Choose Your Path" in the paid-plus-free layout, which already
had one, and "Continue with full program" in the paid-only layout, which did
not. The paid-only layout puts its heading inside the offering cell rather than
emitting a full-row grid child: that layout is data-boxes="2", whose tablet grid
is two columns with auto-flow, so a full-row heading would push the card out of
the metadata block's row. InfoBoxGrid is untouched.

The aid indicator moves onto the heading row only when a breakdown exists, since
the card it otherwise lives in is not rendered at all. It cannot be pending
there: a breakdown only exists once the same lookup has resolved, so
FinancialAidPlaceholder stays behind in the card. FinancialAidLink moves to
EnrollAreaParts and takes its text as children, because the heading row says
"Financial aid applied" where the card says "Financial aid approved (visible at
checkout)".

Measured with the app's own font at the widths the offering cell reaches: the
heading and the aside share a line at the ~346px desktop sidebar (308px of
content in 346px) and the aside wraps to a line of its own at the ~268px tablet
cell, where an auto left margin keeps it at the right edge — `space-between`
would left-align it once alone on a line. "Continue with full program" stays on
one line down to ~180px, so it never breaks mid-phrase at any width the grid
produces.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* feat: show the program's own quote in its InfoBox

ProgramEnrollArea passes the breakdown through, so a learner holding a purchase
credit or an approved aid tier sees what checkout will charge instead of the
advertised price. It also takes compactPrice from the hook's showsRange rather
than recomputing toPriceRange(program), which would keep sizing the price slot
for a range the learner no longer sees.

No priceBlock suppression is needed: priceBlock reaches the DOM only through
CertificateTrackCard, which is not rendered when a breakdown exists.

useProgramEnrollment gains upgradeLabel, which relabels the paid action
"Upgrade to Full Program". ProgramHeaderEnrollButton does not pass it, so the
page-header CTA keeps the offering's own label; it runs its own hook with no
pricing input, and making it follow would mean flipping its label after the
quote resolves. The new label reaches fireEnrollCta verbatim, which splits
nothing: enroll_cta_clicked slices on placement, enrollmentMode, resourceType
and readableId, and carries label only for human readability.

The existing suites already assert that a learner without a discount sees
today's InfoBox — they quote every program at list price — so the new cases
cover only the deduction state.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* feat: show a course's own quote in its InfoBox too

Financial aid applies to a course exactly as it does to a program, so the
breakdown reads the same on all three product pages. Only the purchase
credit stays program-only: it is earned by buying one of the program's own
courses and is worded throughout as an upgrade to the full program, which
neither a course page nor a program presented as a course can say.

`useCourseCertificatePrice` now derives the breakdown from the quote it
already fetched, through the same helper the program hook uses, and its
query is no longer gated on the CMS financial-assistance form — an
automatic or user-tied discount prices a course that offers no aid at all.

The label that differs stays with the caller: the card takes the product
noun and names its first row "Course price" or "Program price".

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* refactor: decide each InfoBox pricing rule in one place

The paid offering box had four independent rules — a quote with a
deduction, an aid form, a purchase credit, an approved aid tier — and
several of them were decided in more than one file, or for one product
type only.

- The credit is decided once, in ProgramEnrollArea, which feeds the
  upgrade label, the heading and the note from a single boolean.
  EnrollOfferingBoxes takes plain paidHeading/paidNote props and no
  longer reads breakdown.kind; CourseEnrollArea passes neither.
- The aid indicator is one component, FinancialAidIndicator, with one
  wording in both homes. "Financial aid approved (visible at checkout)"
  could only render when nothing was visible at checkout: any approved
  tier that lowers the price produces a breakdown, which replaces the
  card that carried it.
- The course and program price hooks shared everything but how they
  found the product, so useCertificatePricing now owns the query, the
  aid object, the breakdown and the range collapse. The collapse for
  approved learners thereby applies to courses too, where it had been
  program-only. useCourseCertificatePrice is deleted.
- OfferingHeadingRow returns null when it has nothing to carry; an empty
  row was still a flex item and opened the cell's 16px gap above the box
  for a paid-only sale with no aid form.
- The (i) button gets aria-haspopup, aria-expanded and a name distinct
  from the row it explains; the popover gets an accessible name, which
  needed ol-components' Popover to accept aria-label/aria-labelledby.
- Two redundant tests dropped; one added for the empty heading row and
  one for a quote whose discount takes nothing off, both mutation-checked.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* If the user's income declaration is approved, but they don't qualify for financial aid, don't show them anything

* fix: stop caching a learner's price quote as if it were ETL content

The browser client defaults staleTime to the CDN TTL, justified for content
that changes on the ETL's cadence. A user_pricing quote is not that: a
program-child-purchase credit appears the moment the learner buys a child
course. Nothing invalidates productsKeys, so the stale quote was served until
the window elapsed. Matches baskets/queries.ts and useComplianceGate.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* docs: cite the constraint that actually makes paid-amount-off a credit

The comment justified the classification by where a program-child-purchase
discount attaches, but the code keys on discount_type. What rules out any
other pairing is mitxonline's paid_amount_off_discount_shape CheckConstraint,
so point at that instead.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* test: rename the dashboard pricing helper to setupCoursePricing

It shared a name with ProductPages/test-utils/userPricing.ts's
setupUserPricing while taking different arguments and meaning something
different, so the two read as one helper.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: open the applied-savings popover above the total it explains

Popper's default placement put it over "Today's price" and its amount at
every width, covering the one row the learner is reading. It still flips
down when there is no room above.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* test: cover the course noun on AppliedSavingsCard

productNoun="course" was only exercised indirectly through the enroll-area suites, so FULL_PRICE_LABEL.course had no direct coverage.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: Carey P Gumaer <gumaerc@mit.edu>
…ges (#3954)

Add a FAQ accordion to MIT Learn course/program product pages, driven by
author-managed FAQs from the MITx Online Wagtail CMS (mitxonline#3961).
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
* adding link to program letter in dashboard

* ensure html is sanitized for program letter

* correct lint lines

* fix v1 spec

* deleted hooks/programCertificates/index.ts and inlined the useQuery call in useProgramLetterMenuItem
@odlbot
odlbot requested a review from a team as a code owner September 22, 2026 17:52
@github-actions

Copy link
Copy Markdown

OpenAPI Changes

19 changes: 4 error, 0 warning, 15 info

View full changelog

Unexpected changes? Ensure your branch is up-to-date with main (consider rebasing).

@odlbot
odlbot merged commit b8a724a into release Sep 23, 2026
26 of 27 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants