Skip to content

q2 preview: implement render-components (parent half) — no longer hub-client-only - #604

Merged
cscheid merged 5 commits into
mainfrom
feature/bd-ue80chl0-preview-spa-render-components
Aug 25, 2026
Merged

q2 preview: implement render-components (parent half) — no longer hub-client-only#604
cscheid merged 5 commits into
mainfrom
feature/bd-ue80chl0-preview-spa-render-components

Conversation

@cscheid

@cscheid cscheid commented Aug 25, 2026

Copy link
Copy Markdown
Member

Fixes #402 (resolution #1: implement the feature in the CLI preview). Tracked internally as braid strand bd-ue80chl0. Plan with design decisions and measurements: claude-notes/plans/2026-08-25-preview-spa-render-components.md.

What

q2 preview now loads user TSX render-components: overrides, in both project and single-file mode — previously the entries were silently dropped (the parent half of the pipeline existed only in hub-client; the iframe half was already shared).

How

Phase 1 — shared helpers (no behavior change). hub-client's @babel/standalone transpiler moved to @quarto/preview-renderer/utils/tsxTranspiler, and its inline meta walk extracted into utils/renderComponents (extractRenderComponentPaths), both unit-tested. hub-client now imports the shared modules — same transpiler, one implementation, no drift between surfaces.

Phase 2 — SPA parent half. q2-preview-spa reads render-components from the rendered AST meta, resolves entries with the shared resolveComponentPath, reads the synced .tsx sources (already synced by hub discovery + the PreviewBroad watcher), transpiles, and passes customComponentsCode to Q2PreviewIframe.

  • Bundle cost: the transpiler module is imported dynamically, so babel lands in a lazy Vite chunk (2.9 MB raw / 664 KB gz) fetched only when a document lists components. The iframe chunk is unchanged; the main chunk grew 4 KB. SPA dist/ 45 → 49 MB.
  • Re-transpile cadence: keyed on the path-list string plus a dedicated tsxTick (bumped only on .tsx changes) — per-keystroke .qmd edits never run babel, and the empty result is referentially stable so LOAD_CUSTOM_COMPONENTS is never re-posted on ordinary edits.
  • No more silence: a missing component file or transpile error surfaces as a render warning in the diagnostics overlay.
  • Shared iframe fix (entry.tsx): LOAD_CUSTOM_COMPONENTS now repaints the cached last AST payload after rebuilding the registry, so live .tsx edits take effect immediately (benefits hub-client too; boot-order LOAD before the first AST still renders nothing).

Phase 3 — single-file mode. resolve_single_file_deps now reads render-components from the parsed doc meta and appends existing in-tree .tsx files to the text-dep channel — synced as text and enrolled in the closure-scoped watcher. Entries resolve against the deck dir; leading / means the synthetic project root (path-resolution contract). Missing/escaping/non-.tsx entries drop fail-soft.

Testing (TDD throughout; every test RED first)

  • preview-renderer: 9 new unit tests (meta walk edge cases, real-babel transpile contract) + 2 iframe integration tests (re-render-after-LOAD contract).
  • SPA: 6 unit tests (incl. a hoisted-flag proof that the babel chunk is not imported for component-free docs) + 5 integration tests (wiring, Q1 cadence in both directions, stable empty identity, overlay surfacing).
  • Rust: 2 new config.rs tests (closure pickup; drops for missing/..//non-tsx).
  • e2e against the real target/debug/q2 binary in Chromium (q2-preview-spa/e2e/render-components.spec.ts, reusing the existing with-render-components smoke fixture): overrides shadow built-ins (p.my-para, div.my-callout, no div.callout); a disk .tsx edit live-repaints; and the same holds under single-file q2 preview index.qmd (harness gained a targetFile option). 3/3 green.
  • Full cargo xtask verify (WASM leg included): 13,426 Rust tests + all TS suites + hub-client build:all — passed. cargo xtask lint clean.

Note: hub-client/changelog.md is intentionally untouched — the hub-client diff is a pure refactor (import moves), and the changelog's header excludes refactors.

🤖 Generated with Claude Code

cscheid and others added 5 commits August 25, 2026 15:01
Move hub-client's TSX transpiler (@babel/standalone) into
@quarto/preview-renderer/utils/tsxTranspiler and extract its inline
render-components meta walk into utils/renderComponents
(extractRenderComponentPaths), both unit-tested. hub-client now imports
the shared modules; behavior is unchanged (its integration tests pass
against the new import path). This gives the q2-preview SPA (Phase 2)
the same transpiler + meta walk hub-client uses, instead of a parallel
implementation.

Part of GH #402 / bd-ue80chl0 (q2 preview silently ignores
render-components). Plan:
claude-notes/plans/2026-08-25-preview-spa-render-components.md

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The CLI preview SPA now implements the parent half of the
custom-components pipeline that only hub-client had: read
render-components from the rendered AST meta, resolve entries with the
shared resolveComponentPath, read the synced .tsx sources via
getFileContent, transpile with the shared @babel/standalone transpiler,
and pass customComponentsCode to Q2PreviewIframe.

