Skip to content

feature: q2-preview (not yet editable)#171

Merged
gordonwoodhull merged 10 commits into
mainfrom
feature/q2-preview
May 12, 2026
Merged

feature: q2-preview (not yet editable)#171
gordonwoodhull merged 10 commits into
mainfrom
feature/q2-preview

Conversation

@gordonwoodhull

@gordonwoodhull gordonwoodhull commented May 9, 2026

Copy link
Copy Markdown
Member

The major restructuring is done and q2-preview lives alongside q2-debug

q2-debug and q2-preview are refactored as independent react-formats.

There is a draft plan claude-notes/plans/2026-05-10-q2-preview-plan-2e-q2-slides-migration.md for migrating q2-slides and the React revealjs format to the same architecture, but it has many open questions and is out of scope for this PR.

Pandoc and custom nodes are all at parity with the html format; a basic page container and title block are also assembled.

Editability is 5 plans away, but we could merge this sooner, as it is safe and independent.

gordonwoodhull added a commit that referenced this pull request May 10, 2026
Latest nightly causes rustc to SIGSEGV when compiling tokio for
wasm32-unknown-unknown under -Zbuild-std=std,panic_unwind. PR #171 hits
this in the "Build WASM module" step on both ubuntu-latest and
macos-latest matrix legs of TS Test Suite.

Mirrors the equivalent fix on chore/e2e-ci for hub-client-e2e.yml
(commits d06e120 + ff51584): pin via the workflow's RUSTUP_TOOLCHAIN
env var so rust-toolchain.toml stays as floating "nightly" for local
developers, and switch the dtolnay action to @master with a toolchain
input (the action no longer accepts @nightly-<date> refs).
gordonwoodhull added a commit that referenced this pull request May 10, 2026
Latest nightly causes rustc to SIGSEGV when compiling tokio for
wasm32-unknown-unknown under -Zbuild-std=std,panic_unwind. PR #171 hits
this in the "Build WASM module" step on both ubuntu-latest and
macos-latest matrix legs of TS Test Suite.

Mirrors the equivalent fix on chore/e2e-ci for hub-client-e2e.yml
(commits d06e120, 245cd25, ff51584):

  - Pin via the workflow's RUSTUP_TOOLCHAIN env var so rust-toolchain.toml
    stays as floating "nightly" for local developers.
  - Switch the dtolnay action to @master with a toolchain input (the
    action no longer accepts @nightly-<date> refs).
  - Request rust-src and wasm32-unknown-unknown explicitly on the pinned
    toolchain. Under floating @nightly rustup auto-installed missing
    components against the active toolchain; once pinned they must be
    requested for that toolchain or the build:wasm step fails with
    "library/Cargo.lock does not exist, unable to build with the
    standard library".
@gordonwoodhull gordonwoodhull changed the title feature: q2-preview feature: q2-preview (not yet editable) May 11, 2026
@gordonwoodhull gordonwoodhull marked this pull request as ready for review May 11, 2026 00:25
gordonwoodhull added a commit that referenced this pull request May 11, 2026
Latest nightly causes rustc to SIGSEGV when compiling tokio for
wasm32-unknown-unknown under -Zbuild-std=std,panic_unwind. PR #171 hits
this in the "Build WASM module" step on both ubuntu-latest and
macos-latest matrix legs of TS Test Suite.

Mirrors the equivalent fix on chore/e2e-ci for hub-client-e2e.yml
(commits d06e120, 245cd25, ff51584):

  - Pin via the workflow's RUSTUP_TOOLCHAIN env var so rust-toolchain.toml
    stays as floating "nightly" for local developers.
  - Switch the dtolnay action to @master with a toolchain input (the
    action no longer accepts @nightly-<date> refs).
  - Request rust-src and wasm32-unknown-unknown explicitly on the pinned
    toolchain. Under floating @nightly rustup auto-installed missing
    components against the active toolchain; once pinned they must be
    requested for that toolchain or the build:wasm step fails with
    "library/Cargo.lock does not exist, unable to build with the
    standard library".
@gordonwoodhull gordonwoodhull force-pushed the feature/q2-preview branch 2 times, most recently from ac3eb9e to b09f792 Compare May 12, 2026 18:53
End-to-end implementation of the q2-preview pipeline in six steps:

1. Factor render_qmd / render_qmd_content into a shared
   render_single_doc_to_response so future format dispatch has one seam.
2. Pass2Payload enum + RenderResponse.ast_json. Internal API extension
   only; consumers still see the same HTML envelope.
3. Pipeline definitions + format detection (q2-preview as a
   format-string-driven pipeline_kind alongside html).
4. Rendering plumbing — entry point, renderer, dispatch.
5. WASM bridge dispatch — q2-preview reachable from JS.
6. hub-client TS — q2-preview reachable from the editor UI; setting
   `format: q2-preview` in YAML frontmatter routes through the full
   shortcodes/Lua/sectionize/crossref pipeline and renders into the
   AstIframe alongside q2-debug. Read-only in v1.

