Skip to content

Fix Quarto website rendering and deployment for mixed-format projects#4048

Merged
zackverham merged 14 commits into
mainfrom
fix/quarto-render-respects-format-4021
Apr 22, 2026
Merged

Fix Quarto website rendering and deployment for mixed-format projects#4048
zackverham merged 14 commits into
mainfrom
fix/quarto-render-respects-format-4021

Conversation

@zackverham

@zackverham zackverham commented Apr 21, 2026

Copy link
Copy Markdown
Collaborator

Summary

Fixes #4021 — Three bugs prevented deploying pre-rendered Quarto websites with mixed formats (e.g. HTML + revealjs slides) to Connect.

1. quarto render forced --to html

QuartoProjectHelper passed --to html to every quarto render command, overriding per-file format declarations like format: revealjs. Removed the flag so Quarto respects each file's declared format.

2. Relative projectDir broke project-level rendering

renderContent() passed the relative projectDir (e.g. ".") from the config directly to QuartoProjectHelper. The helper's _quarto.yml disk check resolved relative to the extension host's cwd, not the project directory, so it fell through to single-document render. Only the source entrypoint was rendered, skipping other files like slides. Fixed by resolving to an absolute path before passing to the render flow.

3. Missing content_category: "site" in manifest

When deploying a pre-rendered Quarto website as HTML, the generated manifest.json was missing content_category: "site". Without this, Connect only serves the entrypoint and returns 404 for other pages. Fixed by detecting multi-page sites in manifestFromConfig when the HTML entrypoint lives inside a known Quarto output directory (_site/ or _book/).

Test plan

  • Unit tests for --to html removal (quartoProjectHelper.test.ts)
  • Integration tests for revealjs and mixed-format rendering (quartoProjectHelper.smoke.test.ts)
  • Integration tests for relative vs absolute projectDir (quartoProjectHelper.smoke.test.ts)
  • Unit tests for content_category detection (manifestFromConfig.test.ts)
  • Integration test for full manifest→bundle→archive pipeline (bundler.test.ts)
  • Lint passes
  • Manual test: clone reprex repo, render, deploy as HTML to Connect, verify slides page loads without 404

🤖 Generated with Claude Code

…ats (#4021)

Remove the --to html flag from quarto render commands so that Quarto
respects each file's declared format (e.g. revealjs) instead of forcing
everything to plain HTML.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@zackverham zackverham requested a review from a team as a code owner April 21, 2026 14:44
@posit-connect-projects

posit-connect-projects Bot commented Apr 21, 2026

Copy link
Copy Markdown
Contributor

Claude encountered an error —— View job


I'll analyze this and get back to you.

@posit-connect-projects

posit-connect-projects Bot commented Apr 21, 2026

Copy link
Copy Markdown
Contributor

Claude encountered an error —— View job


I'll analyze this and get back to you.

…4021)

Add smoke tests that render real Quarto content with revealjs format
to validate the fix. Tests verify standalone revealjs documents produce
slides and mixed-format projects preserve each file's declared format.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@zackverham

Copy link
Copy Markdown
Collaborator Author

Follow-up issue here: #4049

@jonkeane jonkeane left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this!

// index.html should NOT contain revealjs
const indexHtml = fs.readFileSync(indexPath, "utf-8");
expect(indexHtml).not.toContain("reveal");
}, 60_000);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for these tests! I presume they both fail without the change to quartoProjectHelper.ts?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't run these specific tests without the change - the reprex the customer gave executing with / without the change seemed like a better validation that we're actually fixing the thing - but this test does prove that the new behavior is correct!

await helper.render();

expect(capturedCommand).toBe(`quarto render "${tmpDir}" --to html`);
expect(capturedCommand).toBe(`quarto render "${tmpDir}"`);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not related to this PR, but I wonder if we need both the command capture here as well as the similar unit tests in quartoProejctHelper.test.ts? If we do clean it up, IMO it should be in another PR not this one.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do you mean if the assertions are necessary in all of these tests? I do think the capture here is a little too brittle - but I think we care that QuartoProjectHelper does successfully invoke the mocked terminal with some type of render on these inputs.

@claude can you create an issue to improve how these tests are handling the quarto invocation?

