This site is the experiment. Everything you see here is rendered by a dedicated Quartz
checkout that exists for one purpose: to try out the latest Quartz plugins, layouts and
rendering features on a real site, on this host, without any risk to the wikis that people
actually depend on.
The documentation moved
This host used to serve the upstream Quartz documentation at the root of
quartz.loca.zone. That full documentation site now lives at
https://wiki.quartz.loca.zone/ — 223 pages, byte-identical route set to the old
root release. The root you are reading now is the sandbox.
What is running here
Property
Value
Engine
https://github.com/jackyzha0/quartz.git
Branch
v5 (upstream default branch)
Commit
3dff48b5df6d84c9544a5ae19c8f2cbb01dc44e5
package.json version
5.0.0
Node
/usr/bin/node v22.23.1
npm
/usr/bin/npm 10.9.8
Plugin distribution
npm packages @quartz-community/* (35 installed)
Content root
sandbox/vault/ (this vault)
The pinned commit is on top of release tag v5.0.0
(7284f12ebf6b24e69b6cc9417e71b7d26700dc17, an ancestor of the pin). Nothing here floats:
the commit is fixed, npm ci installed from a committed lockfile, and the plugin index is
regenerated deterministically from the sandbox config.
This is isolated from the shared engine
There is a second, completely separate Quartz on this machine at
/home/loca/dev/quartz/quartz. It is the shared engine: 51 wikis are built with it.
Breaking it breaks all 51. The sandbox cannot break it, and the reasons are concrete and
structural rather than procedural:
Isolation boundary
Shared engine
This sandbox
Checkout
/home/loca/dev/quartz/quartz
/home/loca/dev/quartz-sandbox/sandbox/engine
node_modules
its own install tree
its own npm ci tree (328 packages)
Configuration
its own quartz.config.yaml + quartz.ts
sandbox/engine/quartz.config.yaml (this site’s)
Build output
wiki dist trees under /home/loca/dev/wikis
sandbox/dist-* only
Origin port
its own nginx upstreams
127.0.0.1:51947, served only for this site
Local plugins
none of the sandbox’s
sandbox/plugins/*, resolved relatively
So: separate checkout, separate node_modules, separate config, separate dist, separate
origin port. A plugin enabled here is not installed there. A style written here is not
compiled there. A build run here writes only into sandbox/dist-*. There is no shared
cache, no shared lockfile and no shared config file between the two trees.
Every experiment below is additionally labelled safe_for_shared_engine. That label answers
only one question — “would this change, if someone later chose to make it, be low risk for
the shared engine?” It is not permission to edit the shared engine. Nothing in this
sandbox ever does.
flowchart LR
subgraph shared["shared engine (off limits)"]
SE["/home/loca/dev/quartz/quartz<br/>branch v5 @ fd720a71"]
W["51 wikis<br/>/home/loca/dev/wikis"]
SE --> W
end
subgraph sbx["sandbox (this site)"]
EN["engine/<br/>pinned 5.0.0 @ 3dff48b5"]
VA["vault/<br/>showcase content"]
PL["plugins/<br/>local sandbox plugins"]
DI["dist-*/"]
VA --> EN
PL --> EN
EN --> DI
end
DI --> ORIGIN["127.0.0.1:51947"]
ORIGIN --> NG["nginx: quartz.loca.zone"]
W --> NG2["nginx: 51 wiki vhosts"]
Experiment link map
Each page states what it demonstrates, the exact configuration or content that produces it,
what the rendered HTML proves, and its safe_for_shared_engine verdict.
This page describes the actual finalsandbox/engine/quartz.config.yaml, not a proposed
configuration. The source file starts from the pinned checkout’s
quartz.config.default.yaml; the changes below turn it into a deliberately self-describing
showcase.
This is intentionally not the older github:quartz-community/... source syntax used by
the shared engine. The two local plugins use a different, explicit boundary:
Those paths resolve relative to the engine working directory and therefore land in
sandbox/plugins, never in the shared checkout. After changing this list, the generated
.quartz/plugins/index.ts is regenerated with PATH=/usr/bin:/bin /usr/bin/npm run install-plugins before building.
The showcase uses a complete but restrained Quartz frame.
The exact order, options, layout entries, disabled rows and reasons are maintained in
the generated roster page. The roster is the source-derived human view; the
YAML file is the executable truth.
Layout differences
The toolbar group is explicit so its controls stay together:
breadcrumbs uses condition: not-index, so the home page does not display a redundant
Home → Home trail. byPageType clears the right rail for canvas and bases routes, and removes
reader mode from folder/tag pages where that narrow view is not useful. See
the layout experiment for the rendered
comparison.
Explicitly disabled
No analytics, comments, citations, hard-line-breaks, ox-hugo, roam, recent-notes,
stacked-pages, tag-list, tag-page, cname, explicit-publish, quartz-themes/core or
remove-draft? No: remove-draft stays enabled so draft pages cannot accidentally enter
this public showcase. The full enabled/disabled truth table is on
the plugin roster.
Isolation checklist
Checkout: sandbox/engine, pinned v5.0.0 commit.
Dependencies: sandbox/engine/node_modules from npm ci.
Content: sandbox/vault, never /home/loca/dev/wikis.
Output: a fresh sandbox/dist-<epoch> directory per build.
Origin proof: private loopback port 51947 belongs to the sandbox runtime; this showcase’s
browser proof uses a separate private port in the 51960–51990 range.
Shared-engine baseline is checked before and after the build and remains branch v5, commit
fd720a71db0ca16f885e75fe0d689a574960e3d9, with only its two pre-existing dirty paths.
If a feature needs a source change in the shared checkout, it is blocked rather than copied
or monkey-patched here.
This table is the human-readable projection of the finalsandbox/engine/quartz.config.yaml.
kind is the installed package manifest’s Quartz category; when a package declares multiple
categories they are all listed. order is the configured order (a dash means the entry has
no explicit order and therefore uses its manifest/default ordering). No row is aspirational:
every enabled row is in the build that produced this site.
Enabled
Source
Kind
Order
Why enabled
@quartz-community/created-modified-date
transformer
10
Stable dates in metadata and Bases formulas.
@quartz-community/note-properties
transformer, component
5
Visible selected frontmatter properties.
@quartz-community/syntax-highlighting
transformer
20
Build-time light/dark code highlighting.
@quartz-community/obsidian-flavored-markdown
transformer
30
Wikilinks, callouts, mermaid, embeds, tasks and block refs.
@quartz-community/github-flavored-markdown
transformer
40
Tables, strikethrough and GFM task/list syntax.
@quartz-community/table-of-contents
transformer, component
50
Heading navigation on Markdown pages.
@quartz-community/crawl-links
transformer
60
Shortest internal-link resolution and broken-link gate.
@quartz-community/description
transformer
70
Derived descriptions for pages without one.
@quartz-community/latex
transformer
80
KaTeX inline/display math.
@quartz-community/remove-draft
filter
—
Keeps draft-marked content out of this public build.
@quartz-community/alias-redirects
emitter
50
Emits the legacy alias route used by an experiment.
@quartz-community/content-index
emitter
50
Emits content index, sitemap and RSS.
@quartz-community/favicon
emitter
—
Supplies the default Quartz favicon route.
@quartz-community/og-image
emitter
50
Generates per-page social preview images.
@quartz-community/canvas-page
pageType, component
50
Emits the native JSON Canvas page.
@quartz-community/content-page
pageType, component
—
Standard Markdown content frame.
@quartz-community/folder-page
pageType, component
—
Generated folder listing pages.
@quartz-community/tag-page
pageType, component
—
Generated tag listing pages.
@quartz-community/bases-page
transformer, pageType, component
50
Emits the native .base database views.
@quartz-community/explorer
component
50
File-tree navigation.
@quartz-community/graph
component
50
Local/global graph navigation.
@quartz-community/search
component
50
Full-text search toolbar.
@quartz-community/backlinks
component
50
Reverse-link panel.
@quartz-community/article-title
component
—
Article heading component.
@quartz-community/content-meta
component
—
Date/metadata line below titles.
@quartz-community/page-title
component
—
Site title in the left rail.
@quartz-community/darkmode
component
—
Light/dark mode toggle.
@quartz-community/reader-mode
component
—
Reader-mode toolbar toggle.
@quartz-community/breadcrumbs
component
—
Conditional breadcrumb trail.
@quartz-community/footer
component
—
Sandbox and wiki links in the footer.
@quartz-community/quartz-fonts
transformer, emitter
—
Quartz font resources.
@quartz-community/spacer
component
25
Mobile-only left-rail spacing.
@quartz-community/unlisted-pages
transformer
45
Removes explicitly unlisted pages from indexes.
@quartz-community/encrypted-pages
transformer, emitter
900
AES-encrypts the demo body and emits its shadow index.
../plugins/sandbox-reading-stats
transformer
900
Sandbox-only article word/minute badge.
../plugins/sandbox-manifest
emitter
900
Sandbox-only published-content manifest.
Disabled
Source
Kind
Order
Why disabled
@quartz-community/citations
transformer
85
No citation data in this showcase.
@quartz-community/hard-line-breaks
transformer
90
Not part of the showcase’s Markdown contract.
@quartz-community/ox-hugo
transformer
91
No ox-hugo source files.
@quartz-community/roam
transformer
92
No Roam-specific syntax.
@quartz-themes/core
transformer
—
Keep sandbox SCSS and pinned palette explicit.
@quartz-community/explicit-publish
filter
—
This vault uses public-by-default content plus explicit unlisted/encrypted demos.
@quartz-community/comments
component
—
No third-party comment service or credentials.
@quartz-community/tag-list
component
—
Tag pages and frontmatter already demonstrate tags without extra chrome.
@quartz-community/recent-notes
component
—
The Bases recent-notes view is the deliberate discovery example.
@quartz-community/stacked-pages
component
—
Not needed; avoids competing page stacking with SPA navigation.
@quartz-community/cname
emitter
—
This host is not GitHub Pages; nginx owns the hostname.
Options that matter
crawl-links uses markdownLinkResolution: shortest, disableBrokenWikilinks: true and
externalLinkIcon: true; the built proof found no unresolved internal anchors.
The two relative local entries above are the exact block sent by SandboxPlugin and merged
verbatim; their effects are proved on the local plugin page.
Safe boundary
Enabling a plugin in this table never changes /home/loca/dev/quartz/quartz. The package
source tree, generated plugin index, config, node_modules, vault and dist all belong to the
sandbox checkout. See the isolation statement and
the annotated config.
@quartz-community/bases-page renders Obsidian Bases — .base files — as
database-like views over the vault’s own notes. It is the most ambitious plugin in this
sandbox: it ships a small expression language (lexer, Pratt parser, bytecode compiler and
stack interpreter) so that .base formulas evaluate at build time.
The base in this vault queries this very site: every Markdown page, grouped by area, with
computed columns.
linkResolution must match crawl-links’ markdownLinkResolution (both shortest here),
otherwise links rendered inside view cells resolve differently from links in prose.
Four views over one dataset: a grouped table, a filtered table, a capped list and a cards
view. The reader switches between them with tabs; all four are materialised in the HTML.
What the rendered output proves
The base route emits a rendered table DOM — header cells carrying the displayNames from
properties, one row per matching note, and computed cells for formula.area,
formula.updated and formula.tag_count — plus a view switcher with the four view names.
Because the views are server-side rendered at build time, the rows are in the static
HTML: no client-side query runs.
It also proves the filter and the visibility interaction: experiments/unlisted-demo.md is
a Markdown file in experiments/, so file.ext == "md" matches it, yet it is absent from
every view because bases-page honours the unlisted convention and skips unlisted pages
in both the entry loop and the internal lookup used by .asFile().
safe_for_shared_engine
false — not because it is fragile, but because it is the largest new surface here: a
whole expression engine plus four view renderers evaluated for every matching note, and its
views are baked at build time (they never re-hydrate client-side, unlike graph, explorer and
search). Adopting it across 51 wikis would change build cost and introduce a second link
resolution setting that must be kept in sync with crawl-links. Sandbox-only until someone
measures it there.
@quartz-community/obsidian-flavored-markdown recognises a fenced code block whose language
is mermaid and rewrites it at build time. The emitted HTML is not an image: it is a
<code class="mermaid"> element carrying the diagram source in a data-clipboard
attribute, wrapped with an expand button and a fullscreen #mermaid-container dialog. The
mermaid library then draws the SVG in the browser.
That split matters. The build is fast and deterministic (no headless rendering during
build), and the diagram source stays in the page — copyable, diffable, and accessible to
text search — instead of being flattened into a binary.
mermaid: true is the plugin default; it is written out explicitly here so this page states
its own contract rather than relying on an upstream default that could change.
Content used
```mermaidflowchart TD A["quartz.config.yaml"] --> B["install-plugins"] B --> C["plugin index (.quartz/plugins/index.ts)"] C --> D["bootstrap-cli.mjs build"] D --> E["dist-<epoch>/"]```
Rendered result
flowchart TD
A["quartz.config.yaml"] --> B["install-plugins"]
B --> C["plugin index (.quartz/plugins/index.ts)"]
C --> D["bootstrap-cli.mjs build"]
D --> E["dist-<epoch>/"]
A second diagram, to show that more than one per page works and that a different mermaid
grammar (sequence) is passed through untouched:
sequenceDiagram
participant O as Operator
participant B as build
participant D as dist
O->>B: install-plugins
B->>B: regenerate plugin index
O->>B: bootstrap-cli build
B->>D: emit HTML routes
D-->>O: serve on 127.0.0.1
What the rendered output proves
The emitted HTML for this page contains class="mermaid" on a <code> element, a
data-clipboard attribute holding the exact diagram source, an
aria-label="Expand mermaid diagram" button and a #mermaid-container dialog — all present
in the static HTML with no JavaScript executed.
Drawing the SVG is a client-side step performed by the mermaid library, so a browser with no
route to mermaid’s module CDN will show the diagram source and the container, but no <svg>.
The build-time contract (class="mermaid" + data-clipboard + expand control) is what this
sandbox asserts, because that is the part the engine is responsible for.
safe_for_shared_engine
true — it is a per-page content feature of a transformer the shared engine already
enables by default, and pages without mermaid fences emit byte-identical HTML.
@quartz-community/latex with renderEngine: katex converts $...$ and $$...$$ into
KaTeX’s HTML + MathML output during the build. The browser receives finished markup and
a stylesheet; it does not download or run a math library, and it does not reflow the page
after load.
The emitted HTML contains class="katex" spans, a <math MathML subtree (KaTeX emits both
an accessible MathML copy and visually styled HTML), and katex-display wrappers for the
$$ blocks. Because all of that is in the static HTML, the math is present with JavaScript
disabled — the assertion for this page is the presence of katex and katex-display in the
DOM dump, not a screenshot.
safe_for_shared_engine
true — pages with no $ math are unaffected, and the only global cost is the KaTeX
stylesheet, which the engine already ships behind this plugin.
@quartz-community/obsidian-flavored-markdown turns > [!type] blockquotes into
<blockquote class="callout" data-callout="type"> with a generated title row, an icon and —
for the +/- variants — a collapsible body. Nesting works because the transform runs on
the blockquote tree, so a callout inside a callout is just a nested blockquote.
Callouts carry real information architecture value: the type is in the DOM as
data-callout, so a reader’s stylesheet, a print sheet or a scraper can distinguish a
warning from an aside without parsing prose.
> [!note] A note with a custom title> > Body text.> [!warning]- Collapsed by default> > Hidden until the reader expands it.> [!tip] Outer> > > [!example] Nested one level> > > [!quote] Nested two levels
Rendered result
A note with a custom title
The title after the type is arbitrary text. The type still lands in the DOM as
data-callout="note", so styling and scraping stay type-driven.
Collapsed by default
This body is present in the HTML but starts collapsed, because the type was written with a
trailing -. Nothing is lazily fetched — the text is in the emitted page, just not
expanded.
Explicitly expanded
A trailing + marks the callout collapsible but open on load.
Nesting, three levels deep, each level a distinct type:
Outer tip
The outer body renders normally.
Nested example
A callout inside a callout.
Nested quote
And one inside that. Each level gets its own data-callout value, so the nesting is
structural rather than cosmetic.
The remaining built-in types, to prove the icon and colour map is complete rather than a
subset:
Abstract
Summary-style callout.
Question
Interrogative callout.
Success
Positive-outcome callout.
Failure
Negative-outcome callout.
Danger
High-severity callout.
Bug
Defect callout.
Info
Neutral informational callout.
A callout containing other block content — list, checkboxes, code and math — to show the
body is a full markdown context, not a text span:
Mixed content inside a callout
a list item
a checked task (enableCheckbox: true)
an unchecked task
PATH=/usr/bin:/bin /usr/bin/npm run install-plugins
Inline math still works here: E=mc2.
What the rendered output proves
The emitted HTML contains multiple data-callout="..." attributes with distinct values
(note, warning, tip, example, quote, abstract, question, success, failure,
danger, bug, info), class="callout" blockquotes, callout-title rows, and
is-collapsed on the - variant. The nested case shows a data-callout blockquote inside
another data-callout blockquote in the DOM tree.
safe_for_shared_engine
true — callouts are opt-in per paragraph, already the transformer’s default, and add no
global assets beyond the stylesheet the plugin always ships.