Ships alongside the initial drafts of the 8-plan q2-preview epic
(plans 1, 2, 3, 4, 6, 7, 7a, 8) sequenced in landing order. Cross-plan
contracts pinned: By::is_kind / By::as_filter helpers in Plan 4,
is_atomic_custom_node registry in quarto-core per Plan 7, explicit
Plan 6 → Plan 4 audit-feeds-builders flow.
…-debug/

Phase 1: carve the q2-debug renderer into framework/ (format-agnostic
recursive dispatch, types, typed registry contract) and q2-debug/
(bordered-box leaves, dispatchers, format registry, iframe wrapper,
entry point) behind a re-export shim. PandocAST imports consolidated
to framework/types; slide-side types unified with the framework.

Phase 2: sixteen incremental steps rename and rewire the iframe seam.
The iframe route renames from /ast-renderer.html to /q2-debug.html;
the iframe component from AstIframe to Q2DebugIframe; the format
registry from componentRegistry to q2DebugRegistry. q2-debug/entry.tsx
becomes the new entry point. Dead code removed: transpileAndImportTSX,
ReactAstRenderer.tsx, ast-renderer-entry.tsx, ast-renderer.html, the
Phase-1 shim. The __REACT_AST_DEBUG_RENDERER__ window global is
preserved for user-TSX consumers. Behavior preservation verified
end-to-end; the bordered Figure no longer renders the literal
`// TODO:` placeholder text. MathInline added to the InlineNode union.

Bundled plan amendments: Plan 2 split into 2A (iframe foundation) +
2B (builtin components); Plan 2A review applied to plans 1/2A/2B/6;
Plan 2pre restructure plan; latent-bug references linked to bd-3gtn;
wire-format source-info code-allocation policy; soft-drop atomic
semantics across plans 4/7/8.
New q2-preview/ ships parallel to q2-debug/: Q2PreviewIframe, registry
skeleton (Block/Inline placeholder dispatchers + PreviewDocument),
entry.tsx with module-top message listener and __Q2_PREVIEW_RENDERER__
global, PreviewContext for currentFilePath. Theme CSS flows as blob
URLs — parent reads VFS, mints URL.createObjectURL, posts URL string
via UPDATE_THEME; iframe consumes via <link rel="stylesheet">. Three-
way themeFingerprint (string | null | undefined) preserves last-good
styling across transient render errors.

ReactRenderer's combined format branch split into distinct q2-debug /
q2-preview branches; render-components gate extended to cover both.
Shared utilities added: iframeLinkHandlers.ts (event-delegation
extraction), sourceInfo.ts, atomicCustomNodes.ts, artifactPaths.ts.
RegistryContext extended with sourceInfoPool? (Plan 2B atomic-aware
gate consumer). q2-debug's __REACT_AST_DEBUG_RENDERER__ hoisted to
module top.

q2-preview pipeline constructed as the HTML pipeline minus exclusions
so future drift stays minimal.

Two theme fixes: theme_fingerprint is now captured at the pass-2
renderer level (surviving both default-project flush and website
merge); theme CSS is written to /.quarto/project-artifacts/styles.css
in both single-doc and project mode so the iframe path is stable.

Iframe boots empty in 2A — every Pandoc node renders as a muted-gray
"(not yet implemented)" placeholder. Plan 2B fills the registry.

Bundled plan amendments: convert smoke-gates in plans 2A/2B/live-reload
to automated equivalents; revise plans 2A + 2B for Design B (blob-URL
asset transport via parent-side walker).
Phase 1 — framework foundation. customNode.ts ships unwrap/rewrap
walks (mirrors the Rust JSON writer/reader for __quarto_custom_node
Div/Span wrappers, structural-sharing pure to preserve subtree
identity for the future Note-numbering WeakMap). Atomic-aware gate
inside framework/dispatch.tsx's Node no-ops setLocalAst for Derived /
atomic Synthetic / atomic CustomNode subtrees. framework/Ast.tsx
accepts a discriminated input (string OR pre-parsed AST), unconditionally
unwraps custom-node wrappers, and fills the RegistryContext.sourceInfoPool
value that Plan 2A had only typed. blockTypes extended from 11 to 19
entries.

Phase 2.1a — extract hub-client/src/utils/vfsPaths.ts.
resolveRelativePath, normalizePath, and guessMimeType had three private
copies; consolidating to one module avoids fanning out further when the
asset walker becomes the fourth consumer.

Phase 2 — asset manifest plumbing. q2-preview/assetWalker.ts walks the
AST for Image nodes, resolves paths via vfsPaths, reads VFS bytes, and
mints (or reuses cached) blob URLs. Cache identity is the base64 content
string itself — synchronous, deterministic, no hash. External URLs
filtered out at walk time. AssetManifestContext distributes the
{origPath → blobUrl} map; Q2PreviewIframe owns the cache (drained on
unmount) and extends UPDATE_AST to carry the manifest alongside the
AST. entry.tsx's setAst callback now rewrapCustomNodes() back to wire
format before posting SET_AST.

Phase 3 — built-in registry of 14 Block + 20 Inline components. Image
is a pure AssetManifestContext consumer (no VFS access in the iframe).
Math is a near-verbatim port of the Elliot Math leaf with direct ESM
`import katex` (window.katex is only set after LOAD_CUSTOM_COMPONENTS)
and an explicit raw-LaTeX fallback on KaTeX error. Note is the JS-side
number-with-tooltip-body fallback for `reference-location: block` and
`section` (the default `document` configuration runs through
FootnotesTransform upstream).

Pipeline: include FootnotesTransform + AppendixStructureTransform in
q2-preview (both produce pure Pandoc primitives that 2B's leaves
render natively).

Phase 5 — verification: smoke-all q2-preview/image-with-attrs fixture
(1×1 PNG sibling) + assetManifestProject.wasm.test.ts mirror of
themeFingerprint.wasm.test.ts that renders a _quarto.yml-rooted project
doc with a project-relative Image. Inline-footnote pipeline output
pinned via quarto-core test.

Bundled plan amendments: Plan 2B post-2A audit (customNode override,
transform inclusions); plans 3+7 transform inclusions + Transparent
enumeration; Plan 2B implementor-detail pass + Note fallback spec
(bd-1kly); explicit `cargo xtask verify --e2e` gate at step 5.5;
monolithic 2B split into Session A (2B) + Session B (2C).
After a pass-2 render the WASM bridge flushed every artifact back to
VFS, including empty-content placeholders (Vec::new()) that the
render had emitted for already-uploaded user assets. Those zero-byte
writes overwrote the real user-uploaded bytes already in VFS. Guard
the flush loop to skip empty content.

Test added at quarto-core level asserts user-uploaded image bytes
survive a q2-preview render.
Phase 4.1: extend quartoClasses.ts with callout/theorem/proof/quarto-xref
taxonomy. Phase 4.2: utils.ts → utils.tsx; add theoremEnvs.ts.
Phase 4.3: six CustomNode components (Callout, Conjecture, Definition,
Proof, Theorem, quarto-xref) + a generic Fallback for unknown
type_names. Phase 4.4: CustomBlock and CustomInline dispatchers wire
the registry.

Phase 5 verification: Phase 5.1 vitest tests cover the six components
plus the namespace policy. Phase 5.2 smoke-all fixtures cover
multi-element + project + render-components. Phase 5.3
customNodeWireFormatProject.wasm.test.ts pins the wire-format
round-trip. Phase 5.5 e2e fixture corrections + IndexedDB polyfill
land for the browser leg.

Bundled plan amendments: post-2B-implementation amendments to Plan 2C
(registry unification under a single CustomNode dispatcher); Plan 2D
body-container draft.
q2-preview gains a body-level container plus a title-block component.
Plan-2d doc extended with title-block scope + post-2C-landing
alignment notes.
Empty strings in the YAML render-components list crashed the parser.
Skip empty entries silently rather than erroring.
Migration plan for moving ReactAstSlideRenderer.tsx (SlideAst carousel)
and RevealjsReactAstSlideRenderer.tsx (RevealjsSlideAst) into a shared
hub-client/src/components/render/q2-slides/ directory, completing the
hub-client format-restructure that 2pre started.

§2 (two registries sharing leaves) is locked: q2SlidesRegistry and
revealjsRegistry both spread the same blocks/inlines modules and differ
only on the 'Ast' document-root entry. §1 (iframe boundary), §3
(SlideContext shape), §4 (asset resolution), §5 (editing wiring), and
§6 (migration strategy) are open with recommendations.

Phased shim approach (Phases 9-13) mirrors 2pre's pattern: build
q2-slides/ behind a re-export shim, migrate four consumers
(ReactRenderer, RevealjsReactAstSlideRenderer, useCursorToSlide,
useSlideThumbnails) one commit at a time, then delete the shim. Depends
on Plan 2D Phase 6.0 (framework/meta.ts) landing first.
…ing entries

The branch shipped 15+ phase-level entries describing internal
architecture (Plan 2pre / 2A / 2B / 2C / 2D sub-phases, the bd-3gtn
artifact-flush fix, theme-CSS path corrections) that hub-client users
don't see. Replace with two user-facing entries: the new `format:
q2-preview` and the render-components mid-type crash fix. Side effect:
clears the Q-2-7 parse error in the prior 2026-05-10 entry (bd-1qk5)
since the offending text is removed entirely.
@gordonwoodhull gordonwoodhull merged commit f6d3989 into main May 12, 2026
5 checks passed
@gordonwoodhull gordonwoodhull deleted the feature/q2-preview branch May 12, 2026 20:34
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