Skip to content

Release 0.77.12 - #3835

Merged
odlbot merged 5 commits into
releasefrom
release-candidate
Aug 26, 2026
Merged

odlbot merged 5 commits into
releasefrom
release-candidate

Conversation

@odlbot

@odlbot odlbot commented Aug 25, 2026 •

Copy link
Copy Markdown
Contributor

zawan-ila

Chris Chudzicki

Tobias Macey

blarghmatey and others added 5 commits August 25, 2026 10:23
* Add the content-engagement panel to the B2B analytics dashboard

The fifth org-scoped endpoint had a query factory, types and a factory but
no UI, so `mv_b2b_content_engagement_depth` was reaching the browser and
being dropped on the floor.

It is a table rather than a chart. The view mixes counts of people with
counts of events that run orders of magnitude larger, which is the same
reason those event totals were kept off the trend chart — plotting them
together needs a dual axis, and a dual axis invites a comparison the scales
do not support.

Eleven metrics will not fit as eleven columns at this table's density, so
each activity column leads with the per-engaged-learner rate (comparable
across course runs of different sizes) and prints the raw total under it.
Both halves are suppressed independently by the k-anonymity floor, so both
go through SuppressibleValue.

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

* Say "per engaged learner" where the average is over engaged learners

Read mv_b2b_content_engagement_depth.sql rather than inferring the
denominators from the test factory. The two rate families do not share a
base: engagement_rate_pct and chatbot_adoption_pct divide by
total_enrolled_learners, but the two averages divide by engaged_learners,
matching their field names.

"Videos per learner" therefore reads as per enrolled learner and overstates
the figure on any run where engagement is weak. The "of enrolled" copy on
the engaged cell was already correct and stands.

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

* Show the cohort behind each activity total

ol-analytics-api #33 returns video_watchers and problem_attempters, the
distinct learners behind total_videos_watched and total_problems_attempted.
The type mirrors that model column for column, so it gains both.

They are worth rendering, not just carrying. A bare activity total invites
the reader to divide it by the learners they can see, and that is the wrong
denominator: only the learners who did that particular thing contributed,
which is narrower than engaged_learners. It is also the cohort the anonymity
floor is applied to, so showing it is what makes a suppressed total legible
rather than arbitrary. The chatbot cell already read this way; videos and
problems now match it.

Below md the label moves above its value. The shared cell layout sets them
side by side, which a label as long as "Problems per engaged learner" plus
"96 learners, 9,134 attempted" broke across four lines at 390px.

The suppression test now models how the API actually withholds — a sub-floor
cohort takes its total and its average with it, while a healthier activity
in the same row keeps all three figures — rather than an arbitrary mix of
nulls.

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

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
The app shipped no resource hints, so three cross-origin hosts were
discovered far too late to open their own connections:

- api.learn.mit.edu: the first API call is not issued until React
  hydrates (~1.2s into a production load), and then waits ~180ms for
  DNS + TCP + TLS while every sibling API request stalls behind it.
- p.typekit.net: not named anywhere in the document. It is only
  discovered once use.typekit.net's stylesheet downloads and parses.
- use.typekit.net: the stylesheet's connection is not reused for font
  fetches, so the font files paid a fresh TCP + TLS of their own.

Each hint's crossOrigin has to match how that resource is actually
fetched, because socket pools do not mix across CORS and credentials
modes: absent for p.typekit.net's non-CORS stylesheet, anonymous for the
CORS-without-credentials font files, and use-credentials for the API,
which axios calls with withCredentials. A mismatch is silent -- the
socket opens, nothing draws from it, and the request pays the full
handshake anyway.

Verified in Chrome 151 by reading netlog socket groups rather than
request timing, since a preconnect is not reliably observable from
timing (RTT variance swamps the effect). Against a same-site origin the
API request reuses the preconnected socket -- one connect job, no second
handshake -- and its pre-send stall drops from ~180ms to ~1ms.

Two cases are partial, both inherent rather than fixable here. React
hoists stylesheets above other head links, so use.typekit.net's
stylesheet request beats its own hint and only the font files benefit.
And for users who block third-party cookies, p.typekit.net's stylesheet
lands in a partitioned socket pool that no crossOrigin value targets,
leaving the DNS lookup as the only saving there.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
@odlbot
odlbot requested a review from a team as a code owner August 25, 2026 16:33
@github-actions

Copy link
Copy Markdown

OpenAPI Changes

21 changes: 0 error, 0 warning, 21 info

View full changelog

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

@odlbot
odlbot merged commit 90a3f89 into release Aug 26, 2026
17 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