Skip to content

fix(revealjs): resolve cross-references + enforce a transform-phase ordering invariant#316

Merged
cscheid merged 4 commits into
mainfrom
feature/bd-w0c6d38k-revealjs-crossref
Jun 19, 2026
Merged

fix(revealjs): resolve cross-references + enforce a transform-phase ordering invariant#316
cscheid merged 4 commits into
mainfrom
feature/bd-w0c6d38k-revealjs-crossref

Conversation

@cscheid

@cscheid cscheid commented Jun 18, 2026

Copy link
Copy Markdown
Member

Summary

Cross-references were unresolved under format: revealjs: @fig-1 rendered as ?fig-1? and sibling figures were mis-numbered. Root cause was a structural pipeline inversion — the revealjs RevealAutoStretchTransform (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 ![cap](x){#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

  • Adds a TransformPhase enum (Normalization < Crossref < Navigation < Finalization < Unclassified) and a defaulted phase() on the AstTransform trait; classifies all 49 render-pipeline transforms.
  • New format-neutral test test_build_transform_pipeline_phase_ordering (loops over format strings, no is_revealjs branch) asserts no in-pipeline transform is Unclassified and 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

  • Adds reveal_finalization_transforms(is_revealjs) (the presentation-slot sibling of footer_render_stage) and moves RevealAutoStretchTransform into Finalization, after CrossrefRenderTransform. The figure is now numbered/resolved/rendered first, then hoisted to section > img.r-stretch.
  • Side benefit: div-form crossref figures (::: {#fig-…}) on single-image slides now stretch too.

Part 1 — bare-table caption crossref (bd-4ly7ne01)

  • : caption {#tbl-data} parses to a bare Block::Table that the float classifier never saw (unresolved in HTML and revealjs). maybe_wrap_bare_table_into_div wraps it into the canonical Div(#tbl-…) > Table so the existing uniform classifier handles it.

Docs

  • Documents the phase model + the format-agnostic-before-presentation invariant in CLAUDE.md → Architecture Notes and claude-notes/designs/transform-pipeline-phases.md.

Verification

  • End-to-end: q2 render examples/presentations/14-crossrefs/slides.qmd now emits no warning; @fig-1→Figure 1, @fig-2→Figure 2, both r-stretch direct section children. Bare-table @tbl-data→"Table 1" in HTML and revealjs.
  • Added regression tests: revealjs_crossref_attribute_figure_resolves_and_stretches, fixture_bare_table_caption_target.
  • Full quarto-core suite (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-build run 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

  • bd-uwv2eec2 — attribute-form figure ![cap](x){#fig-N} lacks the "Figure N:" caption-number prefix (pre-existing; reproduces in plain HTML).
  • bd-hbloemff — revealjs auto-stretch visual parity for crossref figures in interactive preview.

🤖 Generated with Claude Code

cscheid and others added 4 commits June 18, 2026 12:48
…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 `![cap](x){#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>
@cscheid cscheid merged commit 639accc into main Jun 19, 2026
5 checks passed
@cscheid cscheid deleted the feature/bd-w0c6d38k-revealjs-crossref branch June 19, 2026 16:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant