Skip to content

feat: restore the Sphinx lecture heading typography (PT Serif + scale) - #166

Merged
mmcky merged 10 commits into
mainfrom
feature/heading-font-pt-serif
Sep 5, 2026
Merged

feat: restore the Sphinx lecture heading typography (PT Serif + scale)#166
mmcky merged 10 commits into
mainfrom
feature/heading-font-pt-serif

Conversation

@DrDrij

@DrDrij DrDrij commented Sep 4, 2026

Copy link
Copy Markdown
Member

Summary

Restores the heading typography of the Sphinx lecture builds (e.g. python-programming.quantecon.org), which this theme had left at the Tailwind typography plugin's defaults.

Heading face — the Sphinx builds set h1,h2,h3 in "PT Serif", serif and keep h4/h5 in the sans face; the same split now applies here via styles/quantecon.css. The face is self-hosted through @fontsource/pt-serif (400/700, upright + italic), routed through the same Remix/esbuild pipeline as Source Sans 3 — no Google Fonts request. Family only: the plugin's heading weights resolve against PT Serif's 400+700 faces to the same 700 the Sphinx build renders (it asks for 900, which also resolves to 700).

Heading scale — the Sphinx builds size content headings at h1 2em of the body, then h2 1.7rem / h3 1.4rem / h4 1.2rem / h5 1rem of an 18px desktop root. That root matches the 18px this theme sets on .article, so each value carries over as the same number in em, keeping the scale tied to the content size and the font-scale controls. h5 needs no rule — the plugin already leaves it at 1em.

Line-height and h4/h5 weight — measured computed on the live Sphinx build, every heading level renders at line-height 1.15× its font-size, replacing the plugin's per-level values. h4/h5 also take the Sphinx build's weight 900: unlike h1–h3, where any heavy weight lands on PT Serif's single 700 face, the sans is a variable font with a real weight axis, and the plugin's 600/400 drew visibly lighter.

All of it is mirrored in CRITICAL_CSS so the first WebKit paint on static-build navigations is already serif at the final sizes. The sizes matter there beyond reflow: the UA default for an h1 inside <article> is 1.5em, so the page title would otherwise paint visibly small for a frame. Error pages get the face too — their h1 renders inside .article.

Testing

  • Verified on both shipping paths — myst start against the theme build and myst build --html served statically: computed font-family is "PT Serif", serif on h1–h3, document.fonts confirms the 700 face loads, and no asset request fails (the built CSS references the fonts stylesheet-relatively).
  • Computed sizes on the visual fixture are 36 / 30.6 / 25.2 / 21.6 / 18px across h1–h5, with line-heights 41.4 / 35.19 / 28.98 / 24.84 / 20.7px and h4/h5 at weight 900 — matching the live-site measurements exactly (h3–h5 probed via injected elements; the fixture has no such headings).
  • Darwin visual baselines refreshed and the suite re-run in verify mode: 19 passed, 3 skipped, including the WebKit FOUC-guard tests that exercise the new critical CSS.
  • npm run compile clean.

Known follow-up

  • Linux visual baselines need /update-snapshots once this PR is open.

The Sphinx builds (python-programming.quantecon.org) set h1-h3 in
"PT Serif", serif; h4/h5 stay in the sans face there, so the same split
applies here. Family only: the typography plugin's heading weights
(800/700/600) resolve against PT Serif's 400+700 faces to the same 700
the Sphinx build renders.

The face is self-hosted via @fontsource/pt-serif through the same
Remix/esbuild pipeline as Source Sans 3, and mirrored in CRITICAL_CSS so
the first WebKit paint on static-build navigations is already serif.

Verified on both shipping paths (myst start against the theme build, and
myst build --html served statically): computed heading font-family is
"PT Serif", document.fonts confirms the 700 face loads, and no asset
request fails.
The Sphinx builds size content headings at h1 2em of the body, then
h2 1.7rem / h3 1.4rem / h4 1.2rem / h5 1rem of an 18px desktop root --
the same 18px this theme sets on .article, so each value carries over
as the same number in em and the scale keeps tracking the content size.
h5 needs no rule: the typography plugin already leaves it at 1em.

Mirrored in CRITICAL_CSS: beyond avoiding a reflow when the bundle
lands, the UA default for an h1 inside <article> is 1.5em, so the first
WebKit paint would otherwise draw it visibly small.

