Skip to content

feature: project metadata#30

Draft
gordonwoodhull wants to merge 11 commits intomainfrom
feature/project-metadata
Draft

feature: project metadata#30
gordonwoodhull wants to merge 11 commits intomainfrom
feature/project-metadata

Conversation

@gordonwoodhull
Copy link
Collaborator

_quarto.yml and _metadata.yml contribute to metadata across runtimes.

Includes smoke-all tests for the wasm runtime. quarto-hub uses common render function render_qmd

The browser runtime is a little different - maybe it would be worth also running the smoke tests there or instead of the current wasm smoke-all tests. It took a few more steps to get this running in the browser, tested manually.

Filing as draft for CI until we discuss, but I think this is ready to merge.

Metadata Merge Layers

During the AstTransformsStage, metadata from multiple sources is merged
into a single flat config for the target format. Each layer is first
flattened via resolve_format_config: top-level keys are kept, the
format key is removed, and format.{target}.* keys are merged on top
(overriding same-named top-level keys). The flattened layers are then
merged in order — later layers override earlier ones.

Merge order (lowest → highest precedence)

  1. Project_quarto.yml metadata
  2. Directory_metadata.yml files between project root and document
    directory, walked root-to-leaf (deeper directories override shallower).
    The project root directory itself is excluded (the walk starts from the
    first subdirectory toward the document).
  3. Document — YAML frontmatter of the .qmd file
  4. Runtime — injected by the host environment (e.g., hub-client sets
    format.html.source-location: full for scroll sync)

Layers 1–2 require a project config (_quarto.yml) to exist.
Layer 4 is optional and comes from SystemRuntime::runtime_metadata().

Compatibility with TS Quarto

This matches the TS Quarto merge order in directoryMetadataForInputFile
(src/project/project-shared.ts), which walks from project root toward
the input directory, plus project and CLI metadata layers. TS Quarto's
CLI --metadata / --metadata-file flags correspond to our runtime
metadata layer.

Note: TS Quarto's directory walk uses relative(projectDir, inputDir)
split by path separator, which means root _metadata.yml is only checked
when the relative path is non-empty (i.e., the document is in a
subdirectory). Our implementation matches this behavior.

Implementation

crates/quarto-core/src/stage/stages/ast_transforms.rs (merge logic)
crates/quarto-core/src/project.rsdirectory_metadata_for_document
crates/quarto-config/src/format.rsresolve_format_config

Add resolve_format_config() to extract format-specific settings from metadata.
This enables proper merging of _quarto.yml settings with document frontmatter,
where format.html.* settings override top-level settings when rendering to HTML.

Changes:
- New quarto-config/src/format.rs with resolve_format_config() function
- ProjectConfig stores full metadata as ConfigValue (replaces raw + format_config)
- AstTransformsStage flattens both project and document metadata for target format
- TocGenerateTransform reads from ast.meta instead of format metadata
- WASM updated to use ProjectConfig::with_metadata()

Merge precedence (lowest to highest):
1. Project top-level settings
2. Project format-specific settings (format.{target}.*)
3. Document top-level settings
4. Document format-specific settings (format.{target}.*)

Includes unit tests for format resolution and integration tests for
metadata merging in AstTransformsStage.
Add support for _metadata.yml files in directory hierarchies, matching
TS Quarto behavior. Directory metadata is discovered by walking from
project root to document's parent directory.

Core implementation:
- Add directory_metadata_for_document() in project.rs
- Walk directory hierarchy, parse _metadata.yml files
- Support both .yml and .yaml extensions
- Return layers in root-to-leaf order for merging
- Resolve relative paths in _metadata.yml against their source directory

Merge integration:
- Update ast_transforms.rs to include directory metadata
- Merge order: project -> dir[0] -> dir[1] -> ... -> document
- Each layer flattened for target format before merging

Smoke-all tests for basic inheritance, multi-level hierarchy merging,
document overrides, and path resolution.

Also adds default noErrorsOrWarnings assertion to smoke-all tests
(matching TS Quarto conventions) and supportPath file existence checks.
Replace the hardcoded single-file ProjectContext in render_qmd() with
ProjectContext::discover(), enabling _quarto.yml and _metadata.yml
support in hub-client WASM rendering.

Key changes:
- Unify the two WasmRuntime instances: the global VFS singleton is now
  stored as Arc<WasmRuntime> and shared with the rendering pipeline
  (previously each render created a fresh empty WasmRuntime)
- Port directory_metadata_for_document() from std::fs to SystemRuntime
  trait, enabling it to work with both native filesystem and WASM VFS
- render_qmd() now calls ProjectContext::discover() to find _quarto.yml
  in VFS parent directories (render_qmd_content* variants remain
  single-file since they receive inline content)

Includes 6 end-to-end WASM tests verifying project title inheritance,
document override precedence, parent directory discovery, and directory
metadata merging.
Vitest-based test runner that exercises all 15 smoke-all fixtures through
the WASM rendering pipeline, verifying feature parity with the native
Rust test runner. Implements all assertion types: ensureFileRegexMatches,
ensureHtmlElements (via jsdom), noErrors, noErrorsOrWarnings, shouldError,
and printsMessage. Filesystem assertions (fileExists, folderExists,
pathDoesNotExist) are parsed but treated as no-ops in WASM context.

One skip: expected-error.qmd's printsMessage check is bypassed because
WASM formats the error message differently than native render_to_file.
The shouldError assertion still runs for that fixture.

Adds `yaml` devDependency for frontmatter parsing.
Introduce a general-purpose mechanism for runtimes to inject metadata into the
configuration merge pipeline at the highest precedence, matching how quarto-cli
handles --metadata flags. Any runtime (WASM, native, sandboxed) can now provide
arbitrary metadata without per-feature API additions.

Changes:
- Add runtime_metadata() to SystemRuntime trait (returns Option<serde_json::Value>)
- Implement runtime metadata storage in WasmRuntime with set/get methods
- Add vfs_set_runtime_metadata/vfs_get_runtime_metadata WASM entry points
- Update AstTransformsStage to merge runtime metadata as highest-precedence layer
- Relax merge gate to also trigger when runtime metadata present (no project needed)
- Fix pre-existing bug: pampa HTML writer now reads top-level source-location key
  (consistent with Pandoc, which receives flattened metadata after format resolution)

Merge precedence (lowest to highest):
  Project → Directory → Document → Runtime

Tests: 6 Rust unit tests + 12 WASM integration tests, all passing.
Full suite: 6550 Rust tests + 47 WASM tests green.
Replace render_qmd_content (content string, no project context) with
render_qmd (VFS path, full project context) for the Preview component.
This gives live preview access to _quarto.yml, _metadata.yml, themes,
and all metadata layers.

Key changes:
- renderToHtml() now takes {documentPath} instead of (content, opts)
- Add renderContentToHtml() for standalone rendering (AboutTab)
- Add setScrollSyncEnabled() via runtime metadata (not per-render)
- Add setRuntimeMetadata/getRuntimeMetadata wrappers
- Remove renderQmdContentWithOptions and WasmRenderOptions

Known issue: compileAndInjectThemeCss causes "too much recursion" crash
in dart-sass when called after renderQmd. Needs investigation — theme
CSS compilation works but triggers infinite recursion in a Vite chunk.
The hub server's file discovery only recognized _quarto.yml/yaml,
.qmd files, and binary resources. Directory metadata files
(_metadata.yml/_metadata.yaml) were silently ignored, so they were
never added to the Automerge index, never synced to clients, and
never appeared in the VFS or file browser.

Add _metadata.yml and _metadata.yaml to the config file discovery
filter alongside _quarto.yml/yaml.
directory_metadata_for_document uses strip_prefix to compute the
relative path from project root to document directory. This requires
both paths to be in the same canonical form. ProjectContext::discover
always canonicalizes project.dir, but callers could pass relative or
non-canonical document paths (e.g., WASM render_qmd with VFS paths
like "chapters/chapter1.qmd"), causing strip_prefix to fail silently
and return no directory metadata layers.

Canonicalize the document_path inside the function using the runtime,
matching the established pattern (ProjectContext::discover,
ThemeContext, compile_document_css all canonicalize internally).

Also fix the test helper to canonicalize project.dir, matching the
invariant that discover enforces in production. On macOS, TempDir
paths go through /tmp -> /private/tmp symlinks, so without
canonicalization the test helper created ProjectContexts that didn't
match real-world behavior.
This function is superseded by the runtime metadata layer (ddcc1236).
Source location and other render options are now configured via
vfs_set_runtime_metadata() instead of a separate render function.

Removes WasmRenderOptions struct, the function itself, and all
references from TS interfaces and type definitions. Updates ad-hoc
WASM test scripts to use runtime metadata pattern instead.
The trim_prefix_suffix feature was stabilized in newer nightly Rust,
causing CI failures. The codebase only uses the stable strip_prefix/
strip_suffix methods, so the feature gate was unused.
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