Ovellum

3 min read · Updated

Anchors and protected zones#

Hybrid mode (and a future @preserve-aware auto mode) works because Ovellum and the author agree on two kinds of marker. Together they form a contract: the author owns whatever's inside protected zones, the tool owns everything else.

Anchors#

Every auto-generated section in a Markdown file gets an HTML comment that identifies which source symbol it documents:

<!-- ovellum:anchor id="src/utils/format.ts::formatDate" generated="2026-05-16T..." -->

The id is the symbol's anchor ID, formatted as {relativeFilePath}::{symbolPath}. Class methods use dot notation (src/models/User.ts::User.constructor); module-level docs use the sentinel __module__.

Anchors are invisible to readers — they're HTML comments, stripped by the browser. Their job is to give the merger a stable handle on each section.

Protected zones#

A protected zone is a region of Markdown that Ovellum will never overwrite. You author it; you own it forever.

<!-- @manual:start id="rationale" -->

**Note.** We use `String#padStart` here instead of a manual loop because V8
intrinsifies it and the manual version showed up in flamegraphs. This
commentary was added by hand and should survive regeneration.

<!-- @manual:end -->

Rules:

  • The id attribute is optional but strongly recommended. Without it Ovellum generates a positional fallback like manual-block-3, which breaks when the surrounding file is restructured.
  • With an explicit id, the block survives anything except the disappearance of its anchor.
  • Blocks can appear anywhere — between heading + body, after a code fence, inside a list, wherever.
  • Nested zones are not supported. You'll get a clear error if you nest them.

How they fit together#

Each protected zone is automatically associated with the nearest preceding anchor comment. That anchor is what the merger uses to find a home for the block on the next build:

<!-- ovellum:anchor id="src/utils/format.ts::formatDate" generated="..." -->
## formatDate

Auto-generated description.

| Param  | Type   |
| ------ | ------ |
| ...    | ...    |

<!-- @manual:start id="rationale" -->
Hand-written note.
<!-- @manual:end -->

On the next build, even if the auto-generated table changes shape, the protected zone stays under formatDate — Ovellum sees the anchor ID, looks up the block, and splices it back in at the same position.

When the anchor disappears#

If you rename or delete formatDate in source, its anchor ID (src/utils/format.ts::formatDate) no longer appears in the freshly generated content. The merger can't find a home for the protected block. That block becomes an orphan and is quarantined to .ovellum/orphans/ for your review — never silently dropped.

The inline cousin: @preserve#

The block tag works inside Markdown files. There's a JSDoc counterpart for your source code:

/**
 * Formats a date.
 *
 * @preserve
 * **Note:** uses the user's local timezone by default. Override with the
 * `timezone` option for deterministic output.
 *
 * @param date - The date to format.
 */
export function formatDate(date: Date): string {
  /* ... */
}

When @preserve appears in a source comment, Ovellum will (eventually — this is on the roadmap) auto-wrap the description in a protected zone in the generated Markdown so your handwritten note survives the same way as manually authored zones.

The IR currently captures the flag (DocNode.isPreserved); the auto-wrapping in the generator is tracked in the code-side TODO.

Configuring the tags#

Both tags are configurable, in case @manual or @preserve clash with something else in your project:

{
  "protect": {
    "blockTag": "@keep",
    "inlineTag": "@hand-written"
  }
}

Customise sparingly — the defaults are documented everywhere, and changing them means future Ovellum updates may bring new behaviours tied to the default tag names.

Edit this page