Skip to content

fix(notebooks): centre images in cell outputs - #225

Merged
mmcky merged 5 commits into
mainfrom
claude/funny-meitner-i34zme-206-plots
Sep 12, 2026
Merged

fix(notebooks): centre images in cell outputs#225
mmcky merged 5 commits into
mainfrom
claude/funny-meitner-i34zme-206-plots

Conversation

@quantecon-services

@quantecon-services quantecon-services commented Sep 11, 2026

Copy link
Copy Markdown
Collaborator

Closes #206, implementing the 2026-09-11 decision (images only; tables and text stay left-aligned; both container names; no upstream-registry entry).

Problem, re-verified on main @ d7ec6cd

Added the fixture's image cell, built main, and measured in Chromium at 1280px: the 568px plot sits at left: 0, right: 232 in its 800px outputs container — reproducing the issue's measurement of the lecture-python-programming preview exactly. After the change: left: 116, right: 116.

Change

One rule in styles/quantecon.css's @layer base block:

[data-name='outputs-container'] img,
[data-name='active-outputs-container'] img {
  margin-left: auto;
  margin-right: auto;
}

A plot output is a bare <img> that upstream renders with neither class nor style, inside a container marked only by data-name — so the container attribute is what there is to select on, and no renderer override is needed. text-align on the container cannot move it, because Preflight makes the image a block. Only side margins are set, so vertical rhythm is untouched, and only images, so tables and text outputs stay left-aligned.

Live compute is covered. The stored outputs are re-rendered into active-outputs-container when a reader starts compute. Rather than boot Pyodide, I renamed the container's data-name on a live page to what upstream uses in that state and re-measured: still 116 / 116. The rule also survives Tailwind's content scan (it names no class, so it is always emitted) — confirmed present in the built app.css.

Tests

output-image-centred in theme.spec.ts, on the thebe-enabled fixture whose notebook.ipynb now carries a stored image/png cell (568×320 — wider than the mobile column, narrower than the desktop one, so one page exercises both centring and capping):

  • equal left/right gaps within 1px on desktop;
  • never wider than its container, and no horizontal page scroll, at either viewport;
  • a text output in the same outputs container is still flush left, so the rule has not leaked.

Mutation-tested: with the rule's body emptied, the assertion fails; restored, it passes. (After #205, where my first "no empty slot" assertion turned out to be unfalsifiable, I'm checking that every new assertion can actually fail.)

Visual baselines — notebook.png must be refreshed on both projects

The fixture notebook gains a cell, so the page grows 1095px → 1552px on desktop. That is a size mismatch, so notebook.png fails outright until refreshed.

Worth recording, because it is the reason the DOM assertion exists: the centring itself would not have been caught by the pixel suite. I captured full-page renders from a clean build with and without the rule and diffed them directly:

Viewport Pixels differing from the centring alone Suite tolerance
desktop 7,122 / 1,986,560 = 0.36% 1%
mobile 0 / 608,757 = 0% (the image caps to the column either way) 1%

A plot is mostly white, so shifting it moves very few pixels.