Verified against the theme build on the visual fixture: computed sizes
are 36 / 30.6 / 25.2 / 21.6 / 18px across h1-h5 (h3-h5 probed via
injected elements; the fixture has no such headings), families
unchanged (h1-h3 PT Serif, h4-h5 sans).
Measured computed on the live Sphinx build: every heading level renders
at line-height 1.15x its font-size, replacing the plugin's per-level
values (1.11/1.33/1.6/1.5, and prose's 1.75 on the unstyled h5).

h4/h5 also take the Sphinx build's font-weight 900. Unlike h1-h3, where
any weight from 600 up resolves to PT Serif's single 700 face and the
plugin's values already render identically, the sans is a variable font
with a real weight axis -- the plugin's 600 (h4) and unstyled 400 (h5)
drew visibly lighter.

Both mirrored in CRITICAL_CSS. Verified against the theme build on the
visual fixture: computed line-heights are 41.4/35.19/28.98/24.84/20.7px
across h1-h5 and h4/h5 report weight 900, matching the live-site
measurements exactly.
Every page snapshot moves with the PT Serif face and the new heading
scale, line-height and h4/h5 weight. The linux baselines are refreshed
in CI via the /update-snapshots workflow once a PR is open.
@github-actions

github-actions Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor
PR Preview Action v1.8.1
Preview removed because the pull request was closed.
2026-09-05 01:30 UTC

@github-actions

github-actions Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

🎭 Visual regression results

passed  19 passed
skipped  5 skipped

Details

stats  24 tests across 1 suite
duration  40.6 seconds
commit  46b9fc3

Skipped tests

mobile-chrome › theme.spec.ts › QuantEcon theme — visual regression › without JavaScript › drawer-opens-without-javascript
mobile-chrome › theme.spec.ts › QuantEcon theme — visual regression › drawer-closes-when-search-opens
mobile-chrome › theme.spec.ts › QuantEcon theme — visual regression › launch-colab
mobile-chrome › theme.spec.ts › QuantEcon theme — visual regression › live-compute-toggle
mobile-chrome › theme.spec.ts › QuantEcon theme — visual regression › live-compute-toggle-absent-without-thebe

@DrDrij

DrDrij commented Sep 4, 2026

Copy link
Copy Markdown
Member Author

/update-snapshots

@github-actions

github-actions Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

🎭 Refreshed visual baselines in 626ace8:

  • tests/visual/snapshots/desktop-chrome-linux/features.png
  • tests/visual/snapshots/desktop-chrome-linux/history-open.png
  • tests/visual/snapshots/desktop-chrome-linux/intro.png
  • tests/visual/snapshots/desktop-chrome-linux/launch-open.png
  • tests/visual/snapshots/desktop-chrome-linux/lists.png
  • tests/visual/snapshots/desktop-chrome-linux/notebook.png
  • tests/visual/snapshots/desktop-chrome-linux/sidebar-open.png
  • tests/visual/snapshots/mobile-chrome-linux/features.png
  • tests/visual/snapshots/mobile-chrome-linux/history-open.png
  • tests/visual/snapshots/mobile-chrome-linux/intro.png
  • tests/visual/snapshots/mobile-chrome-linux/lists.png
  • tests/visual/snapshots/mobile-chrome-linux/notebook.png
  • tests/visual/snapshots/mobile-chrome-linux/sidebar-open.png

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The change omits the required ## [Unreleased] CHANGELOG.md entry mandated by CONTRIBUTING.md for feature and dependency changes.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

This PR restores the heading typography of QuantEcon's Sphinx lecture builds in the MyST theme, which previously fell back to the Tailwind typography plugin's defaults. It self-hosts the PT Serif face (via @fontsource/pt-serif, routed through the existing Remix/esbuild pipeline like Source Sans 3), applies it to .article h1/h2/h3, and matches the Sphinx content heading scale, line-height, and h4/h5 weight. All heading rules are mirrored into CRITICAL_CSS so the first WebKit paint on static-build navigations already renders serif at the final sizes, avoiding a small/reflowing flash.

Changes:

  • Add self-hosted PT Serif (@fontsource/pt-serif, 400/700 upright+italic) and a PTSerifCSS link export, loaded on the root route so error pages get it too.
  • Add heading face/scale/line-height/weight rules to styles/quantecon.css (@layer base), documented with specificity reasoning.
  • Mirror the heading rules into CRITICAL_CSS in app/root.tsx, with the sync-source comment extended.
File summaries
File Description
styles/quantecon.css Adds .article heading font-family, font-size scale, line-height, and h4/h5 weight rules matched to the Sphinx build.
app/root.tsx Imports PTSerifCSS, adds heading rules to CRITICAL_CSS, loads PT Serif on the root route, and updates the sync-source comment.
app/links.ts Adds the PTSerifCSS export importing the four @fontsource/pt-serif weight/style stylesheets.
package.json Adds the @fontsource/pt-serif dependency.
package-lock.json Locks @fontsource/pt-serif@5.3.0 (and reconciles the pre-existing prettier spec).
Review details
  • Files reviewed: 4/30 changed files
  • Comments generated: 1
  • Review effort level: Balanced

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread package.json
mmcky and others added 3 commits September 5, 2026 11:10
…pt-serif

# Conflicts:
#	package-lock.json
#	package.json
#	tests/visual/__snapshots__/desktop-chrome-darwin/features.png
#	tests/visual/__snapshots__/desktop-chrome-darwin/history-open.png
#	tests/visual/__snapshots__/desktop-chrome-darwin/intro.png
#	tests/visual/__snapshots__/desktop-chrome-darwin/lists.png
#	tests/visual/__snapshots__/desktop-chrome-darwin/notebook.png
#	tests/visual/__snapshots__/desktop-chrome-darwin/sidebar-open.png
#	tests/visual/__snapshots__/desktop-chrome-linux/features.png
#	tests/visual/__snapshots__/desktop-chrome-linux/history-open.png
#	tests/visual/__snapshots__/desktop-chrome-linux/intro.png
#	tests/visual/__snapshots__/desktop-chrome-linux/launch-open.png
#	tests/visual/__snapshots__/desktop-chrome-linux/lists.png
#	tests/visual/__snapshots__/desktop-chrome-linux/notebook.png
#	tests/visual/__snapshots__/desktop-chrome-linux/sidebar-open.png
#	tests/visual/__snapshots__/mobile-chrome-darwin/features.png
#	tests/visual/__snapshots__/mobile-chrome-darwin/history-open.png
#	tests/visual/__snapshots__/mobile-chrome-darwin/intro.png
#	tests/visual/__snapshots__/mobile-chrome-darwin/lists.png
#	tests/visual/__snapshots__/mobile-chrome-darwin/notebook.png
#	tests/visual/__snapshots__/mobile-chrome-linux/features.png
#	tests/visual/__snapshots__/mobile-chrome-linux/history-open.png
#	tests/visual/__snapshots__/mobile-chrome-linux/intro.png
#	tests/visual/__snapshots__/mobile-chrome-linux/lists.png
#	tests/visual/__snapshots__/mobile-chrome-linux/notebook.png
#	tests/visual/__snapshots__/mobile-chrome-linux/sidebar-open.png
…erif dependency

Filed under [Unreleased] after merging main, so it sits above the frozen
2.4.0 section. Raised by Copilot on #166.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The branch's baselines predated #165's toolbar change and conflicted with the
refresh it landed; these are re-recorded on the merged tree so both the serif
headings and the icon search trigger are the baseline state. Linux baselines
refreshed via /update-snapshots.

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

mmcky commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

/update-snapshots

@github-actions

github-actions Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

🎭 Refreshed visual baselines in 8d3ef56:

  • tests/visual/snapshots/desktop-chrome-linux/features.png
  • tests/visual/snapshots/desktop-chrome-linux/history-open.png
  • tests/visual/snapshots/desktop-chrome-linux/intro.png
  • tests/visual/snapshots/desktop-chrome-linux/launch-open.png
  • tests/visual/snapshots/desktop-chrome-linux/lists.png
  • tests/visual/snapshots/desktop-chrome-linux/notebook.png
  • tests/visual/snapshots/desktop-chrome-linux/sidebar-open.png
  • tests/visual/snapshots/mobile-chrome-linux/features.png
  • tests/visual/snapshots/mobile-chrome-linux/history-open.png
  • tests/visual/snapshots/mobile-chrome-linux/intro.png
  • tests/visual/snapshots/mobile-chrome-linux/lists.png
  • tests/visual/snapshots/mobile-chrome-linux/notebook.png
  • tests/visual/snapshots/mobile-chrome-linux/sidebar-open.png

The update-snapshots workflow pushes with GITHUB_TOKEN, which leaves the CI
run waiting for approval.
@mmcky

mmcky commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

Thanks @DrDrij, this one is very carefully measured and the preview reads exactly like the Sphinx pages. I checked the static build directly: h1–h3 compute to "PT Serif", serif at 36 / 30.6 / 25.2px with the 400 and 700 faces loading stylesheet-relatively, so the subpath deploy is fine too.

As with #165 I pushed a few commits on top to get it mergeable after #165 landed; push back on any of them.

  • dce8a61 merges main. feat: icon-only search trigger with shortcut hint moved into the modal #165's baseline refresh had put this branch into conflict on 24 snapshot images plus package.json and the lockfile, where both branches add a dependency block in the same place. The manifest keeps both; the lockfile was regenerated from the merged manifest rather than merged by hand, which also drops the unrelated prettier *latest flip.
  • 7196b92 adds the changelog entries Copilot asked for, under Changed for the typography and Dependencies for @fontsource/pt-serif. Filed after the merge so they land under [Unreleased] rather than inside the frozen 2.4.0 section.
  • 3bed067 and the bot's 8d3ef56 regenerate the darwin and linux baselines on the merged tree, so both the serif headings and the icon search trigger are the recorded state. The red X on 3bed067 is just the linux images lagging by two commits; the final head is green.

One idea for a follow-up rather than this PR: the first WebKit paint uses generic serif and PT Serif's metrics differ enough that headings reflow on swap. The metric-matched fallback trick from #155 (size-adjust on a local face) would remove that.

CI is green on the final head and Copilot's thread has a reply, so this is ready to merge from my side.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants