Rendering Pipeline

Accent CMS processes markdown through an event-based pipeline rather than a simple parse-and-render approach. The pulldown-cmark parser produces a stream of events (headings, paragraphs, emphasis, links, code blocks, etc.) that flow through a chain of event processors before being rendered to HTML. Each processor can inspect, transform, add, or remove events, and accumulate metadata into a shared result.

How It Works

The pipeline follows three stages:

  1. Parse: The markdown source is parsed into a stream of events using pulldown-cmark with all extensions enabled by default (tables, footnotes, strikethrough, task lists, heading attributes, wikilinks, smart punctuation, math, definition lists, GFM admonitions, superscript, subscript, and metadata blocks). Each extension can be toggled individually via the markdown: section in config.yaml.
  2. Process: The event stream passes through each configured processor in order. The output of one processor becomes the input of the next. Processors share a RenderMetadata struct where they accumulate extracted data.
  3. Render: The final event stream is rendered to HTML.

When no processors are configured, the pipeline produces identical output to direct rendering with no measurable overhead.

Built-in Processors

Accent CMS includes two processors that run on every page by default:

Table of Contents Processor

Walks the event stream to find headings, extracts their text content (including inline code and formatted spans), and generates slug-based anchor IDs. Each heading in the rendered HTML receives an id attribute and a clickable anchor link for navigation.

What it does:

  • Extracts heading text from Text and Code events within headings
  • Generates URL-safe slugs (e.g., “Getting Started” becomes getting-started)
  • Disambiguates duplicate headings with numeric suffixes (introduction, introduction-1, introduction-2)
  • Adds id attributes to heading HTML elements
  • Injects an empty <a class="heading-anchor"> element on each heading; the visible marker (chain icon, #, pilcrow, or nothing) is supplied by the active theme’s CSS based on its heading_anchor_style setting
  • Records entries in page.toc for template-driven navigation

Pages that should look like marketing copy rather than docs can suppress the anchor element entirely by setting anchors: false in frontmatter. The heading id and page.toc entry are still produced, so external links to fragments and template-driven sidebars keep working – only the visible per-heading affordance disappears.

---
title: About
anchors: false  # default is true
---

The four shipped anchor presets (selected per-theme via heading_anchor_style in theme.yaml):

  • copy (default) – chain icon in the left gutter on hover; clicking copies the canonical URL to the clipboard and shows a transient toast.
  • gutter – same gutter chain icon, but click navigates to the fragment as a normal link (no JS dependency).
  • hash – legacy appended # revealed on hover.
  • pilcrow – appended pilcrow revealed on hover.

Syntax Highlight Processor

Intercepts fenced code blocks in the event stream and replaces them with syntax-highlighted HTML using the syntect library. Highlighting happens server-side, so no client-side JavaScript is needed.

Behavior:

  • Fenced code blocks with a recognized language get inline-styled <span> elements
  • Code blocks without a language pass through as plain <pre><code>
  • Unrecognized languages fall back to plain rendering
  • The highlight theme is configurable in config.yaml (see the Basic Formatting code blocks section)

Resolves wiki-style links ([[Page Name]]) to actual page URLs using the content index. This lets you link between pages by title instead of remembering URL paths.

Page links:

[[About]]                    <!-- resolves to /about by title match -->
[[About|Read more about us]] <!-- custom display text -->
[[About#team]]               <!-- link with anchor fragment -->
[[/contact]]                 <!-- absolute path, passed through -->

Image links:

![[photo.jpg]]               <!-- page-local image -->
![[photo.jpg|A nice photo]]  <!-- image with alt text -->

Resolution order:

  1. Case-insensitive title match against the content index
  2. Slug match (e.g., “Getting Started” matches /getting-started)
  3. Direct URL match (e.g., blog/my-post matches /blog/my-post)
  4. Unresolved: renders with class="wikilink-broken" for theme styling

When multiple pages share the same title, sibling pages (same parent directory) are preferred, then shallower pages, then alphabetical order.

Transclusion syntax (![[Page Name]] without an image extension) renders as a styled link with class="wikilink-embed". Full transclusion support is planned for a future release.

Pipeline Metadata

The pipeline extracts metadata during rendering and makes it available to templates. The following fields are populated by processors:

Template VariableTypeDescription
page.toclistHeading entries with level, text, and id
page.wikilinkslistWikilink references with raw_name, resolved_url, has_custom_text, is_image
page.word_countintWord count of the markdown content
page.reading_timeintEstimated reading time in minutes (200 wpm)

The page.toc entries correspond to the id attributes on headings in the rendered HTML, enabling in-page anchor links. See the Templating Guide for template examples using page.toc.

Heading Anchors and Deep Linking

Every heading (H1-H6) automatically becomes a navigation anchor point. The TOC processor adds id attributes and a clickable # link that appears when you hover over any heading. This lets readers copy a permalink to a specific section, and the “On this page” sidebar uses these anchors for in-page navigation.

You can also link directly to any section from other pages using fragment URLs:

See the [installation instructions](/docs/getting-started#installation)

The anchor ID is the slugified heading text: lowercase, non-alphanumeric characters replaced with hyphens, consecutive hyphens collapsed. For example:

HeadingAnchor ID
## Getting Started#getting-started
## Using \render()` function`#using-render-function
## Rust & MiniJinja!#rust-minijinja

Duplicate headings get numeric suffixes to remain unique:

HeadingAnchor ID
First ## Introduction#introduction
Second ## Introduction#introduction-1
Third ## Introduction#introduction-2

Table of Contents in Templates

The page.toc list is available in every template. Each entry has level (1-6), text (plain heading text), and id (the anchor slug). Here is a minimal example:

{% if page.toc %}
<nav class="toc">
    <h3>On this page</h3>
    <ul>
    {% for entry in page.toc %}
        <li class="toc-level-{{ entry.level }}">
            <a href="#{{ entry.id }}">{{ entry.text }}</a>
        </li>
    {% endfor %}
    </ul>
</nav>
{% endif %}

Use entry.level to apply indentation via CSS classes (e.g., toc-level-2, toc-level-3) for a nested table of contents.