Nothing else moves: the rule only matches images in cell outputs, and only /notebook has one. intro, features, lists, history-open, sidebar-open and rtl are untouched, as the issue's criteria require.

  • -linux — already refreshed in 6dd380c28, and nothing further is needed: the Visual regression (Chromium) job passes on the merged tip, so those baselines are correct against main as it now stands. I had planned to re-trigger /update-snapshots after merging main and deliberately held it until CI could answer, rather than spend a refresh commit (whose own CI arrives gated) on a set that turned out to be current.
  • -darwin — refreshed locally in 78c53c3da. Both files were hard size mismatches (desktop 1094 → 1551, mobile 1203 → 1548), so the default update mode was the right tool: it rewrites only a baseline whose comparison actually fails, leaving the other twelve darwin baselines untouched. (A sub-threshold change is the case that needs =all instead, as the rtl baseline did in feat(launch): make the Launch control opt-in and explicitly configured #224.) Verified with a clean non-updating re-run: 44 passed, 0 failed. The heights land a few pixels off the linux set's 1552/1553, which is precisely why these baselines are platform-suffixed — neither platform's pixels can satisfy the other.

Merged with main

#221, #222 and #224 all landed while this was open, and all three added entries under ## [Unreleased], which conflicted textually with this branch's ### Fixed entry while agreeing in substance. Merged main in at 153d1422d, keeping all four in the order Keep a Changelog uses: Added (default site footer, {raw} rewriter), Changed (breaking launch change), then Fixed (this centring fix).

Checked rather than assumed, since a clean text merge is no guarantee the content survived: the CSS rule is present in source and in the built bundle (public/build/_assets/app-*.css, so Tailwind's content scan did not drop it), the fixture still carries its stored image/png cell while fixture-no-thebe still carries none, tsc is clean, and the unit suite is unchanged. A full local run against the merged tree failed on exactly two baselines — the darwin notebook.png pair — and nothing else drifted, with output-image-centred passing on both projects.

Review

Copilot raised two comments, both addressed and answered:

  • The overflow check (theme.spec.ts) — changed in 9343d3b5b, though not in the shape suggested. It now compares the widths numerically with the same 1px allowance the gap assertions beside it use, and returns both values so a failure names them. I kept documentElement.clientWidth as the subject rather than the viewport width used in outline-within-viewport: clientWidth excludes the scrollbar, so adopting that comparison would have made the assertion roughly 15px laxer rather than merely more stable. The cost is stated plainly in the reply — with a tolerance, a genuine 1px overflow now passes here, which is acceptable only because page overflow is really guarded by that 1280/1300/1328px sweep across both text directions.
  • The changelog PR link — added in 153d1422d. Its supporting claim was fair this time ("most existing entries include both"), unlike the same comment on feat(scripts): rewrite Sphinx {raw} blocks out of lecture sources #222 and feat(launch): make the Launch control opt-in and explicitly configured #224; for the record the exceptions are ten issue-only entries across five released versions.

A methodology note that may be worth a follow-up

Two traps cost me real time here, and both could silently mislead anyone doing an A/B build comparison in this repo:

  1. make build-theme never cleans. remix build does not clear build/, so build/index.js from a previous build leaked into my "baseline" bundle and made it render as the candidate. npm run clean first fixes it — but nothing in the Makefile or CONTRIBUTING says so.
  2. Two serve.sh instances on the same FIXTURE_DIR race. serve.sh writes myst.yml into the shared fixture directory, so a second server rewrites the first one's config out from under it. They have to be run sequentially, or on separate fixture copies.

Both are now filed, each verified against the code first rather than taken on report: #233 for the missing clean step (the target removes only .deploy/<theme>, and neither it nor prod:build runs npm run clean, while Makefile lines 21-22 copy public/ and build/ wholesale into the bundle), and #234 for the serve.sh race (myst.yml is generated inside the fixture directory it serves, so two instances on one FIXTURE_DIR overwrite each other — the committed suite is safe, because its three servers use three different fixture directories).

🤖 Generated with Claude Code

https://claude.ai/code/session_01UZLpDVYu1YBZHQfwkRRJj7


Generated by Claude Code

A plot produced by a lecture's code cells sat against the left edge of
the content column with all the spare width beside it: 0px to its left
and 232px to its right, for a 568px figure in the 800px column. The
lecture builds centre the same plots.

A plot output is a bare `<img>` that upstream renders with neither class
nor style, inside a container marked only by `data-name`, so the
container attribute is what there is to select on; `text-align` on the
container cannot move it, because Preflight makes the image a block.
Auto side margins on the image do, matching the lecture builds' own
`.cell_output img` rule.

Both container names are covered: the stored outputs are re-rendered
into `active-outputs-container` when a reader starts live compute, and
the plot should not jump left at that moment. Only images are centred,
so tables and text outputs stay left-aligned as they are on the lecture
sites.

The fixture notebook gains a cell with a stored `image/png` output --
wider than the mobile column and narrower than the desktop one, so the
same page exercises both centring and capping -- and a DOM assertion
measures the gaps. The pixel suite alone could not catch this: the plot
is mostly white, so the shift moves 0.36% of the page, under the
suite's 1% tolerance.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UZLpDVYu1YBZHQfwkRRJj7

Copy link
Copy Markdown
Collaborator Author

/update-snapshots

notebook.png needs refreshing on both projects: the fixture notebook gains a stored image/png cell, so the page grows from 1095px to 1552px on desktop and the snapshot fails on size before any pixel comparison happens.

The -darwin baselines can't be regenerated in this session — @mmcky, they need a local refresh before merge.


Generated by Claude Code

@github-actions

github-actions Bot commented Sep 11, 2026

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

@github-actions

github-actions Bot commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

🎭 Visual regression results

passed  47 passed
skipped  14 skipped

Details

stats  61 tests across 2 suites
duration  1 minute, 38 seconds
commit  78c53c3

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
mobile-chrome › theme.spec.ts › On this page outline › outline-pinned-and-nested
mobile-chrome › theme.spec.ts › On this page outline › outline-tracks-scroll
mobile-chrome › theme.spec.ts › On this page outline › outline-unnumbered
mobile-chrome › theme.spec.ts › On this page outline › outline-within-viewport
mobile-chrome › theme.spec.ts › Meta/SEO and notebook output › social-meta
mobile-chrome › theme.spec.ts › Meta/SEO and notebook output › stderr-collapsed
mobile-chrome › theme.spec.ts › Site options reach the theme › site-options
mobile-chrome › theme.spec.ts › Site footer › default-footer-without-part
mobile-chrome › theme.spec.ts › Site footer › declared-part-replaces-default

@github-actions

Copy link
Copy Markdown
Contributor

🎭 Refreshed visual baselines in 6dd380c:

  • tests/visual/snapshots/desktop-chrome-linux/notebook.png
  • tests/visual/snapshots/mobile-chrome-linux/notebook.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 changelog entry should include a PR link per CONTRIBUTING, and the new overflow assertion can be made more robust to avoid platform-dependent test flakiness.

Get a fresh assessment by requesting another Copilot review.

Pull request overview

This PR aligns notebook cell image outputs with QuantEcon lecture-site behavior by centering output <img> elements within the outputs container, including the live-compute “active outputs” container, and adds a visual/DOM assertion plus fixture data to prevent regressions.

Changes:

  • Center notebook output images via a scoped CSS rule targeting data-name="outputs-container" / data-name="active-outputs-container".
  • Extend the visual test suite with a DOM-based centering/overflow assertion and update the notebook fixture to include a stored PNG output.
  • Document the fix in CHANGELOG.md under Unreleased → Fixed.
File summaries
File Description
styles/quantecon.css Adds a base-layer rule to center images inside notebook outputs containers.
tests/visual/theme.spec.ts Adds a test asserting desktop centering and no horizontal overflow, plus a guard that text outputs remain left-aligned.
tests/visual/fixture/notebook.ipynb Adds a stored image/png output cell to exercise centering behavior.
CHANGELOG.md Adds an Unreleased “Fixed” entry describing the centering change.
Review details
  • Files reviewed: 4/6 changed files
  • Comments generated: 2
  • Review effort level: Lite (auto)

Note

Copilot is running an experiment and ran this review at Lite.


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

Comment thread tests/visual/theme.spec.ts Outdated
Comment thread CHANGELOG.md Outdated
mmcky and others added 3 commits September 12, 2026 11:35
#221, #222 and #224 all landed entries under `## [Unreleased]` while this
was open, which conflicted textually with this branch's `### Fixed` entry
while agreeing in substance. Resolved by keeping all four, in the order
Keep a Changelog uses: Added (default site footer, `{raw}` rewriter),
Changed (breaking launch change), then Fixed (output image centring).

The Fixed entry also gains its PR link alongside the issue link, which is
what CONTRIBUTING.md asks for.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The overflow check was a strict boolean on `scrollWidth > clientWidth`.
Both values are integers rounded from sub-pixel layout, so a page that
fits exactly can still report one more pixel of scrollWidth than
clientWidth -- and `expect(false).toBe(false)` names neither value when it
fails. It now compares them with the same 1px allowance as the gap
assertions beside it, and returns the two widths so a failure reports
them.

Page overflow itself is guarded by the 1280/1300/1328px sweep across both
text directions in `outline-within-viewport`; this line is a sanity check
that the page holding a centred image still fits, which is why it keeps
`clientWidth` as its subject rather than the viewport width -- the latter
includes the scrollbar and would tolerate roughly 15px more.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The fixture notebook gains a stored `image/png` cell, so /notebook grows
from 1094 to 1551px on desktop and from 1203 to 1548px on mobile. Both
darwin baselines were size mismatches against that and failed outright,
so the default update mode was sufficient: it rewrites only a baseline
whose comparison fails, which is why the other twelve darwin baselines
are untouched. (A sub-threshold change needs `=all` instead, as the rtl
baseline did in #224.)

The heights land a few pixels off the linux set's 1552/1553, which is the
reason these baselines are platform-suffixed at all -- the CI font stack
wraps slightly differently, so neither platform's pixels can satisfy the
other.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@mmcky
mmcky merged commit f0fbf7f into main Sep 12, 2026
4 checks passed
@mmcky
mmcky deleted the claude/funny-meitner-i34zme-206-plots branch September 12, 2026 01:49
mmcky added a commit that referenced this pull request Sep 12, 2026
#221, #222, #224 and #225 all landed entries under `## [Unreleased]`
while this was open. This branch's entry is `### Added` like two of
theirs, so it folds into that list rather than forming a second section:
Added (default site footer, `{raw}` rewriter, this checklist), then
main's Changed and Fixed sections unchanged.

The entry also gains its PR link alongside the issue link, which is what
CONTRIBUTING.md:225-227 asks for.

`docs/configuration.md` auto-merged, and was checked rather than assumed:
main's launch rows from #224 and this branch's `project.description`,
`project.keywords` and `project.math` rows are all present, with no row
duplicated.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
mmcky added a commit that referenced this pull request Sep 12, 2026
#221, #222, #224, #225 and #226 all landed entries under `## [Unreleased]`
while this was open. This branch adds one `### Added` entry and one
`### Fixed` entry, so each folds into the matching section rather than
opening a second: Added (default site footer, `{raw}` rewriter, migration
checklist, this canonical link), Changed (the breaking launch change),
Fixed (output image centring, these base-URL head links).

Both entries also gain their PR link beside the issue link, which is what
CONTRIBUTING.md:225-227 asks for.

`README.md`, `docs/configuration.md`, `template.yml` and
`tests/visual/theme.spec.ts` auto-merged. Each was checked rather than
assumed: both sides' content is present in all four.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
mmcky added a commit that referenced this pull request Sep 12, 2026
#221, #222, #224, #225, #226 and #227 all landed entries under
`## [Unreleased]` while this was open. This branch adds one `### Added`
entry, so it folds into that list rather than opening a second section,
and it gains its PR link beside the issue link, which is what
CONTRIBUTING.md:225-227 asks for.

`docs/layout.md`, `styles/quantecon.css` and `tests/visual/theme.spec.ts`
auto-merged. Each was checked rather than assumed: the branch's h4 indent
rule sits beside main's output-image centring rule, the branch's outline
assertions beside main's footer, launch, centring and canonical tests, and
the branch's h2-to-h4 documentation beside main's default-footer section.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
mmcky added a commit that referenced this pull request Sep 12, 2026
#221, #222, #224, #225, #226, #227 and #228 all landed `[Unreleased]`
entries while this was open. This branch adds a `### Fixed` entry, so it
folds into main's existing Fixed section (output image centring, base-URL
head links) rather than opening a second one, and it gains its PR link
beside the issue link per CONTRIBUTING.md:225-227.

`docs/configuration.md`, `docs/index.md` and `docs/layout.md` auto-merged,
and each was checked rather than assumed: layout.md holds this branch's
Header and Widths rewrites beside #228's h2-to-h4 outline text and #221's
default-footer section; index.md holds #226's Pages-table row beside this
branch's rewritten closing paragraph; configuration.md holds the rows from
all four PRs that touched its table.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.

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

4 participants