Jump to
↵select↑↓navigateescclose

Guide

Writing documentation

The Markdown conventions that work on GitHub, in Obsidian and on the site, and the few constructs the site cannot render.

Updated
Tags
  • guide
  • markdown
  • authoring
On this page

You write Markdown in docs/ and nothing else. The conventions below work on GitHub, in Obsidian and on the published site at once, except where what does not render says otherwise.

Files and folders

  • docs/README.md (or docs/index.md, in any letter case) is the documentation home at /docs/ and is required. When it is missing the build stops and says cp README.md docs/README.md.

  • Every other .md file is a page at /docs/<path>/: docs/Install Steps.md is /docs/install-steps/.

  • A folder is a section of the sidebar. Its README.md (or index.md) is the section's "Overview".

  • Files and folders whose names start with . or _, and node_modules, are ignored.

  • The Markdown folder is docs/ beside the site folder unless the config's docs key says otherwise. It must lie inside the repository.

  • Images and downloads go anywhere under docs/ and are linked relatively: ![Diagram](images/flow.png).

  • A symbolic link is followed only when its target is inside the repository. Any other one is skipped and reported, and under strict that is an error (see troubleshooting).

Front matter

Front matter is optional. Without it a page is titled by its first # Heading (or its file name) and described by its first paragraph. These keys are understood:

KeyMeaning
titleThe page title. Wins over the first heading.
descriptionOne sentence under the title, in search results and in link previews.
nav_titleA shorter name for the sidebar.
orderA number. Lower comes first; pages without one come last, alphabetically.
hiddentrue publishes the page but leaves it out of the sidebar and of previous and next links.
drafttrue keeps the page out of the site entirely.
publishfalse does the same as draft: true.
updatedA date such as 2026-10-01, shown under the title.
tagsA list, shown under the title.

