You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Part of #147 (parity study 2026-09-11 — content correctness).
Revised in place 2026-09-11 after the maintainer review. The fix no longer lands in the theme: the lecture sources drop their {raw} blocks at cutover, and the notebook header moves into the ipynb export (QuantEcon/mystmd#108). The original theme-renderer options are kept below under "Options considered". The revision is recorded in the comments.
Problem
Most QuantEcon lecture sources open with a notebook-only logo header, <div id="qe-notebook-header">, in a {raw} jupyter or a {raw} html block. On mystmd neither form is dropped or rendered as HTML. The block's source appears as escaped text in a paragraph directly under the page title, so markup is the first thing a reader sees. Under Sphinx the {raw} jupyter form never reached the HTML page, and the book theme hid the {raw} html form with #qe-notebook-header { display: none; } (_quantecon-defaults.scss#L9-L11).
Both forms show today (checked 2026-09-11). Each page below serves <p><div id="qe-notebook-header" ... immediately after its <h1>:
This repo's PR previews show the same text. They build lecture-python-programming's main branch with stock mystmd (preview.yml#L62-L88); an example is the PR #200 preview's /numpy.
The notebooks are broken too. The QuantEcon mystmd fork's ipynb export drops both forms and logs Unsupported node type: raw for each, so notebooks built on the fork have no header (QuantEcon/mystmd#108).
Nothing renders raw nodes. The theme's renderer overrides cover only list and output (app/renderers.tsx), and myst-to-react 1.3.0 has no raw entry. The default renderer wraps the escaped text in a <div>.
A CSS rule alone cannot hide it: the page has no #qe-notebook-header element, only a <p> holding the escaped source.
What the lecture sources contain
Every {raw} block in the Markdown sources of 18 lecture repositories, counted on each default branch on 2026-09-11. 549 of the 554 blocks are the same header; no source uses a raw role.
Repository
Commit
Header, {raw} jupyter
Header, {raw} html
Other {raw} blocks
lecture-python.myst
3b5fde64
107
2
1 (opt_transport.md table)
lecture-python.zh-cn
3f423564
107
2
1 (opt_transport.md table)
lecture-python-advanced.myst
7f42c5e2
3
52
0
lecture-dp
da83d88d
21
19
0
lecture-python-programming
5e574c17
23
0
0
lecture-python-programming.fa, .fr, .zh-cn
1ef279e7, f0f6ce0a, 90336d28
23 each
0
0
lecture-python-programming.ml
4a33f615
2
0
0
lecture-julia.myst
18877b5d
0
60
0
lecture-dle
975b7a43
0
25
0
lecture-tools-techniques
e1d01560
0
14
1 (opt_transport.md table)
lecture-eqm
5c574371
0
12
0
lecture-stats
54114b26
0
11
0
lecture-python-intro
c8b8a71c
2
6
1 (simple_linear_regression.md iframe)
lecture-intro.zh-cn
156fe455
2
6
1 (simple_linear_regression.md iframe)
lecture-jax
24ef0c8a
0
3
0
lecture-wasm
149182bd
0
1
0
Total
336
213
5
What each kind of block does under Sphinx today:
Block
Web page
Published notebook
Header, {raw} jupyter
not emitted
the logo cell (cell 0 of lecture-python-programming.notebooks numpy.ipynb, cell 1 of lecture-python.notebooks kalman.ipynb)
absent (lecture-python.notebooks and lecture-tools-techniques.notebooks opt_transport.ipynb)
Our World in Data <iframe>
an embedded chart
absent; the cell keeps only the link (lecture-python-intro.notebooks simple_linear_regression.ipynb)
The {raw} html header does nothing in either output: its 213 blocks are dead content.
Options considered
The first version of this issue proposed a theme-side fix. The review weighed four approaches:
Option
What it does
Why it was not chosen
A, broad (theme)
Render nothing for raw nodes other than latex, tex and text
Hides the web symptom only. It also deletes the opt_transport.md table, and leaves the notebooks without a header.
A, narrow (theme)
Render nothing for raw nodes containing qe-notebook-header
Hides the header only. The table still shows as literal markup, and the notebooks still have no header.
B, pass through (theme)
Drop jupyter nodes, inject html nodes as HTML, and hide the header with CSS
Puts unsanitised author HTML into the server-rendered page, and still leaves the notebooks without a header.
C, format-aware {raw} in the fork
Teach mystmd {raw} jupyter (notebook only) and {raw} html
Builds permanent Sphinx compatibility into the fork for a builder name upstream does not use, to keep 549 copies of branding in lecture files.
Decision (2026-09-11)
Sources become mystmd-native; there is no Sphinx compatibility layer. The migration is all-or-nothing, so no lecture repo builds with both Sphinx and mystmd after cutover. The sources must stay valid for Sphinx until then, so the rewrite is scripted and runs at cutover.
This repo's PR preview runs the same script after myst init (preview.yml#L62-L88). Previews then show post-cutover sources, and every theme PR exercises the script.
Open: where the rewrite script lives. The proposal is scripts/ in this repo, because preview.yml is its first user; it moves to the lecture migration tooling if that has a home before cutover.
The rewrite script deletes both header forms, converts the two iframes to {iframe} and the three tables to HTML in the Markdown, leaves a file unchanged on a second run, and exits non-zero naming any other {raw} block.
The PR preview workflow runs the script, and a PR preview's /need-for-speed and /numpy show no header text.
Scope history: the first version of this issue counted 456 header files in 15 repositories (122 {raw} html, 334 {raw} jupyter). The revision's audit covers 18 repositories, adding lecture-julia.myst, lecture-dle and lecture-intro.zh-cn, and counts blocks: 549 headers. The study's addendum had once limited the defect to the {raw} html form, from a preview URL that returned 404; both forms leak.
Part of #147 (parity study 2026-09-11 — content correctness).
Revised in place 2026-09-11 after the maintainer review. The fix no longer lands in the theme: the lecture sources drop their
{raw}blocks at cutover, and the notebook header moves into the ipynb export (QuantEcon/mystmd#108). The original theme-renderer options are kept below under "Options considered". The revision is recorded in the comments.Problem
Most QuantEcon lecture sources open with a notebook-only logo header,
<div id="qe-notebook-header">, in a{raw} jupyteror a{raw} htmlblock. On mystmd neither form is dropped or rendered as HTML. The block's source appears as escaped text in a paragraph directly under the page title, so markup is the first thing a reader sees. Under Sphinx the{raw} jupyterform never reached the HTML page, and the book theme hid the{raw} htmlform with#qe-notebook-header { display: none; }(_quantecon-defaults.scss#L9-L11).Both forms show today (checked 2026-09-11). Each page below serves
<p><div id="qe-notebook-header" ...immediately after its<h1>:{raw} html{raw} jupyter{raw} jupyterThis repo's PR previews show the same text. They build lecture-python-programming's main branch with stock mystmd (preview.yml#L62-L88); an example is the PR #200 preview's
/numpy.The notebooks are broken too. The QuantEcon mystmd fork's ipynb export drops both forms and logs
Unsupported node type: rawfor each, so notebooks built on the fork have no header (QuantEcon/mystmd#108).Why it happens
{raw}directive becomes arawnode that keeps itslangand its source invalue(myst-directives raw.ts#L15-L29).latexandtex. Every other raw node gets aparagraph > textchild holding the source verbatim, and anylangother thantextlogsunknown format for raw content: <lang>(myst-cli transforms/raw.ts#L14-L27). Upstream jupyter-book/mystmd has the same code, and its request for raw HTML support is still open (Allow raw HTML / script / style blocks in MyST documents jupyter-book/mystmd#1040).rawnodes. The theme's renderer overrides cover onlylistandoutput(app/renderers.tsx), and myst-to-react 1.3.0 has norawentry. The default renderer wraps the escaped text in a<div>.#qe-notebook-headerelement, only a<p>holding the escaped source.What the lecture sources contain
Every
{raw}block in the Markdown sources of 18 lecture repositories, counted on each default branch on 2026-09-11. 549 of the 554 blocks are the same header; no source uses a raw role.{raw} jupyter{raw} html{raw}blocksopt_transport.mdtable)opt_transport.mdtable)opt_transport.mdtable)simple_linear_regression.mdiframe)simple_linear_regression.mdiframe)What each kind of block does under Sphinx today:
{raw} jupyternumpy.ipynb, cell 1 of lecture-python.notebookskalman.ipynb){raw} htmlarma.ipynb,lqramsey.ipynb)opt_transport.mdfactory tableopt_transport.ipynb)<iframe>simple_linear_regression.ipynb)The
{raw} htmlheader does nothing in either output: its 213 blocks are dead content.Options considered
The first version of this issue proposed a theme-side fix. The review weighed four approaches:
latex,texandtextopt_transport.mdtable, and leaves the notebooks without a header.qe-notebook-headerjupyternodes, injecthtmlnodes as HTML, and hide the header with CSS{raw}in the fork{raw} jupyter(notebook only) and{raw} htmlDecision (2026-09-11)
Sources become mystmd-native; there is no Sphinx compatibility layer. The migration is all-or-nothing, so no lecture repo builds with both Sphinx and mystmd after cutover. The sources must stay valid for Sphinx until then, so the rewrite is scripted and runs at cutover.
The notebook header is generated by the ipynb export from a project setting,
settings.myst_to_ipynb.header, in ipynb export has no project-level notebook header, so the QuantEcon logo is copied into 549 lecture source files as raw blocks mystmd#108. That makes one edit point per repository, or one across all repositories through a shared config pulled in withextends:. Nothing about the header reaches the web page, so the theme needs no renderer and no CSS rule.A rewrite script removes every
{raw}block from the lecture sources:{iframe}directivemystv1.10.1 (qe-v10): renders aniframenodeopt_transport.mdtable (3){raw} htmlfence and keep the<table>as HTML in the Markdownmystv1.10.1 (qe-v10): mystmd's HTML transform gives a real table withcolspan: 3androwspan: 2{raw}blockSites already building on mystmd take the rewrite now: lecture-wasm (one header,
short_path.md, reported on short-path renders its qe-notebook-header raw HTML block as literal visible text lecture-wasm#66) and lecture-python-programming's jb2 branch ([Experimental] Build the lectures with QuantEcon/mystmd instead of jupyter-book lecture-python-programming#363).This repo's PR preview runs the same script after
myst init(preview.yml#L62-L88). Previews then show post-cutover sources, and every theme PR exercises the script.General output targeting belongs elsewhere. The design for content targeted by output (Sphinx
{only}style: one way to mark a node's outputs, applied where each output's pipeline splits off) is on Sphinx {only} blocks render as red Unknown Directive callouts because mystmd has no {only} directive mystmd#104, with upstream Tag content for optional inclusion in build outputs jupyter-book/mystmd#1280.Open: where the rewrite script lives. The proposal is
scripts/in this repo, because preview.yml is its first user; it moves to the lecture migration tooling if that has a home before cutover.Work items
myst_to_ipynb.headersetting in the ipynb exportshort_path.mdmyst.ymlAcceptance criteria
{iframe}and the three tables to HTML in the Markdown, leaves a file unchanged on a second run, and exits non-zero naming any other{raw}block./need-for-speedand/numpyshow no header text./need-for-speedshows no header text.settings.myst_to_ipynb.header.Export gaps found while testing
The test build for the decision also showed three ipynb export gaps in the fork, now filed:
colspan/rowspanrenders correctly on the site, but is written into the notebook as a pipe table, so its columns misalign. After the rewrite, theopt_transportnotebooks would carry that misaligned table where Sphinx shipped none.markdown: commonmark, an{iframe}is written as MyST directive syntax, which Jupyter and Colab show as a literal code block. An earlier version of this section said the iframe was dropped; it is not. Sphinx's notebook kept only a link.rawnode is dropped from the notebook, with anUnsupported node type: rawerror that--strictdoes not count. The rewrite removes every raw node from the lecture sources, so this matters mainly to other projects.Context
{raw} html, 334{raw} jupyter). The revision's audit covers 18 repositories, adding lecture-julia.myst, lecture-dle and lecture-intro.zh-cn, and counts blocks: 549 headers. The study's addendum had once limited the defect to the{raw} htmlform, from a preview URL that returned 404; both forms leak.