Skip to content

Commit b9a60e7

Browse files
cscheidclaude
andcommitted
docs(pipeline): document the transform-phase invariant in CLAUDE.md + 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>
1 parent db3fc6b commit b9a60e7

3 files changed

Lines changed: 37 additions & 4 deletions

File tree

CLAUDE.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,10 @@ The render pipeline has a **profile checkpoint** between `MetadataMergeStage` an
366366

367367
Q2 emits HTML **directly from the Pandoc AST** and has **no post-Pandoc DOM-mutation stage**. Quarto 1, by contrast, leans heavily on DOM postprocessors (functions that parse the rendered HTML into a `Document` and mutate it — e.g. reveal's `applyStretch`, which hoists a stretched `<img>` to be a direct child of its `<section>`). **When porting such a feature, re-express it as an AST transform** (operate on `Block`/`Inline` so the writer emits the right HTML the first time) rather than adding a DOM postprocessor. A DOM-postprocessor stage is a large new architectural surface with no current consumer; do not introduce one without an extremely strong, explicitly-discussed reason. Worked example: revealjs auto-stretch unwraps a `Paragraph[Image]` into a `Plain[Image]` in `crates/quarto-core/src/revealjs/auto_stretch.rs` to get `section > img.r-stretch`, instead of mutating the DOM after the fact (bd-zkstclhl).
368368

369+
### Transform pipeline phases — format-agnostic semantics before format-specific presentation
370+
371+
The single transform pipeline (`build_transform_pipeline` in `crates/quarto-core/src/pipeline.rs`) runs every output format, splicing format-specific transforms in at chosen positions. It must visit transforms in **non-decreasing phase rank**: `Normalization` → `Crossref` → `Navigation` → `Finalization`. The hard rule: **a format-specific *presentation* transform that consumes semantic structure (a float, caption, number, or resolved `@ref`) must run in `Finalization`, after `crossref-render` — never earlier.** Each `AstTransform` declares its phase via `fn phase(&self) -> TransformPhase` (defaults to `Unclassified`; every pipeline member overrides it). The invariant is enforced by `test_build_transform_pipeline_phase_ordering`, which is format-neutral (loops over format strings, no `is_revealjs` branch). When adding a new format-specific late transform, add it via a named seam like `reveal_finalization_transforms`/`footer_render_stage`, not an inline `if is_revealjs { … }` at an arbitrary position. This is why a revealjs presentation transform (auto-stretch) once silently broke crossrefs by running before they were numbered (bd-w0c6d38k). Full contract + author rule + the preview-pipeline shape contract: `claude-notes/designs/transform-pipeline-phases.md`.
372+
369373
## hub-client Commit Instructions
370374

371375
**IMPORTANT**: When making commits that include changes to `hub-client/`, you MUST also update `hub-client/changelog.md`.

claude-notes/designs/transform-pipeline-phases.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
# Transform pipeline phases & the format-agnostic-first invariant
22

3-
**Status:** Design — proposed under bd-w0c6d38k (2026-06-18). The `phase()` trait
4-
method and the ordering-invariant test are **not yet implemented**; this document
5-
is the target contract they will enforce. Until then, treat the "Invariant" and
6-
"Enforcement" sections as the intended state, not the current one.
3+
**Status:** Implemented under bd-w0c6d38k (2026-06-18). `TransformPhase` and the
4+
defaulted `phase()` method live on `AstTransform` (`crates/quarto-core/src/transform.rs`);
5+
all render-pipeline transforms are classified; the invariant is enforced by
6+
`test_build_transform_pipeline_phase_ordering` in `pipeline.rs`. The first
7+
consumer of the seam is the revealjs auto-stretch move (auto-stretch now runs in
8+
`Finalization`, after `crossref-render`).
79

810
**Audience:** anyone adding or reordering an `AstTransform` in
911
`build_transform_pipeline` (`crates/quarto-core/src/pipeline.rs`), and especially

claude-notes/plans/2026-06-18-revealjs-crossref.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -420,6 +420,33 @@ Invocation: `cargo run --bin q2 -- render examples/presentations/14-crossrefs/sl
420420
asymmetry; reproduces in plain HTML (`<figcaption>A figure</figcaption>`), so it
421421
is **not** caused by the reorder. Fix belongs in `crossref_render.rs`.
422422

423+
## Part 1 sub-plan — bare-table caption desugar (bd-4ly7ne01) — DONE
424+
425+
`: caption {#tbl-…}` parses to a **bare `Block::Table`** with the id on the
426+
table's own `attr` (verified via `pampa -t native`:
427+
`Table ("tbl-bare",[],[]) (Caption Nothing [Plain …])`). The uniform
428+
`classify_div`/`convert_div` path never saw it.
429+
430+
Fix (matches "all syntaxes desugar into divs"): a `maybe_wrap_bare_table_into_div`
431+
step at the top of `FloatRefTargetSugarTransform::transform_block` wraps such a
432+
table into `Div(#tbl-…) > Table` — id (and classes/kvs) move onto the Div, the
433+
Table is left anonymous (no duplicate id) — so the existing `[Block::Table(_)]`
434+
arm of `convert_div` handles it. Integrating it inside the sugar transform means
435+
it runs everywhere the transform runs (full pipeline, analysis pipeline, fixtures)
436+
with no new registration.
437+
438+
### Part 1 checklist
439+
440+
- [x] 1.1 Confirm AST shape (`pampa -t native`): id on bare `Block::Table`.
441+
- [x] 1.2 Tests first: `fixture_bare_table_caption_target` in `crossref_fixtures.rs`
442+
— confirmed **RED** (`idx.get("tbl-bare")` = None).
443+
- [x] 1.3 Implement `maybe_wrap_bare_table_into_div` in `float_ref_target.rs`.
444+
- [x] 1.4 Fixture **GREEN**; index entry `("tbl-bare","tbl",vec![],1)`.
445+
- [x] 1.5 End-to-end: rendered two bare-caption tables + `@tbl-data`/`@tbl-two`
446+
in HTML **and** revealjs — both resolve to `Table 1`/`Table 2`, no
447+
unresolved placeholders.
448+
- [x] 1.6 Full `cargo nextest run -p quarto-core`**2386 passed**.
449+
423450
## Reference: key files
424451

425452
- Pipeline order: `crates/quarto-core/src/pipeline.rs:1078-1309`

0 commit comments

Comments
 (0)