Skip to content

MD ↔ HMD interoperability

Markdown is JavaScript. Hyper-markdown is TypeScript.

That analogy is the shortest way to say what hyper-markdown is for, and it carries further than a slogan should. This card sketches how far. Nothing here is decided — no proposal owns it yet.

What the analogy claims

TypeScript is a superset of JavaScript that adds structure a machine can check, compiles away to the thing it extends, and can be adopted one file at a time. .hmd stands in the same relation to .md:

TypeScript Hyper-markdown
every .js is valid .ts every .md is valid .hmd
types, interfaces, imports wikilinks, embeds, frontmatter import
tsc erases types, emits .js hmd resolves the graph, emits .md
tsc --noEmit checks without building hmd lint
adopt file by file, allowJs adopt card by card, red links are warnings
.d.ts describes untyped code (no analogue yet — see below)

The claim is not type checking. The added structure is a graph: which page a name means, which block a reference points at, which namespace a card lives in. Ambiguity is the error condition, the way a type mismatch is.

Compilation is the deeper step

The MVP stops at checking. The analogy says the toolchain is really a compiler with two directions, and linting is just the --noEmit mode of one of them.

hmdmd is erasure. Resolve every link to a path, expand every embed inline, drop the frontmatter the toolchain owns, and what remains is ordinary markdown that GitHub, Obsidian, or a plain renderer reads without knowing the format exists. This is exactly what compiling TypeScript does, and it is lossy in the same way: the source of a link — spine walk, named import, sweep — is gone from the output, and so is the boundary between a card and the content it embedded.

That last loss is not free. A preview that renders an expanded embed as anonymous prose has thrown away the one thing it does that a markdown previewer cannot, which is why the editor line rejected flat markdown as its transport even while erasure stays the right build output. Erasure is a shipping format, not an interchange format.

mdhmd is conversion. Take a folder of ordinary markdown and convert it: relative links become wikilinks where the target is unambiguous, folders become namespaces, sibling references collapse to bare names. Where the converter cannot be certain it leaves the relative link alone — the mixed state is valid, which is the whole point of a superset.

Conversion is not only a migration path. It is the fastest way to write hyper-markdown: draft in plain markdown at speed, with ordinary relative links and no namespace in your head, then convert and let the resolver tell you what it could not place. Authoring stops requiring you to know the tree.

That is where the analogy earns its keep. Nobody rewrites a wiki by hand, and allowJs is why TypeScript won.

Why this is canonical, not a plugin

Both directions are semantics, and semantics live in the Python implementation. An editor, a second parser, or a future language server consumes them; none of them redefine them. A md → hmd promotion that guessed differently in the editor than on the command line would be worse than no promotion at all.

The reverse direction also suggests the missing table row: what plays the part of .d.ts — a way to describe a body of plain markdown to the resolver without touching it? Sidecar frontmatter, a manifest, or nothing at all. Open.

Open threads

  • Is erasure a build product only, or is round-tripping hmd → md → hmd a guarantee worth paying for? The lossy parts say no; the adoption story says it would be nice.
  • What does erasure emit? "Ordinary markdown" is not one target — plain markdown and GFM disagree about tables, task lists, and strikethrough, and neither expresses callouts or math. A third target, single-file HTML, expresses everything and reads back as nothing. Tracked as issue 0007.
  • Does conversion ever run destructively, or only ever as a diff a human accepts? An authoring loop wants it in place; a migration wants review.
  • Does a compiler framing want a hmd build command distinct from hmd render, the way tsc is distinct from a formatter?

See hyper-markdown for the format itself, and HMD-0002 for the erasure direction as it is actually being built — MkDocs output, one way only.