Other keys (the vault's slug, category or status, for instance) are not used by the site. A field of the wrong type (draft: "yes", order: "2") is reported by the build and fails it in CI: write true and 2.

---
title: Repair a corrupted table
description: Find and repair a damaged table without losing the site's data.
nav_title: Repair a table
order: 3
updated: 2026-10-01
tags: [database, repair]
---

The page title

The title is shown once, as the page's single h1. The first # Heading that says the same as the title (ignoring case and punctuation) is that h1, wherever it stands in the file (after a badge, a comment or a logo), and links to its anchor keep working. Every other level-1 heading is shown as a section heading, so a page always has exactly one h1.

A comment above the front matter, such as a copyright stamp that some pre-commit hooks add, is tolerated: staging moves it below the front matter for the build. GitHub and Obsidian still read the file as written and then show the front matter as text, so keep docs/ out of such hooks.

Callouts

Use GitHub's alert notation. The five types are NOTE, TIP, IMPORTANT, WARNING and CAUTION:

> [!NOTE]
> Background that helps but is not required.

> [!TIP] A custom title
> The Obsidian form with a title works too.

> [!WARNING]
> Something that can break a site, lose data or weaken security.

They render as callouts on the site and as alerts on GitHub.

  • Link to other pages by file, relative to the file you are in, with %20 for a space: [Configuration](guides/configuration.md#options), [Install Steps](Install%20Steps.md). The site rewrites them to the page's address. A link to a heading uses the heading's anchor.

  • A link or image to a file of the repository that is not a page (a source file, LICENSE, another README, a folder of examples) becomes a link to it on GitHub, on the repository's default branch. That works whether you wrote it from docs/ (../worker/README.md) or from the repository root, as in a README copied into docs/ (worker/README.md). Each rewrite is listed in the build output.

  • A link written from the repository root to a page inside docs/ (docs/chat.md) is made relative to the file you are in.

  • A link to a page that does not exist, or that is a draft, is shown as plain text and fails the CI build with the file and the target named. While you fix them, docusystem build --lenient reports them as warnings.

  • Raw HTML gets the same repairs, because a README uses it for its centred logo and its badges: the href of an <a>, the src of an <img> or <source>, and each file of a srcset. Which elements raw HTML may use at all is in Raw HTML, addresses and files. <p align="center"><img src="./assets/logo.png" alt="Logo"></p> and <div align="center"><a href="docs/chat.md">Chat</a></div> pass a strict build, and each rewrite is listed in the build output. Tags in code and in HTML comments are left as written. Prefer Markdown links all the same: a raw <a> inside a paragraph is not kept (see what does not render).

Code

  • Give every code block a language. These are highlighted at build time, in light and dark: json, jsonc, js or javascript, ts or typescript, md or markdown, html, css, bash, sh or shell, yaml or yml, sql, php, python or py, ruby or rb, nix, nginx, caddyfile, toml, ini, diff, xml and dockerfile.

  • text (and txt, plain, output, log) is plain on purpose. Any other language, such as rust, go, vue, dotenv, console, jinja or scss, is shown as plain code and the build prints a postbuild: warning: naming the page. It never fails the build.

  • Shell commands carry no $ or # prompt, so they can be pasted. Show their output in a separate text block.

  • Write placeholders as <UPPER_SNAKE_CASE> and use example.com for domains. Never paste credentials or customer data.

Raw HTML, addresses and files

The published pages are served from the project's own domain, so what a page may carry is limited, whoever wrote it. Raw HTML in Markdown follows what GitHub shows, and everything else is an error that fails the CI build, with file:line:

  • Elements. Text, structure, table and image elements are fine: <a>, <img>, <picture>, <br>, <kbd>, <sub>, <details>, <summary>, <table> and its parts, <p>, <div>, <span>, headings, lists and similar. Anything that runs code, embeds or loads another page, collects input or changes the whole page is refused: <script>, <iframe>, <object>, <embed>, <form>, <input>, <button>, <style>, <link>, <meta>, <base>, <svg>, <math>, <template>, <video> and the site's own components (<docs-header> and the like). Put a drawing in an image file and write ![alt](drawing.svg); link to a video.

  • Attributes. Event handlers (onclick, onerror, onload and every other on...), style, srcdoc, formaction, ping and is are refused. align, width, height, class, id, name and the like are fine.

  • Addresses. A link, an image, an autolink and the href or src of raw HTML may be http:, https:, mailto:, tel: or relative (a path, #anchor, ?query). javascript:, data: and every other scheme are refused, however they are spelled (JaVa&#115;cript:, a tab inside the word).

  • Directives. The :name{...} syntax makes elements too, so it follows the same rules: :script[...], ::iframe{...}, an on... attribute, innerHTML or a $ref are refused. A sentence that happens to hold :link or :button is read as a directive: put it in backticks.

  • Files. A file that a page links to is published beside the pages. Images, PDFs, archives and data files are fine; an HTML, script or stylesheet-transform file (.html, .js, .xsl and similar), an SVG with scripts or handlers, and an XML file that says it is XHTML are refused. Link to the source on GitHub instead.

  • A tag that is not HTML, such as the <name> of docker run <name>, written outside backticks, is a warning: the site writes it as an empty element and the text between the brackets is lost. Write it in backticks.

Code, in a fenced block or in backticks, is shown and never run, so <script> there is fine. The check is made twice: docusystem lint reads your Markdown, and the build then reads the pages Jx wrote and refuses any that hold a script, an event handler, a javascript: address or an embedded page, in every mode, --lenient included, because that is what a reader's browser would run. Every page also carries a Content-Security-Policy that lets a browser run only the site's own scripts, so that a handler or a script that slipped past both checks does nothing. See the policy.

What does not render

Jx drops or reshapes a few constructs that GitHub and Obsidian handle. The build reports each one as docs/<file>:<line> with what to write instead. The ones marked "error" lose text, leave an empty link or would run code, so a strict build (every CI run) fails on them; the others are warnings.

WrittenWhat the site doesBuild
Reference-style links ([text][ref] and [ref]: url)The text of every such link vanisheserror in CI
Footnotes ([^1] and [^1]: note)The marker and the note vanisherror in CI
Inline HTML such as <kbd>, <b> or <a id>The text stays, outside the elementwarning
<a href> around an <img> in a paragraph (a badge)The image stays, the link is losterror, fails lenient builds too
An inline <a href>The text stays, the link is losterror, fails lenient builds too
A <div> or <details> with a blank line insideAn empty element, then the contentwarning
Task lists (- [ ])Plain list itemswarning
Table column alignment (:---:)Columns are left-alignedwarning
${...} in an address, a URL, an HTML tag or a directiveThe build writes it inert, so the address is not what you wroteerror in CI
A tag that is not HTML (<name>) outside backticksAn empty element; the text is lostwarning
Elements, attributes and addresses that run codeSee the section aboveerror in CI, and never published

A raw <a href> in a paragraph, around text or around an image, is worse than a lost link: Jx writes an empty link, <a href></a>, and puts the text or the image after it. The post-build assertion "no page has an empty link" then fails every build, strict or lenient, and nothing is published. Under --lenient the lint error is printed as a warning, but the assertion still fails, so fix it either way: write [text](url), and [![alt](image)](url) for a badge. An anchor without href (<a id="top"></a>) only warns. A badge inside a block such as <p align="center"> is not affected.

Block HTML without a blank line inside it (<p align="center"><img ...></p>) works, as do <br>, <img> and HTML comments. In Markdown content ${...} is literal in prose, code spans, code blocks, headings and front matter, so ${HOME} stays ${HOME}.

A ${...} in an address (a link, an image, an autolink or a bare URL), in a raw HTML tag, in a directive or in the language of a code fence is an error. Jx evaluates every string that holds ${ as JavaScript when it builds an attribute, so a page that held one could run code in the build, in CI too. That never happens: staging writes every ${ of a page, in code and front matter as well and in every spelling that Markdown, HTML or YAML decodes to it (${, ${), with a zero-width space between $ and {, and the post-build step writes the plain text back wherever it is text. In an address or an attribute the zero-width space stays, so the link does not say what you wrote: write the address as %24%7B...%7D, or put the text in a code span or a code block.

Run docusystem lint to see only these checks, with file and line, without building.

Strict and lenient

CI=true makes every document problem fail the build, and check is always strict. Leniency is never a file setting: it is a per-run choice for the first pass, docusystem build --lenient (or DOCUSYSTEM_LENIENT=1). docusystem dev is always lenient. See strictness.