@posit-connect-projects posit-connect-projects Bot Apr 21, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude finished @zackverham's task in 53s —— View job


  • Read the smoke test and unit test files to understand the current approach
  • Draft and create a GitHub issue for improving quarto invocation handling in tests

Created #4058 to track this. The issue covers the overlap between the command construction smoke tests and the unit tests, and suggests relaxing the exact command-string assertions in the smoke tests to focus on what they uniquely validate (filesystem-to-render-mode integration) while leaving precise command construction assertions to the unit tests.

zackverham and others added 5 commits April 22, 2026 11:55
…4021)

When deploying a pre-rendered Quarto website as HTML, the manifest was
missing content_category="site", causing Connect to serve only the
entrypoint and return 404 for other pages (e.g. revealjs slides).

Detect multi-page sites in manifestFromConfig by checking whether the
HTML entrypoint lives inside a subdirectory (e.g. _site/index.html).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ne file (#4021)

renderContent() passed the relative projectDir (e.g. ".") from the
config directly to QuartoProjectHelper. The helper's _quarto.yml
disk check resolved relative to the extension host's cwd instead of
the actual project directory, causing it to fall through to single-
document render. Only the source entrypoint was rendered, skipping
other project files like revealjs slides.

Resolve projectDir to an absolute path before passing it to the
render flow, matching the pattern already used in the publish path.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…4021)

Verifies that QuartoProjectHelper detects _quarto.yml correctly with
an absolute projectDir but fails with a relative one (e.g. ".") when
cwd differs from the project directory. Also confirms that an absolute
path triggers a full project render, not a single-document render.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The previous heuristic set content_category="site" for any HTML deployment
with a subdirectory entrypoint, which could false-positive on standalone
HTML files nested in arbitrary subdirectories. Narrow the check to only
known Quarto output directories (_site/, _book/) and remove the unused
path import since TOML paths always use forward slashes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…add cwd precondition guard (#4021)

- Remove duplicate section divider comment block between test sections
- Add explicit precondition check that process.cwd() does not contain
  _quarto.yml, so a failure in unusual environments is immediately clear
- Remove now-redundant inline comment about the guard condition

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@zackverham zackverham changed the title Fix Quarto render forcing --to html, breaking revealjs and other formats Fix Quarto website rendering and deployment for mixed-format projects Apr 22, 2026
zackverham and others added 2 commits April 22, 2026 12:41
Resolve conflict in manifestFromConfig.ts: keep contentCategoryField
spread and adopt main's updated comment wording. Fix TS strict error
for array index access in contentCategoryField.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@zackverham zackverham marked this pull request as draft April 22, 2026 16:49
@zackverham zackverham marked this pull request as ready for review April 22, 2026 16:49
@posit-connect-projects

posit-connect-projects Bot commented Apr 22, 2026

Copy link
Copy Markdown
Contributor

Claude finished @zackverham's task in 1m 33s —— View job


Code Review Complete

  • Gather context and review changed files
  • Review quartoProjectHelper.ts changes (--to html removal)
  • Review manifestFromConfig.ts changes (content_category detection)
  • Review homeView.ts changes (projectDir resolution)
  • Review test coverage
  • Check for accidentally committed files
  • Post final review feedback

Summary

This PR fixes three interconnected bugs that prevented deploying pre-rendered Quarto websites with mixed formats. The core implementation changes are well-designed and properly tested. However, there are accidentally committed files that must be removed before merging.

Accidentally Committed Files (Blocking)

The following files appear to have been accidentally staged and committed. These need to be removed:

File Size Issue
.husky/post-commit 8 lines Husky hook file
.husky/post-rewrite 8 lines Husky hook file
archives/publisher-1.37.15-30-g062c2eb8b-darwin-arm64.tar.gz ~7.5 MB Build artifact
cover.out 4292 lines Coverage output
harness ~11.5 MB Binary file
smoke-test-content/ Multiple files Test content directory

Fix this →

Implementation Review

1. quartoProjectHelper.ts--to html removal

The change correctly removes the hardcoded --to html flag, allowing Quarto to respect per-file format declarations (like format: revealjs). The implementation is clean and minimal.

2. manifestFromConfig.tscontent_category detection

The heuristic for detecting multi-page sites is well-designed:

const quartoSiteOutputDirs = new Set(["_site", "_book"]);

function contentCategoryField(cfg) {
  if (cfg.type === ContentType.HTML && cfg.entrypoint) {
    const parts = cfg.entrypoint.split("/");
    if (parts.length > 1 && quartoSiteOutputDirs.has(parts[0]!)) {
      return { content_category: "site" };
    }
  }
  return undefined;
}

The restriction to known Quarto output directories (_site, _book) avoids false positives for arbitrary subdirectories. The inline comments clearly explain the rationale.

3. homeView.tsprojectDir resolution

The fix at lines 291-301 properly resolves relative projectDir values to absolute paths before passing to QuartoProjectHelper:

const absProjectDir = path.resolve(root, projectDir);

This ensures filesystem checks in the helper resolve correctly regardless of the extension host's working directory.

Test Coverage

The test coverage is excellent:

  • Unit tests (quartoProjectHelper.test.ts): Verify command construction logic for both document and project render modes
  • Smoke tests (quartoProjectHelper.smoke.test.ts):
    • Real filesystem integration for _quarto.yml detection
    • Real Quarto rendering tests (when available) including mixed-format projects
    • Relative vs absolute projectDir edge cases
  • manifestFromConfig.test.ts: Comprehensive tests for content_category field with various entrypoint patterns
  • bundler.test.ts: End-to-end integration test verifying the manifest survives the full bundle→archive pipeline

The smoke tests at lines 137-177 specifically validate the mixed-format scenario (html + revealjs) that was broken before this fix.

Minor Observations

  1. The copyright year in manifestFromConfig.ts and quartoProjectHelper.smoke.test.ts is 2026 — likely a typo that should be 2025 (non-blocking).

  2. The follow-up issue Reduce brittleness of quarto command assertions in smoke tests #4058 (created per @jonkeane's review comment) correctly identifies the opportunity to reduce overlap between smoke tests and unit tests for command construction.


Verdict: The implementation is solid and well-tested. Please remove the accidentally committed files before merging.

zackverham and others added 4 commits April 22, 2026 12:50
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@zackverham zackverham requested a review from jonkeane April 22, 2026 17:12
@dotNomad

Copy link
Copy Markdown
Collaborator
  • The copyright year in manifestFromConfig.ts and quartoProjectHelper.smoke.test.ts is 2026 — likely a typo that should be 2025 (non-blocking).

🙄

@dotNomad dotNomad left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes look great. Thank you for the additional test coverage.

Comment on lines +219 to +222
test("relative projectDir '.' only finds _quarto.yml if cwd matches", async () => {
// Guard: this test assumes cwd does NOT contain _quarto.yml.
// If it does, the assertion below would pass trivially or fail confusingly.
expect(fs.existsSync(path.join(process.cwd(), "_quarto.yml"))).toBe(false);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we can make QuartoProjectHelper handle this in the future so it doesn't need to be passed the absolute dir, but can create it itself.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@claude can you create a follow-up PR for this?

@posit-connect-projects posit-connect-projects Bot Apr 22, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude finished @zackverham's task in 5m 18s —— View job


  • Read current implementation of QuartoProjectHelper and homeView.ts
  • Create a new branch from main
  • Move path resolution logic into QuartoProjectHelper
  • Update homeView.ts to pass rootDir instead of resolving path manually
  • Update tests (unit + smoke)
  • Run lint and tests (all pass)
  • Push and create PR

Summary

Created follow-up PR #4070 which adds an optional rootDir parameter to QuartoProjectHelper's constructor. When provided, it resolves projectDir against rootDir internally using path.resolve(), so callers like homeView.ts no longer need to resolve paths themselves.

Changes:

  • quartoProjectHelper.ts — new rootDir constructor param, resolves projectDir internally
  • renders.ts — forwards rootDir to the helper
  • homeView.ts — passes this.root?.uri.fsPath as rootDir
  • Unit and smoke tests covering relative, nested relative, absolute, and omitted rootDir scenarios
    |

@zackverham zackverham merged commit 1caee58 into main Apr 22, 2026
31 checks passed
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.

Rendering a Quarto website from the Publisher panel produces different output from quarto render when a page uses revealjs

3 participants