Custom syntax highlighting now works in Quarto Hub projects#187
Merged
Conversation
The hub-client's project-rendering path was dropping user_grammars
because RenderToHtmlRenderer constructed its own per-page
RenderContext with no API for attaching a provider. Result: any qmd
under a `_quarto.yml` ancestor rendered fenced blocks for
user-grammar languages (e.g. the highlighting/03-user-grammar-toml
fixture) unhighlighted, even though the single-file render path was
wired correctly.
Migrate `RenderContext::user_grammar_provider` and the matching
`StageContext` field from `Option<Box<dyn UserGrammarProvider>>` to
`Option<Rc<RefCell<dyn UserGrammarProvider>>>` so a single handle
can be shared across every page a renderer touches without being
consumed. Add `RenderToHtmlRenderer::with_user_grammars(...)` and
install the provider on each per-page `RenderContext`. Update the
WASM project entry point `render_project_active_page_to_response`
to wrap the incoming `JsUserGrammars` in `Rc<RefCell<_>>` and pass
it through instead of dropping it.
The pipeline-internal `.take()` becomes `.clone()` so the `Rc` is
shared with the `StageContext` rather than transferred — matters
for multi-page renders that reuse the same renderer.
Plan + investigation notes:
`claude-notes/plans/2026-05-10-thread-user-grammars-renderer.md`.
Tests: new `crates/quarto-core/tests/render_to_html_user_grammars.rs`
drives `RenderToHtmlRenderer` through the orchestrator with a stub
provider and asserts the rendered HTML carries the matching
`<span class="hl-...">` wrapper. Confirmed it fails at HEAD without
the wiring and passes after.
End-to-end harness fixes folded in (without these the
highlighting/03-user-grammar-toml.qmd e2e fixture can't actually
exercise the fix in a browser):
- smokeAllDiscovery.ts: detect binary fixture extensions (.wasm
grammars + common image/font types), read as bytes, base64-
encode, and tag `contentType: 'binary'` with a matching
mimeType so the Automerge sync client routes them as binary
documents instead of corrupting them through utf-8 decoding.
Add `contentType`/`mimeType` fields on `DiscoveredTest` and
forward them in smoke-all.spec.ts.
- previewExtraction.ts::getPreviewHtml: derive a userGrammars
context from VFS state (vfsListFiles + vfsReadFile /
vfsReadBinaryFile, with the /project/ prefix stripped to match
discoverUserGrammars's project-relative shape) so the re-render
matches what Preview.tsx → automergeSync produces in the live
iframe.
End-to-end verification (snippet from the iframe at assertion time):
<pre class="sourceCode toml" …><code><span class="hl-property">
<span class="hl-type">name</span> <span class="hl-operator">=</span>
<span class="hl-string">"example"</span></span> …
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.
I uncovered a failing test when running hub e2e tests on Playwright; Claude's fix sounds reasonable.
crates/quarto/tests/smoke-all/highlighting/03-user-grammar/03-user-grammar-toml.qmdSummary
_quarto/grammars/<lang>/only highlighted code blocks in single-file documents. The moment a qmd file lived under a_quarto.ymlancestor, the hub-client routed throughrenderPageInProject→render_project_active_page_to_response, which literally discarded the grammars handle (let _ = user_grammars;).user_grammarsthroughRenderToHtmlRendererto the per-pageRenderContext, soCodeHighlightStageconsults the sameJsUserGrammarshandle the single-file path was already wired for.RenderContext::user_grammar_provider(and the matchingStageContextfield) move fromOption<Box<dyn UserGrammarProvider>>toOption<Rc<RefCell<dyn UserGrammarProvider>>>so one handle can be shared across every page a renderer touches.highlighting/03-user-grammar-toml.qmdsmoke-all fixture can actually verify the fix in a browser: binary fixture loading (.wasmgrammars were being read as utf-8 and corrupted before reaching Automerge), andgetPreviewHtmlnow passes a VFS-deriveduserGrammarscontext so the re-render matches the live iframe.bd-izfv. Plan + investigation:
claude-notes/plans/2026-05-10-thread-user-grammars-renderer.md.Test plan
cargo build --workspacecargo nextest run --workspace— 8805 passedcargo nextest run -p quarto-core --test render_to_html_user_grammars— passes; confirmed failing with the wiring temporarily revertedcargo xtask verify— all 9 steps greennpx playwright test --grep "user-grammar" smoke-all.spec.ts— passes; iframe innerHTML at assertion time shows<pre class="sourceCode toml">…<span class="hl-string">"example"</span>(snippet recorded in the plan's "Phase 4 end-to-end verification artifact" section)Full
cargo xtask verify --e2ehad 6 unrelated CI-load timeout flakes — the kind documented inbc562bd4's commit message — all of which passed when re-run in isolation. The user-grammar fixture itself was the only e2e test directly relevant to this change.