Motivation
In-page live compute (#98) is opt-in per project via project.thebe, with JupyterLite/Pyodide as the QuantEcon default. But WASM is an optional run environment that won't be available to every lecture series — Pyodide runs pure Python plus packages compiled for it (numpy, scipy, pandas, matplotlib, sympy), while numba and JAX won't import (see the README "Live compute" caveat). Even within a compatible series, individual lectures may not run.
Goal: the live-compute Power button appears only on lectures that are certified compatible; incompatible lectures in the same project don't show it. That needs per-lecture metadata, a theme-side gate, and a testing story that certifies compatibility before the yaml entry is added.
What the MyST schema supports today (investigated 2026-08-06)
Page-level thebe: frontmatter is not supported: in myst-frontmatter v1.9.5, thebe appears in PROJECT_FRONTMATTER_KEYS only ("These keys only exist on the project", dist/project/types.js:30-39), it's absent from PAGE_FRONTMATTER_KEYS, and validatePageFrontmatter silently drops a page-level thebe: key with a warning. So per-lecture enablement can't be expressed through the standard thebe config, and shouldn't wait on an upstream schema change.
What does pass through per page is the site: frontmatter object — validated only as "an object" with arbitrary keys preserved (page/validators.js:70-74), and already used by the theme for per-page options and (in #83) the git_metadata override. That's the natural carrier.
Proposed design
1. Per-lecture metadata — one yaml entry in each certified lecture's frontmatter:
project.thebe stays as-is (it carries the kernel configuration); the page flag gates whether this lecture surfaces the control.
2. Theme gate — two pattern-consistent implementation points exist in the current code (feat/thebe-live-compute branch):
| Option |
Where |
Effect |
| Narrow |
Extend the render condition in PageContent.tsx (compute?.enabled && features.notebookCompute && article.kind === Notebook && <ComputeToolbarSlot/>) with the page flag, and mirror it on ExecuteScopeProvider's enable prop |
Hides the toolbar button and disables the execution machinery for that page; the empty:hidden header slot collapses cleanly |
| Broad |
Pass a page-frontmatter-derived optionOverrideFn to ComputeOptionsProvider in Page.tsx — a shipped-but-unused escape hatch (`(opts) => ExtendedCoreOptions |
undefined); returning undefinedflipscompute.enabled` wholesale |
Either way the new key should be documented in app/types.ts TemplateOptions alongside the launch_* keys.
3. Compatibility testing to stamp the flag — the metadata should come from testing, not guesswork. Sketch: a job in the lecture repo executes each lecture's cells under the Pyodide kernel and records pass/fail per lecture; the report drives which lectures get the yaml entry. This can start manual (run the lecture in the browser, record the result) and be automated later (Pyodide under Node, or headless jupyterlite). Certification needs re-running when a lecture's package usage changes.
Open questions
- Default when the flag is absent. Absent = today's behaviour (button shows wherever
project.thebe is set) is the least surprising and lets series adopt incrementally by adding live_compute: false to known-bad lectures; the inverse (default off, opt-in per lecture) is safer for the certification story but makes enabling a series a many-file change.
- Narrow vs broad gate — should the flag hide only the toolbar control, or also figure compute and the execution scope? Broad is probably what "not compatible" means.
- Upstream — worth proposing page-level
thebe to jupyter-book/mystmd as the first-class version of this; the site: passthrough works today without waiting on it.
References
Related decision, same investigation: relocating the Power toggle into the launch dropdown was assessed and rejected for the control itself (Radix unmounts popover content on close, so a live kernel's status/controls would be invisible whenever the popover is shut; the providers also sit outside the toolbar tree). A trigger-only "Run in browser" entry in the launch menu that boots the kernel via an event bridge — with controls staying in the header — remains feasible as a follow-up if discoverability warrants it; details on #98.
Motivation
In-page live compute (#98) is opt-in per project via
project.thebe, with JupyterLite/Pyodide as the QuantEcon default. But WASM is an optional run environment that won't be available to every lecture series — Pyodide runs pure Python plus packages compiled for it (numpy, scipy, pandas, matplotlib, sympy), while numba and JAX won't import (see the README "Live compute" caveat). Even within a compatible series, individual lectures may not run.Goal: the live-compute Power button appears only on lectures that are certified compatible; incompatible lectures in the same project don't show it. That needs per-lecture metadata, a theme-side gate, and a testing story that certifies compatibility before the yaml entry is added.
What the MyST schema supports today (investigated 2026-08-06)
Page-level
thebe:frontmatter is not supported: inmyst-frontmatterv1.9.5,thebeappears inPROJECT_FRONTMATTER_KEYSonly ("These keys only exist on the project",dist/project/types.js:30-39), it's absent fromPAGE_FRONTMATTER_KEYS, andvalidatePageFrontmattersilently drops a page-levelthebe:key with a warning. So per-lecture enablement can't be expressed through the standardthebeconfig, and shouldn't wait on an upstream schema change.What does pass through per page is the
site:frontmatter object — validated only as "an object" with arbitrary keys preserved (page/validators.js:70-74), and already used by the theme for per-page options and (in #83) thegit_metadataoverride. That's the natural carrier.Proposed design
1. Per-lecture metadata — one yaml entry in each certified lecture's frontmatter:
project.thebestays as-is (it carries the kernel configuration); the page flag gates whether this lecture surfaces the control.2. Theme gate — two pattern-consistent implementation points exist in the current code (
feat/thebe-live-computebranch):PageContent.tsx(compute?.enabled && features.notebookCompute && article.kind === Notebook && <ComputeToolbarSlot/>) with the page flag, and mirror it onExecuteScopeProvider'senablepropempty:hiddenheader slot collapses cleanlyoptionOverrideFntoComputeOptionsProviderinPage.tsx— a shipped-but-unused escape hatch (`(opts) => ExtendedCoreOptions); returningundefinedflipscompute.enabled` wholesaleEither way the new key should be documented in
app/types.tsTemplateOptionsalongside thelaunch_*keys.3. Compatibility testing to stamp the flag — the metadata should come from testing, not guesswork. Sketch: a job in the lecture repo executes each lecture's cells under the Pyodide kernel and records pass/fail per lecture; the report drives which lectures get the yaml entry. This can start manual (run the lecture in the browser, record the result) and be automated later (Pyodide under Node, or headless jupyterlite). Certification needs re-running when a lecture's package usage changes.
Open questions
project.thebeis set) is the least surprising and lets series adopt incrementally by addinglive_compute: falseto known-bad lectures; the inverse (default off, opt-in per lecture) is safer for the certification story but makes enabling a series a many-file change.thebetojupyter-book/mystmdas the first-class version of this; thesite:passthrough works today without waiting on it.References
site:page-frontmatter passthrough precedent (git_metadataoverride)Related decision, same investigation: relocating the Power toggle into the launch dropdown was assessed and rejected for the control itself (Radix unmounts popover content on close, so a live kernel's status/controls would be invisible whenever the popover is shut; the providers also sit outside the toolbar tree). A trigger-only "Run in browser" entry in the launch menu that boots the kernel via an event bridge — with controls staying in the header — remains feasible as a follow-up if discoverability warrants it; details on #98.