revealjs preview↔render parity: footer/logo, section attrs, resources: VFS sync#303
Merged
Merged
Conversation
…review (bd-n2w0sxgd)
A `format: revealjs` deck with `footer:`/`logo:` rendered the footer
(`.footer.footer-default`), logo (`img.slide-logo`) and `.reveal.has-logo`
under `q2 render`, but NONE of them under `q2 preview` — a complete
feature miss, not a style nudge.
Root cause: `RevealDeck` never read the `rendered.reveal.{footer,logo}`
meta slots, even though the `reveal-footer-logo` transform
(`pipeline.rs::footer_render_stage`) runs in the q2-preview pipeline and
so populates them in the AST handed to preview.
Native contract: `crates/quarto-core/src/revealjs/assemble.rs`
(`render_revealjs_document` + `footer_logo_html`) places footer/logo as
DIRECT children of `.reveal`, OUTSIDE `.slides` (where `position: fixed`
resolves against the viewport — inside `.slides` it would resolve against
reveal's per-slide CSS `transform`), logo before footer, and sets
`has-logo` on `.reveal` iff a logo is present.
Fix: `revealChromeFromMeta` reads the two slots; a new `RevealChrome`
component injects the markup into the `.reveal` element via `useReveal()`
→ `getRevealElement()` and toggles `has-logo`, cleaning up on unmount —
the same imperative escape-hatch `HeaderIncludesEffect` uses for
`document.head`, because `@revealjs/react`'s `<Deck>` only renders
children inside `.slides`.
Tests: `RevealDeck.integration.test.tsx` (7 cases) — slot extraction,
placement outside `.slides`, logo-before-footer order, conditional
`has-logo`, empty-deck no-op, unmount cleanup. preview-renderer suite
green (219 unit + 224 integration); `cargo xtask verify` 14/14 green.
E2E (live `q2 preview examples/presentations/10-footer-logo`, Chrome):
.reveal.has-logo > img.slide-logo (parent === .reveal, not in .slides)
.reveal.has-logo > div.footer.footer-default (link href preserved)
Known follow-up: the footer/logo render `position: static` because the
preview's runtime-delivered reveal theme omits the `.reveal .footer` /
`.slide-logo` rules render's `theme-*.css` carries — a theme-content gap
tracked in bd-d0f9xoa6 (F4), separate from this DOM fix.
Plan: claude-notes/plans/2026-06-17-revealjs-preview-render-parity-audit.md
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…l slides (bd-vv8jft5n)
`q2 preview`'s `RevealDeck` rendered every slide `<section>` with only
reveal's runtime classes (`present`/`future`/`stack`) — dropping the
`id` and the Quarto-authored classes that `q2 render` puts on each
`<section>` (`crates/quarto-core/src/revealjs/assemble.rs`). Most
visibly, the title slide lost `center`, so it rendered like a normal
slide instead of being vertically + horizontally centered (user report).
Fix: `sectionAttrProps(div)` derives `{id, className}` from the section
Div's Attr (`div.c[0]` = [id, classes, kvs]); `renderTopSection` spreads
it onto each leaf/inner `<Slide>` and the `<Stack>`. The title slide now
gets `id="title-slide" class="section title-slide center"` and reveal
centers it; every section regains its `id` + `section` class.
Tests: `RevealDeck.integration.test.tsx` — `sectionAttrProps`
(class-join, empty-Attr collapse) and `renderTopSection` (leaf carries
id+classes; stack carries className, inner slides carry their id+classes).
preview-renderer suite green (219 unit + 228 integration); `cargo xtask
verify` 14/14 green.
E2E (live `q2 preview examples/presentations/10-footer-logo`, Chrome):
.slides > section#title-slide.section.title-slide.center.present
getComputedStyle(section).top === 288.5px (reveal vertical-centers it)
getComputedStyle(h1.title).textAlign === center
Known gaps (left on bd-vv8jft5n): `@revealjs/react`'s `<Stack>` accepts
only `className` (drops `id`), so a `# Section`-divider stack's own id
can't round-trip yet; kvs (`data-background-*`) not forwarded (deferred
to authoring, bd-bea550b0); config-option parity (Deck config from
`format.revealjs.*`) still pending.
Also retracts the earlier F4 theme-gap finding (bd-d0f9xoa6): it was a
stale-WASM artifact, not a real defect — footer/logo style correctly on
a clean build.
Plan: claude-notes/plans/2026-06-17-revealjs-preview-render-parity-audit.md
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…d-k5rxujiy, Layer 1)
Single-file `q2 preview` only synced assets the AST image walker could
see (`Image` nodes + their includes). Assets referenced through metadata
or raw HTML — `logo:`, footer images, shortcode / CSS `url()` refs —
were invisible, so e.g. a reveal deck's `logo:` 404'd in preview.
This is "Layer 1" of the F5 fix: get the declared bytes into the VFS.
`resolve_single_file_deps` now also expands a document's front-matter
`resources:` (the same declaration used for publishing) right beside the
existing image collection, reusing the publish-path machinery —
`project_resources::extract_resource_patterns(meta, ["resources"])` +
`expand_patterns(canonical_root, deck_dir_anchor, …, DocumentMetadata,
Page)` (the same `expand_patterns` already used in this file for project
`resources:`) — folding the matches into `SingleFileDeps.binary_files`.
They then ride the existing `single_file_assets` → `with_binary_files`
sync into the VFS, exactly like the deck's images.
Properties: reuses publish semantics (declare once → publish + preview),
handles globs, format-agnostic, and on-policy (`resources:` is already
the sync upload trust boundary; see `resources_scoped_html_files`).
Layer 1 puts the bytes in the VFS but is NOT sufficient on its own: a raw
`<img src="logo.svg">` still fetches `/logo.svg` (SPA catch-all) and needs
its `src` rewritten to the minted blob URL to load — that's Layer 2
(per-asset rewrite in preview-renderer, or the bd-msp0 service worker).
See the plan for the full Layer 1 / Layer 2 split.
Tests (config.rs): `single_file_deps_includes_declared_resources`
(declared file lands in binary_files though nothing references it as an
image) and `single_file_deps_resources_glob` (`*.svg` pattern). 8/8
`single_file_deps` tests green; `cargo xtask verify --skip-hub-build`
green (Rust-only, native-crate change — not in the WASM path).
E2E (real `q2 preview` binary, RUST_LOG, A/B):
deck `logo: logo.svg` + `resources: [logo.svg]`, logo.svg NOT a md image
→ "Discovered project files … binary_count=1"
same deck, `resources:` removed
→ "Discovered project files … binary_count=0"
proving the `resources:` declaration (not image walking) syncs the asset.
Plan: claude-notes/plans/2026-06-17-revealjs-preview-render-parity-audit.md
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
Closes three
q2 preview↔q2 renderparity gaps forformat: revealjsdecks, surfaced by an audit ofexamples/presentations/*(plan:claude-notes/plans/2026-06-17-revealjs-preview-render-parity-audit.md). Each commit is one focused fix.has-logooutside.slides, mirroringassemble.rsAttrid+ classes onto<Slide>/<Stack>— fixes the title slide rendering uncentered (it lostcenter); every section regains itsid+sectionclassresources:→ VFSresources:so declared assets (e.g.logo:) sync into the preview VFS1. Deck footer/logo (bd-n2w0sxgd)
RevealDecknever readrendered.reveal.{footer,logo}(already populated in the preview AST by thereveal-footer-logostage). NewRevealChromeinjects the markup into the.revealelement viauseReveal()→getRevealElement()(theHeaderIncludesEffectescape-hatch pattern, since@revealjs/react's<Deck>only renders children inside.slides) and toggleshas-logo.2. Section attributes (bd-vv8jft5n)
sectionAttrProps(div)derives{id, className}from the section Div's Attr;renderTopSectionspreads it onto each<Slide>/<Stack>. The title slide now getsid="title-slide" class="section title-slide center"and reveal vertically + horizontally centers it.@revealjs/react's<Stack>accepts onlyclassName(dropsid), so a# Section-divider stack's own id can't round-trip yet;kvs(data-background-*) not forwarded; config-option parity (Deck config fromformat.revealjs.*) still pending.3.
resources:VFS sync (bd-k5rxujiy, Layer 1)resolve_single_file_depsexpandsmeta.resources(reusing the publish-pathextract_resource_patterns+expand_patterns, globs included) intobinary_files, riding the existingsingle_file_assets → with_binary_filesVFS sync.<img src="logo.svg">still 404s until Layer 2 serves the bytes to it — either a per-asset rewrite inpreview-renderer, or the bd-msp0 service-worker path-server. Decision deferred; see the plan.Testing
RevealDeck.integration.test.tsx(section-attr + chrome injection);single_file_deps_includes_declared_resources+_glob.cargo xtask verifygreen (full for the TS legs;--skip-hub-buildfor the Rust-onlyresources:change —quarto-previewis native-only, not in the WASM path).q2 preview, Chrome, onexamples/presentations/10-footer-logo): footer/logo are direct children of.revealoutside.slides, footerposition: fixed, title slide centered.resources:Layer 1 verified viabinary_countA/B (1 withresources:, 0 without).Not included
wasm-quarto-hub-client/Cargo.lockworkspace version-sync (regenerated by the WASM build) is intentionally left out.🤖 Generated with Claude Code