From c32d11cb8babf43478a10fef8536aabf5ed8200c Mon Sep 17 00:00:00 2001 From: saberzero1 Date: Fri, 17 Apr 2026 03:05:36 +0200 Subject: [PATCH] fix(build): remove extension-stripped slug aliases that pollute allSlugs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- quartz/build.ts | 49 ++----------------------------------------------- 1 file changed, 2 insertions(+), 47 deletions(-) diff --git a/quartz/build.ts b/quartz/build.ts index b585ff2..bc36832 100644 --- a/quartz/build.ts +++ b/quartz/build.ts @@ -9,7 +9,7 @@ import { parseMarkdown } from "./processors/parse" import { filterContent } from "./processors/filter" import { emitContent } from "./processors/emit" import cfg from "../quartz" -import { FilePath, FullSlug, joinSegments, slugifyFilePath } from "./util/path" +import { FilePath, joinSegments, slugifyFilePath } from "./util/path" import { detectSlugCollisions, formatCollisionWarning } from "./util/slugCollisions" import chokidar from "chokidar" import { ProcessedContent } from "./plugins/vfile" @@ -20,7 +20,7 @@ import { options } from "./util/sourcemap" import { Mutex } from "async-mutex" import { getStaticResourcesFromPlugins } from "./plugins" import { randomIdNonSecure } from "./util/random" -import { ChangeEvent, QuartzPageTypePluginInstance } from "./plugins/types" +import { ChangeEvent } from "./plugins/types" import { minimatch } from "minimatch" function reportSlugCollisions(content: ProcessedContent[]): void { @@ -29,37 +29,6 @@ function reportSlugCollisions(content: ProcessedContent[]): void { console.warn(styleText("yellow", formatCollisionWarning(collisions))) } -function getPageTypeExtensions(ctx: BuildCtx): Set { - const extensions = new Set() - const pageTypes = (ctx.cfg.plugins.pageTypes ?? []) as unknown as QuartzPageTypePluginInstance[] - for (const pt of pageTypes) { - if (pt.fileExtensions) { - for (const ext of pt.fileExtensions) { - extensions.add(ext) - } - } - } - return extensions -} - -// For files whose extensions are handled by PageType plugins (e.g. .canvas, .base), -// add extension-stripped slug aliases so that wikilink resolution (CrawlLinks) maps -// `![[file.canvas]]` to the virtual-page slug `file` instead of the raw `file.canvas`. -function addVirtualPageSlugAliases(allSlugs: FullSlug[], extensions: Set): FullSlug[] { - const extra: FullSlug[] = [] - for (const slug of allSlugs) { - for (const ext of extensions) { - if (slug.endsWith(ext)) { - const stripped = slug.slice(0, -ext.length) as FullSlug - if (!allSlugs.includes(stripped) && !extra.includes(stripped)) { - extra.push(stripped) - } - } - } - } - return extra -} - type ContentMap = Map< FilePath, | { @@ -121,14 +90,6 @@ async function buildQuartz(argv: Argv, mut: Mutex, clientRefresh: () => void) { ctx.allFiles = allFiles ctx.allSlugs = allFiles.map((fp) => slugifyFilePath(fp as FilePath)) - // Add extension-stripped slug aliases for PageType-registered extensions - // so that wikilinks like ![[file.canvas]] resolve to virtual page slugs - const ptExtensions = getPageTypeExtensions(ctx) - if (ptExtensions.size > 0) { - const aliases = addVirtualPageSlugAliases(ctx.allSlugs, ptExtensions) - ctx.allSlugs.push(...aliases) - } - const parsedFiles = await parseMarkdown(ctx, filePaths) reportSlugCollisions(parsedFiles) const filteredContent = filterContent(ctx, parsedFiles) @@ -305,12 +266,6 @@ async function rebuild(changes: ChangeEvent[], clientRefresh: () => void, buildD ctx.allFiles = Array.from(contentMap.keys()) ctx.allSlugs = ctx.allFiles.map((fp) => slugifyFilePath(fp as FilePath)) - // Add extension-stripped slug aliases for PageType-registered extensions - const ptExtensions = getPageTypeExtensions(ctx) - if (ptExtensions.size > 0) { - const aliases = addVirtualPageSlugAliases(ctx.allSlugs, ptExtensions) - ctx.allSlugs.push(...aliases) - } const markdownContent = Array.from(contentMap.values()) .filter((file) => file.type === "markdown") .map((file) => file.content)