Skip to content

Release 0.78.5 - #3889

Merged
odlbot merged 13 commits into
releasefrom
release-candidate
Sep 3, 2026
Merged

odlbot merged 13 commits into
releasefrom
release-candidate

Conversation

@odlbot

@odlbot odlbot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Sar

Matt Bertrand

Danielle Frappier

Ahtesham Quraish

Tobias Macey

Carey P Gumaer

Shankar Ambady

renovate[bot]

renovate Bot and others added 13 commits September 1, 2026 13:28
* Update dependency youtube-transcript-api to >=1.2.4,<1.3

* Migrate to youtube-transcript-api instance API

v1.2.0 removed the static list_transcripts/get_transcript methods that were
deprecated in v1.0.0; the instance method list() is the replacement.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018S52vCvVmYyY7Q3ji519Nn

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Tobias Macey <tmacey@mit.edu>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* hedge requests

* tests

* switch default hedging delay to 0

* change default delay

* change default delay

* Isolate and bound hedged embedding request capacity

Address review feedback that abandoned losing requests could occupy the
shared pool and make later primary requests queue behind them.

- split the primary and speculative requests into separate thread pools
- bound each pool with a semaphore so submissions are rejected rather than
  queued: a full primary pool runs the request inline, a full hedge pool
  skips the backups
- give every hedged provider call an explicit timeout so losers release
  their worker
- cover pool saturation, inline fallback, and concurrent saturation in tests

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

* fix ssl_cert_reqs

* Address PR feedback on embedding request hedging

- fail loudly with RuntimeError instead of returning None when every
  hedged request is abandoned
- drop the duplicated hedge_count/hedge_delay defaults, embed_query
  always passes them explicitly
- log when backups are fired and whether the primary or a backup won,
  so the hedge delay can be tuned from prod
