reveal.js: link shared assets (render) + preview/render parity; embedded-example iframes#271
Merged
Merged
Conversation
…ame (bd-z1smhvuo) Phase 1 of the example-iframe embed feature. A built-in AST transform rewrites `Div.embed-example-iframe[file=…]` placeholders into a live `<iframe class="embed-example-iframe" src=…>` plus the author's source link as a caption. Runs in the shared normalization pipeline, so render and q2-preview, HTML and revealjs all get it. Static-asset-only contract: `file=` must name a pre-rendered static asset. A `.qmd` (or other source doc) is rejected with a diagnostic and the placeholder degrades to just its fallback link — keeping the page useful and dodging iframe self-recursion. Structured error codes (project subsystem): - Q-5-4 Example Embed Missing `file=` - Q-5-5 Example Embed Target Is Not a Static Asset Catalog entries, docs/errors/project pages + sidebar, and a catalog-registration test included. Verified end-to-end through `q2 render` (HTML and --to revealjs). Workspace build + quarto-core + quarto-error-reporting suites green (2314 tests). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…onvention findings) for bd-z1smhvuo Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…e docs (bd-z1smhvuo) Phase 2 of the example-iframe embed feature. Staging (xtask + manifest): - examples/manifest.yml: explicit allow-list of example projects to stage (no globs, so a stray dir is never rendered/published by accident). - cargo xtask stage-doc-examples: renders each manifest project with q2 and copies its static output (*.html + *_files/) into docs/examples/<entry>/. Generated + gitignored (docs/.gitignore), regenerated, never committed. - docs/_quarto.yml: project.resources: [examples] copies the staged tree into _site/examples/ so the iframes resolve on render. Page-relative iframe src (resolve_static_resource_href): - New helper in navigation_href.rs, sibling of resolve_doc_relative_href but for static assets: no ProjectIndex lookup, no .qmd diagnostic — normalize + page_url_for. ExampleEmbedTransform threads the page source + resolver and routes file= through it, so the emitted src is page-relative (../../examples/.../slides.html for a depth-2 page), NOT a host-absolute /examples/... that breaks under a deploy subpath. Mirrors how every other Quarto link is rewritten. Docs migration: the 8 placeholders in docs/presentations/revealjs/index.qmd now use .embed-example-iframe file="/examples/presentations/<name>/slides.html". Verified end-to-end through `q2 render docs/` and a real browser: all 8 iframes load real reveal decks via the ../../examples/... relative src; staged decks land under _site/examples/. cargo xtask verify --skip-hub-build green (2327 tests, fmt, -D warnings). Preview-mode (q2 preview) verification is pending a WASM rebuild and tracked in the plan. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…-t3cert81)
Make `.embed-example-iframe` a first-class cross-referenceable Quarto type.
When the div carries a `#demo-…` id, the example is auto-numbered "Demo N"
and prose can `@demo-…`-reference it — resolved through the same crossref
index/resolve/render machinery as figures and theorems.
`demo`/"Demo" is a new built-in ref-type, deliberately distinct from the
theorem-like `exm`/"Example" (which `@exm-` would collide with).
Architecture (sugar → CustomNode → render, mirroring callouts/floats):
- ExampleEmbedTransform is now a SUGAR step (runs before the theorem/float
sugar so a `#demo-…` div isn't claimed as a generic float): rewrites the
placeholder into CustomNode("ExampleEmbed"), populating the crossref triple
{ref_type,kind,identifier} only when `file=` validates AND the id is `demo-…`.
- The crossref core is unchanged — CrossrefIndex/Resolve/Render handle the node
generically via the triple (CrossrefRender leaves the unknown type-name
alone). Only the registry gains `("demo","Demo")`.
- New ExampleEmbedRenderTransform (finalization, after CrossrefRender): emits
the iframe (page-relative src) + a "Demo N: caption" line when numbered +
source link; container carries the `#demo-…` anchor. No id → plain embed
(Phase-1 parity).
The Phase-1/2 static-only contract (Q-5-4/Q-5-5) and page-relative src carry
over unchanged. Backward compatible: unnumbered embeds render exactly as before.
Tests: 12 unit (sugar + render) + 3 crossref integration fixtures (numbering,
exm-vs-demo counter independence, unnumbered-not-indexed). Verified end-to-end
through `q2 render` (HTML and revealjs) and in the real docs site
(`#demo-fragments` + `@demo-fragments`). `cargo xtask verify --skip-hub-build`
green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…pya2d) Revert the CLI-only disk approach (resolver vfs-branch + quarto-preview disk route) — it worked for q2 preview but can't serve a hub-client/Automerge project with no disk. Chosen instead: VFS-native fix (TS iframe post-processor falls back to the VFS source path when the artifact path misses), avoiding per-render Automerge duplication. New strand bd-kjrpya2d + plan; embed plan updated. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ath (bd-kjrpya2d) Option B, part 1. Teach the TS iframe post-processor (ts-packages/preview-renderer/src/utils/iframePostProcessor.ts) to inline `<iframe>` elements whose src is artifact-rooted (`/.quarto/project-artifacts/X`) via `srcdoc`, reading the bytes from the VFS — with a fallback to the source path `X` when the artifact path misses. Why the fallback: a static project asset (e.g. an `.embed-example-iframe` deck) is referenced by its artifact-rooted URL (page_url_for roots output hrefs there in VFS-root mode), but a static asset lives at its *source* path, not the rendered-artifact path — nothing copies a `resources:` asset under the artifact root in the WASM render. This is the VFS-native counterpart to how `q2 render` copies the resource into `_site/`, and it avoids the rejected per-render artifact-copy (which would re-duplicate the resource set into the VFS every render — see bd-q3bxnq2e). New helper `readArtifactOrSource`; 4 jsdom tests (source-path fallback, artifact preferred, external src untouched, both-miss left alone). Existing post-processor integration tests still green; package typecheck clean. Part 2 (get the staged deck into the preview VFS — broaden the q2-preview VFS sync beyond .qmd/config) is still needed for end-to-end; tracked on bd-kjrpya2d. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rt 2 scoped (bd-kjrpya2d) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…tion point; security strand bd-teh4hbli (bd-kjrpya2d) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rpya2d next) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…FS (bd-kjrpya2d) Make `.embed-example-iframe` decks render under `q2 preview` (part 2). The deck must be (a) present in the preview VFS source and (b) inlined by the renderer, since the in-browser SPA has no server to answer the iframe's `/examples/...` request. VFS sync (Rust). Resolve the `resources:`-scoped `.html` set in `quarto-preview` (which already depends on `quarto-core`), keeping `quarto-hub` free of an engine dep: - `config::resolve_project_resource_html` — `ProjectContext::discover` + `expand_patterns` (project scope), filtered to `.html`, returned as project-relative paths (best-effort; empty on any failure). - `PreviewConfig.resource_html_files` → `HubConfig.resource_files` → `ProjectFiles::with_resource_files`, a new TEXT-synced discovery category (`.html` decks ride the text reconcile path so the SPA's `vfsReadFile` can read them). The bare discovery walk never sees `.html`; the caller-resolved set is injected. Real-binary verified: `q2 preview docs/` logs `resource_count=8` and `Reconciled ... count=163`. Renderer (TS, q2-preview). Plan correction surfaced by the e2e run: `q2 preview` does NOT use `iframePostProcessor.ts` (that is the hub-client preview pane). q2-preview renders via `Q2PreviewIframe` + React `<Ast>`; the embed is a `RawBlock(html)` and assets resolve through the parent-side `assetWalker.ts` (previously Image-only): - `assetWalker` also collects `.embed-example-iframe` srcs from `RawBlock(html)`, reads the deck text via `vfsReadFile`, and mints a `text/html` blob URL into the asset manifest. - `blocks/RawBlock.tsx` rewrites the deck `<iframe src>` to that blob URL via `AssetManifestContext`. - shared scan/rewrite helpers in `q2-preview/embedIframe.ts`. `iframePostProcessor.ts` was also generalized (page-relative `/X` → VFS source) — kept because it is correct for the hub-client pane. E2E (Playwright vs live `q2 preview docs/`): all 8/8 `.embed-example- iframe` decks resolve to `blob:` srcs; `#demo-fragments` loads the real Fragments deck (reveal "FRAGMENTS" slide rendered); "Demo 1:" caption + `@demo-fragments` xref work; zero console errors. Tests: discovery (+3), config resolver (+4), embedIframe (+8), assetWalker (+3 embed), RawBlock (+4), iframePostProcessor (+6); full preview-renderer suite (202) green; `cargo xtask verify` green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Plan for making reveal.js emit linked shared assets (site_libs) instead of inlining ~700KB into every slides.html — mirroring how format:html handles vendored deps. Records the decisions from the design discussion (linked-only, no embed-resources opt-in yet, single theme for now, render-side scope, keep the Rust VFS-sync) and a Phase R to revert the inline-embedding behavior introduced by the embed-in-preview work. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…-jij5gge2 Phase R) Restores the preview-renderer TS to its `main` state, undoing the embed-in-preview work that inlined deck bytes into the preview HTML (blob URL / srcdoc / data URI). That approach only worked because reveal.js currently self-contains all assets into one HTML file; once reveal emits linked shared assets (bd-jij5gge2), decks reference site_libs/revealjs/... and must be SERVED, not inlined (revisited under bd-kjrpya2d). Reverted to main: - utils/iframePostProcessor.ts (drop <iframe> srcdoc inlining) - q2-preview/assetWalker.ts (Image-only; drop embed-deck blob URLs) - q2-preview/blocks/RawBlock.tsx (plain dangerouslySetInnerHTML) - q2-preview/assetWalker.test.ts Deleted (branch-only): - q2-preview/embedIframe.ts + test - q2-preview/blocks/RawBlock.test.tsx - utils/iframePostProcessor.embed.test.ts KEPT (per Q-R1, reusable by the future served approach): the Rust VFS-sync of resources-scoped .html (discovery resource_files category, HubConfig/PreviewConfig wiring, resolve_project_resource_html). It does not embed into HTML; it makes deck files available in the preview VFS. The .embed-example-iframe transform (bd-z1smhvuo) and crossref Demo blocks (bd-t3cert81) are untouched — they emit a plain <iframe src> reference (the docs feature), not resource embedding. preview-renderer: tsc clean, 181 tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…g (bd-jij5gge2)
reveal.js render no longer inlines ~700KB of reset/reveal/theme/quarto
CSS + reveal.js into every slides.html. Instead it registers them as
Project-scoped artifacts and links them, so a website with N decks
shares ONE copy under site_libs/revealjs/ — matching how format:html
handles bootstrap/themes. (The "q2 single binary" goal is unaffected:
the assets are still include_str!-embedded in the binary; only the
render-time *output* changes from inline to linked.)
- revealjs/assemble.rs:
- render_revealjs_document(body, meta, css_urls, js_urls) emits
<link>/<script src> in cascade order; only Reveal.initialize stays
inline (per-document config).
- reveal_assets(theme) + register_reveal_assets(store, theme): the
ordered asset set as artifacts (keys css:revealjs:<order> /
js:revealjs:reveal so collect_artifact_urls' sorted-key order ==
CSS cascade; paths revealjs/<file>; Project scope). Theme name is
resolved (unknown → white) so the key/filename stay stable for dedup.
- compile_theme_css.rs: the reveal branch (which already skips Bootstrap)
now registers the reveal assets instead of just early-returning.
- apply_template.rs: the reveal branch collects css:revealjs: / js:revealjs:
URLs via the resolver (never the Bootstrap css:theme:) and passes them
to the assembler.
Verified end-to-end via the real binary (q2 render talk.qmd → 1016-byte
HTML, 4 <link> + reveal.js <script>, assets at talk_files/revealjs/*).
Tests: assemble unit tests (link emission, artifact registration,
theme fallback); revealjs_format integration (links-not-inlined +
on-disk flush); artifact_scoping_pipeline (two-deck website shares one
site_libs/revealjs/ copy). Full quarto-core suite: 2260 passed.
Remaining (Phase 4): re-stage docs examples + full cargo xtask verify
(incl. WASM); preview/WASM direct-deck path not yet exercised.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…-jij5gge2) Phases 0-4 complete: render-side linking + dedup implemented, tested, and browser-verified on a static server. Full cargo xtask verify (incl WASM) green. Remaining: direct-deck preview ([Q-6]) and the served-iframe embed rework (bd-kjrpya2d). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ep React path (bd-ibqkf9ry) Converge q2 preview's reveal rendering with q2 render WITHOUT removing the React-component path (required for the collaborative-editing AST<->source mapping). Four prongs: (A) one enforced reveal version, (B) one CSS source (vendored resources/revealjs/), (C) CSS isolation — gate the HTML-theme/ Bootstrap injection off for reveal decks (the grey-line root), (D) transitions. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…bqkf9ry Prong A) q2 render embeds the vendored resources/revealjs/ copy (include_str!); q2 preview renders via @revealjs/react, which peer-depends on the npm reveal.js package. They must stay on the SAME version/bytes or the two pipelines drift. - Pin reveal.js -> 6.0.0 and @revealjs/react -> 0.2.0 (exact, drop ^) in the 3 package.json (root, hub-client, preview-renderer); regenerate lock. - Add assemble.rs test `vendored_reveal_assets_match_npm_package`: asserts the include_str!'d constants (reset/reveal/reveal.js/theme-white) are byte-identical to node_modules/reveal.js/dist/*. Fails if the vendored copy drifts from a future npm bump (re-sync per resources/revealjs/ README); skips gracefully when node_modules is absent. @revealjs/react@0.2.0 has reveal.js as a peerDependency (no bundled copy), so it uses the hoisted top-level reveal.js@6.0.0 — one engine for both. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ibqkf9ry Prongs B+C)
Two render/preview reveal divergences, both from the deck drawing on a
different CSS source/environment than `q2 render`:
Prong B — one CSS source. RevealDeck.tsx now imports the SAME vendored
resources/revealjs/{reset,reveal,theme/white,quarto-reveal}.css that
render links (in cascade order), instead of the npm reveal.js/*.css.
Adds the previously-missing reset.css. @revealjs/react injects no CSS of
its own, so this is a clean import swap. Combined with Prong A's sync
check, resources/revealjs/ is the single source of truth for both pipelines.
Prong C — same CSS environment (the grey-line fix). The q2-preview iframe
injected the compiled HTML-theme (Bootstrap) CSS via a data-q2-theme <link>
for EVERY document, so Bootstrap's `h2 { border-bottom: 1px solid #dedede }`
leaked onto reveal slide headings (a faint grey line render never shows).
entry.tsx now reconciles the theme link against the active format: it
remembers the last theme URL + whether the doc is a slide deck (the two
arrive on separate UPDATE_THEME/UPDATE_AST messages) and attaches the
link ONLY for non-slide docs. A format switch re-applies/removes it.
Verified in a fresh preview (rebuilt SPA+q2) vs the live render server:
every slide h2 borderBottom is now `0px none` (was `1px solid #dedede`),
no data-q2-theme link present, deck renders the white theme cleanly —
matching render. preview-renderer: tsc clean, 181 tests pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… by C (bd-ibqkf9ry) Root cause for BOTH symptoms is the Bootstrap CSS leaking into the reveal deck: h2 border-bottom (grey line) AND the prefers-reduced-motion reboot override (transition-duration .01ms !important) that killed transitions for reduced-motion users. Prong C (suppress the HTML-theme link for reveal decks) fixes both. Browser-verified. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…9ry) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…er-preview-convergence # Conflicts: # package-lock.json
…bd-kjrpya2d)
The `.embed-example-iframe` decks rendered blank in `q2 preview` with
module-script MIME errors: the iframe requests the deck at the artifact-
rooted path `/.quarto/project-artifacts/examples/.../slides.html`, but the
hub's SPA fallback returned `index.html` for every unmatched path — so the
iframe loaded the SPA shell, whose `<script type=module>` then 404'd→text/html.
Since the reveal linked-assets change (bd-jij5gge2) decks LINK their
`slides_files/…` rather than self-containing, so they must be SERVED (the
inline/srcdoc approach can't resolve the relative asset links and was reverted).
Add an axum route in the preview hub that serves the project's `resources:`-
declared files from disk at that artifact path:
- `config::resolve_project_resource_files` resolves the FULL resources: set
(deck HTML + every `slides_files/…` sidecar) to (output-relative → absolute
source) pairs.
- `RESOURCE_DISK_MAP` (OnceLock, populated once in `run()`) backs an
`/.quarto/project-artifacts/{*rest}` handler: serves a declared resource from
disk (correct content-type) or falls through to the SPA index — so it only
ever serves files in the `resources:` publish set (the bd-teh4hbli boundary)
and never shadows the existing VFS-served artifacts.
Scope: CLI/disk-only. Diskless hub-client embeds need the service-worker-over-
VFS (separate colleague workstream); tracked under bd-kjrpya2d.
Verified e2e: `q2 preview docs` → the #demo-fragments iframe loads & renders
the real Fragments deck (reveal initializes, nav arrow, "Demo 1" caption),
zero console errors; `curl` confirms slides.html + slides_files/revealjs/
reveal.js serve with correct MIME. config resolver unit-tested; full
quarto-preview suite (75) green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…(PR #271) The merge-conflict resolution regenerated package-lock.json via rm+npm install on macOS, which dropped the @rollup/rollup-linux-x64-gnu package entry (npm optional-deps bug npm/cli#4828) — breaking the WASM build on Linux CI. Restored origin/main's complete cross-platform lock and re-pinned reveal.js->6.0.0 / @revealjs/react->0.2.0 (the only intended delta; also realigns npm reveal.js with the vendored resources/revealjs 6.0.0 the sync test checks). All 25 rollup platform binaries retained. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…er-preview-convergence # Conflicts: # ts-packages/preview-renderer/src/q2-preview/entry.tsx
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.
Bundles the reveal.js + embedded-example work accumulated off
main(thebranch was rebuilt/merged onto current
origin/main). Opening to run CI onthe combination.
Highlights
reveal.js linked shared assets — render side (bd-jij5gge2)
q2 renderno longer inlines ~700 KB of reveal CSS/JS into everyslides.html.It registers the vendored
resources/revealjs/*asProject-scoped artifactsand links them, so a website with N decks shares one copy under
site_libs/revealjs/— matching howformat: htmlhandles bootstrap/themes.The "q2 single binary" property is unchanged (assets are still
include_str!-embedded; only the render output changed inline→linked).q2 preview reveal converged with render (bd-ibqkf9ry)
The React preview path (
RevealDeck.tsx+@revealjs/react) stays — it'srequired for the collaborative-editing AST↔source mapping — but the drift
is removed:
reveal.js→6.0.0+@revealjs/react→0.2.0exact, plus a test that fails if the vendored
resources/revealjs/ever driftsfrom the npm
reveal.js/dist.RevealDeck.tsximports the vendoredresources/revealjs/*(incl. the previously-missing
reset.css), not the npm copy.HTML-theme/Bootstrap CSS into reveal decks. That single leak caused both
visible bugs: a grey
h2 { border-bottom }line under slide titles, anddead transitions (Bootstrap reboot's
prefers-reduced-motionrule forcedtransition-duration: .01ms !important). Browser-verified parity with render.embedded-example iframes + cross-referenceable Demos (bd-z1smhvuo, bd-t3cert81)
::: {.embed-example-iframe file="…"}→ a live<iframe>to a staged exampledeck;
#demo-foo→ auto-numbered, cross-referenceable "Demo N" via the normalcrossref machinery. (The preview inline-embed experiment was reverted; the
deck must be served, tracked under bd-kjrpya2d.)
Verification
Full
cargo xtask verify(workspace build + tests + WASM + hub-client) wasgreen on the pre-merge branch tip; this push merges current
origin/mainandregenerates
package-lock.json(the only merge conflict). CI re-runs the fullsuite on the combination.
Follow-ups (filed)
resources:(publish ≠ upload trust boundary).🤖 Generated with Claude Code