Design points (plan Q1/Q3 decisions):
- Re-transpilation is keyed on the path-list string + a dedicated
  tsxTick (bumped only for .tsx changes), so per-keystroke .qmd edits
  never run babel. The empty result is referentially stable so the
  iframe never re-posts LOAD_CUSTOM_COMPONENTS on ordinary edits.
- The transpiler module is imported dynamically: babel lands in a lazy
  Vite chunk (2.9 MB raw / 664 KB gz) fetched only when a document
  lists components. Iframe chunk size is unchanged (no leak); main
  chunk grew 4 KB.
- Component failures (missing file, transpile error) surface as render
  warnings in the diagnostics overlay — no more silent divergence.

Shared-iframe fix (entry.tsx): LOAD_CUSTOM_COMPONENTS now repaints the
cached last UPDATE_AST payload after rebuilding the registry, so a
component (re)load with no AST change takes effect immediately (live
.tsx edits repaint; benefits hub-client too). Boot-order LOAD before
the first AST still renders nothing.

Tests: 2 iframe integration tests (re-render contract), 6 SPA unit
tests (key extraction, lazy-import guarantee, warnings), 5 SPA
integration tests (wiring, Q1 cadence both directions, stable empty
identity, overlay surfacing). Full preview-renderer + SPA suites green.

Part of GH #402 / bd-ue80chl0. Plan:
claude-notes/plans/2026-08-25-preview-spa-render-components.md

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
New q2-preview-spa/e2e/render-components.spec.ts drives target/debug/q2
preview on the existing with-render-components smoke fixture: both user
overrides shadow the built-ins (p.my-para, div.my-callout; no
div.callout), and a disk edit of overrides.tsx live-repaints the
preview with the re-transpiled component. 2 passed; basic-preview.spec
still green. End-to-end record added to the plan.

Part of GH #402 / bd-ue80chl0.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…sure

q2 preview deck.qmd now syncs the deck's declared render-components
.tsx files: resolve_single_file_deps reads the key from the parsed
document meta (as_plain_text, so bare-string YAML works), resolves
entries against the deck dir (leading `/` = the synthetic project
root, per the path-resolution contract), and appends existing in-tree
.tsx files to the text-dep channel — which both syncs them as text for
the SPA parent half and enrolls them in the closure-scoped watcher.
Missing, escaping, and non-.tsx entries drop fail-soft (the SPA
already surfaces a missing component as a render warning).

Without this, single-file mode had no dir walk to discover the .tsx
and would have re-created GH #402's silent divergence one mode over.

Tests: two new config.rs unit tests (TDD, RED first); new
[single-file] e2e in render-components.spec.ts (previewServer harness
gained a targetFile option) proving overrides fire and a disk .tsx
edit live-repaints under the real binary. 10/10 single_file_deps unit
tests, 3/3 e2e green.

Part of GH #402 / bd-ue80chl0. Plan:
claude-notes/plans/2026-08-25-preview-spa-render-components.md

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
cargo xtask verify (full, WASM leg) passed: 13,426 Rust tests, all TS
suites, hub-client build:all, SPA build. cargo xtask lint clean. Docs
check: render-components is undocumented/experimental — nothing to
update.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@posit-snyk-bot

posit-snyk-bot commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Snyk checks have passed. No issues have been found so far.

Status Scan Engine Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues
Licenses 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

@cscheid
cscheid merged commit 6e3d396 into main Aug 25, 2026
10 checks passed
@cscheid
cscheid deleted the feature/bd-ue80chl0-preview-spa-render-components branch August 25, 2026 21:09
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.

q2 preview (CLI) silently ignores render-components: — feature is hub-client-only

2 participants