# Decisions ## Plain markdown is a flavour of the grammar 2026-09-27, the maintainer. Goal 2. Valid while the plain flavour's spellings are ones the markdown grammar can read and write. The lossy pair is the plain flavour: the markdown grammar's reader and writer with the flavour set, its spellings — alerts, callouts, task markers, `==` — read and written there, so a marker line and a backslash reach them intact; what the flavour cannot spell reduces ADF→ADF ahead of the writer. ## The round-trip is the product 2026-08-23, the maintainer. Goal 1. Valid while a consumer saves back through the lossless pair. `markdownToAdf(adfToMarkdown(doc))` and `htmlToAdf(adfToHtml(doc))` must equal `doc` — anything less silently destroys content an editor could not represent, in a document it did not author. When losslessness and readability conflict, losslessness wins. Round-trip equality is a property tested over a corpus, not a claim made in prose. ## Markdown in is a canonical fixpoint 2026-08-23, the maintainer. Goals 1 and 3. Valid while markdown input may be written by hand. The other direction is a canonical fixpoint, not byte-identity: human markdown normalizes, the way back yields the library's canonical spelling, and that spelling round-trips byte-identically — where there is a way back. CommonMark spells some things the flavour has no escape for — a paragraph opening with a code span whose backticks read back as a fence — so a parse succeeding does not imply a spellable document; `corpus/commonmark-spec/exceptions.json` names those. ## Equality is editor-normal 2026-08-24, the maintainer. Goal 1. Valid while markdown cannot tell apart the ADF shapes this merges. "Equals" is structural equality over editor-normal ADF — adjacent text nodes with identical marks and no attributes merged, JSON number semantics, an empty attrs object, marks array or content array the absent key — the only domain markdown can restore. ## Unknown nodes ride the carry 2026-08-23, extended to misplaced known nodes 2026-08-26, the maintainer. Goal 1. Valid while ADF holds nodes, or node positions, this library does not spell. An unknown ADF node is carried opaquely — raw JSON rides a dedicated syntax in both formats and restores to a deep-equal node. The round-trip holds for documents newer than the library. So does a known node no section spells where it stands: a markdown serializer spells a node by type without checking its position, and refusing loses a document ADF itself keeps in an `unsupportedBlock`. Where a container's own spelling cannot hold the child it has — a `bulletList` outside `listItem`, a `codeBlock` outside text — the error result names that instead. ## Foreign HTML is refused by name 2026-08-23, the maintainer. Goals 1 and 6. Valid until the HTML dialect's element set lands (`todo.md`, 6). An unmappable foreign HTML element is an error result naming the element — never a silent drop. ## Names stay text 2026-08-23, the maintainer. Goal 7. Valid while resolving a name to an id needs I/O. A bare `@name` or `:smile:` in typed text stays a text node. Only directives produce mention/emoji/media nodes; resolving names to ids is the consumer's job. ## Directives under `!adf:` 2026-08-23, prefixed `!adf:` 2026-09-16, the maintainer. Goals 3 and 4. Valid while prose does not write `!adf:`. Directives are one grammar for everything markdown lacks, namespaced under `!adf:`: `!adf:panel info` … `!adf:/panel` blocks, `!adf:mention[@Mikael]{id=5b10a2}` inline, `\!adf:` the one escape. Not CommonMark's generic-directives proposal: its `:::` claims a form prose writes, and its fence-length discipline ties a container's opener to its own body, where closing from the opener nests by itself and leaf versus container falls out of the node's content model. ## CommonMark is a subset 2026-08-23, the maintainer. Goal 3. Valid while prose rarely writes the shapes the carve-outs claim. Plain CommonMark is a subset, with carve-outs (`spec/flavour.md`): literal text shaped like a directive, a pipe table or a `~~` pair is claimed — plus one image gap. ## Tables 2026-08-23, the maintainer. Goal 4. Valid while a pipe table holds only one header row and inline cells. One header row plus plain inline cells → pipe table; anything richer → directive form. ## Links 2026-09-13, nesting 2026-09-17, the maintainer. Goals 1 and 4. Valid while CommonMark's link syntax is what readers edit. `[text](url "title")`, or `` for a bare autolink-shaped text, wherever CommonMark spells the mark; `!adf:link[text]{attrs}` where it does not — an attribute CommonMark cannot hold, an `href` or `title` no canonical escape spells, a paragraph opening whose CommonMark spelling would read as a link reference definition — and a directive link CommonMark could spell is refused. No link wraps a link — the bracket form goes literal, the directive form refused — which is CommonMark's prose where its reference implementation nests one `` in another. ## Ids stay site-local 2026-08-23, the maintainer. Goal 1. Valid while ADF ids are minted per site. Identity-bearing nodes carry their ids in attributes; a document is only portable within its site — accepted. ## The HTML dialect 2026-08-23, the maintainer. Goals 4 and 7. Valid while HTML output is read by consumers styling it themselves. The HTML dialect mirrors the markdown flavour: semantic elements, stable `adf-*` classes, `data-*` for what HTML cannot express, text always escaped. No stylesheet ships. ## No runtime dependencies 2026-08-23, the maintainer. Goal 7. Valid while ~20 lines of own code, or a vendored table, do each job a dependency would. `dependencies` is empty. A runtime dependency enters only through an entry here stating why ~20 lines of own code cannot do the job, who maintains it, and what auditing it costs. So the CommonMark and HTML parsers are written in this repo. ## Standards ship as data 2026-08-30, the CommonMark suite 2026-09-05 and ADF's schemas 2026-09-13, the maintainer. Goals 1, 3 and 7. Valid while each table is fixed data a dependency would only wrap. A table a standard fixes is data rather than a dependency: HTML5's 2125 semicolon-terminated character references ship packed in their own module, so entity decoding is complete without one. The CommonMark spec suite is the same shape of data and ships vendored at `corpus/commonmark-spec/` rather than as the `commonmark-spec` dev dependency — that package is CommonJS-only, and Renovate auto-bumping a spec version would silently point the vendored exception list's example numbers at a renumbered suite. A spec bump is a deliberate re-pin, exceptions re-derived by hand beside it. Atlassian's ADF JSON Schemas ship vendored the same way, at `spec/adf-schema/`, rather than as the `@atlaskit/adf-schema` dev dependency — CommonJS-only, some fifty packages with React among them, and a release most days for Renovate to automerge — re-pinned by hand when a payload or a report shows the need. ## fast-check 2026-09-14, the maintainer. Goal 1. Valid while a failing generated document needs shrinking by hand otherwise. `fast-check` earns its place as a devDependency shrinking a failing generated document to the nodes that break it. ## Any ES2022 engine 2026-09-01, the maintainer. Goal 7. Valid while ES2022 is the floor browsers and servers share. The library runs on any ES2022 engine, not only Node — a browser as readily as a server. The shipped source is ECMAScript and nothing else: no host import, no host global, no DOM. `tsconfig.build.json` is that gate, typechecking and emitting the shipped files alone, so `node:fs`, `process` and an ES2024 method are compile errors here rather than a consumer's crash there. The standard is the line, never an engine list: one implementing it in part — Hermes is the live doubt, on the Unicode property escapes emphasis matching leans on and on lookbehind — is out of scope rather than a bug. Node's test runner, the corpus reads and the build are the repo's own, never the library's, and `engines.node` states the floor the shipped JavaScript needs — `>=18` — never the higher one those repo-only tools want. ## ESM only 2026-08-23, the maintainer. Goal 7. Valid while the audience's toolchains all import ES modules. No CommonJS build, no dual-package hazard. ## One built entrypoint 2026-08-23, the maintainer. Goal 7. Valid while Node refuses to type-strip under `node_modules`. Built JavaScript, `.d.ts` beside it. Do not add a TypeScript-source entrypoint — Node refuses to type-strip under `node_modules` (`ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING`), so it cannot serve an npm consumer. ## Public on npm 2026-08-23, the maintainer. Goal 7. Valid while the package's source stays public beside it. Published to public npm as `@larvit/adf-codec`. Public source: the Gitea repo goes public, LICENSE in place, before the first publish.