Skip to content

bug: Pyodide fence install= option is silently dropped when running under Zensical #100

Description

@michaelweinold

Description of the bug

When markdown-exec runs under Zensical (markdown_exec._caller == "zensical"), the install= option of pyodide fences is lost. The Zensical-mode HTML scaffold renders the root element as a bare <div class="pyodide">, while Zensical's theme runtime reads the package list from a data-install attribute on that element (parsePackages(el.dataset.install) in zensical/ui's components/content/exec/index.ts). Since the attribute is never emitted, micropip never installs anything, and code in the block that imports the requested package fails at run time in the browser. The build itself succeeds with no warnings, so the breakage is silent until someone clicks Run.

Under classic MkDocs the option works, because there the install list travels via the _script template (setupPyodide(..., install=%(install)s, ...)), which is exactly the part that is (correctly) skipped in Zensical mode. In _format_pyodide(), install is computed into the data dict, but _template contains no placeholder for it, so the value is discarded.

To Reproduce

With Zensical ≥ 0.0.47 and markdown-exec ≥ 1.12.2 installed, and the plugin enabled, add this to any page:

```pyodide install="pint"
import pint
pint.__version__
```

zensical serve, open the page, click Run. The output pane shows:

Traceback (most recent call last):
  File "<exec>", line 1, in <module>
ModuleNotFoundError: No module named 'pint'

pint was never installed: the rendered root element is <div class="pyodide"> — missing the data-install="pint" attribute that Zensical's runtime reads (el.dataset.install).

Full traceback

Full traceback

There is no build-time traceback since zensical build finishes with "No issues found". The failure appears in the block's output pane in the browser after clicking Run:

Traceback (most recent call last):
  File "<exec>", line 2, in <module>
ModuleNotFoundError: No module named 'pint'

Expected behavior

The install list reaches Zensical's runtime, matching the behavior under MkDocs: the packages given in install= are installed via micropip before the block's code runs. Concretely, the Zensical-mode template should emit the attribute the theme already looks for, e.g. <div class="pyodide" data-install="pint,other-pkg"> (zensical/ui's parsePackages expects a comma-separated list).

Environment information

  • System: Linux-7.0.11-orbstack-00360-gc9bc4d96ac70-aarch64-with-glibc2.36
  • Python: cpython 3.14.6 (/tmp/repro/.venv/bin/python)
  • Environment variables:
  • Installed packages:
    • markdown-exec v1.12.2

(Zensical 0.0.47, which bundles zensical-ui 0.0.20.)

Additional context

  • Introduced with the Zensical integration: refactor: Better integrate with Zensical #99 on this repo and feature: support markdown-exec plugin zensical/zensical#790, both merged 2026-07-05.

  • Root cause: in src/markdown_exec/_internal/formatters/pyodide.py, the Zensical branch of _format_pyodide() returns only _template % data; data["install"] is referenced solely by _script, which is skipped in Zensical mode.

  • The theme= and height= fence options are likewise ignored under Zensical (its UI applies its own Ace theme and auto-sizing). That may well be intentional as part of the native theme integration — if so, a note in the Pyodide docs page would help.

  • Workaround for affected sites: install explicitly at the top of the first block of a session — Zensical's runtime always loads micropip and executes via runPythonAsync, so top-level await works:

    import micropip
    await micropip.install("pint")

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions