addVirtualPageSlugAliases injected extension-stripped aliases for
every PageType-registered extension into ctx.allSlugs (e.g. for
`Canvas.canvas` it added an alias `canvas`). Intended to help
`![[file.canvas]]` transclusions resolve to virtual pages, but:
1. CrawlLinks' shortest strategy treats allSlugs entries as real
files. When an author writes `[[Canvas]]` in a note, shortest
lookup matches BOTH the alias `canvas` and real basenames
like `features/canvas`. With matchingFileNames.length === 2,
the uniqueness check fails and transformLink falls back to
absolute resolution, producing a dead href `./canvas` that
points to no emitted file.
2. The aliases were never actually needed for transclusion.
renderPage.tsx already has its own extension-stripping
fallback (lines 107-119) that consults allFiles directly.
Virtual pages (canvas-page, bases-page) emit at slugs that
KEEP their extension (e.g. `canvas.canvas`), so
`![[Canvas.canvas]]` resolves naturally without any alias.
3. The aliases mispresent Obsidian semantics. In Obsidian,
`[[Canvas]]` resolves to `Canvas.md`; non-md files require
the explicit extension (`[[Canvas.canvas]]`). Injecting
extension-stripped aliases into allSlugs made Quartz claim
bare names can refer to non-md virtual pages, which is
wrong.
Reproduction: docs/features/index.md contains `[[Canvas]]` which
should resolve to docs/features/Canvas.md (slug features/canvas).
Under the old behavior it produced href="../canvas" with
data-slug="canvas" pointing nowhere. After this fix it produces
href="../features/canvas" with data-slug="features/canvas"
correctly.
Verified with fresh `npx quartz build -d docs`:
- [[Canvas]] → ./features/canvas ✓
- ![[Canvas.canvas]] transclusion still works ✓
- ![[Base.base]] virtual page still emits ✓
- 0 internal broken links across all output ✓
- all 91 existing tests pass
The normalizeHastElement helper and its internal _rebaseHastElement
have been promoted to @quartz-community/utils so that plugins can
consume the same cross-slug HAST rebasing logic Quartz core uses
for transclude expansion. Previously they lived only in Quartz's
internal util/path module, which out-of-tree plugins (e.g.
canvas-page, whose buildEmbeddedContent embeds vfile.data.htmlAst
from arbitrary pages) could not import from.
normalizeRelativeURLs and its DOM-element sibling _rebaseHtmlElement
stay local - those operate on DOM Elements at runtime, not HAST AST
nodes, and belong with Quartz's DOM-specific code.
Compute the base path from cfg.baseUrl and set it as a data attribute on
<body> so client-side scripts can resolve links correctly when Quartz is
deployed to a subdirectory (e.g., user.github.io/repository).
Replace per-plugin npm install with a single aggregated npm install
in the quartz root. Plugins declaring requiresInstall in their quartz
manifest now have their peerDependencies collected and installed
together, letting npm resolve compatible versions across plugins.
Add logOverride to the main esbuild build context to silence three
warning types that originate from upstream third-party code bundled
inside plugin dist files:
- direct-eval: gray-matter's JS frontmatter engine in note-properties
- equals-negative-zero: webidl-conversions in citations
- duplicate-object-key: BibTeX month mapping in citations
- validatePluginExternals() scans plugin dist/ for unbundled imports
and warns when non-allowlisted externals are detected
- installPluginDepsIfNeeded() runs npm install for plugins with
quartz.requiresInstall flag (for native deps like sharp)
- Added requiresInstall field to PluginManifest type