Skip to content

Commit ff31c8e

Browse files
cscheidclaude
andauthored
RevealJS themes & authoring features: Quarto 1 → Quarto 2 (reveal.js 6) (#297)
* docs(revealjs): plan for importing Quarto 1 revealjs themes into Q2 (reveal 6) Study-phase assessment + resolved decisions for bd-yown2ts4. Captures: - Q2 today: reveal branch short-circuits before SCSS; resolve_theme_name collapses every theme to white; no Quarto reveal layer for title/bullets. - reveal 5->6: theme *authoring* changed (camelCase->kebab vars, @import->@use module system) but visual output + --r-* runtime contract are stable. Verified grass 0.13.4 compiles all 14 reveal-6 themes. - Q1 adaptation: 5-region SCSS layers, $presentation-*/$body-* vocabulary, quarto.scss mapping, exposer.scss bridge. - Root cause of "feels different": Q1 sets slide-text-align:left and heading-text-transform:none; Q2 makes neither override (confirmed via q2 render + Chrome computed-style inspection). - Decisions: single unified SCSS compilation (D1); keep $presentation-* vocabulary (D2); ship Stage A first, branch per stage (D3); accept both theme naming schemes (D4); "feels at home" not pixel-perfect (D5). Stages tracked as bd-r9mkybwl (A), bd-n697m7u7 (B), bd-j8qoyc0s (C). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore(wasm): sync wasm-quarto-hub-client Cargo.lock to workspace 0.2.0 The WASM crate's lockfile still carried 0.1.0 for the workspace crates; the 0.1.1 -> 0.2.0 bump (7de45f9) didn't regenerate it. A WASM build updates it in passing. No dependency changes — version fields only. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(revealjs): compile a Quarto reveal theme through the SCSS subsystem (Stage A) Quarto 2's revealjs render path shipped only a precompiled stock reveal `white.css` and no Quarto reveal SCSS layer, so decks inherited reveal's centered slides + uppercase headings — unfamiliar to Quarto 1 users. This adds the Quarto reveal theming layer, compiled in one unified SCSS pass (decision D1) and adapted to reveal.js 6's kebab-case variables + stable --r-* runtime contract. Stage A of bd-r9mkybwl (epic bd-yown2ts4). Render path only; q2 preview still uses the vendored stock white.css (Stage C convergence — see plan). What changed: - Vendor reveal-6 theme template SCSS locally under resources/scss/revealjs/ reveal-template/ (External Sources Policy): settings split into var declarations (_settings-vars) + the :root{--r-*} emitter (_expose) so the emitter runs in the rules layer after !defaults collapse; theme rules; mixins. - Author resources/scss/revealjs/quarto-revealjs.scss: the Quarto reveal layer (port of Q1 quarto.scss, Stage-A subset) — $presentation-*/$body-* vocabulary (D2), mapping onto reveal-6 kebab vars, and the look-fixing rules (slide text-align:left, heading-text-transform:none, centered h2-sized title). - quarto-sass: load_reveal_framework + load_quarto_reveal_layer + assemble_reveal_scss (reuses the shared 5-region assemble_scss); embedded REVEALJS_RESOURCES; compile_reveal_theme_css (native grass + WASM dart-sass). - quarto-core: CompileThemeCssStage reveal branch now compiles the Quarto reveal theme (cfg-split compile_reveal helper) and registers it as the 3-theme-* slot; register_reveal_assets takes compiled_theme_css: Option<&str> (None = stock fallback on compile failure); RevealAsset.content is now Cow<'static, str>. reset.css + core reveal.css stay vendored; quarto-reveal.css (columns/aside/ footnotes) stays separate. Verified: 7 new quarto-sass assembly tests + a quarto-core integration test that reads the flushed compiled theme; full quarto-sass + quarto-core suites (2520 tests) green; cargo xtask verify (incl. WASM build) green. E2E through `q2 render` + Chrome computed styles confirmed the flip (left-aligned mixed-case content, centered h2-sized title slide). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(revealjs): top-align slides (center:false) + keep title slide centered Q2 defaulted reveal `center: true`, vertically centering all slide content. Quarto 1 defaults `center: false` (content top-aligns) and re-centers only the title slide via a per-slide `.center` class. Match that: - reveal_config_json: default `center` to false (reveal's own default is true). - build_title_slide: add the `center` class to the title-slide section so it stays vertically centered when the global center config is off (reveal honors the per-slide class). Mirrors Quarto 1's format-reveal.ts. TDD: updated config_defaults_match_quarto1 (center:false) and extended revealjs_render_emits_title_slide (title-slide carries `center`). E2E + Chrome: body slides top-align (top:18, no inline offset); title slide stays vertically centered (reveal applies top from the `.center` class); `.reveal` has no global center class. Found during user experimentation with Stage A. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(revealjs): the 12 themes + real theme selection (Stage B) Brings Quarto 1's full revealjs theme set and `theme:` selection to Quarto 2, adapted to reveal.js 6. Stage B of bd-r9mkybwl (epic bd-yown2ts4); stacked on Stage A's branch. Themes (resources/scss/revealjs/themes/*.scss): default, dark, beige, blood, dracula, league, moon, night, serif, simple, sky, solarized — ported from Q1 to reveal-6 form (kebab-case for direct reveal-var sets; bodyBackground()/ radial-gradient -> $background gradient since reveal 6 dropped the mixin; local league-gothic font -> Google Fonts CDN, per the B7 font decision). dracula's local $background palette var renamed -> $drac-background to avoid colliding with reveal-6's own $background. Selection + resolution: - quarto-sass: REVEAL_BUILTIN_THEMES + resolve_reveal_theme_name (white->default, black->dark aliases) + load_reveal_theme_layer; assemble_reveal_scss now takes a slice of theme layers (merged via merge_layers); compile_reveal_theme_css takes theme layers + load paths. - quarto-core: revealjs::resolve_reveal_theme parses theme: (string|array| absent->default) into built-in/user-.scss layers (reusing load_custom_theme); unknown/missing theme -> loud stage error. Replaces the hardcoded resolve_theme_name. Per-deck themes in websites now work like format: html: the theme artifact is keyed by CONTENT FINGERPRINT (css:revealjs:3-theme-<hash> -> theme-<hash>.css). Same theme dedups to one shared file; distinct themes get distinct files and each deck links its own (collected per-document from that deck's StageContext.artifacts). Future-proof for Stage-C brand/per-deck vars. Verified: 13 quarto-sass reveal tests + quarto-core reveal/resolution/integration tests (incl. theme: dark end-to-end); full quarto-sass+quarto-core suites green; cargo xtask verify (incl. WASM build) green. E2E + Chrome confirmed dark and dracula render faithfully (dark bg, theme link/heading/bullet colors), top- and left-aligned. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(revealjs): Q1/reveal-5 look-and-feel audit + prioritized backlog After merging Stage A+B, audited Q1's revealjs look-and-feel surface vs Q2 (at the user's request, prompted by the A8 center-bug class). Records into the plan: - config-default divergences (headline: transition defaults to "slide" in Q2 but "none" in Q1; plus width/margin/navigationMode/controlsLayout/... and the slideNumber c/t rewrite) + the RevealDeck.tsx preview parity bug; - quarto.scss SCSS systems not yet ported (callouts, .has-light/dark-background, code-block scroll, .smaller, light/dark sentinel, blockquote, kbd, ...) and the Sass-helper foundation they depend on; - chrome/transform/plugin gaps (fancy title slide, auto-stretch, logo/footer, zero reveal plugins shipped today); - a prioritized quick-wins-vs-larger-efforts shortlist and a suggested re-scoping (config fixes + Sass-helper foundation + SCSS quick-wins before Stage C). No code changes — backlog for the user to prioritize. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(revealjs): adopt Quarto 1's opinionated Reveal.initialize defaults (C1) The 2026-06-16 look-and-feel audit found Q2 emitted only 8 reveal config keys and diverged from Q1 on user-visible defaults — most notably `transition` defaulted to "slide" (Q2) vs "none" (Q1), so every Q2 deck animated on slide change. Port Q1's full opinionated default block (format-reveal.ts:343-361) into reveal_config_json, each overridable from front-matter: transition:none, backgroundTransition:none, center:false, width:1050, height:700, margin:0.1, navigationMode:linear, controlsLayout:edges, controlsTutorial:false, history:true, hashOneBasedIndex:false, fragmentInURL:false, pdfSeparateFragments:false. Also: slide-number:true now rewrites to Quarto's "c/t" (linear) / "h.v" (vertical-navigation) format like Q1, and width/height accept "%" strings. Preview parity: RevealDeck.tsx (q2-preview SPA) and RevealjsReactAstSlideRenderer (hub-client) hardcoded center:true + transition:'slide' + margin:0.04, which contradicted render. Updated both to the render defaults (center:false, transition:'none', margin:0.1, navigationMode:'linear', controlsLayout:'edges'). Stage C of bd-jxyqjf15 (epic bd-yown2ts4). TDD: config_defaults_match_quarto1 updated + new vertical-nav/override tests; integration slideNumber assertion updated. E2E: `q2 render` + Chrome confirm Reveal.getConfig() carries the new defaults (transition none, edge controls, linear nav). Full quarto-core suite + cargo xtask verify (incl. hub-client build) green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(hub-client): changelog for revealjs preview default-parity (3706176) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(revealjs): mark Stage C1 (config defaults) complete in plan Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(revealjs): Sass-helper foundation + SCSS quick-wins (Stage C2+C3) From the 2026-06-16 look-and-feel audit. Stage C of bd-jxyqjf15. C2 — Sass-helper foundation in the Quarto reveal layer (quarto-revealjs.scss): colorToRGB / tint-color / shade-color / shift-color functions; shift_to_dark (adapted to reveal-6 kebab $background-color + quarto-color.blackness) and make/undo-smaller-font-size (quarto-math.pow) mixins; the quarto-color/quarto-math @use imports + supporting defaults (border/gray vars, $code-block-theme-dark- threshhold, monospace-block/inline fonts, light/dark-bg colors, kbd vars). Probed grass first: it supports color.blackness, color.scale, math.pow, mix. C3 — SCSS quick-wins ported from quarto.scss: - .has-light/dark-background text+link+code recoloring (legibility on contrasting slide backgrounds); - code blocks: bordered div.sourceCode, full-width pre, scrollable max-height, output-vs-code background differentiation; - .smaller font-scaling (global `.reveal.smaller` + per-slide `section.smaller`) with headings kept full size via undo-smaller-font-size; - blockquote restyle (left accent border, muted, not italic-centered); - kbd keycaps (shift_to_dark background); slide-number (muted, bg-aware); - figure captions; multi-column gutters; ordered-list type= + task-list checkboxes; edge nav-control spacing (we default controlsLayout: edges); - link weight/decoration; --r-*-code-font custom properties. Deferred to Stage D (need markup/consumer/plugins): light/dark sentinel, footer/logo, panels/tabsets, callouts, code-annotation, tippy. Verified: reveal_theme_test quick_wins_compile_and_emit + shift_to_dark_picks_dark_value_on_dark_theme; full quarto-sass+quarto-core suites; cargo xtask verify (incl. WASM build). E2E + Chrome confirmed code blocks, blockquote, kbd keycaps, .smaller scaling, and dark-background legibility render faithfully. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(revealjs): Stage D scope + defer light-dark/code-annotation/plugins to own strands Records the 2026-06-16 decision to defer three audit items to their own strands (bd-904h9kmt light/dark, bd-h176qcgp code-annotation, bd-buwhvpc2 plugins) and sets the remaining Stage D scope (callouts, brand, fancy title, footer/logo, auto-stretch, panels/tabsets, docs). Concrete decomposition to follow the prerequisite investigation. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(revealjs): concrete Stage D decomposition (D1-D5) from prerequisite investigation Callouts = pure SCSS (Q2 already emits .callout markup for reveal); brand = config plumbing (brand_to_layers already emits reveal vars); footer/logo + auto-stretch = new AST transforms. Fancy title slide (bd-tntzuvzl, blocked on author-model normalization) and panels/tabsets (bd-ewfppclm, greenfield) moved to their own strands. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(revealjs): port Quarto 1 callout styling into the reveal layer (Stage D1) Q2 already emits identical `.callout callout-{type} callout-style-{simple,default}` markup for reveal (CalloutTransform + CalloutResolveTransform run before the reveal branch), so this is a pure-SCSS port of Quarto 1 quarto.scss:873-1116 into quarto-revealjs.scss: - base callout rules (margins, border, titled/simple/default layouts, icon sizing, body/title typography); - the $callouts map (note/tip/warning/caution/important) with per-type accent color + inlined, URL-encoded Bootstrap-icon SVGs recolored via shift-color; - default-style title background adapted to light/dark slide bg via shift_to_dark. Added the $callout-* + $table-border-color defaults, the `sass:map` (quarto-map) @use, and a local `str-replace` function (Quarto 1 gets it from Bootstrap; the reveal layer has none). Stage D of bd-j8qoyc0s. TDD: callouts_compile_and_emit_per_type. E2E: rendered a deck with note/tip/warning callouts; Chrome confirms per-type colored borders, recolored icons, and titled vs simple layouts render faithfully. Full cargo xtask verify (incl. WASM) green. Note: SCSS-only, so callouts style correctly via `q2 render`; the q2-slides preview SPA still needs convergence (tracked) — it imports stock white.css and renders callouts as React leaves. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(revealjs): _brand.yml integration for reveal decks (Stage D2) Thread a resolved brand layer into the reveal theme compile so _brand.yml colors/typography reach a deck. Config plumbing only — brand_to_layers already emits the reveal vocabulary. - quarto-sass: new `resolve_brand_layers(config, runtime, base_dir, font_prefix)` — extracts the `brand:` ref, resolves it to a typed Brand, and returns its SCSS layers, independent of Bootstrap `theme:` parsing (which rejects reveal theme names). - quarto-core: the reveal branch of CompileThemeCssStage resolves brand against the project dir and appends the brand layers LAST (highest priority) to the theme layers, so brand overrides the built-in theme. Relative `brand:` paths resolve against the project dir, mirroring the HTML path. - Fix: brand's reveal base-font mapping targeted reveal-5 camelCase `$mainFont`, a dead var under reveal 6; retarget to kebab `$main-font` (which the Quarto reveal layer reads). Brand colors already worked via `$primary`/`$link-color`. Stage D of bd-j8qoyc0s. TDD: revealjs_brand_yml_flows_into_theme (renders a deck with a _brand.yml, asserts --r-link-color + --r-main-font carry the brand values). Existing brand_layer/brand_compile tests still green. E2E: `q2 render` + inspection confirmed --r-link-color:#c00 and --r-main-font:Georgia. Full cargo xtask verify (incl. WASM) green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(revealjs): mark Stage D1 (callouts) + D2 (brand) complete in plan Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(revealjs): handoff for next session (Stage D3/D4/D5 continuation) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(revealjs): update handoff — D1/D2 merged to feature, agent branches off feature Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(revealjs): footer/logo via shared footer-generate + reveal render (Stage D3) Adds Quarto-1-style deck-level `footer:` / `logo:` to revealjs decks, built on the same generate/render-slot architecture the `format: html` chrome uses so the footer/logo entries sit on the same user-filter-manipulation surface as navbar/sidebar/TOC/footer. Architecture (generate = format-agnostic, render = format-specific): - Reuse the format-agnostic `FooterGenerateTransform` (`page-footer:` -> `navigation.footer`, already registered unconditionally). A reveal-scoped `RevealFooterAliasTransform` maps Q1's `footer:` -> `page-footer:` (when `page-footer:` is absent) BEFORE generate, so a bare Q1 `footer:` flows through the shared generate (string -> `center` region) with no reveal knowledge leaking into the generate step. - New `RevealFooterLogoTransform` (the reveal-specific render) reads `navigation.footer`'s `center` region -> `rendered.reveal.footer`, and reads `logo:` (no format-agnostic logo-generate exists) -> `rendered.reveal.logo`. Each render skips when its slot is already populated, so a filter/config can pre-populate the slot to override (tested). - `footer_render_stage(is_revealjs)` selects the format-specific footer-render stage, gating the html `FooterRenderTransform` off for reveal so the reveal render owns its own `rendered.reveal.*` slot (no collision with html's `rendered.navigation.footer` skip-hook). This is a deliberately small, localized first step toward format-driven pipeline composition. - The scaffold (`render_revealjs_document`) reads the slots and places the footer/logo as direct children of `.reveal`, OUTSIDE `.slides`. This matters: reveal applies CSS `transform` to `.slides`/`<section>` (verified in vendored `reveal.css`), under which a `position: fixed` element resolves against the transformed ancestor, not the viewport — so a fixed deck-level footer must live outside `.slides`. Q1 relocates it at runtime via its quarto-support plugin; Q2 ships no plugin and places it statically. `.reveal` gains `has-logo` (drives slide-number repositioning). - SCSS: footer colors (muted + `.has-dark/light-background` variants, quarto.scss:570-592) + footer/logo positioning + responsive sizing (plugins/support/footer.css) in quarto-revealjs.scss. The bg-aware footer recoloring is DORMANT without a plugin (Q1 toggles those classes per-slide); ported for forward-compat (bd-buwhvpc2). Out of scope (plugin/other-strand territory): per-slide footers, `data-footer="false"` overrides, runtime relocation, brand/object-form logos, left/right footer regions for reveal. End-to-end user Lua filter support for reveal is split to its own phase (bd-ocxnva1f). TDD throughout. No snapshot files changed; no hub-client changes. Verified: full `cargo xtask verify --skip-hub-tests` (incl. WASM build) green; E2E through `q2 render` + Chrome confirmed footer/logo `position: fixed`, direct children of `.reveal` outside `.slides`, footer link rendered. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(revealjs): auto-stretch single-image slides (Stage D4) Ports Quarto 1's auto-stretch (`format-reveal.ts` `applyStretch`) so a lone slide image fills the available vertical space instead of overflowing. New `RevealAutoStretchTransform` (registered in the reveal branch after `RevealFootnotesTransform`) adds reveal's core `.r-stretch` class to the image — a pure AST transform; `.r-stretch` is reveal-core CSS, no Quarto SCSS. Default-on, gated by `auto-stretch: false` (Q1's schema default is true). Scope is deliberately conservative — narrower than Q1: - Stretches only when the slide body (ignoring the heading) is exactly ONE block: a `Paragraph` wrapping a single `Image`, or a `Figure` wrapping a single image. Q1 also stretches a lone image on text-bearing slides; we don't, avoiding any collision with slide text. - No DOM hoisting. Q1 moves the `<img>` to be a direct child of `<section>` (and re-inserts the figure caption); Chrome E2E confirmed reveal sizes the nested `<p><img class=r-stretch>` correctly without it, so we skip that complexity for the single-image case. - Opt-outs ported: `.nostretch` on the slide or the image (image class is consumed), `.absolute`, already `.stretch`/`.r-stretch`, and explicit sizing. Q1 guards only `height`; we also guard `width` (attr or inline style) so an author who sized an image is never overridden — a small conservative divergence. TDD: 11 unit tests + integration `revealjs_auto_stretch_single_image_slides` (lone image stretches; sized / inline-among-text / `auto-stretch: false` do not). E2E: `q2 render` + Chrome confirmed the lone image fills the slide (350px of an 816px container) while a `width=200` image stays small. Preview-parity caveat: the transform runs only in the `is_revealjs` branch, which the `q2-slides` preview pseudo-format does not enter, so the preview does not auto-stretch (same gap class as D3). No SCSS, snapshot, or hub-client changes. Full `cargo xtask verify --skip-hub-tests` (incl. WASM build) green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(revealjs): stretch a lone standalone image even amid slide text (D4 refinement) The first auto-stretch cut (Stage D4) stretched only when the slide body — ignoring the heading — was exactly ONE block. Writing the D4 documentation example surfaced that this is too narrow: the natural "heading + a sentence of explanation + a diagram" slide did not stretch, even though Quarto 1's `applyStretch` stretches it. Relax the rule to match Q1: a slide qualifies when it holds exactly one image (peripheral `.notes`/`.aside` content aside), carries no `.aside`, and that image sits in a *standalone* top-level block — a `Paragraph` whose only inline is the image, or a `Figure`. Sibling blocks (a heading, explanatory paragraphs) are now allowed; reveal sizes the image to the space they leave. An image among inline text, nested in a `.column`/layout/fragment div, or a multi-image slide is still skipped. Surfaced by end-to-end review of the docs example, per the project's "verify through the real artifact" practice. TDD: updated the unit tests (lone-image-with-sibling-text now stretches; added two-images / aside-present / image-in-column skip guards) and added an integration assertion that a standalone image beside text stretches. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(revealjs): document themes, footer/logo, and auto-stretch with examples (Stage D5) Documents three reveal features in docs/presentations/revealjs/index.qmd, each following the established prose + minimal-example + `.embed-example-iframe` pattern, backed by new runnable example projects: - `examples/presentations/09-themes` — `theme:` selection + built-in aliases. - `examples/presentations/10-footer-logo` — deck-level `footer:` and `logo:`. - `examples/presentations/11-auto-stretch` — auto-stretch single-image slides. Also: - Register the three in `examples/manifest.yml` and the examples README table. - Fix the page's stale callout — it claimed themes and transitions are not available, but Stage A–D shipped both (and callouts, brand, footer/logo, auto-stretch). - Extend `cargo xtask stage-doc-examples` to stage top-level image assets (png/jpg/svg/…), so an example that references a logo or figure has the image copied into the docs embed alongside its HTML. Image-less examples are unaffected. Unit tests for the extension predicate. Doc prose written with the reader-expectations methodology (Gopen & Swan, "The Science of Scientific Writing"). Scope note: the deep theming-variables guide ($presentation-*/$body-* vars, custom themes, _brand.yml, the --r-* runtime escape hatch) and the Quarto-1 theme migration how-to are deferred to a dedicated theming page (own pass). E2E: `q2 render docs/` builds the page (the lone render error is a pre-existing broken link in brand.qmd, unrelated); Chrome confirmed the new sections render and the footer/logo demo iframe shows the live deck with its footer and logo. The staged `docs/examples/` output is gitignored (regenerate with `cargo xtask stage-doc-examples`). No hub-client changes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent e35f01b commit ff31c8e

56 files changed

Lines changed: 5557 additions & 149 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

claude-notes/plans/2026-06-16-revealjs-themes-q1-to-q2.md

Lines changed: 848 additions & 0 deletions
Large diffs are not rendered by default.

crates/quarto-core/src/pipeline.rs

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1057,6 +1057,24 @@ fn capture_untransformed_ast_json(content: &[u8], source_name: &str) -> Option<S
10571057
/// 18. `AppendixStructureTransform` - Consolidate appendix content into container
10581058
/// 19. `CrossrefRenderTransform` - Resolve crossref custom nodes to final HTML structure
10591059
/// 20. `ResourceCollectorTransform` - Collect image dependencies
1060+
/// Select the format-specific footer-render stage.
1061+
///
1062+
/// Footer *generation* is format-agnostic (`FooterGenerateTransform` →
1063+
/// `navigation.footer`); footer *rendering* is not. `format: html` emits
1064+
/// page-footer chrome into `rendered.navigation.footer`; `format: revealjs`
1065+
/// emits a deck-level `.footer`/`.slide-logo` into `rendered.reveal.*` (and so
1066+
/// must *not* run the html render, whose "skip if slot populated" hook keys a
1067+
/// different slot). This is the one place that maps format → render stage; see
1068+
/// the call site for why it's the seam a future format-driven composition layer
1069+
/// would grow from.
1070+
fn footer_render_stage(is_revealjs: bool) -> Box<dyn crate::transform::AstTransform> {
1071+
if is_revealjs {
1072+
Box::new(crate::revealjs::RevealFooterLogoTransform::new())
1073+
} else {
1074+
Box::new(FooterRenderTransform::new())
1075+
}
1076+
}
1077+
10601078
pub fn build_transform_pipeline(
10611079
shortcode_paths: Vec<std::path::PathBuf>,
10621080
extensions: Vec<crate::extension::types::Extension>,
@@ -1107,6 +1125,11 @@ pub fn build_transform_pipeline(
11071125
// construction (the column Divs are still flat at this point).
11081126
pipeline.push(Box::new(crate::revealjs::RevealColumnsTransform::new()));
11091127
pipeline.push(Box::new(crate::revealjs::RevealSlidesTransform::new()));
1128+
// Alias Quarto-1's reveal `footer:` → `page-footer:` so it flows
1129+
// through the format-agnostic `FooterGenerateTransform` below. Must run
1130+
// before that generate (and it only touches metadata, so its position
1131+
// relative to slide construction is irrelevant).
1132+
pipeline.push(Box::new(crate::revealjs::RevealFooterAliasTransform::new()));
11101133
} else {
11111134
pipeline.push(Box::new(TitleBlockTransform::new()));
11121135
pipeline.push(Box::new(SectionizeTransform::new()));
@@ -1118,6 +1141,11 @@ pub fn build_transform_pipeline(
11181141
// `Div#footnotes`), so it must run *after* it. Pure AST → benefits
11191142
// render and preview alike. See `revealjs::footnotes`.
11201143
pipeline.push(Box::new(crate::revealjs::RevealFootnotesTransform::new()));
1144+
// Auto-stretch single-image slides (add reveal's `.r-stretch`). Runs
1145+
// *after* footnote/aside coalescing so a slide that gained a coalesced
1146+
// aside has >1 body block and is correctly skipped. Default-on, gated
1147+
// by `auto-stretch: false`. See `revealjs::auto_stretch`.
1148+
pipeline.push(Box::new(crate::revealjs::RevealAutoStretchTransform::new()));
11211149
}
11221150
// Example-iframe embeds (bd-z1smhvuo / bd-t3cert81). Sugars
11231151
// `Div.embed-example-iframe[file=…]` into a `CustomNode("ExampleEmbed")`.
@@ -1209,7 +1237,14 @@ pub fn build_transform_pipeline(
12091237
pipeline.push(Box::new(NavbarRenderTransform::new()));
12101238
pipeline.push(Box::new(SidebarRenderTransform::new()));
12111239
pipeline.push(Box::new(PageNavRenderTransform::new()));
1212-
pipeline.push(Box::new(FooterRenderTransform::new()));
1240+
// Footer *generation* (above) is format-agnostic; footer *rendering* is
1241+
// format-specific — html emits page-footer chrome, revealjs emits a
1242+
// deck-level `.footer`/`.slide-logo` into `rendered.reveal.*`. Selecting the
1243+
// render stage by format here (rather than scattering `is_revealjs` checks)
1244+
// is a deliberately small first step toward format-driven pipeline
1245+
// composition: as more "non-`html` HTML formats" appear, this is the seam
1246+
// where a format → stage-sequence mapping would grow.
1247+
pipeline.push(footer_render_stage(is_revealjs));
12131248

12141249
// === FINALIZATION PHASE ===
12151250
// LinkRewriteTransform runs first in the Finalization Phase

0 commit comments

Comments
 (0)