- cover EMBEDDING_HEDGE_COUNT=1 (short circuits to unhedged) and =3

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

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
page-components/TiptapEditor embeds both VideoResourcePlayer and
PodcastEmbedPlayer, but both lived in app-pages, which the
import/no-restricted-paths zone in frontends/.eslintrc.js forbids
page-components from importing. eslint-plugin-import 2.29 missed it;
2.31 (in #3851) reports all four.

Moving only the two player entry points would have relocated the
violation rather than fixed it, since each pulls in siblings, so each
cluster moved with the modules it needs:

  page-components/VideoPlayer/        VideoResourcePlayer, VideoJsPlayer,
                                      YouTubeIframePlayer, videoSources
  page-components/PodcastEmbedPlayer/ PodcastEmbedPlayer, useAudioPlayer,
                                      usePlaybackRecovery, AudioPlayer.styled

Only what the players need moved. The old shared.styled.ts keeps its
seven page-chrome exports and gives up NoVideoMessage and
ScreenReaderOnly; PodcastPlayer stays on the podcast page and now imports
the hooks from page-components, which is an allowed direction.

getEpisodeAudioUrl went to common/podcasts.ts instead of either player
folder: the listing page and the embed player both need it, and common/
is already where this kind of pure helper lives. helpers.ts re-exports it
so its existing importers and tests are untouched.


Claude-Session: https://claude.ai/code/session_01FyJ72HRTyQf6jVYmw3GZSt

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
* updating serializer

* render metadata doc as context

* update tests

* truncate content to model limits

* fix and test update

* Revert "fix and test update"

This reverts commit 2964009.

* address code review comments. add checksum versioning

* make course numbers consistent markdown

* make course numbers a md list

* fixed sorting so the checksums are consistent

* adding test to lockdown the checksum diff

* fix sparse hash so rare terms are not dropped
* Add the refund request hook and its test fixtures

`useCreateRefundRequest` wraps the refund request endpoint and invalidates the
order's receipt on settle: a successful request moves the order's
`refund_status` to `requested`, and the card that renders it has to follow.

The order factory grows the three fields the receipt now returns, and defaults
to `eligible` rather than `false`. A freshly purchased order is refundable, so
tests should opt in to the states that are unusual rather than out of an
arbitrary one.

* Show the refund status on the receipt

Adds the refund card to the receipt's right-hand column, under the order
summary, where the design places it. On mobile that column already comes
first, so it needs no reordering.

The card renders whatever `refund_status` the API reports: the deadline while
the order is still eligible, a notice that the window has closed, the date a
request was made, why one was declined, or the amount that was refunded.
Orders that were never refundable — unfulfilled ones and B2B contract orders —
report `ineligible`, and the card is left out rather than shown empty.

All of that comes from the single status field. Deriving it here instead would
mean restating the refund window, the B2B rule and the duplicate-request check
that MITx Online already applies when it accepts or rejects a request, and the
two would eventually disagree.

Two labels depart from the design, both because the date underneath the status
is not the date the design assumed:

- A submitted request is labelled "Requested", not "Processed". Nothing has
  been processed yet, and telling a learner otherwise invites a support ticket.
- A declined request is also labelled "Requested". The design says "Reviewed",
  but no review timestamp is stored — only the submission date.

The button is only rendered when a handler is passed, which nothing does yet.
It arrives with the dialog it opens.

Hidden from print, like the other page actions.

* Note the pending source for the declined card's date

mitxonline 170fb603c adds refund_reviewed_on, which is the date this card
should show. The branch client predates it, so the submission date stands in
and is labelled honestly until the client is rebuilt.

* Date the declined card by when it was reviewed

The new client carries `refund_reviewed_on`, so the declined card can show
the date the request was actually decided rather than the date it was
submitted. That restores the design's "Reviewed {date}".

* Add the refund request dialog

Wires the card's Request Refund button to the dialog the design specifies,
in both its forms. Inside the refund window it collects one of seven preset
reasons; past the window there are no presets, the request goes for review
rather than straight through, and the wording says approval is not
guaranteed.

The consequences warning follows `has_free_audit`: refunding normally drops
the learner to the audit track, but a course without one leaves their
dashboard entirely.

Validation mirrors what the API accepts, so the learner is told what is
missing rather than meeting a 400: a reason inside the window, free text
whenever no preset explains the request, and consent either way.

Two things worth noting for anyone changing this later. The required asterisk
is rendered rather than set through `required`, because that marks the input
required to the browser and native validation then blocks submit before
formik reports anything. And the mutation uses `mutate` rather than
`mutateAsync`, since a rejected request is already surfaced through `isError`
and awaiting it leaves the rejection for formik to warn about.

* Keep the receipt reachable after a refund

The dashboard hid the Receipt menu item for exactly the order a learner is
most likely to go looking for: a refunded one. Two independent gates did it.

`getReceiptMenuItem` required a verified enrollment. A refund moves the learner
back to audit, so the link disappeared the moment the refund landed. Enrollment
mode was the wrong question — whether an order exists is the only thing that
matters, and the lookup below already answers it. Someone who reached the
verified track without paying has no order, so they still get no link.

`useOrderIdForResource` then matched fulfilled orders only, which a refunded
order is not. Its comment already anticipated this: "revisit when the refund
section is built".

Found by seeding a refunded order and looking at the dashboard, which is the
only place either gate is visible.

* Move "Other" to the end of the refund reasons

The design puts "Other" fourth, with three more specific reasons after it, so
both the tab order and the rendered column read as though the list restarts.
It now comes last. The options themselves are unchanged.

The radio grid also flows down its first column before starting the second, as
the design does. It was filling row by row, which paired options across a row
that have nothing to do with each other and put the list in an order that
matched neither the design nor the markup.

* Look up the receipt regardless of enrollment mode

The Receipt item was still missing on a refunded course. Removing the mode
check from `getReceiptMenuItem` was not enough: both cards decide whether to
run the lookup at all, and passed a null resource id for anything not on the
verified track. A refund returns the learner to audit, so the lookup never ran
and there was no order id to build the link from.

Both now pass the id unconditionally. Every card shares one `orders/history`
query, so this is the same single request it always was.

Enrollment cards therefore always fetch order history, which is why the
ProgramAsCourseCard suite needed the mock it had never required before.

* build(deps): pin mitxonline-api-axios to 2026.8.31-1

Carries the refund fields this branch consumes (refund_status,
refund_deadline, refund_requested_on, refund_reviewed_on, has_free_audit)
alongside hubspot_form_id from main.

* Address review feedback on the refund request dialog

- Validate and submit the free-text reason only when it is on screen, so a
  hidden field cannot block submission or send text the learner abandoned.
- Announce field errors through role="alert" and move focus to the first
  failing field on submit.
- Clear a field's error as soon as it changes, rather than leaving stale text.
- Let the browser skip its own validation UI (noValidate) while keeping
  required on the inputs it applies to.
- Cap the reason text at 1000 characters, state the limit in the help text,
  and announce the counter politely.
- Opt out of the global mutation error toast: this dialog reports a rejected
  request inline, beside the button it came from.

* Cover the refund dialog's error recovery and focus behaviour

Each test fails if its behaviour regresses: dropping the abandoned free
text, clearing one field's error without clearing the others, and moving
focus to the first field that failed rather than leaving it on submit.

* Gate the refund card behind the self-service-refunds flag

The card is the only way into the refund dialog, so gating it at the mount
point covers the whole flow. `useFeatureFlagEnabled` is undefined until the
flags arrive, which keeps the card hidden rather than showing it and then
taking it away.

* Say why the reason group's requirement rides in the label

The description sibling is not associated with the group, so a requirement
stated there would be silent to a screen reader.

* Test that refunded orders still resolve a receipt

The states this branch added to RECEIPT_STATES had no coverage: stripping
Refunded and PartiallyRefunded passed the entire suite, including all 518
CoursewareDisplay tests, so the regression could return unnoticed.

* Sum every refund on the completed card

A partially refunded order reports the same completed status, and an order
can carry several refund transactions, so naming only the first understated
what was returned and dated the card by an arbitrary one.

* Say the consent checkbox is required, and note the single-line assumption

smoot-design's Checkbox takes no required prop and forwards nothing to its
input, so the requirement goes in the label, as it does for the reason group.

The dialog reads line 0 for the audit warning and the title while refunding
the whole order. mitxonline can model several lines but no checkout has ever
produced one, so the assumption is recorded rather than handled.

* build(deps): bump smoot-design to 6.34.0

Adds the required prop on RadioChoiceField.

* Mark the reason group required with the new smoot-design prop

Replaces the "(required)" label text, which was a stand-in while
RadioChoiceField had no way to set aria-required on the group.

* Let the consent checkbox grow to fit its wrapped label

smoot-design's Checkbox fixes its container at 24px, which assumes a
single-line label. This one wraps, so the text overflowed the box and the
inline error sat too close to it.

* Confirm a refund request outright after it is submitted

The panel behind the dialog re-renders from the invalidated receipt, but a
changed panel is not an acknowledgement. A success alert says the request
landed.

Copy promises no email on purpose: the only notification mitxonline sends on
a request goes to customer service, not the learner.
* Bound Renovate to the service versions we actually run

Renovate had no ceiling on the datastore images, so it kept proposing
majors we cannot deploy: #2758 targets postgres 18 while
ol-mitlearn-db-production is on 15.17, and #2395 targets OpenSearch 3.8
while opensearch-mitlearn-producti is on OpenSearch_3.3 (both read from
the AWS APIs on 2026-08-31). The ceilings keep those out of the queue
until the managed services move first.

qdrant was on :latest, which Renovate cannot track at all. Pinning it to
the 1.18 series ties the local server to what qdrant-client ~=1.18.0
targets.

pinDigests comes from config:best-practices and regenerates a "Pin
dependencies" PR against the compose files every few months (#3809,
#2985, both closed unmerged). Turning it off for the docker-compose
manager only leaves Dockerfiles and GitHub Actions digest-pinned.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FyJ72HRTyQf6jVYmw3GZSt

* Split the postgres ceiling by manager

A single postgres rule covered both consumers: the CI service image in
.github/workflows/ci.yml (github-actions manager, currently 15) and the
local compose image in docker-compose.services.yml (docker-compose
manager, currently 16). With one <16 ceiling the compose image sat above
its own allowed range, so Renovate offered it nothing at all -- not even
16.x patches -- which is not what leaving local dev on 16 was meant to
mean.

CI keeps <16 so it tests against prod's engine. Compose gets <17, which
tracks 16.x and still keeps 17 and 18 out.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FyJ72HRTyQf6jVYmw3GZSt

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
* Take the canonical page URL from learn_url

The video, playlist, podcast and episode pages each rebuilt their canonical URL
from (id, parent, title), slugifying the title locally. They now read it from
the resource's learn_url, so the backend is the only thing that decides how a
resource's URL is spelled.

Playlists and podcasts have no parent, so they use learn_url as given. Videos
and episodes take only its slug: a video in several playlists, or an episode in
several podcasts, is legitimately viewable under any of them, so the parent
segment stays resolved against the request. Their canonical *tag* is learn_url
regardless, which points every context at one URL per resource.

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

* run linting

---------

Co-authored-by: Ahtesham Quraish <ahtesham.quraish@arbisoft.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Ahtesham Quraish <ahtesham.quraish@192.168.1.167>
…romium install (#3888)

Since ol-python-base rebased onto Docker Hardened Images (mitodl/
ol-infrastructure#5698), its libc6 carries a DHI-patched version
(...+dhiN). Installing chromium/chromium-driver from the sid apt
source added above still requires upgrading libc6 to sid's newer
build, and dpkg can no longer confirm the on-disk conffile
(/etc/ld.so.conf.d/x86_64-linux-gnu.conf) matches what the previously-
installed (patched) package shipped -- so it stops at an interactive
conffile prompt instead of upgrading silently. With no stdin in a
Concourse build, that prompt just hangs and the build fails:

  dpkg: error processing package libc6:amd64 (--configure):
   end of file on stdin at conffile prompt

--force-confnew tells dpkg to take the incoming package's version of
any ambiguous conffile rather than prompting. It does not change
which packages get installed -- the libc6 upgrade to sid was already
decided by apt's dependency resolution before this point (visible in
the build log: the new libc6 files are already unpacked when the
prompt appears) -- it only lets dpkg finish configuring the packages
it already decided to install.

Note this does mean libc6 ends up as sid's stock build rather than
DHI's hardened one for this image specifically, since chromium's own
dependency chain forces the upgrade regardless. Verified locally:
build succeeds against the current mitodl/ol-python-base:3.12, and a
headless chromium run in the resulting image renders successfully.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
@odlbot
odlbot requested a review from a team as a code owner September 2, 2026 15:51
@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown

OpenAPI Changes

No changes detected

View full changelog

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

@odlbot
odlbot merged commit 737b817 into release Sep 3, 2026
18 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.

8 participants