Found in the 2026-09 design review of the deployed theme (https://6a9b93b9108dd4beb6bebadc--epic-agnesi-957267.netlify.app, v2.5.0 / main @ 6d773b6). Nothing in this theme sets a font-size on any callout container, so an admonition, exercise, solution or prf:* body renders at .article's full 18px — exactly the size of the copy around it. The Sphinx build steps every one of them down with a single rule that is present verbatim in the stylesheet python-programming.quantecon.org serves, and which off its 18px desktop root resolves to 16.2px, so the box reads as subordinate to the prose. There is no equivalent rule in styles/quantecon.css, in @myst-theme/styles, or in the myst-to-react components.
What this is not. The same screenshot shows the inline maths inside that block reading large against the copy beside it — that ratio is KaTeX's fixed 1.21em, and shrinking the container does not change the maths-to-copy ratio at all, so it is a separate decision.
Measured
Sphinx figures were fetched and grepped directly: python-programming.quantecon.org/functions.html and its _static/styles/quantecon-book-theme.css?digest=1c349b9a0045df0ce6f4a606c2cc9441cbf3bef6. The theme's 18px is a computed font-size read from .myst-exercise on the reviewed deploy during triage; every source-side figure below I confirmed by opening the file.
| Surface |
Theme v2.5.0 |
Sphinx (≥992px) |
Difference |
| Body copy |
18px |
18px |
0% |
| Admonition / exercise / proof body |
18px |
16.2px |
+11.1% |
| Callout title |
18px |
16.2px |
+11.1% |
| Callout body ÷ body copy |
1.00 |
0.90 |
— |
The last row is the defect: the Sphinx build expresses a size hierarchy between an aside and the prose it interrupts, and this theme expresses none. The only remaining cues here are the border and a bg-gray-50/10 ground, which on a white page are faint — which is what the review screenshot shows.
The Sphinx side is one rule, quoted from the fetched stylesheet:
.admonition,div.admonition{font-size:.9rem;margin:1.5rem auto;padding:0 1rem .5rem;page-break-inside:avoid;box-shadow:0 .2rem .5rem rgba(0,0,0,.1),0 0 .05rem rgba(0,0,0,.1)}
That is .9rem off a root the same stylesheet sets to 16px, raised to 18px at @media(min-width:992px) — hence 16.2px at desktop. Every callout on the reviewed page carries the class, so exercises, solutions and dropdowns all inherit the step down: class="exercise admonition" appears 5 times, class="solution dropdown admonition" 5 times, class="dropdown admonition hint" once. Titles have no size of their own — of the ten rules in that stylesheet mentioning admonition-title, none sets font-size, and the base rule sets weight, ground, margin and padding only — so they take the container's 16.2px.
Why nothing sets it here
.article { font-size: 1.125rem; /* 18px */ } at styles/quantecon.css:314, inside the @layer components block that opens at line 289, and pinned on first paint by :where(.article){font-size:1.125rem} at app/root.tsx:125. Grepping styles/quantecon.css for admonition, exercise, myst-proof or dropdown returns exactly one hit — a comment at line 282 — and no rule at all. Nothing downstream sizes a callout either: myst-to-react 1.3.0 puts text-sm only on the dropdown chevron.
The selectors to write against
Read from myst-to-react 1.3.0 dist, which is what the deploy renders through — not guessed from the Sphinx class names.
| Directive family |
Wrapper |
Body |
Header |
Header size today |
{note} {warning} {tip} {admonition} |
.myst-admonition (admonitions.js:100) |
.myst-admonition-body (:122) |
.myst-admonition-header (:107) |
text-lg → 18px, rem-fixed |
the same with :class: simple |
.myst-admonition |
.myst-admonition-body |
.myst-admonition-header (:109) |
text-md — inherits |
{exercise} {solution} |
.myst-exercise (exercise.js:40) |
.myst-exercise-body (:66) |
.myst-exercise-header (:48) |
text-md — inherits |
{prf:*} — theorem, lemma, definition, example, remark, algorithm, … |
.myst-proof (proof.js:73) |
.myst-proof-body (:90) |
.myst-proof-header (:81) |
text-md — inherits |
{dropdown} |
.myst-dropdown (dropdown.js:10) |
.myst-dropdown-body (:10) |
.myst-dropdown-header (:10) |
text-lg → 18px, rem-fixed |
Two corrections to carry forward, because triage got both wrong. prf:* renders through proof.js as .myst-proof, not through the exercise renderer — the repo's own test asserts ol.closest(".myst-proof-body") at tests/visual/theme.spec.ts:156. And a selector on .admonition or aside.admonition, the Sphinx class names, matches nothing this theme emits.
{dropdown} is included above on this evidence: on the reviewed page Sphinx renders every dropdown as admonition-derived (solution dropdown admonition, dropdown admonition hint), already at .9rem, and both map to .myst-exercise and .myst-admonition here. What is not established is what Sphinx emits for a bare {dropdown}, because there is no instance on that page; including .myst-dropdown is the low-risk choice, leaving it out until an instance turns up is defensible.
.myst-aside is deliberately excluded: the Sphinx stylesheet has no size rule for aside.sidebar, and the margin column is a mystmd surface with no Sphinx counterpart, so there is nothing to bring to parity.
text-md does not exist, so three of the five headers silently inherit
.myst-exercise-header, .myst-proof-header and the simple-admonition header all carry text-md. That class is never generated. Tailwind's default fontSize scale has no md key (xs, sm, base, lg, xl, 2xl, …); @myst-theme/styles' themeExtensions carries no fontSize key at all, its only keys being gridTemplateColumns, gridColumn, typography, keyframes and animation; and this theme's own tailwind.config.js extend adds gridTemplateColumns, gridColumn, colors, fontFamily, keyframes and animation, also no fontSize. Confirmed against the installed package:
node -e "const t=require('@myst-theme/styles'); console.log(Object.keys(t.themeExtensions), 'fontSize' in t.themeExtensions)"
So those three headers take whatever their container gives them, while .myst-admonition-header (non-simple) and .myst-dropdown-header are pinned at text-lg — 18px, root-relative, immune to a container rule. That is invisible today because everything is 18px, and it becomes visible the moment the container rule lands: admonition and dropdown titles would hold at 18px while exercise and proof titles step down with their container, swapping one inconsistency for a new one. That is why this is two rules and not one. Sphinx renders every callout title at its container's size.
The change
Two rules in @layer components, next to the .article rule.
The container value must be root-relative — rem, not em. em off .article compounds: a {note} nested inside an {exercise}, which is common in the lecture corpus, would take the step twice. Sphinx's own value is root-relative for the same reason. This is also the precedent the file already set for inline literals at styles/quantecon.css:225-230, whose comment makes the argument explicitly:
/* Root-relative on purpose: this must NOT scale with `.article`. The
Sphinx build sizes inline code at `.9rem` off its own root (16.2px),
fixed regardless of the surrounding text -- including inside headings.
Against this theme's 16px root, `1rem` is the same 16px. `1em` would
inherit the heading scale and blow code in an h2/h3 up to match it. */
font-size: 1rem; /* 16px; the Sphinx build renders 16.2px */
The two rules, with the container size standing in at 1rem to show the shape:
.article .myst-admonition,
.article .myst-exercise,
.article .myst-proof,
.article .myst-dropdown {
font-size: 1rem;
}
/* without this, `text-lg` holds admonition and dropdown headers at 18px while
the exercise and proof headers step down with their container */
.article .myst-admonition-header,
.article .myst-dropdown-header {
font-size: inherit;
}
Specificity and purging both check out. .article .myst-admonition at (0,2,0) beats the .text-lg utility at (0,1,0) regardless of emission order. And the class names are found candidates — myst-admonition, myst-exercise, myst-proof and myst-dropdown are string literals in node_modules/myst-to-react/dist/*.js, which the Tailwind content list covers via node_modules/myst-to-react/{src,dist}/**/*.{js,ts,jsx,tsx} — so the rules survive the @layer components tree-shaking the file header warns about at styles/quantecon.css:39-44.
Knock-ons to expect. Every em inside a callout re-resolves against the container size instead of 18px, so heading sizes and figure spacing within callouts move with it. The text-lg line-height (1.75rem = 28px) survives the header override and is loose against smaller text, so the header rule may want a matching line-height.
Callout title weight is a separate divergence and is out of scope here: Sphinx sets font-weight:700 on .admonition > .admonition-title, while this theme's headers carry font-medium (500).
Baselines
Five of the six snapshot names move, across all four platform directories (desktop-chrome and mobile-chrome × -darwin and -linux).
| Snapshot |
Page |
Callouts present |
Moves? |
intro.png |
/, full page |
{note} at tests/visual/fixture/intro.md:22 |
yes |
features.png |
/features, full page |
{warning} and {tip} at tests/visual/fixture/features.md:65,69 |
yes |
history-open.png |
/features, full page |
the same two |
yes |
lists.png |
/lists, full page |
{prf:theorem} at tests/visual/fixture/lists.md:49 |
yes, and only if .myst-proof is in the selector |
sidebar-open.png |
/, viewport only |
the {note} header sits at the fold, its body below |
yes — from the header rule alone |
notebook.png |
/notebook |
none |
no |
Locally the refresh needs --update-snapshots=all: the change on lists.png in particular sits under the 1% maxDiffPixelRatio at tests/visual/theme.spec.ts:32-34, and a plain --update-snapshots will skip it silently (#113). CI's -linux set goes through an /update-snapshots PR comment.
Both open PRs that carry binaries rewrite these same files. #171 rewrites 20 PNGs — intro, features, history-open, notebook and sidebar-open across the four directories, which is every name here except lists.png. #174 rewrites 28 — the same five plus lists.png and a new rtl.png. So this change should be branched from whichever of those lands last rather than refreshing the same binaries in parallel.
No textual conflict in source. #171's styles/quantecon.css hunks are at @@ -81, @@ -98, @@ -243 and @@ -468; none reaches the .article block at 289-316, though they do shift its line numbers. #174 touches app/, styles/app.css and styles/rtl.css, no styles/quantecon.css. #175 touches app/root.tsx and tests/visual/fouc.spec.ts.
The fixture has no exercise
tests/visual/fixture/ covers {note} (intro.md:22), {warning} and {tip} (features.md:65,69) and {prf:theorem} (lists.md:49) — and contains no {exercise} or {solution} directive anywhere. So .myst-exercise, the family the review screenshot is actually of and one of the three carrying the non-existent text-md header, is untested. Adding an {exercise}, and ideally a {solution} dropdown, to features.md in the same change is cheap, and it is the difference between a baseline that proves the change and one that merely records it. Worth doing in this PR; note that #171 already edits features.md, in the Code section above the Admonitions heading.
Found in the 2026-09 design review of the deployed theme (
https://6a9b93b9108dd4beb6bebadc--epic-agnesi-957267.netlify.app, v2.5.0 /main@ 6d773b6). Nothing in this theme sets afont-sizeon any callout container, so an admonition, exercise, solution orprf:*body renders at.article's full 18px — exactly the size of the copy around it. The Sphinx build steps every one of them down with a single rule that is present verbatim in the stylesheetpython-programming.quantecon.orgserves, and which off its 18px desktop root resolves to 16.2px, so the box reads as subordinate to the prose. There is no equivalent rule instyles/quantecon.css, in@myst-theme/styles, or in themyst-to-reactcomponents.What this is not. The same screenshot shows the inline maths inside that block reading large against the copy beside it — that ratio is KaTeX's fixed
1.21em, and shrinking the container does not change the maths-to-copy ratio at all, so it is a separate decision.Measured
Sphinx figures were fetched and grepped directly:
python-programming.quantecon.org/functions.htmland its_static/styles/quantecon-book-theme.css?digest=1c349b9a0045df0ce6f4a606c2cc9441cbf3bef6. The theme's 18px is a computedfont-sizeread from.myst-exerciseon the reviewed deploy during triage; every source-side figure below I confirmed by opening the file.The last row is the defect: the Sphinx build expresses a size hierarchy between an aside and the prose it interrupts, and this theme expresses none. The only remaining cues here are the border and a
bg-gray-50/10ground, which on a white page are faint — which is what the review screenshot shows.The Sphinx side is one rule, quoted from the fetched stylesheet:
That is
.9remoff a root the same stylesheet sets to16px, raised to18pxat@media(min-width:992px)— hence 16.2px at desktop. Every callout on the reviewed page carries the class, so exercises, solutions and dropdowns all inherit the step down:class="exercise admonition"appears 5 times,class="solution dropdown admonition"5 times,class="dropdown admonition hint"once. Titles have no size of their own — of the ten rules in that stylesheet mentioningadmonition-title, none setsfont-size, and the base rule sets weight, ground, margin and padding only — so they take the container's 16.2px.Why nothing sets it here
.article { font-size: 1.125rem; /* 18px */ }atstyles/quantecon.css:314, inside the@layer componentsblock that opens at line 289, and pinned on first paint by:where(.article){font-size:1.125rem}atapp/root.tsx:125. Greppingstyles/quantecon.cssforadmonition,exercise,myst-proofordropdownreturns exactly one hit — a comment at line 282 — and no rule at all. Nothing downstream sizes a callout either:myst-to-react1.3.0 putstext-smonly on the dropdown chevron.The selectors to write against
Read from
myst-to-react1.3.0 dist, which is what the deploy renders through — not guessed from the Sphinx class names.{note}{warning}{tip}{admonition}.myst-admonition(admonitions.js:100).myst-admonition-body(:122).myst-admonition-header(:107)text-lg→ 18px, rem-fixed:class: simple.myst-admonition.myst-admonition-body.myst-admonition-header(:109)text-md— inherits{exercise}{solution}.myst-exercise(exercise.js:40).myst-exercise-body(:66).myst-exercise-header(:48)text-md— inherits{prf:*}— theorem, lemma, definition, example, remark, algorithm, ….myst-proof(proof.js:73).myst-proof-body(:90).myst-proof-header(:81)text-md— inherits{dropdown}.myst-dropdown(dropdown.js:10).myst-dropdown-body(:10).myst-dropdown-header(:10)text-lg→ 18px, rem-fixedTwo corrections to carry forward, because triage got both wrong.
prf:*renders throughproof.jsas.myst-proof, not through the exercise renderer — the repo's own test assertsol.closest(".myst-proof-body")attests/visual/theme.spec.ts:156. And a selector on.admonitionoraside.admonition, the Sphinx class names, matches nothing this theme emits.{dropdown}is included above on this evidence: on the reviewed page Sphinx renders every dropdown as admonition-derived (solution dropdown admonition,dropdown admonition hint), already at.9rem, and both map to.myst-exerciseand.myst-admonitionhere. What is not established is what Sphinx emits for a bare{dropdown}, because there is no instance on that page; including.myst-dropdownis the low-risk choice, leaving it out until an instance turns up is defensible..myst-asideis deliberately excluded: the Sphinx stylesheet has no size rule foraside.sidebar, and the margin column is a mystmd surface with no Sphinx counterpart, so there is nothing to bring to parity.text-mddoes not exist, so three of the five headers silently inherit.myst-exercise-header,.myst-proof-headerand the simple-admonition header all carrytext-md. That class is never generated. Tailwind's defaultfontSizescale has nomdkey (xs,sm,base,lg,xl,2xl, …);@myst-theme/styles'themeExtensionscarries nofontSizekey at all, its only keys beinggridTemplateColumns,gridColumn,typography,keyframesandanimation; and this theme's owntailwind.config.jsextendaddsgridTemplateColumns,gridColumn,colors,fontFamily,keyframesandanimation, also nofontSize. Confirmed against the installed package:So those three headers take whatever their container gives them, while
.myst-admonition-header(non-simple) and.myst-dropdown-headerare pinned attext-lg— 18px, root-relative, immune to a container rule. That is invisible today because everything is 18px, and it becomes visible the moment the container rule lands: admonition and dropdown titles would hold at 18px while exercise and proof titles step down with their container, swapping one inconsistency for a new one. That is why this is two rules and not one. Sphinx renders every callout title at its container's size.The change
Two rules in
@layer components, next to the.articlerule.The container value must be root-relative —
rem, notem.emoff.articlecompounds: a{note}nested inside an{exercise}, which is common in the lecture corpus, would take the step twice. Sphinx's own value is root-relative for the same reason. This is also the precedent the file already set for inline literals atstyles/quantecon.css:225-230, whose comment makes the argument explicitly:The two rules, with the container size standing in at
1remto show the shape:Specificity and purging both check out.
.article .myst-admonitionat (0,2,0) beats the.text-lgutility at (0,1,0) regardless of emission order. And the class names are found candidates —myst-admonition,myst-exercise,myst-proofandmyst-dropdownare string literals innode_modules/myst-to-react/dist/*.js, which the Tailwindcontentlist covers vianode_modules/myst-to-react/{src,dist}/**/*.{js,ts,jsx,tsx}— so the rules survive the@layer componentstree-shaking the file header warns about atstyles/quantecon.css:39-44.Knock-ons to expect. Every
eminside a callout re-resolves against the container size instead of 18px, so heading sizes and figure spacing within callouts move with it. Thetext-lgline-height (1.75rem= 28px) survives the header override and is loose against smaller text, so the header rule may want a matchingline-height.Callout title weight is a separate divergence and is out of scope here: Sphinx sets
font-weight:700on.admonition > .admonition-title, while this theme's headers carryfont-medium(500).Baselines
Five of the six snapshot names move, across all four platform directories (
desktop-chromeandmobile-chrome×-darwinand-linux).intro.png/, full page{note}attests/visual/fixture/intro.md:22features.png/features, full page{warning}and{tip}attests/visual/fixture/features.md:65,69history-open.png/features, full pagelists.png/lists, full page{prf:theorem}attests/visual/fixture/lists.md:49.myst-proofis in the selectorsidebar-open.png/, viewport only{note}header sits at the fold, its body belownotebook.png/notebookLocally the refresh needs
--update-snapshots=all: the change onlists.pngin particular sits under the 1%maxDiffPixelRatioattests/visual/theme.spec.ts:32-34, and a plain--update-snapshotswill skip it silently (#113). CI's-linuxset goes through an/update-snapshotsPR comment.Both open PRs that carry binaries rewrite these same files. #171 rewrites 20 PNGs —
intro,features,history-open,notebookandsidebar-openacross the four directories, which is every name here exceptlists.png. #174 rewrites 28 — the same five pluslists.pngand a newrtl.png. So this change should be branched from whichever of those lands last rather than refreshing the same binaries in parallel.No textual conflict in source. #171's
styles/quantecon.csshunks are at@@ -81,@@ -98,@@ -243and@@ -468; none reaches the.articleblock at 289-316, though they do shift its line numbers. #174 touchesapp/,styles/app.cssandstyles/rtl.css, nostyles/quantecon.css. #175 touchesapp/root.tsxandtests/visual/fouc.spec.ts.The fixture has no exercise
tests/visual/fixture/covers{note}(intro.md:22),{warning}and{tip}(features.md:65,69) and{prf:theorem}(lists.md:49) — and contains no{exercise}or{solution}directive anywhere. So.myst-exercise, the family the review screenshot is actually of and one of the three carrying the non-existenttext-mdheader, is untested. Adding an{exercise}, and ideally a{solution}dropdown, tofeatures.mdin the same change is cheap, and it is the difference between a baseline that proves the change and one that merely records it. Worth doing in this PR; note that #171 already editsfeatures.md, in the Code section above the Admonitions heading.