feat(preview): single-file deck transitive include + image closure (bd-9cyza5vy)#300
Merged
cscheid merged 2 commits intoJun 17, 2026
Merged
Conversation
…closure (bd-9cyza5vy)
Single-file `q2 preview deck.qmd` (no `_quarto.yml`) previously synced only
the deck, a sibling `_brand.yml`, and the deck's *direct* images into the
preview VFS. So `{{< include part.qmd >}}` rendered as a literal `?include`
placeholder, and any image referenced *inside* an included file was missing —
single-file preview diverged from both `q2 render` and project-mode preview.
Resolve the deck's full transitive static dependency closure natively, without
walking the directory (preserving the bd-tnm3k safety property), by running the
renderer's OWN `ParseDocumentStage` + `IncludeExpansionStage` against the real
filesystem and reading back what it consumed:
- includes from `DocumentAst::recorded_includes` (transitive, cycle-truncated)
- images from `collect_referenced_asset_urls` over the *expanded* AST,
deck-dir-anchored — matching render's "no path retargeting" design
Reusing the real stages (rather than a parallel walker) keeps preview path
resolution in exact lock-step with render, including the latent nested-include
retargeting bug (filed separately as bd-udrn0q47), which preview now inherits
automatically.
Plumbing:
- quarto-preview/config.rs: `resolve_single_file_deps -> SingleFileDeps`
(supersedes + deletes `resolve_single_file_assets`); `build_hub_config` makes
one resolver call filling both channels.
- quarto-hub/discovery.rs + context.rs: included `.qmd` ride a new INVISIBLE
text path — `HubConfig.single_file_text_deps` -> `ProjectFiles.text_dep_files`
+ `with_text_deps` (in `text_files()`/`all_files()`/counts, NOT `qmd_files`,
mirroring the `resource_files` pattern). The single-file preview SPA has no
file list, so included files stay VFS-only dependencies.
- quarto-hub/watch.rs + server.rs: extend the single-file watch set to the
resolved closure (`WatchConfig.single_file_deps`) so editing an included file
or referenced image re-renders, while unrelated siblings still never surface.
Tests (TDD): 6 resolver tests (include + image-in-include, transitive,
deck-dir anchoring/render parity, cycle, under-root guard, deck-own image);
discovery + context invisible-text-dep invariants; watcher closure-dep surfaces
while unrelated sibling is ignored. E2E: browser render of `q2 preview main.qmd`
shows the include expanded and the in-include image displayed via blob URL.
Docs: claude-notes/research/2026-06-16-include-shortcode-path-resolution.md
(the "no retargeting" design + the bd-udrn0q47 discrepancy, confirmed against
Quarto 1 source) and the implementation plan.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.
What
Single-file
q2 preview deck.qmd(no_quarto.yml) previously synced only the deck, a sibling_brand.yml, and the deck's direct images into the preview VFS. So{{< include part.qmd >}}rendered as a literal?includeplaceholder, and any image referenced inside an included file was missing — single-file preview diverged from bothq2 renderand project-mode preview.This resolves the deck's full transitive static dependency closure natively — without walking the directory (preserving the bd-tnm3k safety property) — by running the renderer's own
ParseDocumentStage+IncludeExpansionStageagainst the real filesystem and reading back what it consumed:DocumentAst::recorded_includes(transitive, cycle-truncated)collect_referenced_asset_urlsover the expanded AST, deck-dir-anchored — matching render's "no path retargeting" designReusing the real stages (rather than a parallel walker) keeps preview path resolution in exact lock-step with render, including a latent nested-include retargeting bug (filed separately as bd-udrn0q47), which preview now inherits automatically instead of hard-coding one side of a contested rule.
Changes
config.rs:resolve_single_file_deps -> SingleFileDeps(supersedes + deletesresolve_single_file_assets);build_hub_configmakes one resolver call filling both channels.discovery.rs+context.rs: included.qmdride a new invisible text path —HubConfig.single_file_text_deps→ProjectFiles.text_dep_files+with_text_deps(intext_files()/all_files()/counts, notqmd_files, mirroringresource_files). The single-file preview SPA has no file list, so included files stay VFS-only dependencies.watch.rs+server.rs: extend the single-file watch set to the resolved closure (WatchConfig.single_file_deps) so editing an included file or referenced image re-renders, while unrelated siblings still never surface (bd-tnm3k preserved).Tests
TDD throughout:
qmd_files, in the index).cargo xtask verify --skip-hub-buildpasses (clippy-D warnings+ full-workspace nextest). The fix is native-only; WASM/quarto-core unchanged.E2E
Browser inspection of
q2 preview main.qmd(no_quarto.yml) wheremain.qmdincludespart.qmdwhich referencesinc-image.png: the include expands ("Included Section" renders, no literal?include), and the image inside the include displays via ablob:URL (naturalWidth 1, the 1×1 PNG loaded).Notes
claude-notes/research/2026-06-16-include-shortcode-path-resolution.md,claude-notes/plans/2026-06-16-single-file-preview-transitive-deps.md.🤖 Generated with Claude Code