Cap body images at their container width (bd-images-no-max-width-e5ywgnma) - #644
Merged
gordonwoodhull merged 1 commit intoSep 2, 2026
Conversation
…gnma)
An image with no author-supplied size laid out at its intrinsic pixel
width, because q2 emitted every body image with no class at all. A
2680x1720 screenshot in an 850px content column rendered 2680px wide,
overrunning the margin and putting a horizontal scrollbar on the page.
`.img-fluid{max-width:100%;height:auto}` was already in the compiled
theme -- only the pass that applies it was missing.
Add ResponsiveImageTransform, a port of Quarto 1's
quarto-post/responsive.lua, in the Finalization phase beside
TableBootstrapClassTransform. Per-image it keeps Q1's two exclusions:
an explicit `height`, which `height:auto` would override, and
`data-no-responsive`. An explicit `width` is deliberately not an
exclusion -- `max-width:100%` refines a fixed width rather than
fighting it.
The document switch is `fig-responsive`, and its default is not simply
"on for HTML". Q1's filter tests isHtmlOutput() *and* reads
param('fig-responsive'), whose default is per-format:
createHtmlPresentationFormat sets it false for HTML presentations, so
revealjs is untagged despite isHtmlOutput() being true, and
format-html.ts does the same for `minimal: true`. Both are suppliers of
last resort, so an explicit `fig-responsive: true` still wins. The
`minimal` check reads the raw flag, not is_minimal_html, which also
fires on `theme: none` where both engines do tag.
Also strip `img-fluid` in the llms.txt sanitizer. The pass runs
immediately before LlmsCaptureTransform, so without it every markdown
companion carried `{.img-fluid}` -- Bootstrap presentation
in output whose purpose is clean semantic markdown.
Not ported: the `figure-img` class Q1 also puts on figure images. It
comes from a DOM postprocessor in format-html-bootstrap.ts that sweeps
blockquote/figure/figure-img together, and is cosmetic; porting one of
its three classes alone would be worse than deferring the sweep.
Contributor
✅ Snyk checks have passed. No issues have been found so far.
💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse. |
gordonwoodhull
deleted the
bugfix/bd-images-no-max-width-e5ywgnma-images-no-max-width
branch
September 2, 2026 19:26
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
An image with no author-supplied size lays out at its intrinsic pixel width, because q2 emits every body image with no class at all. A 2680×1720 screenshot in an 850px content column renders 2680px wide: it overflows the column, overruns the margin, and puts a horizontal scrollbar on the page. On the Positron docs site this hits 63 images across 20 pages, median intrinsic width 2470px. The images that look fine only escape because their author happened to write an explicit
width=, which is why the bug hides in a real corpus — about half of any given page is fine and only the unsized images blow out..img-fluid { max-width: 100%; height: auto }is already in the compiled theme; it arrives with Bootstrap, byte-identical to Quarto 1's. Nothing else in that stylesheet constrains a figure image, and there's no bareimg { max-width }fallback. The only thing missing was the pass that applies the class.This adds
ResponsiveImageTransform, a port of Quarto 1'squarto-post/responsive.lua. It runs in the Finalization phase besideTableBootstrapClassTransform, its sibling in intent — both inject the Bootstrap classes the theme is keyed off, and both run late enough that crossref-rendered figures are tagged too. Chrome emitted as raw HTML rather than AST nodes (listing thumbnails, navbar logos, page-footer images) stays untagged, as in Quarto 1.Per-image, both of Quarto 1's exclusions are preserved: an explicit
height, sinceimg-fluidcarriesheight: autoand would silently override the size the author asked for; anddata-no-responsive, honored for any value. An explicitwidthis deliberately not an exclusion —max-width: 100%still lets awidth=450image shrink inside a narrower column, so the class refines the author's width rather than fighting it.The document-level default is per-format
fig-responsiveis the switch, but its default is not simply "on for HTML". Quarto 1's filter testsisHtmlOutput()and readsparam('fig-responsive'), whose default is set per format:createHtmlPresentationFormatsets itfalsefor every HTML presentation format, so revealjs is not tagged even thoughisHtmlOutput()is true there, andformat-html.tsdoes the same forminimal: true. Both are suppliers of last resort, so an explicitfig-responsive: truestill wins.Verified against the real Quarto 1 binary on all five cases — plain, revealjs,
minimal: true,theme: none, and each with an explicit override. Two details worth knowing if you touch the gate:target_format, not the format identifier. Every live deck arrives asrevealjsor — since the bd-vwp4y5ku convergence rewroteformat: revealjson the way into preview — asq2-slides, which resolves toFormatIdentifier::Html. An identifier test would tag deck images in preview and not in render.minimalcheck reads the raw metadata flag rather thanis_minimal_html, which also returns true fortheme: none/theme: pandoc— where both engines do tag.llms.txt
img-fluidis stripped in the llms.txt sanitizer. The transform runs immediately beforeLlmsCaptureTransform, so without the strip every page's.mdmirror would read{.img-fluid}— Bootstrap presentation escaping into output whose whole purpose is clean semantic markdown.docs/setsllms-txt: trueand carries 76 image references, so this is the difference between a clean mirror and 76 pieces of noise inq2 docs llms.Verification
Against the shared acceptance fixture (
q2-positron-docs/llms-info/repros/images-no-max-width) — a plain image, one withwidth=, one withheight=, and one inline. Quarto 1 tags three of four; q2 now tags the same three:Full
cargo xtask verifypasses (all 14 steps, WASM and hub-client included). Workspace suite: 13578 passed, 199 skipped — +28 against main, exactly the 28 tests added here.The preview path is covered separately, since
q2 previewand hub-client hand serialized AST to a React renderer rather than emitting HTML: three tests againstrender_qmd_to_preview_astpin thatq2-previewtags, honors theheightexclusion, andq2-slidesdoes not tag. The rest of that chain was traced rather than assumed —Image.tsxcopies AST classes onto the<img>viaclassName, and the preview iframe loads the compiled Bootstrap theme through<link data-q2-theme>.Not included
The
figure-imgclass Quarto 1 also puts on figure images. Despite sitting one class away in the output it comes from elsewhere — the DOM postprocessor informat-html-bootstrap.tsthat sweepsblockquote,figureandfigure imgin a single pass — and it is cosmetic (margin-bottom: .5rem; line-height: 1). Per the repo's no-DOM-postprocessor rule that sweep needs its own AST transform, and splitting one of its three classes off into this one would be worse than deferring all three together. A follow-up should also fold in Quarto 1's thirdimg-fluidsite (format-html-bootstrap.ts:333-341), which force-tags margin-column images, deliberately overriding theheightexclusion.Also spotted but out of scope:
TableBootstrapClassTransformleaks{.caption-top .table}into llms companions the same wayimg-fluiddid. That predates this branch.Fixes bd-images-no-max-width-e5ywgnma