Conversation
…client WASM entry point (bd-kltzdhle, Phases 1-2) Groundwork for making q2-preview hub-client's default renderer (plan: claude-notes/plans/2026-09-09-hub-client-default-q2-preview.md). Phase 1 - `q2-html-render` is a builtin pseudo-format (base `html`, no preview pipeline_kind), the explicit opt-out into the full-DOM MorphIframe renderer once html defaults to q2-preview. In the pipeline it behaves exactly like `q2-debug`: `q2 render` writes an ordinary HTML file for it (verified end-to-end; previously "Unknown format: q2-html-render"). Registered in `builtin_pseudo_format`, `lua_format_for`, and the WASM `coerce_format_for_print`. Phase 2 - `render_page_in_project_with_attribution` gains a trailing `prefer_preview_format: Option<bool>`. When true it applies the same default-format substitution `q2 preview` uses (html -> q2-preview, revealjs -> q2-slides) while keeping `RenderHost::HubClient`, so the host-dependent Q-5-12 render-scripts warning still fires - the reason hub-client cannot reuse `render_page_for_preview`, which hard-codes the native-preview host. Omitted/false is byte-identical to before. The TS wrapper `renderPageInProjectWithAttribution` exposes it as an optional fifth argument. Tests: quarto-core `format::tests` (4 new/extended cases) and the new hub-client WASM tier `previewFormatSubstitution.wasm.test.ts` (7 cases, run red against the pre-knob WASM first). Full workspace nextest, hub-client `test:wasm` (140), typecheck, and clippy green. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0169cS5WjZuUvam43it8hdtX
…r opts back into the full-DOM iframe (bd-kltzdhle, Phase 3)
A document with no `format:` key, `format: html`, or a `format: html:
{...}` map now previews through the React AST renderer
(Q2PreviewIframe) - the same choice `q2 preview` makes - so comments,
the Edit pill, Authors, and the printable-document affordance are
available on every plain document. `format: q2-html-render` is the
explicit opt-out into the full-DOM MorphIframe renderer, which is
otherwise only the fallback for non-html formats (pdf, docx,
extension formats).
Mechanism (plan D2/D3):
- `getQ2Format` maps `html` -> `q2-preview` and `q2-html-render` ->
null (full-DOM); other `q2-*` and `revealjs` pass through. This is
the JS half of the `map_format_for_preview` substitution in the WASM.
- `ReactPreview.doRender` renders every preview-pipeline format through
`renderPageInProjectWithAttribution(..., preferPreviewFormat=true)`,
so the WASM applies the same substitution after re-detecting the
format from the file. The revealjs branch moves off the SPA-only
`renderPageForPreview` onto this call, which gives decks attribution
(bd-zvh2p) and the correct hub-client render host.
- `Editor.tsx`'s chrome gates already key on the router-reported
format, so the pills enable for plain documents with no change there.
Tests (written first, red before the change): `getQ2Format.test.ts`
(6), new `PreviewRouter.integration.test.tsx` (3: no-format ->
ReactPreview as q2-preview; q2-html-render -> Preview; q2-debug pass
through), the capture and rerender ReactPreview tests now pin the fifth
argument and that `renderPageForPreview` is never called from
hub-client, and `pipelineKind.test.ts` pins q2-html-render as a non-
preview pipeline. hub-client unit (1102) / integration (128) / WASM
(140) tiers, typecheck, and `npm run build:all` green under Node 24.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0169cS5WjZuUvam43it8hdtX
…ender (6d0aa43) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0169cS5WjZuUvam43it8hdtX
…no live preview (bd-kltzdhle, Phases 4-6) Phase 4 - hub-client e2e. The iframe kind now names the renderer, not the document format: `'html'` is renamed `'q2-html-render'`, and the default kind is removed so every caller states which iframe it expects. smoke-all picks the kind from the fixture's OWN front matter (new `DiscoveredTest.documentFormat`): q2-html-render -> MorphIframe, q2-debug -> Q2DebugIframe, everything else (including html-spec fixtures with no format: key) -> Q2PreviewIframe. The three plain- fixture specs and the extraction spec switch to the q2-preview iframe; the click-to-editor-scroll control fixture declares q2-html-render explicitly since it tests that renderer on purpose. First run: 121/157 passed. 24 failures were one harness bug - `getPreviewCss` read <link> tags from the MorphIframe DOM. It now parses the render string `renderForAssertions` already produces, so the CSS assertions are renderer-independent. The other 11 are DOM selectors on page chrome / missing components in the q2-preview iframe; per plan D8 they are listed in `DOM_ASSERTIONS_PENDING_PARITY` with an owning strand each (bd-3cpv7dah, bd-c3dtpe36, bd-b3oq2fsy, bd-fandfn60, bd-bg0jze2i, bd-47afd5ro, and new bd-xiz1a2go / bd-q2wqj24c). Only the DOM assertion type is skipped for those fixtures - they still render in the default iframe and keep their regex/CSS/diagnostics assertions - and each skip prints a `[smoke-diag] dom-assertions-skipped` line. Rerun: 156 passed, 1 skipped, 0 failed. Phase 4b works the list down. Phase 5 - `q2 preview` has no full-DOM renderer, so a successful render that came back as `html` (format: q2-html-render, pdf, ...) now shows a "no live preview in q2 preview ... run `q2 render`" message naming the format, instead of `console.error` + the boot screen. The format name comes from a new `format` field on the WASM `RenderResponse` (the resolved target_format after any preview substitution; omitted on errors), pinned by `previewFormatSubstitution.wasm.test.ts`. Phase 6 - notes in the two plans that called the html preview the default; bd-zvh2p closed (attribution reaches reveal decks via D3). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0169cS5WjZuUvam43it8hdtX
…ull verify) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0169cS5WjZuUvam43it8hdtX
…cpv7dah; bd-kltzdhle Phase 4b)
`DraftAlertTransform` already runs in the q2-preview pipeline and
publishes the localized label at `meta.rendered.draft-alert-text`; only
the banner markup lived in the HTML template, which the React preview
never uses. Add `DraftAlertSlot` (chromeSlots.tsx) with Q1's exact
markup - `<div id="quarto-draft-alert" class="alert alert-warning">
<i class="bi bi-pencil-square"></i>{label}</div>` - and render it in
`PreviewDocument` before the site header, mirroring the template's
`$if(rendered.draft-alert-text)$` above `$quarto-header()$`.
Tests (written first, red): three PreviewDocument cases - markup and
position before `header#quarto-header`, a page with no header, and no
banner without the label. The two smoke-all fixtures
(drafts/draft-banner, localization/lang-es-draft-banner) leave the
e2e DOM-assertion skip-list and pass in the q2-preview iframe.
Verified in `q2 preview` on a `lang: es` draft: label "Borrador",
Bootstrap `.alert-warning` colours applied, banner before `main`.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0169cS5WjZuUvam43it8hdtX
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0169cS5WjZuUvam43it8hdtX
…itle-banner items Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0169cS5WjZuUvam43it8hdtX
…y > selector (bd-xiz1a2go; bd-kltzdhle Phase 4b) The three title-block banner fixtures asserted `body > header#title-block-header ...` as the P5 "banner renders above #quarto-content" check. The q2-preview renderer emits the same header, banner, title/subtitle/date, and `main.quarto-banner-title-block` (verified in the iframe, theme colour applied), but React mounts into div#root, so nothing it renders can be a direct child of body - the selector encoded page structure, not banner parity. Drop the `body >` prefix from the DOM selectors and pin the placement with an `ensureFileRegexMatches` (`<body[^>]*>\s*<header id="title-block-header"`) on the render output, which is where the placement is a property of the HTML writer. Native smoke-all, the hub-client WASM smoke runner, and the e2e runner (the three fixtures leave DOM_ASSERTIONS_PENDING_PARITY; 3/3 pass in the q2-preview iframe) are green. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0169cS5WjZuUvam43it8hdtX
…r removal (bd-q2wqj24c) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0169cS5WjZuUvam43it8hdtX
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0169cS5WjZuUvam43it8hdtX
Contributor
✅ Snyk checks have passed. No issues have been found so far.
💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse. |
…r-block wrapper Re-verifies the 2026-09-10 handoff note at PR #670's branch HEAD and narrows it: only five block components can carry comment chrome (not 14), reveal decks have no #quarto-content for a layer to live in, and AttributionWrap is a second wrapper of the same class (out of scope). Adds the theme direct-child selector inventory as an investigation artifact. Verdict: ready to design; seven design questions for the user. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017bB83744mRBrrQHrN1vDcm
…us; file bd-ijlb2yui for AttributionWrap Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017bB83744mRBrrQHrN1vDcm
…per (bd-q2wqj24c) CommentBlock wrapped every commentable block in a positioned <div> so the bubble could be absolutely positioned against it. The theme CSS is written for the native writer's DOM, where a block is a direct child of its container, so every `parent > child` rule (`blockquote > h4`, `.callout-body > :first-child`, `li > p:last-of-type`, `.tab-pane > p`, `section > section`, …) stopped matching in the live preview — a visual parity drift against `q2 render`, invisible to the parity harness (whose read-only mount renders no chrome). Now the block renders untouched. Its chrome (bubble + block glow) is portalled into one lazily created body-level layer (`[data-q2-comment-layer]`) and placed by the relayout pass from the block's measured rect in layer coordinates (`top = block.top − 11`, right-aligned 10px past `block.right`, `translate(-100%, nudge)`), on the existing triggers plus a ResizeObserver on anchors and body. Hover is one delegated document mousemove resolving the pointer to the deepest registered anchor (right-half test) or bubble; the glow is an overlay outline over the block's rect, never a style on the block. How a bubble finds its block (commentAnchor.tsx): CommentBlock provides a per-block `CommentAnchorContext`; the five chrome-eligible host components (Para, Header, CodeBlock, MermaidCodeBlock, Div) spread the ref it hands back when the context's node is theirs. `Plain` has no element, so tight-list `<li>`s and definition `<dd>`s render through `PlainHost` and provide themselves; a Plain with no host renders passthrough with the comment spans left in the text. Overrides that do not adopt the hook simply get no chrome. Decks: the layer sits outside reveal's `.slides` transform, so the measured-scale / counter-scale / DECK_BUBBLE_FUDGE machinery is gone; `.present`-slide gating and the `q2-reveal-scale` trigger stay. The chrome adopts its block's computed font-family on every pass (it no longer inherits the deck theme's font) and deck chrome is scaled ×1.2. Tests (red first): CommentBlock.structure (DOM shape with/without comments and hover, layer, glow, Plain-in-li, no-host passthrough, and a read-only-vs-live parity guard the harness lacks) and CommentBlock.geometry (placement in layer coords, overlap nudge); resolveLast/defensive/bubbleText helpers rewritten for the layer. The test setup's ResizeObserver/IntersectionObserver stubs are now constructible classes (vitest 4 will not `new` an arrow-function mock). The `div-heading-becomes-section` fixture leaves the e2e DOM-assertion skip-list; its `blockquote > h4#quoted` now runs live (smoke-all 156 passed / 1 pre-existing skip; comment specs 4/4). Full `cargo xtask verify` green. Plan + browser verification record: claude-notes/plans/2026-09-10-commentblock-wrapper-removal.md. Follow-ups filed: bd-ijlb2yui (AttributionWrap is the same defect class), bd-5lwrvjp6 (tree-sitter shared-cache hermeticity in verify), bd-1nuiqmoe (quarto-nav.js observer teardown noise in jsdom). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017bB83744mRBrrQHrN1vDcm
); bd-q2wqj24c plan closes out Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017bB83744mRBrrQHrN1vDcm
…ub-client-default-q2-preview
…ts out via q2-html-render (bd-kltzdhle) The spec arrived from main in the merge (5b5baab) and pinned the old default: a document with no `format:` key had no printable version, so the header held only New and Upload. Since bd-kltzdhle a plain document renders through q2-preview, which is printable, and `.print-file-btn` appears — CI failed deterministically at files-header.spec.ts:61 on the merged branch (4/4 attempts). Swap the fixtures rather than the gate: the two-button case now declares `format: q2-html-render` (the full-DOM opt-out, no printable), and the three-button case is the plain document that exercises the new default. Both pass locally; the full default e2e suite is 75/75 on the merged tree. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
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.
Summary
Brings
q2 preview's renderer choice to hub-client. A document with noformat:key,format: html, or aformat: html: {…}map now previews through the q2-preview React renderer (Q2PreviewIframe), so comments, the Edit pill, Authors, and the printable version are available on every plain document. The old full-DOM MorphIframe renderer stays reachable with the newformat: q2-html-render(a builtin pseudo-format thatq2 rendertreats exactly likeq2-debug: plain HTML output).Plan and decision record:
claude-notes/plans/2026-09-09-hub-client-default-q2-preview.md(strand bd-kltzdhle).What changed
quarto-core,wasm-quarto-hub-client): registerq2-html-render;render_page_in_project_with_attributiongains an optionalprefer_preview_formatargument that applies theq2 previewsubstitution (html → q2-preview,revealjs → q2-slides) while keepingRenderHost::HubClient(so Q-5-12 still fires — the reason the SPA'srender_page_for_previewcould not be reused). The response now reports the resolvedformat.getQ2Formatmapshtml → q2-previewandq2-html-render → full-DOM;ReactPreviewrenders every preview-pipeline format through the one entry point with the knob, which also moves reveal decks off the SPA-only entry point (attribution now reaches decks; the Authors pill gate is a follow-up, bd-jx8b0ax9). Editor chrome gates key on the router-reported format and need no change.html(q2-html-render,pdf, …) shows a "no live preview in q2 preview … runq2 render" message naming the format instead ofconsole.error+ the boot screen.q2-html-render | q2-debug | q2-preview, no default); smoke-all picks the kind from the fixture's own front matter; the CSS assertion helper reads the render string instead of the old iframe's DOM.body >selector the React root can never satisfy (bd-xiz1a2go). Each is its own commit.Known: 6 fixtures keep their e2e DOM assertions skipped
DOM_ASSERTIONS_PENDING_PARITY(hub-client/e2e/helpers/smokeAllDiscovery.ts) lists fixtures whoseensureHtmlElementsselectors do not yet hold in the q2-preview iframe. Only that assertion type is skipped — the fixtures still render in the default iframe with their regex / CSS / diagnostics assertions live — and each skip logs a[smoke-diag] dom-assertions-skipped strand=…line. Owners: bd-c3dtpe36 (mermaid), bd-b3oq2fsy (usercss:links), bd-fandfn60 (repo actions in TOC), bd-bg0jze2i (callout body heading), bd-q2wqj24c (CommentBlockwraps every block in a positioned div, breaking parent > child theme rules — handoff note atclaude-notes/plans/2026-09-10-commentblock-overlay-handoff.md), bd-47afd5ro (tabsets).Verification
cargo xtask verify(full, with the hub build) green at the branch head; hub-client unit / integration / WASM tiers under the pinned Node 24; smoke-all e2e 156 passed, 1 pre-existing skip.q2 preview --ui editor(the embedded hub-client bundle): a plain document mounts the q2-preview iframe with Edit/Authors/Comments enabled and a comment added from the preview lands in the source as[>> …];format: q2-html-rendermounts the MorphIframe with the pills disabled;format: html: {toc: true}and a reveal deck route correctly. Screenshot inclaude-notes/plans/bd-kltzdhle-plain-doc-q2-preview.png.q2 previewon aq2-html-renderdocument shows the new message;q2 renderon it writes HTML.Review notes
body >fixture edit (3a7653a) touches the P5 title-block parity corpus; the placement intent is preserved withensureFileRegexMatcheson the render.🤖 Generated with Claude Code
https://claude.ai/code/session_0169cS5WjZuUvam43it8hdtX