diff --git a/AGENTS.md b/AGENTS.md index b965041..2fd056b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -19,7 +19,8 @@ The other direction is a canonical fixpoint, not byte-identity: human markdown n back yields the library's canonical spelling, and that spelling round-trips byte-identically. "Equals" is structural equality over editor-normal ADF — adjacent text nodes with identical marks -merged, JSON number semantics — the only domain markdown can restore. +merged, JSON number semantics, an empty attrs object, marks array or content array the absent +key — the only domain markdown can restore. Round-trip equality is a property tested over a corpus, not a claim made in prose. @@ -36,7 +37,7 @@ Round-trip equality is a property tested over a corpus, not a claim made in pros - Directives, one grammar for everything markdown lacks: `:::panel info` … `:::` blocks, `:mention[@Mikael]{id=5b10a2}` inline. Prior art: CommonMark's generic-directives proposal. - Plain CommonMark is a subset, with carve-outs (`spec/flavour.md`): literal text shaped like a - directive or a pipe table is claimed. + directive or a pipe table is claimed — plus one image gap. - Tables: one header row plus plain inline cells → pipe table; anything richer → directive form. - Identity-bearing nodes carry their ids in attributes; a document is only portable within its site — accepted. diff --git a/README.md b/README.md index beaadb8..819053c 100644 --- a/README.md +++ b/README.md @@ -40,8 +40,9 @@ isAdfDocument(v: unknown): v is AdfDocument attributes. - Plain CommonMark is valid input to `markdownToAdf`, with two carve-outs — literal text matching directive or pipe-table syntax is claimed (escapable — `spec/flavour.md`) — and one - gap: an image title has no ADF home (an error result). Converting back yields the library's - canonical spelling, which round-trips byte-identically. + gap: a CommonMark image fits only as its own title-less paragraph; mid-text and titled images + are error results. Converting back yields the library's canonical spelling, which round-trips + byte-identically. - Foreign HTML maps a documented element set; an unmappable element is an error, never a silent drop. Well-formed HTML only — no tag-soup recovery. - The emitted formats are semver surface (AGENTS.md §8). diff --git a/spec/flavour.md b/spec/flavour.md index 2e78e3a..1cc89ef 100644 --- a/spec/flavour.md +++ b/spec/flavour.md @@ -3,7 +3,8 @@ The grammar of the extended markdown `adfToMarkdown` emits and `markdownToAdf` parses. Plain CommonMark is a subset with two carve-outs: literal text that matches directive syntax below or opens a pipe table is claimed by the flavour (escape the `:` or `|` to keep it literal) — and -one gap: an image title has no ADF home (a named error). The emitted form is contract +one gap: a CommonMark image fits only as its own title-less paragraph — mid-text and titled +images are named errors (ADF has a slot for neither). The emitted form is contract (AGENTS.md §8). Per-node syntaxes build on this grammar in the sections below (inline nodes and marks: todo.md 1c). @@ -125,8 +126,8 @@ payloads hold; the type stays string and any value round-trips verbatim. Values type: strings verbatim, numbers and booleans in canonical JSON spelling — quoted where not bare (`width="33.33"`) — and `json` values as the inline carry's serialization (compact, keys sorted), quoted. `markdownToAdf` emits `attrs`, `content` and `marks` keys only when non-empty; -editor-normal ADF (AGENTS.md §2) also reads an empty attrs object, marks array or content array -as the absent key — the grammar's empty-`{attrs}` omission already collapses the two spellings. +editor-normal ADF reads an empty attrs object, marks array or content array as the absent key +(AGENTS.md §2) — the grammar's empty-`{attrs}` omission already collapses the two spellings. Marks on a block node ride the reserved attribute key `marks` — the node's marks array as a `json` value: `::::layoutSection {marks="[{\"attrs\":{\"mode\":\"wide\"},\"type\":\"breakout\"}]"}`. @@ -194,8 +195,9 @@ One header row plus plain inline cells is a pipe table; anything richer is the d (AGENTS.md §4). Precisely: a table emits as a pipe table exactly when the `table`, every row and every cell carry no attrs and no marks, the first row is all `tableHeader` and the rest all `tableCell`, every row has the header's cell count, and every cell holds exactly one attr-less, -mark-less paragraph — an empty cell holds one empty paragraph. A pipe table parses back to -exactly that shape. +mark-less paragraph — an empty cell holds one empty paragraph — with no `|` in a code span: +backslash escapes are inert there, so pipe form cannot spell that pipe and the table takes the +directive form. A pipe table parses back to exactly that shape. ``` | Part | Qty | @@ -207,8 +209,9 @@ Claiming at block level, symmetric with directives: a line opening with an unesc claimed and must parse as part of a pipe table, else it is a named error — escape the pipe (`\|`) to keep it literal text. A pipe table is a header row, a delimiter row whose cells are runs of one or more `-` (canonical `---`), and body rows; rows follow code-fence indentation. -Cells split on unescaped `|` before inline parsing; each cell is the inline content of one -paragraph, trimmed; canonical form pads cells with single spaces and ends rows with `|` +Cells split on unescaped `|` before inline parsing — `\|` stays in the cell text, and the +inline layer's ordinary CommonMark escaping yields the pipe; each cell is the inline content of +one paragraph, trimmed; canonical form pads cells with single spaces and ends rows with `|` (optional in input). Named errors: a delimiter or body row whose cell count differs from the header's, and an alignment colon in the delimiter row — ADF holds no column alignment. In a pipe cell a hard break is `:hardBreak{}`, a literal `|` is `\|`, and `|` inside a quoted diff --git a/todo.md b/todo.md index 4000569..c4c3648 100644 --- a/todo.md +++ b/todo.md @@ -16,7 +16,8 @@ detail is settled at its own milestone. lists, layout, extensions, syncBlock. - [ ] **1c — Inline node syntaxes and marks** in `spec/flavour.md`: mention, emoji, status, date, inlineCard, mediaInline; underline, subsup, textColor, border; the spelling for text nodes - whose whitespace CommonMark cannot hold (literal newlines, leading or trailing spaces). + whose whitespace CommonMark cannot hold (literal newlines, leading or trailing spaces) — + escape-based, never literal, since pipe cells trim and pad. - [ ] **1d — Corpus start** (§10): checked-in ADF ↔ canonical-markdown fixture pairs per spec'd node. - [ ] **2 — `adfToMarkdown`.** First real code — decide here where §10's coverage check lives.