orz-mdhtml — documents
This page describes orz-mdhtml 1.0 (orz-mdhtml-browser 1.0), which embeds orz-markdown 2.3
Markdown that edits itself, as a continuous document that reads like a themed web page: notes, handouts, articles, READMEs. This page covers the text and its file, reading, editing and saving, the header, themes and line length, delivery and browsers, the CLI, older files and web apps. Part of the orz editable-document family, with orz-slides (decks) and orz-paged (print). New to the words "text twin", "assemble" or "shell"? See the guide's short glossary.
npx orz-mdhtml@1 new notes.md) and write. For anything beyond that, ask an AI agent:
point it at the orz-mdhtml agent skill
(orz-mdhtml-skills/SKILL.md)
and say what you want — “turn these notes into a handout,” “check this file and bring it up to
date.” This page is the reference the agent (and curious humans) can draw on.
A text twin and a self-editing file
A document is its text twin — notes.md, plain Markdown with a small header at the
top — and its self-editing file notes.md.html, one HTML page that reads like a themed
website, edits itself in the browser and saves back in place. The file is always built from the text
(orz-mdhtml assemble): the exact text rides inside it (byte for byte, line endings kept), everything
else is a shell that can be rebuilt, and orz-mdhtml extract gives the text back. Keep the
.md in your repository or hand it to an agent; make the .md.html whenever someone needs a
file. A relative link to another text ([Week 2](week2.md)) becomes a link to its
.md.html in the file.
A quiet read view
The document is rendered in the page with the
header's theme and line length; math, code highlighting, diagrams, chemistry and charts are drawn once their
libraries arrive. Copying rendered content copies Markdown. (orz-mdhtml 1.0 embeds orz-markdown 2.3: charts
and Mermaid diagrams use the libraries' own colours, on a light panel in a dark theme. The theme-coloured charts
and diagrams of orz-markdown 2.4 reach .md.html files when orz-mdhtml moves to that kit.)
One pencil
The page shows one control, a small pencil at the bottom-left. Click it to edit; hover it (tap it on a touch screen, or Tab to it and press ↑) for everything else.
One family
The same text twin, pencil, editor, saving, quiet update, host protocols and CLI as orz-slides and orz-paged 1.0 — built on the orz-markdown carrier kit. The content is full orz-markdown.
Published on npm as orz-mdhtml (CLI and Node
library) and orz-mdhtml-browser (engine and editor component), always the same version.
In the file: read, edit, save
The pencil's list
Hover over the pencil (tap it on a touch
screen) to open its list: Edit, Save,
Download a copy, New document, Save as new copy (the same text with a new
uid), Theme, Width, Text size and, when there is
one, the update. A dot on the pencil means unsaved changes; the page asks before it closes with unsaved
changes.
Edit
The pencil (or Ctrl/⌘ E) opens the editor: CodeMirror beside a live preview — split, editor only or preview only — with Theme and Width pickers that edit the header, and undo per document. Done goes back to the read view; More has Download a copy, New document and Save as new copy. The bar ends with the orz seal (this site) and the GitHub link.
Theme and width are the document's
They live in the header, so choosing one — in the editor or from the read view — changes the document: it is unsaved until you Save, and Ctrl/⌘ Z undoes it. Text size (80–160 %) is yours, not the document's: kept in this browser for this document, never saved into the file.
Save
Save (Ctrl/⌘ S)
writes the file again from the text — never the live page — back in place (Chromium), or downloads it. If the
file changed on disk since the page opened it (another program, an agent, an orz-mdhtml assemble),
Save asks: Replace it or Download a copy instead. A page opened from a website cannot be saved
over: use Download a copy.
Keys. Ctrl/⌘ E edit / done · Ctrl/⌘ S save · Ctrl/⌘ Z undo, Shift Ctrl/⌘ Z redo · in the editor Ctrl/⌘ \ cycles split / editor / preview and Esc leaves the text for the toolbar · Ctrl/⌘ P in the editor's preview prints the page (in the read view it is the browser's own print).
A quiet update. A local file checks at most once a day whether a newer release of its engine exists; the pencil then gets a small dot and its list offers Update to x.y.z (in the editor, an "Update available" chip). Update now rebuilds the file from the same text with the new release (its bundle checked by SHA-384) and saves it; nothing changes until you ask. Served pages, framed pages and pages with a host never check.
The header
One {{nyml kind: document}} block at the very top of the text holds the
document's metadata and settings. new writes carrier and a uid (the document's id; upgrade, New document and Save as new copy write one too, Save does not — a starter file has none, which is fine); once there is one, leave it as it is.
{{nyml
kind: document
carrier: md
uid: doc-7kq2m9x4p1r8s3t5
title: Cell Biology — Week 3
author: Yu Wang
lang: en
theme: light-academic-1
measure: wide
license: CC-BY-4.0
}}
# Cell Biology — Week 3
| Key | Values |
|---|---|
theme | a theme id (below); default light-neat-3 (Orchard) |
measure | the line length: normal (about 70 characters, the default), wide (about 90), full (the whole window) — Width in the pencil's list |
lang | the language (BCP 47), default en |
title, author, description, date, keywords, source, license / license_name / license_url | metadata, written into the file's HTML head; no title? the first heading is the title |
carrier, uid | written by new: md and the document's id |
One key: value per line (key: | for several lines), no comments (text after a value
becomes part of it). Unknown keys are kept; your own tool's keys start with x_<tool>_. A Markdown
file with no header is fine too — the tool treats it as a document without a uid; to give it one, run
npx orz-mdhtml@1 new with no file name and paste the header it prints at the top.
Twelve themes
The orz-markdown themes, by name and id (orz-mdhtml themes lists
them): Orchard light-neat-3 (the default), Neat light-neat-1, Neat 2
light-neat-2, Academic light-academic-1, Academic 2 light-academic-2, Beige
beige-decent-1, Beige 2 beige-decent-2, Playful light-playful-1 (a ruled
notebook on screen: text on the lines, tables and code pasted on as sheets), Playful 2 light-playful-2,
and the dark Dark Elegant dark-elegant-1, Dark Elegant 2 dark-elegant-2 and Nocturne
dark-elegant-3.
Create and build with the CLI
Node 20.16 or newer; nothing to install with npx (or npm i -g orz-mdhtml@1):
npx orz-mdhtml@1 new notes.md --title "Field notes" # a new text twin # edit notes.md (by hand or with an agent), then: npx orz-mdhtml@1 notes.md # → notes.md.html
| Command | What it does |
|---|---|
orz-mdhtml new [x.md] [--title T] [--theme id] [--measure normal|wide|full] [--lang l] [--html] [--cdn] | writes a new text twin: a header with carrier: md, a fresh uid and the given keys, then a starter body; --html also assembles it; without a file name the text goes to stdout |
orz-mdhtml assemble x.md [-o out] [--cdn | --inline] [--resolve-includes | --no-includes] [--replaces sha256] | writes x.md.html from the text — the same bytes every time (also: orz-mdhtml x.md). Keeps the existing file's delivery and the includes it recorded; --resolve-includes reads {{md part.md}} files from the text's folder (never outside it). An include left out shows an "Included file not available" note and is reported once with why (unresolved in --json; stale when its file changed since); the CLI fetches no URL includes |
orz-mdhtml extract x.md.html [-o out] [--replaces sha256] | writes the exact text twin back out of a file (-o -: to stdout) |
orz-mdhtml upgrade old.md.html [-o out] [--text t | --no-text] [--cdn | --inline] | turns a 0.x file (or an older 1.x shell) into a file of this version, in place by default, and writes its text twin; also repairs a damaged engine |
orz-mdhtml inspect <file> [--json] | says what any text or file is — version, delivery, uid, header, lint, whether the file is intact, whether its twin holds the same text and which is newer — and what to do next, naming your files |
orz-mdhtml themes | lists the theme ids a header may name |
Content — title, theme, width, language, metadata — comes only from the text's header;
assemble has no content options (--title, --theme are refused with a pointer
to the header). Nothing that could hold other work (a file saved in the browser, an edited twin, another document)
is overwritten without --force, which keeps a <file>~ backup — unless what it replaces
is a known earlier state: what the CLI itself last wrote or read for that pair, or a committed
version of the twin in git (a shallow clone knows only its last commit).
For scripts and agents. Every command takes --json: one object on stdout, and on a
refusal { "ok": false, "error", "reason", "next" } — reason is a stable code
(twin-differs, replaces-mismatch, legacy-file, output-differs,
newer-shell, …) and next the one command that resolves it, built from your own arguments
and safe to run as it is. assemble, extract (to a file), inspect and
upgrade print the sha256 of the text they wrote or read (and every --json
result that wrote a text carries it; new prints it only with --json); pass it back as
--replaces <sha256> and the file is replaced only while it still holds that text — it works with
no cache, no git, in a fresh sandbox. Exit status: 0 done, 1 refused or failed, 2 usage error. Before editing
x.md, inspect x.md says whether x.md.html holds work saved in the browser;
extract x.md.html -o x.browser.md brings it out for a merge.
Delivery, browsers and limits
Delivery. Files are inline by default: the engine is inside the file (about
1 MB), so it opens without fetching it. --cdn writes a small file for pages you publish on the
web; it loads the engine from jsDelivr,
https://cdn.jsdelivr.net/npm/orz-mdhtml-browser@<version>/orz-mdhtml.browser.js (the file's own
version, e.g. @1.0.2). Either way — an inline file too — the theme stylesheets, KaTeX's
and highlight.js's styles, the content libraries (Mermaid, SMILES, Chart.js) and the editor (CodeMirror) load from
CDNs: reading styled content and editing need a network connection the first time. A slow or unreachable CDN never
holds the page blank: the document shows at once, unstyled, and takes its theme when the stylesheets arrive.
| Chromium (Chrome, Edge, …) | Firefox, Safari | |
|---|---|---|
| Read, themes, copy as Markdown | yes | yes |
| Edit (editor, live preview, header pickers) | yes | yes |
| Save in place | yes | — Save downloads the file instead |
| New document, Save as new copy | file picker | download |
| Quiet update | saved in place | downloaded |
Older files, web apps and agents
0.x files: inspect, then upgrade
A file made by orz-mdhtml 0.9.3 or earlier still opens as it was, but the 1.0 tools and editor work on it only after a one-time upgrade:
npx orz-mdhtml@1 inspect old.md.html npx orz-mdhtml@1 upgrade old.md.html
inspect says what the file is and what to do; upgrade rewrites it as a 1.0 file and
writes its text old.md (when old.md already exists, add
--text old.browser.md and merge the two). It keeps the source exactly, moves the metadata and the saved theme into the header (the
0.x <title> too, but only when the text has no heading of its own to give the title — otherwise
it is left out, with a warning), keeps the file's delivery and gives it a uid. A 0.9.x file's own "Framework 1.0.0 available"
banner cannot make this move (it ends with "check your connection" and changes nothing) — use
upgrade. The 0.9 API (buildMdHtml and friends) and --theme /
--title on generation are gone: content lives in the header.
In a web app
The editor is a component:
npm i orz-mdhtml-browser@1, then mountEditor(element, { text, hooks, nonce }) (ES module,
or globalThis.OrzCarrier from a plain <script>). Any number of editors per page;
your hooks keep the text wherever you like (save, AI with a review step, includes, assets, links, ids); it works
under a nonce Content-Security-Policy. Store the text; call assemble(text) only when a file is needed.
See docs/embedding.md (shipped
in both packages). A platform that shows assembled files in an iframe speaks the host protocols
(orz-host-save@1, orz-host-ai@1, orz-host-include@1).
In Node
import { assemble, extract, readDocument,
newDocument, inspect, upgrade, THEMES } from 'orz-mdhtml' — the same operations as the CLI, as functions.
assemble(text, { delivery: 'cdn', includes }); extract(html)?.text gives the exact text
back.
With an AI agent
Every file starts with a short note for agents
(where the text is and how to read it, a link to the skill). The rule: edit the text, never the HTML, then
assemble again; when unsure what a file is, inspect --json and follow its advice. The
skill has the full procedures and
every reason code.
.md.html runs the engine inside it and, because Markdown may contain raw HTML, anything in its content;
open and edit files only from sources you trust — appearance proves nothing, and a file cannot prove its own
integrity (the verifier helps). The quiet update only fetches this engine's own jsDelivr
package, checks its SHA-384, and changes nothing until you click Update now.