Skip to content

Notebook output plots sit flush left in the content column, where the Sphinx lecture sites centre them #206

Description

@mmcky

Part of #147 (parity study 2026-09-11 — content correctness).

Problem

Plots produced by a lecture's code cells sit at the left edge of the content column, with all the spare width on their right. The Sphinx lecture sites centre the same plots. The lecture-python-programming deploy preview is built on v2.7.0. On its /matplotlib page at 1440px, each 568px-wide matplotlib image sits flush left in its 800px container: 0px gap on the left, 232px on the right (verified 2026-09-11). That page has 16 image outputs, all rendered with the same markup.

The same code path renders every code-cell output whose MIME bundle holds only PNG, GIF, JPEG or BMP data plus text/plain, which is what an inline matplotlib figure stores. It applies whenever live compute is not running. Every such plot narrower than the column will therefore sit flush left on the lecture sites that move to this theme.

The server-rendered markup on that page is three nested elements: div[data-name="outputs-container"] carrying text-left, then div[data-name="safe-output-image"], then a bare <img> with no class or style. Each layer comes from upstream or the CLI, and the theme changes none of them:

Layer Effect on an output image Evidence
Tailwind preflight Every img is display: block with max-width: 100%, and preflight gives it no side margins tailwindcss 3.4.19, src/css/preflight.css lines 361-371 and 377-381
Outputs container text-left unless the outputs node has an align @myst-theme/jupyter 1.3.0, dist/outputs.js lines 24 and 41-46
Output router While live compute is not running, an output whose MIME types are all text/plain, PNG, GIF, JPEG or BMP goes to SafeOutput @myst-theme/jupyter 1.3.0, dist/output.js lines 17-40 and 70-72
SafeOutput A bare <img> with only src and alt, inside <div data-name="safe-output-image"> @myst-theme/jupyter 1.3.0, dist/safe.js lines 39-42 and 55-56
mystmd fork Only two places build an outputs node, and neither sets align, so the container gets text-left code.ts L257-L261, notebook.ts L192-L200
This theme Its only output override wraps stderr streams and passes every other output to upstream. Its only img rule targets the footer licence badge. app/renderers.tsx L85-L103, styles/quantecon.css L483-L488

Changing the container's text alignment would not help, because text-align does not move a block-level element. Markdown and {figure} images are already centred: myst-to-react defaults their align to center and applies it as auto side margins (myst-to-react 1.3.0, dist/image.js lines 23-34 and 40-44). Output images never go through that renderer.

The book theme centres output images with auto side margins inside .qe-page__content. Two rules do it: .output_png img and .cell_output img. On python-programming.quantecon.org, the matplotlib lecture's 16 plots are bare <img> elements directly inside div.cell_output. The live stylesheet ships the compiled rule .qe-page__content .cell_output img{display:block;margin-left:auto;margin-right:auto} (checked 2026-09-11).

The visual suite cannot catch this. The fixture notebook behind the notebook.png snapshot stores only a stdout stream, a text/plain result and an error (fixture/notebook.ipynb L13-L71; see also the note at theme.spec.ts L3-L8). The no-thebe fixture's copy adds only a stderr cell. Neither notebook stores an image output.

Proposed change

Add a rule to styles/quantecon.css that gives an img inside a notebook outputs container auto left and right margins, matching the book theme's .cell_output img. Select on the upstream container attributes, so no renderer override is needed:

  • [data-name="outputs-container"] img for static output.
  • [data-name="active-outputs-container"] img for output once live compute starts. Starting compute re-renders the stored outputs inside that container (@myst-theme/jupyter 1.3.0, dist/active.js lines 36-39 and 74).

Like the book theme's rule, this applies to every img in a cell's outputs, not only to matplotlib figures.

It is an element rule, so the file's layering note (styles/quantecon.css L36-L44) places it in the @layer base block that opens at L116. Its selector names no class, so Tailwind always emits it, whatever it finds while scanning content (tailwindcss 3.4.19, lib/lib/setupContextUtils.js lines 230-255, 344-356 and 701-708). Preflight already supplies display: block and max-width: 100%.

Cover it in the visual suite:

  • Add a code cell to tests/visual/fixture/notebook.ipynb with a stored image/png and text/plain output. Make the image narrower than the desktop column but wider than the mobile one. Then notebook.png shows an image that has room to centre on desktop and has to cap on mobile.
  • Add a DOM assertion that loads /notebook on the thebe-enabled fixture, the one behind notebook.png, and measures the image's left and right gaps inside its outputs container. Follow the shape of the existing stderr-collapsed test but not its URL: that test loads the no-thebe fixture, which will not carry the new cell.

Acceptance criteria

  • In the desktop-chrome project, the fixture's image output has equal left and right gaps inside its outputs container (within 1px), asserted in tests/visual/theme.spec.ts.
  • In the mobile-chrome project, the image is no wider than its outputs container and the page does not scroll horizontally.
  • In the refreshed notebook.png, the existing stream, plain-text and error outputs are unchanged and still left-aligned; the new image cell is the only change.
  • The intro, features and lists snapshots pass without a baseline update.
  • The notebook.png baselines for desktop-chrome and mobile-chrome are refreshed: -darwin locally, -linux via /update-snapshots.
  • After live compute is started on the thebe-enabled fixture's notebook page, the stored image, now inside [data-name="active-outputs-container"], is also centred (manual check).
  • On a lecture-python-programming deploy preview built with the change, the /matplotlib plots at 1440px have equal left and right gaps.

Context

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething is wrong or broken in a lecture or build

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions