Skip to content

Publish source: file brand fonts beside the theme CSS (bd-ve916wr8) - #668

Merged
cscheid merged 4 commits into
mainfrom
feature/bd-ve916wr8-brand-file-fonts
Sep 9, 2026
Merged

Publish source: file brand fonts beside the theme CSS (bd-ve916wr8)#668
cscheid merged 4 commits into
mainfrom
feature/bd-ve916wr8-brand-file-fonts

Conversation

@cscheid

@cscheid cscheid commented Sep 9, 2026

Copy link
Copy Markdown
Member

Fixes bd-ve916wr8.

Problem

Local fonts declared in _brand.yml (source: file) were never copied into the output, and the @font-face URL was document-relative while the compiled theme CSS lives in site_libs/quarto/ (website) or {stem}_files/ (single doc). Every local brand font 404'd. The per-document prefix also split a site into one theme bundle per page depth on a cold SASS cache, and aliased pages onto whichever compiled first on a warm one, because the cache key never included it. Evidence and repro fixtures: claude-notes/plans/brand-file-fonts-website-investigation/NOTES.md.

Fix

Fonts are published as project-scope artifacts in a fonts/ directory beside whichever theme CSS references them (site_libs/quarto/fonts/, {stem}_files/fonts/, …/revealjs/fonts/), and the SCSS layer emits the constant url('fonts/<basename>') format('woff2'). The URL no longer depends on the document, so one compiled theme serves every page and the cache key is complete by construction. Artifacts flush through both the native sink and the preview VFS, so the same mechanism covers q2 preview.

  • quarto-brand: published_font_name; BrandFontFileEntry::Explicit becomes a struct that tolerates unsettled per-file keys (format:, display:) and exposes them; ResolvedBrand remembers its file.
  • quarto-sass: brand_to_layers(&Brand) drops the font-path prefix; ThemeContext::with_brand drops brand_dir; resolve_brand_layers is gone (reveal resolves the brand and publishes fonts itself).
  • quarto-core: new brand_fonts module (publish, leading / = project root per the path contract, dedupe identical bytes). ArtifactMergeConflict carries the producers' source metadata, and font clashes map to Q-14-10 from both the serial tail and the parallel Pass-2 reducer.
  • Diagnostics: Q-14-9 font file missing (warn, continue), Q-14-10 same name / different bytes (hard error naming both sources), Q-14-11 unsupported key on a file entry (warn, ignore). Numbered past Q-14-6/7 (Theme compile failures are structured hard errors, not silent DEFAULT_CSS (bd-jsvetdea) #661) and Q-14-8 (Support brand.yml font weight ranges; make invalid weights a Q-14-8 error (bd-5fseopxy) #663).
  • Docs: "Local font files" section in the brand guide; three error pages + sidebar; inventory row in path-resolution-model.md.

Design decisions (fonts as artifacts, collision is an error, / = project root, warn on unknown keys) are recorded in claude-notes/plans/2026-09-08-brand-file-fonts-website.md.

Tests

  • 12 end-to-end cases in crates/quarto-core/tests/integration/brand_fonts.rs through render_to_file / ProjectPipeline: website at two depths (one bundle), nested-only page, rooted path, brand in a subdirectory, light/dark distinct fonts, identical-bytes dedupe, both collision shapes, missing file, unknown key, single doc, reveal. All confirmed failing at HEAD first.
  • Unit coverage in quarto-brand (font_files_test.rs), quarto-sass (brand_layer_test.rs), quarto-core (brand_fonts module).
  • cargo nextest run --workspace 13,763 passed; clippy -D warnings clean on the touched crates; cargo xtask verify --skip-hub-tests green including the WASM leg (hub-client vitest is red on main from the Node 26 localStorage issue, bd-lh30hlvd).

End-to-end

cargo run --bin q2 -- render claude-notes/plans/brand-file-fonts-website-investigation/repro

One bundle site_libs/quarto/quarto-theme-<fp>.css linked by index.html and posts/one.html, containing

@font-face{font-family:"EB Garamond";src:url("fonts/EBGaramond-VariableFont_wght.woff2") format("woff2");font-weight:400;font-style:normal}

and site_libs/quarto/fonts/EBGaramond-VariableFont_wght.woff2 byte-identical to the source. The single-doc fixture publishes doc_files/fonts/… next to doc_files/styles.css.

Coordination

Expected textual conflict with #663 (bd-5fseopxy) in file_font_face_block, types.rs, and the theme stage; whichever lands second rebases. On merge: comment on bd-r1y48cx0 (css: files never copied) pointing at this mechanism.

🤖 Generated with Claude Code

https://claude.ai/code/session_01CoQm2W4rtvPQNDtkYKdb6D

@posit-snyk-bot

posit-snyk-bot commented Sep 9, 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 and others added 4 commits September 9, 2026 14:10
…Ls mis-based

Plan skeleton + committed repro fixtures (website and single-doc) with
evidence in NOTES.md. Confirms the strand at HEAD and adds two findings:
single-doc renders are equally broken (styles.css lives in {stem}_files/),
and the persistent SASS cache key omits the font URL prefix, so pages
alias onto whichever compiled first.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CoQm2W4rtvPQNDtkYKdb6D
Records the design decisions agreed with the user (artifact placement,
collision-is-an-error, leading-slash semantics, format() hint, warn on
unknown file-entry keys, conflict expectation with bd-5fseopxy, merge-time
comment on bd-r1y48cx0) and expands the phases into a TDD checklist.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CoQm2W4rtvPQNDtkYKdb6D
Local fonts declared in _brand.yml (`source: file`) were never copied
into the output, and the @font-face URL was document-relative while
the compiled theme CSS lives in site_libs/quarto/ (website) or
{stem}_files/ (single doc) — so every local brand font 404'd. The
per-document prefix also split a site into one theme bundle per page
depth on a cold SASS cache, and aliased pages onto whichever compiled
first on a warm one, because the cache key never included it.

Fonts are now published as project-scope artifacts in a `fonts/`
directory beside whichever theme CSS references them
(`site_libs/quarto/fonts/`, `{stem}_files/fonts/`, `…/revealjs/fonts/`),
and the SCSS layer emits the constant `url('fonts/<basename>')` with a
`format()` hint from the extension. The URL no longer depends on the
document, so one compiled theme serves every page and the cache key
is complete by construction. Artifacts flush through both the native
sink and the preview VFS, so the same mechanism covers `q2 preview`.

- quarto-brand: `published_font_name`; `BrandFontFileEntry::Explicit`
  is a struct that tolerates unsettled per-file keys (`format:`,
  `display:`) and exposes them; `ResolvedBrand` remembers its file.
- quarto-sass: `brand_to_layers(&Brand)` drops the font-path prefix;
  `ThemeContext::with_brand` drops `brand_dir`; `resolve_brand_layers`
  is gone (reveal resolves the brand and publishes fonts itself).
- quarto-core: new `brand_fonts` module (publish, resolve `/` as
  project root, dedupe identical bytes); `ArtifactMergeConflict`
  carries the producers' `source` metadata and font clashes map to
  Q-14-10 from both the serial tail and the parallel Pass-2 reducer.
- Diagnostics: Q-14-9 (font file missing, warn + continue), Q-14-10
  (same name, different bytes — hard error naming both sources),
  Q-14-11 (unsupported key on a file entry, warn + ignore), each with
  its docs page and sidebar entry.
- Tests: 12 end-to-end cases through render_to_file / ProjectPipeline
  (website depths, single doc, reveal, rooted paths, brand subdir,
  light/dark, dedupe, both collision shapes, both warnings), plus
  quarto-sass / quarto-brand unit coverage.
- Docs: "Local font files" section in the brand guide; inventory row
  in the path-resolution contract.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CoQm2W4rtvPQNDtkYKdb6D
…bering

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CoQm2W4rtvPQNDtkYKdb6D
@cscheid
cscheid force-pushed the feature/bd-ve916wr8-brand-file-fonts branch from 1f91465 to b6b1e94 Compare September 9, 2026 19:30
@cscheid
cscheid merged commit 5a12a77 into main Sep 9, 2026
10 checks passed
@cscheid
cscheid deleted the feature/bd-ve916wr8-brand-file-fonts branch September 9, 2026 19:54
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.

2 participants