Skip to content

Commit cc246ee

Browse files
cscheidclaude
andauthored
reveal.js: link shared assets (render) + preview/render parity; embedded-example iframes (#271)
* feat(embed): ExampleEmbedTransform — .embed-example-iframe → live iframe (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> * docs(plan): record Phase 1 done + Phase 2 design (staging/resources/convention findings) for bd-z1smhvuo Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(embed): stage example output + page-relative iframe src + migrate 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> * docs(plan): design for cross-referenceable Example blocks (bd-t3cert81) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(embed): cross-referenceable Demo blocks (@demo-… → "Demo N") (bd-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> * docs(plan): preview embed iframe via VFS source-path fallback (bd-kjrpya2d) 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> * feat(preview): inline embedded-resource iframes from the VFS source path (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> * docs(plan): option B part 1 done (post-processor iframe fallback); part 2 scoped (bd-kjrpya2d) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(plan): part-2 scope (resources-scoped .html) + discovery integration point; security strand bd-teh4hbli (bd-kjrpya2d) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(handoff): session handoff for embed/crossref/preview work (bd-kjrpya2d next) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(preview): inline embedded-example decks in q2 preview from the VFS (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> * docs(plan): reveal.js linked-assets plan + revert scope (bd-jij5gge2) 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> * revert(preview): undo inline-embedding of decks into preview HTML (bd-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> * feat(revealjs): link vendored assets via site_libs instead of inlining (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> * docs(plan): mark reveal linked-assets render side done + verified (bd-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> * docs(plan): preview revealjs convergence — kill CSS/version drift, keep 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> * feat(revealjs): pin reveal.js exact + enforce vendored↔npm sync (bd-ibqkf9ry 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> * fix(preview): reveal deck uses vendored CSS + no HTML-theme leak (bd-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> * docs(plan): reveal convergence prongs A-D done; D (transitions) fixed 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> * docs(plan): reveal convergence complete — full verify green (bd-ibqkf9ry) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(plan): mark reveal convergence DONE (bd-ibqkf9ry) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(preview): serve embedded-example decks from disk in q2 preview (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> * fix(deps): restore cross-platform package-lock (Linux rollup binary) (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> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 32f1ef5 commit cc246ee

48 files changed

Lines changed: 3904 additions & 119 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
# Handoff — embed-example iframes + cross-referenceable Demos + preview
2+
3+
**Date:** 2026-06-09
4+
**Branch:** `beads/bd-z1smhvuo-embed-example-iframe` (9 commits ahead of `main`,
5+
**nothing pushed** — per project policy, do not push without explicit OK).
6+
**Working tree:** clean.
7+
8+
Read this first, then the three plan files below. You can start cold from here.
9+
10+
## TL;DR — where we are
11+
12+
Three features shipped and committed this session; one is half-done and is the
13+
**next task**:
14+
15+
1.**Embed feature** (bd-z1smhvuo) — `::: {.embed-example-iframe file="…"}`
16+
live `<iframe>`. Sugar+render split, static-asset-only contract
17+
(Q-5-4/Q-5-5), page-relative `src`, `cargo xtask stage-doc-examples` staging,
18+
`resources: [examples]` in `docs/_quarto.yml`, 8 docs placeholders migrated.
19+
**Browser-verified in `q2 render`.**
20+
2.**Cross-referenceable Demo blocks** (bd-t3cert81, **strand closed**) —
21+
`::: {.embed-example-iframe #demo-foo file="…"}` is auto-numbered "Demo N" and
22+
`@demo-foo` resolves to a "Demo N" xref, through the normal crossref
23+
machinery. **Verified in HTML + revealjs + the live docs site.**
24+
3.**Preview resolution** (bd-kjrpya2d) — make the embed iframes load under
25+
`q2 preview` / hub-client. **Part 1 done** (TS post-processor fallback).
26+
**Part 2 is THE NEXT TASK** (see below).
27+
28+
## THE NEXT TASK — bd-kjrpya2d part 2
29+
30+
**Goal:** make `.embed-example-iframe` decks actually render in `q2 preview docs/`
31+
(and in real hub-client/Automerge projects). Part 1 already inlines an
32+
artifact-rooted iframe from the VFS via `srcdoc` *with a source-path fallback*
33+
(`ts-packages/preview-renderer/src/utils/iframePostProcessor.ts`,
34+
`readArtifactOrSource`). But the deck **isn't in the preview VFS**, so the
35+
fallback reads nothing.
36+
37+
**Scope (decided with user):** sync the `.html` files **visible via
38+
`resources:`** (resources-scoped — NOT all project `.html`). Trust-boundary
39+
hardening is split into **bd-teh4hbli** (don't reuse the publish-control
40+
`resources:` as an upload-control; this resources-scoped sync is the interim it
41+
tightens).
42+
43+
**Integration points (already traced):**
44+
1. `crates/quarto-hub/src/discovery.rs``ProjectFiles::discover` classifies
45+
qmd/config/binary/extension/`.tsx`. `.html` is **not** a binary extension, so
46+
it falls through and is never discovered/synced. **Add a resources-scoped
47+
`.html` category**: resolve the project `resources:` (via
48+
`quarto-core::project_resources` + `ProjectConfig`) and include matched
49+
`.html`. (Note: `is_preview_relevant` in `watch.rs:207` /
50+
`WatchFilter::PreviewBroad` is a *separate* event gate — `discover` is the
51+
load path.)
52+
2. Thread the new set through the hub **sync → VFS at the source path** (binary
53+
resources already flow this way — mirror `binary_files`).
54+
3. **SPA population**: `vfsAddFile` them on the TS side (hub-client).
55+
56+
**Suggested first slice (verifiable, like part 1 was):** the Rust discovery
57+
change in `discovery.rs` with unit tests (a `resources:`-matched `.html` is
58+
discovered; a non-resources `.html` is not). Then the sync + SPA wiring.
59+
60+
**End-to-end verification (required before "done"):** full rebuild —
61+
`cd hub-client && npm run build:wasm``cargo xtask build-q2-preview-spa`
62+
`cargo build --bin q2` — then `q2 preview docs/`, open
63+
`http://127.0.0.1:<port>/?page=presentations/revealjs/index.qmd`, and confirm the
64+
`#demo-fragments` iframe **inlines the real deck** (it's nested: descend into the
65+
SPA preview `<iframe>`, then the `.embed-example` iframe; check `srcdoc` is set
66+
and the inner doc has `.reveal .slides section`). Crossref ("Demo 1" caption +
67+
`@demo` xref) **already works in preview** — verified.
68+
69+
## Plan files (authoritative detail)
70+
71+
- `claude-notes/plans/2026-06-09-website-example-iframe-embed.md` — embed feature
72+
(Phases 1–2 done; preview row points at bd-kjrpya2d).
73+
- `claude-notes/plans/2026-06-09-crossreferenceable-examples.md` — Demo crossref
74+
(all phases done).
75+
- `claude-notes/plans/2026-06-09-preview-embed-vfs-resolution.md`**the active
76+
one**: part 1 done, part 2 scoped with the integration points above.
77+
78+
## Open strands
79+
80+
| Strand | What | Status |
81+
|---|---|---|
82+
| **bd-kjrpya2d** | Preview embed iframe via VFS source-path fallback | **active — part 2 next** |
83+
| bd-teh4hbli | Security: decouple sync-upload from `resources:` | filed (relates; tighten after part 2) |
84+
| bd-cic0dfdp | Lua API for `resolve_static_resource_href` | filed |
85+
| bd-q3bxnq2e | Perf: WASM re-flushes ALL artifacts to VFS per render (`lib.rs:1417`) | filed (p1) |
86+
87+
## Key decisions / gotchas (so you don't relearn them)
88+
89+
- **Option B, not A.** We do NOT copy resources into the VFS artifact tree per
90+
render (A) — that would re-duplicate ~6 MB of decks into the VFS on *every*
91+
render (the artifact flush at `wasm-quarto-hub-client/src/lib.rs:1417` is
92+
per-render); in hub-client that tree is in the Automerge doc. B reads source
93+
bytes on demand. The CLI-only disk approach (a `vfs_root`-mode resolver branch
94+
+ a `quarto-preview` disk route) was prototyped, browser-verified, then
95+
**reverted** — it can't serve a diskless hub-client project. `page_url_for`
96+
stays uniform; `resolve_static_resource_href` has no vfs special-case.
97+
- **`exm` is taken** (theorem-like "Example", `registry.rs`). The embed crossref
98+
prefix is **`demo`** / kind **"Demo"** (`crossref/registry.rs`), deliberately
99+
distinct.
100+
- **Fenced-div attribute order:** the qmd parser wants `#id` **first**
101+
`::: {#demo-foo .embed-example-iframe file="…"}` (class-first errors).
102+
- **Decks are self-contained** (slides.html embeds its assets; `slides_files/` is
103+
vestigial for these). So part-2 sync likely needs only the `.html`, not
104+
`slides_files/`.
105+
- **Staging:** `cargo xtask stage-doc-examples` renders `examples/manifest.yml`
106+
projects → `docs/examples/<entry>/` (gitignored; regenerate, never commit).
107+
Needed before `q2 render docs/` or any preview check shows live iframes.
108+
- **Verify gate:** `cargo xtask verify --skip-hub-build` (matches CI `-D
109+
warnings`) for Rust-only; full `cargo xtask verify` when the WASM leg is
110+
affected. quarto-core suite is ~2250+ tests.
111+
112+
## Commands you'll want
113+
114+
```bash
115+
# Rust tests for the embed/crossref work
116+
cargo nextest run -p quarto-core -E 'test(example_embed) | test(crossref_fixtures) | test(navigation_href)'
117+
# Preview-renderer TS tests (part 1)
118+
cd ts-packages/preview-renderer && npx vitest run src/utils/iframePostProcessor.embed.test.ts
119+
# Stage example decks (gitignored output)
120+
cargo xtask stage-doc-examples
121+
# Full preview rebuild chain (needed to see Rust/WASM changes in q2 preview)
122+
cd hub-client && npm run build:wasm && cd .. && cargo xtask build-q2-preview-spa && cargo build --bin q2
123+
# Run the docs preview
124+
cargo run --bin q2 -- preview docs/ --port 7656
125+
```
126+
127+
## Commit ledger (this branch vs main)
128+
129+
```
130+
76f64969 docs(plan): part-2 scope + discovery integration point; security strand
131+
bce5975e docs(plan): option B part 1 done; part 2 scoped
132+
bfff2d3e feat(preview): inline embedded-resource iframes from VFS source path (part 1)
133+
39cb994a docs(plan): preview embed iframe via VFS source-path fallback
134+
8a1ca4d5 feat(embed): cross-referenceable Demo blocks (@demo-… → "Demo N")
135+
d4540d37 docs(plan): design for cross-referenceable Example blocks
136+
867aa7c1 feat(embed): stage example output + page-relative iframe src + migrate docs
137+
74c945b4 docs(plan): record Phase 1 done + Phase 2 design
138+
de20ca96 feat(embed): ExampleEmbedTransform — .embed-example-iframe → live iframe
139+
```

0 commit comments

Comments
 (0)