From 7cf160706e092a339f079da2d5565bfbbe4665f4 Mon Sep 17 00:00:00 2001 From: Christophe Dervieux Date: Wed, 10 Jun 2026 14:30:44 +0200 Subject: [PATCH 1/4] Fix empty blockquote crash in revealjs output (#14585) `#b.content` returns 0 for an empty blockquote, and 0 is truthy in Lua (only nil and false are falsy). The subsequent `b.content[1].t` then indexes nil, crashing the render. Guard with `> 0`. --- src/resources/filters/quarto-post/reveal.lua | 2 +- .../smoke-all/revealjs/empty-blockquote.qmd | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 tests/docs/smoke-all/revealjs/empty-blockquote.qmd diff --git a/src/resources/filters/quarto-post/reveal.lua b/src/resources/filters/quarto-post/reveal.lua index 4f3dc6c2940..ae7971ccc71 100644 --- a/src/resources/filters/quarto-post/reveal.lua +++ b/src/resources/filters/quarto-post/reveal.lua @@ -61,7 +61,7 @@ function render_reveal_fixups() -- Prevent BulletList in blockquote to be made incremental with .fragment class -- https://github.com/quarto-dev/quarto-cli/issues/7715 BlockQuote = function(b) - if #b.content and b.content[1].t == "BulletList" then + if #b.content > 0 and b.content[1].t == "BulletList" then b.content = pandoc.Div(b.content, pandoc.Attr('', {'blockquote-list-scaffold'})) return b end diff --git a/tests/docs/smoke-all/revealjs/empty-blockquote.qmd b/tests/docs/smoke-all/revealjs/empty-blockquote.qmd new file mode 100644 index 00000000000..47cb1a18f31 --- /dev/null +++ b/tests/docs/smoke-all/revealjs/empty-blockquote.qmd @@ -0,0 +1,18 @@ +--- +title: "Empty blockquote" +format: revealjs +_quarto: + tests: + revealjs: + ensureHtmlElements: + - ["blockquote"] + noErrors: [] +--- + +## Slide with empty blockquote + +> + +## Slide with content blockquote + +> This is a normal blockquote From ab57e5271049ff842c716767f4251c066cad59dd Mon Sep 17 00:00:00 2001 From: Christophe Dervieux Date: Wed, 10 Jun 2026 14:37:02 +0200 Subject: [PATCH 2/4] Use quarto.utils.match() for safe content type checking Replaces manual `#b.content > 0 and b.content[1].t` guard with `quarto.utils.match("[1]/BulletList")`, which handles nil/empty content internally. --- src/resources/filters/quarto-post/reveal.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/resources/filters/quarto-post/reveal.lua b/src/resources/filters/quarto-post/reveal.lua index ae7971ccc71..de21831cde3 100644 --- a/src/resources/filters/quarto-post/reveal.lua +++ b/src/resources/filters/quarto-post/reveal.lua @@ -61,7 +61,7 @@ function render_reveal_fixups() -- Prevent BulletList in blockquote to be made incremental with .fragment class -- https://github.com/quarto-dev/quarto-cli/issues/7715 BlockQuote = function(b) - if #b.content > 0 and b.content[1].t == "BulletList" then + if quarto.utils.match("[1]/BulletList")(b) then b.content = pandoc.Div(b.content, pandoc.Attr('', {'blockquote-list-scaffold'})) return b end From 5c0c01bb740e20b8c4aef45941bba51eb59b1cf1 Mon Sep 17 00:00:00 2001 From: Christophe Dervieux Date: Wed, 10 Jun 2026 14:43:35 +0200 Subject: [PATCH 3/4] docs: document quarto.utils.match() in Lua filter rules Add AST pattern matching section covering selector syntax, nil-safety, and preference over manual .content[1].t checks. --- .claude/rules/filters/lua-development.md | 31 ++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/.claude/rules/filters/lua-development.md b/.claude/rules/filters/lua-development.md index 2bb1ab3a166..19a5ff89cb9 100644 --- a/.claude/rules/filters/lua-development.md +++ b/.claude/rules/filters/lua-development.md @@ -79,6 +79,36 @@ if node.is_custom_node then end ``` +### AST Pattern Matching with `quarto.utils.match` + +Use `quarto.utils.match()` for safe structural checks on AST nodes. It handles nil/empty content internally — no manual length or nil guards needed. Defined in `src/resources/pandoc/datadir/_utils.lua`. + +```lua +-- ✅ Correct - safe, handles empty content +if quarto.utils.match("[1]/BulletList")(b) then ... + +-- ❌ Wrong - crashes on empty content (0 is truthy in Lua) +if #b.content and b.content[1].t == "BulletList" then ... + +-- ❌ Fragile - manual nil guard, verbose +if #b.content > 0 and b.content[1].t == "BulletList" then ... +``` + +Selector syntax supports `/` child traversal, `[n]` nth-child, `{Type}` capture, `:child`/`:descendant` search, and CSS-like class matching: + +```lua +-- Check nested structure +quarto.utils.match("Figure/[1]/Plain/[1]/Image")(fig) + +-- Capture matched nodes (returned in a list) +quarto.utils.match("[1]/{Plain}")(content) + +-- Search direct children for class +quarto.utils.match(".cell-output-display/:child/{Para}")(div) +``` + +Prefer `match()` over manual `.content[1].t ==` checks — it's nil-safe, readable, and already used in 20+ filter callsites. + ### Slot Assignment Use the proxy pattern for slot modification: @@ -285,3 +315,4 @@ These type definition files document the complete API surface. 8. **Gate format inside element fns** - Constructor runs before format is resolved 9. **Detect meta shape with `quarto.utils.type`** - Meta values have no `.t` tag 10. **Gate expensive filters with `flags`** - Skips the whole pass when unneeded +11. **Use `quarto.utils.match()` for content checks** - Nil-safe, prefer over manual `.content[1].t` From 0b873983a69689ffa4eee158717d18dd74742c29 Mon Sep 17 00:00:00 2001 From: Christophe Dervieux Date: Wed, 10 Jun 2026 16:06:40 +0200 Subject: [PATCH 4/4] Add changelog entry for empty blockquote revealjs crash (#14585) --- news/changelog-1.10.md | 1 + 1 file changed, 1 insertion(+) diff --git a/news/changelog-1.10.md b/news/changelog-1.10.md index 529b08cc4e8..374920714e4 100644 --- a/news/changelog-1.10.md +++ b/news/changelog-1.10.md @@ -39,6 +39,7 @@ All changes included in 1.10: ### `revealjs` - ([#14354](https://github.com/quarto-dev/quarto-cli/pull/14354)): Fix trailing whitespace after author name on title slide when ORCID is not set. (author: @jnkatz) +- ([#14585](https://github.com/quarto-dev/quarto-cli/issues/14585)): Fix empty blockquote (`> `) crashing render for revealjs format. ## Projects