Keep the decisions to their reasons and link the grammar; name the bare adf: fence's refusal exactly; call the doc panel a writer panel

This commit is contained in:
2026-10-03 15:13:30 +02:00
parent 230d590fb4
commit e4eecebeab
2 changed files with 33 additions and 40 deletions
+31 -38
View File
@@ -36,28 +36,23 @@ does not imply a spellable document; `corpus/commonmark-spec/exceptions.json` na
2026-08-24, deep 2026-10-03, the maintainer. Goal 1. Valid while a pipeline or a bot can build a 2026-08-24, deep 2026-10-03, the maintainer. Goal 1. Valid while a pipeline or a bot can build a
shape the editor would not. shape the editor would not.
"Equals" is deep equality over the document's JSON values: `assert.deepStrictEqual` on plain "Equals" is deep equality over the document's JSON values: `assert.deepStrictEqual` on plain objects
objects as `JSON.parse` builds them, since ADF is JSON. Every key and value `doc` holds counts, two as `JSON.parse` builds them, since ADF is JSON. Every key and value in `doc` counts, including two
adjacent text nodes, an empty `attrs`, `content` or `marks` and `-0` included, with no adjacent text nodes, an empty `attrs`, `content` or `marks`, and `-0`. Neither side is normalized.
normalization on either side. CommonMark's spelling stays wherever a document holds none of those shapes. The plain reader builds
CommonMark's spelling stays wherever a document holds none of those shapes. The plain reader what is written, as `markdownToAdf` does. Only the plain writer is lossy: its reduction reads and
builds what is written, as `markdownToAdf` does. Only the plain writer is lossy: its reduction writes editor-normal ADF — adjacent text nodes of identical marks and no attributes merged, `-0` as
reads and writes editor-normal ADF — adjacent text nodes of identical marks and no attributes `0`, and an empty `attrs`, `content` or `marks` the absent key — so two documents the editor holds
merged, `-0` as `0`, and an empty `attrs`, `content` or `marks` the absent key — so two documents equal write the same plain markdown.
the editor holds equal write the same plain markdown.
## `!adf:textBreak{}` parts text CommonMark would join ## `!adf:textBreak{}` parts text CommonMark would join
2026-10-03, the maintainer. Goal 1. Valid while CommonMark reads adjacent text as one run. 2026-10-03, the maintainer. Goal 1. Valid while CommonMark reads adjacent text as one run.
Two adjacent text nodes a reader would build back as one — neither carried, neither holding Two adjacent text nodes CommonMark would read back as one are parted by the reserved inline leaf
`attrs` or an empty key, their marks identical, attributes included — are parted by the reserved `!adf:textBreak{}`, mirroring `!adf:listBreak`: a leaf building no node keeps both nodes and asks
inline leaf `!adf:textBreak{}`, mirroring `!adf:listBreak`: it builds no node, and anywhere but nothing of the text around it. A code span holds no directive, so the spans close and reopen
between two such nodes, or given `[content]` or `{attrs}`, it is `unsupported-node-shape`. It sits around the leaf. The grammar: `spec/flavour.md` §Inline nodes, **Adjacent text nodes**.
inside every mark spelling the pair shares, `**Hello, !adf:textBreak{}world**`; a code span holds
no directive, so it closes and reopens, `` `a`!adf:textBreak{}`b` ``. A carried node never joins
its neighbour, so a carry needs no break. A mark run breaks on any difference in the mark, `attrs:
{}` against no `attrs` included.
## An empty key spells `empty` ## An empty key spells `empty`
@@ -84,14 +79,14 @@ the directive form, since no list marker spells the sign.
## Empty markdown is a document of no blocks ## Empty markdown is a document of no blocks
2026-10-03, a panel and the maintainer. Goals 1 and 5. Valid while ADF's schema requires 2026-10-03, a writer panel and the maintainer. Goals 1 and 5. Valid while ADF's schema requires
`content` on `doc`. `content` on `doc`.
Markdown holding no block reads as `{ content: [], type: 'doc', version: 1 }`, the document Markdown holding no block reads as `{ content: [], type: 'doc', version: 1 }`, the document
`spec/adf-schema/full.json` requires. A document holding no `content` key is `spec/adf-schema/full.json` requires. A document holding no `content` key is
`!adf:doc {content=none}` standing alone as its only block, and a named error anywhere else. The `!adf:doc {content=none}` as its only block, and a named error anywhere else. The writer panel split
writer panel split 4 for `none` and 3 for `absent`, and the maintainer chose `none`; all seven rejected a 4 for `none` and 3 for `absent`, and the maintainer chose `none`; all seven rejected a bare
bare `!adf:doc`. `!adf:doc`.
## Unknown nodes ride the carry ## Unknown nodes ride the carry
@@ -114,10 +109,10 @@ The block carry is a code fence whose info string `adf:<type>` names the node's
node's JSON without `type`: ```` ```adf:blockCard ````. A type no info string carries back — by the node's JSON without `type`: ```` ```adf:blockCard ````. A type no info string carries back — by the
rule a code language follows — leaves the info string `adf:` and keeps `type` in the body. Every rule a code language follows — leaves the info string `adf:` and keeps `type` in the body. Every
info string opening `adf:` is reserved, so a `codeBlock` whose language opens so takes the info string opening `adf:` is reserved, so a `codeBlock` whose language opens so takes the
`language` attribute, and `carry` is an ordinary language. A body holding `type` under a named `language` attribute, and `carry` is an ordinary language. A body holding `type` under a named type,
type, or an `adf:` fence whose type an info string carries, is `unsupported-node-shape`. The or a bare `adf:` fence whose body's `type` an info string could carry, is `unsupported-node-shape`.
reservation claims a fence CommonMark reads as code until `todo.md` item 43 gives CommonMark its own The reservation claims a fence CommonMark reads as code until `todo.md` item 43 gives CommonMark its
reader. own reader.
## A code block is a fence per text node ## A code block is a fence per text node
@@ -125,11 +120,9 @@ reader.
node. node.
A `codeBlock` holding several text nodes is the `!adf:codeBlock` container holding one fence per A `codeBlock` holding several text nodes is the `!adf:codeBlock` container holding one fence per
node, each fence's info string the language; its other attributes sit on the opener, and a node, so each node keeps its own text. The fences carry one info string, since ADF holds one
language no info string carries stays the opener's `language` with bare fences. A `codeBlock` language, and none is empty beside another, since a text node holds text. The grammar:
spelling `content=empty` has no fence to carry the language, so the opener does. Fences with `spec/flavour.md` §The CommonMark blocks, the `codeBlock` bullet.
differing info strings are `unsupported-node-shape` — ADF holds one language — and so is an empty
fence beside another, since a text node holds text.
## Foreign HTML sorts three ways ## Foreign HTML sorts three ways
@@ -372,14 +365,14 @@ handles one cause alike whichever node, attribute or direction raised it.
colon rather than ADF's missing column model doing it. What the grammar itself refuses stays a colon rather than ADF's missing column model doing it. What the grammar itself refuses stays a
claim code, key order among it, and a leaf given a body is refused at its opener, as a container claim code, key order among it, and a leaf given a body is refused at its opener, as a container
missing its closer is (2026-09-16). missing its closer is (2026-09-16).
- A directive whose name reads back to no node is `unknown-directive-name` rather than a claim - A directive whose name reads back to no node is `unknown-directive-name` rather than a claim code
code — the spelling is well formed, and telling that apart from a typo is what a consumer — the spelling is well formed, and telling that apart from a typo is what a consumer switches on
switches on when a later MINOR gives the name meaning. A reserved name is a known name, so never when a later MINOR gives the name meaning. A reserved name is a known name, so never that code,
that code, and the names the flavour reserves part on form: a form the grammar does not have is and the names the flavour reserves part on form: a form the grammar does not have is a claim code
a claim code — `!adf:carry`, whose carry is the fence — and a well-formed form in the wrong place — `!adf:carry`, whose carry is the fence — and a well-formed form in the wrong place is
is `unsupported-node-shape`, `!adf:listBreak` parting anything but two adjacent lists of one `unsupported-node-shape`, `!adf:listBreak` parting anything but two adjacent lists of one type,
type, `!adf:textBreak{}` anything but two text nodes a reader joins, `!adf:doc` standing beside `!adf:textBreak{}` anything but two adjacent text nodes CommonMark would read back as one,
another block (2026-09-01, the text break and `doc` 2026-10-03). `!adf:doc` standing beside another block (2026-09-01, the text break and `doc` 2026-10-03).
- A well-formed directive the node tables refuse — an attribute a node does not hold or spells - A well-formed directive the node tables refuse — an attribute a node does not hold or spells
elsewhere, a value outside its kind or its canonical spelling, an argument, or a body of a shape elsewhere, a value outside its kind or its canonical spelling, an argument, or a body of a shape
its content model does not take — is `unsupported-node-shape`, the emitter's code for the same its content model does not take — is `unsupported-node-shape`, the emitter's code for the same
+2 -2
View File
@@ -181,8 +181,8 @@ product). Block and inline positions canonicalize differently, each fitting wher
- **Block position**: a fenced code block with info string `adf:` and the node's type, body = the - **Block position**: a fenced code block with info string `adf:` and the node's type, body = the
node's JSON without its `type` — two-space indent, object keys sorted: ```` ```adf:blockCard ````. node's JSON without its `type` — two-space indent, object keys sorted: ```` ```adf:blockCard ````.
A type no info string carries back, by the rule a `codeBlock`'s language follows, leaves the info A type no info string carries back, by the rule a `codeBlock`'s language follows, leaves the info
string `adf:` and keeps `type` in the body. A body holding `type` under a named type, or an string `adf:` and keeps `type` in the body. A body holding `type` under a named type, or a bare
`adf:` fence whose type an info string carries, is a named error. `adf:` fence whose body's `type` an info string could carry, is a named error.
- **Inline position**: `!adf:carry{json="…"}` — compact serialization (keys sorted, no whitespace), - **Inline position**: `!adf:carry{json="…"}` — compact serialization (keys sorted, no whitespace),
JSON-string-escaped into the attribute. JSON-string-escaped into the attribute.