Jupyter engine: emit real figures for image outputs (bd-5t6wvu7m)#412
Merged
Conversation
Two fixes in text_execute.rs format_outputs: 1. MIME priority now follows Q1's displayDataMimeType for HTML targets: text/html -> image/svg+xml -> image/png -> image/jpeg -> text/plain LAST. Previously text/plain was checked first, so a matplotlib display bundle (image/png + '<Figure size ...>' text fallback) always rendered as its text repr — the image branch was unreachable for typical figures. 2. The image branch writes real files instead of the '[Image output]' placeholder: new FigureWriter emits <stem>_files/figure-html/cell-<i>-output-<j>.<ext> next to the source doc (Q1's naming; the layout JupyterEngine:: intermediate_files already declares and knitr already produces), references it as  inside the .cell-output-display div, and reports the <stem>_files dir via ExecuteResult:: with_supporting_files — so q2 render copies figures to the output dir (bd-o8pr) and the preview capture embeds them for browser replay (bd-qbhp2cvv). SVG payloads starting with '<svg' are written as text (Q1 rule); base64 is decoded with nbformat multiline newlines stripped. Fail-soft: unwritable images fall back to the text/plain sibling, or the placeholder when none. TDD: the priority test was written first and observed failing (text repr emitted); six new unit tests cover file writing, naming, multi-output numbering, svg-as-text, html-beats-image, and the invalid-base64 fallback. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…divs jupyter figure divs now carry ONLY cell-output-display (no generic cell-output), matching q2-knitr's vendored figure hook (hooks.R:627). Q1's own engines disagree here (Q1 jupyter adds .cell-output to every output; Q1 knitr does not for figures); q2 resolves the asymmetry toward knitr so the parity suite's strict semantic-class comparison holds with no figure-specific carve-out. The only generic .cell-output consumer (printStylesheet.ts page-break list) also matches img directly, so CSS behavior is unchanged. New parity_plot_output case in engine_output_parity.rs: knitr plot(1) vs jupyter matplotlib (trailing ; suppresses the execute_result so the cells are semantically equivalent) must produce the same block shape, exactly one image each under doc_files/figure-html/, and captures reporting non-empty supporting_files. Skips when knitr, jupyter, or matplotlib is unavailable; verified actually running (2.1s, real engines) and passing locally. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds the plan/E2E record (claude-notes/plans/2026-07-23-jupyter- figure-outputs.md): q2 render emits a real 556x413 PNG + <img src="pyfig_files/figure-html/cell-1-output-1.png">, manifest records the Engine/jupyter resource; q2 preview capture embeds the PNG (bd-qbhp2cvv transport) and Chrome renders the matplotlib plot via blob URL — output inspected. Clippy: is_ok_and in the matplotlib probe. cargo xtask verify passes end to end. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
✅ 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. |
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.
Summary
A minimal jupyter doc (python cell with
plt.plot([1,2,3])) produced no image inq2 renderorq2 preview— only the text reprs. The kernel already sends the PNG (display_datawith['image/png', 'text/plain'], noMPLBACKENDforcing needed); q2 discarded it informat_outputsthrough two compounding bugs:text/plainwas checked first, so a matplotlib bundle (PNG +<Figure size …>text fallback) always took the text branch; the image branch was unreachable for typical figures. Now follows Q1'sdisplayDataMimeTypeorder scoped to what q2 handles:text/html→image/svg+xml→image/png→image/jpeg→text/plainlast.[Image output]instead of a file. The newFigureWriterwrites<stem>_files/figure-html/cell-<i>-output-<j>.<ext>next to the source doc (Q1's naming; the layoutJupyterEngine::intermediate_filesalready declares and knitr already produces), emitsinside the.cell-output-displaydiv, and reports the<stem>_filesdir viaExecuteResult::with_supporting_files— soq2 rendercopies figures to the output dir (bd-o8pr) and the preview capture embeds them for browser replay (bd-qbhp2cvv, Preview: transport engine-generated figures in the capture doc (bd-qbhp2cvv) #410). SVG payloads starting with<svgare written as text (Q1 rule); base64 decodes with nbformat multiline newlines stripped. Fail-soft: unwritable images fall back to the text/plain sibling.Design decision for review: jupyter figure divs emit
::: {.cell-output-display}without the generic.cell-outputclass, converging on knitr's vendored figure hook (hooks.R:627). Q1's own engines disagree here (Q1-jupyter adds.cell-outputto every output; Q1-knitr doesn't for figures); q2 resolves the asymmetry toward knitr so the cross-engine parity suite's strict semantic-class comparison holds with no figure-specific carve-out. The only generic.cell-outputconsumer (printStylesheet.ts page-break list) also matchesimgdirectly.Test plan
parity_plot_outputcase inengine_output_parity.rs(per this strand's original scope): knitrplot(1)vs jupyter matplotlib must produce the same block shape, exactly one image each underdoc_files/figure-html/, and captures reporting non-emptysupporting_files. Skips without R/jupyter/matplotlib; verified actually running (~2s, real engines) and passing locally.claude-notes/plans/2026-07-23-jupyter-figure-outputs.md):q2 renderemits a real 556×413 PNG +<img>, manifest records the Engine/jupyter resource;q2 previewcapture embeds the PNG and Chrome renders the plot via blob URL — output inspected.cargo xtask verifypasses end to end.Discovered-from bd-qbhp2cvv (#410); closes the gap that blocked jupyter in that PR's E2E.
🤖 Generated with Claude Code