Files
adf-codec/docs/decisions.md
T
lilleman becd12e294
CI / gate (push) Successful in 42s
CI / publish (push) Has been skipped
36a - §1–§6's decisions move to docs/decisions.md, indexed from AGENTS.md
2026-09-28 00:37:01 +02:00

8.7 KiB

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 gains node types faster than this library spells them.

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 the carve-outs stay the flavour's only claims.

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.

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 <url> 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 <a> 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 Goal 7 names no runtime dependencies.

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 and 7. Valid while each table's upstream package is CommonJS-only or heavy.

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 every supported engine loads 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 and the Audience. Valid while the audience installs from public npm.

Published to public npmjs as @larvit/adf-codec. Public source: the Gitea repo goes public, LICENSE in place, before the first publish.