.md.html

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.

You don't have to learn any of this. Start from the blank .md.html starter (or 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
KeyValues
themea theme id (below); default light-neat-3 (Orchard)
measurethe line length: normal (about 70 characters, the default), wide (about 90), full (the whole window) — Width in the pencil's list
langthe language (BCP 47), default en
title, author, description, date, keywords, source, license / license_name / license_urlmetadata, written into the file's HTML head; no title? the first heading is the title
carrier, uidwritten 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
CommandWhat 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 themeslists 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 Markdownyesyes
Edit (editor, live preview, header pickers)yesyes
Save in placeyes— Save downloads the file instead
New document, Save as new copyfile pickerdownload
Quiet updatesaved in placedownloaded
Limits: save in place needs Chromium (File System Access); a page served over http(s) is never saved in place. Themes, math styles, diagram libraries and the editor need the network the first time, also in an inline file. A file shows only the includes it recorded when it was assembled. A file is a program and cannot prove its own integrity (below).

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.

In-place Save needs a Chromium-based browser (Chrome or Edge); elsewhere, save downloads a copy. Reading and editing work in any modern browser.
These files are programs. Opening a .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.