fix(preview): re-resolve _brand.yml added/removed mid-preview#14609
Merged
Conversation
projectResolveBrand caches the project-level brand on first resolve and never invalidates it. In a long-lived preview context a _brand.yml added or removed mid-session is ignored until the process restarts. These tests drive the fix at the resolve level (no preview server): single-file and project, add and remove.
preview-architecture.md covered the per-file fileInformationCache (fullMarkdown guard, invalidateForFile) but never the caches that live directly on ProjectContext. brandCache and outputNameIndex are write-once and survive every re-render, reset only on a full context rebuild. The doc also conflated project.brandCache with the per-file frontmatter brand field. Records why #14593 needs a resolve-time filesystem guard rather than active invalidation: the render-request path (RStudio Render button) re-renders an unchanged .qmd while a sibling _brand.yml appears or disappears, so there is no change event to invalidate on.
projectResolveBrand populated project.brandCache on first resolve and early-returned it forever. During a long-lived preview the context persists across re-renders, so a sibling _brand.yml added or removed mid-session was ignored until the process restarted. The render-request path (RStudio Render button) carries no change signal — the input .qmd is unchanged and the watcher does not watch _brand.yml — so only a resolve-time filesystem check can notice. Guard the cache with a source-state token over the candidate brand paths the resolver already consults (existence + mtime + size), mirroring the fullMarkdown mtime+size guard. The cache is reused only while that token matches; any add, remove, or content edit re-resolves. Format-independent, so typst and HTML are both covered by construction.
Collaborator
✅ 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. |
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.
When
quarto previewis running and a sibling_brand.ymlis added or removed, the output ignores the change until the preview process is restarted.Root Cause
projectResolveBrand(src/project/project-shared.ts) populatedproject.brandCacheon first resolve and returned it unconditionally on all subsequent calls. During a long-lived preview session the project context persists across re-renders, so the cached brand state — "no brand" or "brand present" — is frozen from the first render. The render-request path (including RStudio's Render button) carries no change signal: the source.qmdis unchanged, and_brand.ymlis not in the file watcher.Fix
Guard the cache with a source-state token over the candidate brand file paths the resolver already consults — each path's existence, mtime, and size, joined into a string. The cache is reused only while that token matches; any add, remove, or content edit re-resolves. Mirrors the
fullMarkdownmtime+size guard in the same function. Format-independent: both typst and HTML go throughprojectResolveBrand, so both are covered by construction.Test Plan
Tested manually against
tests/docs/manual/preview/brand-detection/(T28–T32 from the preview test matrix):_brand.ymladded mid single-file Typst preview →report.typgains#show link: set text(fill: rgb("#bc1e22"), )_brand.ymlremoved mid single-file Typst preview → line disappears fromreport.typ--bs-primary: #BC1E22_quarto.yml— brand applies on add and reverts on remove (CSS hashece4→cb9d→ece4)_brand.ymlwith repeated saves — CSS hash stays constant (cache hit, no spurious re-resolve)Unit coverage in
tests/unit/preview-brand-cache.test.ts.Fixes #14593