fix(revealjs): resolve cross-references + enforce a transform-phase ordering invariant#316
Merged
Merged
Conversation
…ssref phase (bd-w0c6d38k)
Under `format: revealjs`, `RevealAutoStretchTransform` ran in the
Normalization phase — before float-sugaring and the crossref phase — so on a
single-image slide it hoisted an attribute-form figure `{#fig-1}`
(a Pandoc `Figure`) to a bare `<img>` before it was ever registered as a
crossref float. Result: `@fig-1` rendered unresolved (`?fig-1?`) and the
sibling `{#fig-2}` div was mis-numbered "Figure 1".
Root cause is a structural inversion: a format-specific *presentation*
transform that consumes semantic structure ran ahead of the format-agnostic
*semantic normalization* that produces it. Fix + guardrail:
Part 5 (guardrail): add a `TransformPhase` enum (Normalization < Crossref <
Navigation < Finalization < Unclassified) and a defaulted `phase()` on the
`AstTransform` trait; classify all 49 render-pipeline transforms. A new
format-neutral test, `test_build_transform_pipeline_phase_ordering`, loops
over the supported format strings (no `is_revealjs` branch) and asserts no
in-pipeline transform is `Unclassified` and that phase ranks are
non-decreasing by position. This test was RED on the old ordering and is the
red/green test for the fix below.
Part 2 (fix): add `reveal_finalization_transforms(is_revealjs)` — the
presentation-slot sibling of `footer_render_stage` — and move
`RevealAutoStretchTransform` out of Normalization into Finalization, spliced
in after `CrossrefRenderTransform`. The figure is now numbered/resolved/
rendered to a real `Figure` first, then hoisted to `section > img.r-stretch`.
Side benefit: div-form crossref figures (`::: {#fig-…}`) on single-image
slides now stretch too, since auto-stretch finally sees the rendered `Figure`.
Adds an end-to-end regression test
(`revealjs_crossref_attribute_figure_resolves_and_stretches`). Full
quarto-core suite passes (2385). Design:
claude-notes/designs/transform-pipeline-phases.md; plan:
claude-notes/plans/2026-06-18-revealjs-crossref.md.
Discovered, filed separately (pre-existing, reproduces in plain HTML, NOT
caused by this reorder): bd-uwv2eec2 — attribute-form figure caption lacks
the "Figure N:" number prefix.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…at Div (bd-4ly7ne01)
The `: caption {#tbl-…}` pipe-table syntax parses to a *bare* `Block::Table`
with the crossref id on the table's own attr — no wrapping div — so the
uniform `classify_div`/`convert_div` path never saw it, and `@tbl-…` rendered
unresolved (`?tbl-…?`) in both HTML and revealjs.
`maybe_wrap_bare_table_into_div`, run at the top of
`FloatRefTargetSugarTransform::transform_block`, wraps such a table into the
canonical `Div(#tbl-…) > Table` shape (id + classes/kvs move onto the Div, the
Table is left anonymous to avoid a duplicate id), so the existing
`[Block::Table(_)]` arm of `convert_div` handles it exactly like the
`::: {#tbl-…}` authoring form. Integrating it inside the sugar transform means
it runs everywhere that transform runs (full pipeline, analysis pipeline,
fixtures) with no new registration.
Adds `fixture_bare_table_caption_target` (index-level, RED before / GREEN
after). Verified end-to-end through the binary: two `: caption {#tbl-…}` tables
+ `@tbl-data`/`@tbl-two` resolve to "Table 1"/"Table 2" in both HTML and
revealjs. Full quarto-core suite: 2386 passed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… plan/design Now that `phase()` + the ordering invariant are implemented (bd-w0c6d38k), link the phase model from CLAUDE.md → Architecture Notes (next to the no-DOM-postprocessor and document-profile entries), flip the design doc's status to "implemented", and check off the Part 1/2/5 work in the plan. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…cs done Investigation found the q2-preview React renderer already renders crossref custom nodes correctly (FloatRefTarget/CrossrefResolvedRef/Theorem/Proof/ Equation; 53 integration tests pass), and Part 2 additionally fixed preview's attribute-form figure crossref. nbsp link-text parity is already correct (byte-confirmed). Only visual revealjs auto-stretch parity remains, deferred to bd-hbloemff. bd-zecehtnc closed as semantics-done. 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.
Summary
Cross-references were unresolved under
format: revealjs:@fig-1rendered as?fig-1?and sibling figures were mis-numbered. Root cause was a structural pipeline inversion — the revealjsRevealAutoStretchTransform(a format-specific presentation transform) ran in the Normalization phase, before float-sugaring and the crossref phase, so on a single-image slide it hoisted an attribute-form figure{#fig-1}to a bare<img>before it was ever registered as a crossref float.This PR fixes the bug, adds a guardrail so it can't recur, and closes an adjacent table-crossref gap.
Part 5 — phase-ordering guardrail
TransformPhaseenum (Normalization<Crossref<Navigation<Finalization<Unclassified) and a defaultedphase()on theAstTransformtrait; classifies all 49 render-pipeline transforms.test_build_transform_pipeline_phase_ordering(loops over format strings, nois_revealjsbranch) asserts no in-pipeline transform isUnclassifiedand phase ranks are non-decreasing by position. This test was RED on the old ordering — it's the red/green test for the fix.Part 2 — the fix
reveal_finalization_transforms(is_revealjs)(the presentation-slot sibling offooter_render_stage) and movesRevealAutoStretchTransformintoFinalization, afterCrossrefRenderTransform. The figure is now numbered/resolved/rendered first, then hoisted tosection > img.r-stretch.::: {#fig-…}) on single-image slides now stretch too.Part 1 — bare-table caption crossref (bd-4ly7ne01)
: caption {#tbl-data}parses to a bareBlock::Tablethat the float classifier never saw (unresolved in HTML and revealjs).maybe_wrap_bare_table_into_divwraps it into the canonicalDiv(#tbl-…) > Tableso the existing uniform classifier handles it.Docs
CLAUDE.md→ Architecture Notes andclaude-notes/designs/transform-pipeline-phases.md.Verification
q2 render examples/presentations/14-crossrefs/slides.qmdnow emits no warning;@fig-1→Figure 1,@fig-2→Figure 2, bothr-stretchdirect section children. Bare-table@tbl-data→"Table 1" in HTML and revealjs.revealjs_crossref_attribute_figure_resolves_and_stretches,fixture_bare_table_caption_target.quarto-coresuite (2386) + workspace tests +cargo xtask verify --skip-hub-build(-D warnings) pass locally. The WASM/hub leg is left to CI (a local--skip-hub-buildrun hit a pre-existing stale-WASM drift-guard unrelated to this change; CI rebuilds WASM fresh).Interactive preview (bd-zecehtnc)
Crossref semantics already render correctly in the q2-preview React renderer (numbering, links, captions; 53 integration tests pass), and Part 2 fixed the attribute-form figure there too. Only visual auto-stretch parity for crossref figures in preview remains, deferred to bd-hbloemff.
Follow-ups filed
{#fig-N}lacks the "Figure N:" caption-number prefix (pre-existing; reproduces in plain HTML).🤖 Generated with Claude Code