GuidePipelineDesignEvergreenupdated 2026.084 min read
Guide · from zero to a garden

Getting started

Neither the paper nor the engine is published to npm — both are zero-build and export raw TS/CSS source. A site vendors the two repos as git submodules, imports three stylesheets, and wires one function into astro.config. Everything on this site is built that way.

two submodules · three stylesheets · one siteMarkdown() · three commands

PART IInstall

§1.1A pair of submodules

Vendor the two repos and point file: dependencies at them:

site repo root
git submodule add https://github.com/ventusff/astro-inkstone.git packages/astro-inkstone
git submodule add https://github.com/ventusff/astro-inkbrush.git packages/astro-inkbrush
package.json (dependencies)
{
  "dependencies": {
    "@astrojs/mdx": "^7.0.5",
    "astro": "^7.1.6",
    "astro-inkstone": "file:packages/astro-inkstone",
    "astro-inkbrush": "file:packages/astro-inkbrush"
  }
}

§1.2Styles

In your global stylesheet: tokens first, then the content layer, then — for a site with browse pages — the shelf, then only your own chrome:

src/styles/site.css
@import 'astro-inkstone/styles/tokens.css';
@import 'astro-inkstone/styles/base.css';
@import 'astro-inkstone/styles/browse.css'; /* landing / facet pages, on body.wb-root */

/* identity: override the tier-one pigments; both semantic contexts and the component layer follow */
:root {
  --p-shi: #b03a48; /* your reading accent */
  --p-zhu: #3b4a7a; /* your shelf accent and mark */
  --p-shi-n: #e0919b; /* the night twins: the dark theme reads --p-*-n */
  --p-zhu-n: #9fb0e4;
}

The shelf's serif is --font-display (Source Serif 4 + Noto Serif SC); self-host it as this demo does and the masthead looks exactly like this one:

src/styles/site.css (fonts, optional)
@import '@fontsource-variable/source-serif-4/opsz.css';
@import '@fontsource-variable/source-serif-4/opsz-italic.css';
@import '@fontsource-variable/inter/index.css';
@import '@fontsource/noto-serif-sc/400.css';
@import '@fontsource/noto-serif-sc/600.css';

Math sites add one line for KaTeX's stylesheet in the layout (without it, formula layout falls apart):

src/layouts/Base.astro (frontmatter)
import 'katex/dist/katex.min.css';
PART IIWire up

§2.1The Markdown pipeline, one line

A minimal configuration in this site's shape — chapter numbering, math, callouts, diagrams, and [[wikilinks]] resolved against the note collection (the full version, with every hook this site wires, is astro.config.mjs in the repository):

astro.config.mjs (minimal)
import { defineConfig } from 'astro/config';
import mdx from '@astrojs/mdx';
import { normalizeBase } from 'astro-inkstone';
import { siteMarkdown } from 'astro-inkstone/markdown-preset';
import { buildWikilinkResolver, cachedScan } from 'astro-inkbrush/wikilinks';

const WIKI_MODE = process.env.WIKI === '1';
const inkbrush = WIKI_MODE ? (await import('astro-inkbrush')).inkbrush : null;
const BASE = normalizeBase(process.env.DEMO_BASE || '/'); // '/docs' and '/docs/' both work

export default defineConfig({
  site: 'https://example.com',
  base: BASE || '/',
  integrations: [mdx(), ...(inkbrush ? [inkbrush()] : [])],
  markdown: siteMarkdown({
    numbering: 'chapters', // part/chapter numbers + ToC via remarkPluginFrontmatter
    math: true, // remark-math + KaTeX
    codeFrame: true, // code frames: title bar / copy / collapse / annotations
    mermaid: true, // mermaid fences render client-side
    callouts: true, // Obsidian-style > [!note] syntax
    wikiBlocks: WIKI_MODE, // editing mode: block ↔ source-line mapping, always last
    guard: { autoNumberedHeadings: true }, // content guard: reject hand-typed numbers
    wikilinks: {
      resolve: buildWikilinkResolver({
        notes: cachedScan('src/content/notes'),
        urlFor: (id) => `${BASE}/${id}/`,
        locales: [{ code: 'en', prefix: '' }, { code: 'zh', prefix: 'zh/' }],
      }),
    },
  }),
});

siteMarkdown sits on the engine's dialect (GFM, CJK-friendly emphasis, the content guard); the preset itself only assembles and orders the site-side plugins — the pipeline order is the contract, so your site never worries about who runs before whom. Every switch is demonstrated on the kitchen sink page, and the reasoning behind the package split lives in boundaries. Chinese-labelled callouts? Pass calloutLabels — the defaults are English.

§2.2Run it

The scripts, as this demo's package.json defines them (build runs the engine's check-dist as postbuild; wiki is WIKI=1 astro dev):

bash
npm install
npm run dev        # the garden
npm run build      # static build + the engine's check-dist (postbuild)
npm run wiki       # WIKI=1 astro dev — CMS editing mode

The static build carries zero engine bytes: outside WIKI mode the engine's integration code is never even imported (dynamic import), and no editing metadata is emitted. What remains CMS-shaped in the output is the site's own inert docking markup — the empty data-inkbrush-slot spans in the nav bar and their CSS — which does nothing until the CMS docks into it.

Titles, sections and body text, in this language.
    ↑↓ · Enter · Escastro-inkstone