From a2c620da2798f754c275e022cb12cfdb9ad53165 Mon Sep 17 00:00:00 2001 From: Lilleman auf Larv Date: Sat, 3 Oct 2026 15:13:50 +0200 Subject: [PATCH] Define the writer panel, split the carry fence into its own changelog entry, and say how an adf: fence stays code --- AGENTS.md | 6 +++--- CHANGELOG.md | 21 ++++++++++++--------- MIGRATION.md | 8 ++++---- README.md | 17 +++++++++-------- spec/flavour.md | 2 +- 5 files changed, 29 insertions(+), 25 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 368df4d..af7002b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -174,9 +174,9 @@ allow, rendered, shuffled, with no rationale and nothing saying what is implemen settle it; otherwise four more read, five of seven settle it, and less is a missing goal, asked. The verdict lands in `docs/decisions.md`. -Every new or changed markdown or HTML spelling goes to such a panel, seated by the people who read -and write that format (README `## Audience`); a question about what an app relies on seats the -developer personas. +A writer panel settles every new or changed markdown or HTML spelling: a reader panel whose readers +are the people who read and write that format (README `## Audience`). A question about what an app +relies on goes to the developer personas instead. ### Stated numbers diff --git a/CHANGELOG.md b/CHANGELOG.md index 6e548ed..a10c5de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,18 +4,21 @@ - **Breaking:** directives, the inline opaque carry among them (now `!adf:carry{json="…"}`), are spelled under an `!adf:` prefix (`!adf:name … !adf:/name`, `!adf:name[content]{attrs}`, - `!adf:name arg {attrs}`) in place of the `:::`/`::`/`:name` forms, and the block carry is a code - fence whose info string `adf:` names the node's type, its body the node's JSON without - `type`: text holding an unescaped `!adf:` and a code fence whose info string opens `adf:` are - claimed, and `adf` is an ordinary code block language. Convert stored markdown per `MIGRATION.md`. + `!adf:name arg {attrs}`) in place of the `:::`/`::`/`:name` forms: text holding an unescaped + `!adf:` is claimed, and `adf` is an ordinary code block language. Convert stored markdown per + `MIGRATION.md`. +- **Breaking:** the block carry is a code fence whose info string `adf:` names the node's + type, its body the node's JSON without `type`, and a code fence whose info string opens `adf:` is + claimed. Convert stored markdown per `MIGRATION.md`. - **Breaking:** `markdownToAdf` and `plainMarkdownToAdf` read markdown holding no block as a document whose `content` is empty, as Atlassian's schema requires; `!adf:doc {content=none}` spells a document holding no `content` key. -- `markdownToAdf(adfToMarkdown(doc))` deep-equals `doc`: two adjacent text nodes a reader would - join are parted by `!adf:textBreak{}`, an empty `attrs`, `content` or `marks` is spelled - `{attrs=empty}`, `{content=empty}` or `{marks=empty}`, `-0` is spelled `-0`, and a `codeBlock` - of several text nodes is a fence per node. A `codeBlock` holding other than plain text nodes - rides the block carry, where it was refused. +- `markdownToAdf(adfToMarkdown(doc))` deep-equals `doc` as JSON, for a document of plain objects as + `JSON.parse` builds them: two adjacent text nodes CommonMark would read back as one are parted by + `!adf:textBreak{}`, an empty `attrs`, `content` or `marks` is spelled `{attrs=empty}`, + `{content=empty}` or `{marks=empty}`, `-0` is spelled `-0`, and a `codeBlock` of several text + nodes is a fence per node. A `codeBlock` holding other than plain text nodes rides the block + carry, where it was refused. - **Breaking:** `unspellable-link` leaves `ConvertErrorCode`; a link whose `href` or `title` no CommonMark escape spells is written as `!adf:link[text]{attrs}`. - **Breaking:** some directive refusals carry `malformed-directive` where they carried diff --git a/MIGRATION.md b/MIGRATION.md index f9b9b49..d213215 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -5,9 +5,9 @@ Directives moved under the `!adf:` prefix. `0.2.0` reads `0.1.0`'s spelling without an error, turning each directive into text and each carried node into an `adf` code block. Before `0.2.0` reads any `0.1.0` markdown, convert what is stored or in flight (an open editor, a queue) with the -recipe below, and rewrite markdown your code writes or matches (templates, prompts, patterns) by -the tables below. Stored ADF needs one change: a document `0.1.0` read from empty markdown holds -no `content` key, and gets `content: []`, the empty document it meant. +recipe below, and rewrite markdown your code writes or matches (templates, prompts, patterns) by the +tables below. Stored ADF needs one change: give a document holding no `content` key `content: []`. +`0.1.0` read empty markdown to that shape, meaning the empty document. ### Convert markdown @@ -23,7 +23,7 @@ import { markdownToAdf as markdownToAdf010 } from 'adf-codec-0.1' function migrateMarkdown(stored: string) { const parsed = markdownToAdf010(stored) - // 0.1.0's reader was editor-normal, so a document with no content key always meant an empty one. + // 0.1.0 dropped an empty content array, so a document with no content key meant an empty one. return parsed.ok ? adfToMarkdown({ ...parsed.value, content: parsed.value.content ?? [] }) : parsed } ``` diff --git a/README.md b/README.md index bf3c06c..0fb2474 100644 --- a/README.md +++ b/README.md @@ -175,7 +175,7 @@ Parsing — `markdownToAdf` and `plainMarkdownToAdf`, and `htmlToAdf` at `0.2.0` | Code | Fires when | What you can do | | --- | --- | --- | -| `malformed-directive` | an `!adf:` the grammar cannot read — a prefix completing no directive, an unclosed container, `[content]` or `{attrs}`, a closer with no container of its name open, a leaf given a body, `{attrs}` out of order or duplicated, invalid JSON in an opaque carry | write the spelling the message names, or keep it literal: escape the prefix — `\!adf:`, block and inline alike — or wrap a code fence whose info string opens `adf:` in `!adf:codeBlock {language="adf:…"}` with a bare fence | +| `malformed-directive` | an `!adf:` the grammar cannot read — a prefix completing no directive, an unclosed container, `[content]` or `{attrs}`, a closer with no container of its name open, a leaf given a body, `{attrs}` out of order or duplicated, invalid JSON in an opaque carry | write the spelling the message names, or keep it literal: escape the prefix — `\!adf:`, block and inline alike — or, for a code fence whose info string opens `adf:`, drop the info string and wrap the fence in `!adf:codeBlock {language="adf:…"}` | | `malformed-pipe-table` | a pipe row that is no pipe table — a missing or ragged `---` delimiter row, an alignment colon in it, or a row not opening with a pipe | open every row with a pipe and give the delimiter row the header's cell count; to keep the lines literal text instead, escape the leading pipe of every one — escaping a single row leaves the next to open a fresh table and fail the same way | | `unknown-directive-name` | a directive whose name is no node or mark this version spells | check the name in `spec/flavour.md`, or escape the prefix as `\!adf:`; the spelling itself is well formed, so a later minor may give the name meaning | | `unmappable-html` | the input holds an HTML construct the documented element set does not map, a comment and a processing instruction among them — at this version that is every raw HTML construct in markdown, the element set landing at `0.2.0` | remove the construct, or write what it holds in the lossless flavour | @@ -198,7 +198,7 @@ emit refuses: | `unspellable-line-start` | a paragraph line begins with a code span whose backticks would read back as a code fence | put any text before the code span | | `unspellable-whitespace` | an `emoji`, `mention` or `status` holds a newline in the text its inline directive spells in the content slot | replace it with a space — an inline directive never spans lines | | `unsupported-nesting-depth` | blocks, marks, an attribute's JSON or a carried node's JSON nest past 500 levels | keep the ADF and pass the document over, or show it read-only; flatten the input where you are the one who wrote it | -| `unsupported-node-shape` | a node carries an attribute, value, argument or body its type does not take, or lacks one it needs — or markdown writes as a directive a node or mark the lossless flavour spells as CommonMark | write the shape the message names, or remove the reserved directive it names; `spec/flavour.md` lists every type's attributes and body | +| `unsupported-node-shape` | a node carries an attribute, value, argument or body its type does not take, or lacks one it needs — or markdown writes as a directive a node or mark the lossless flavour spells as CommonMark, or a reserved directive (`!adf:textBreak{}`, `!adf:listBreak`, `!adf:doc`) stands where it parts nothing | make the edit the message opens with; `spec/flavour.md` lists every type's attributes and body | ## The guarantees @@ -213,12 +213,13 @@ Serves Goals 1, 3 and 4. name every exception. - Plain CommonMark is valid input to `markdownToAdf` apart from the raw HTML `unmappable-html` names, with four carve-outs — literal text matching directive, pipe-table or strikethrough syntax, - and a code fence whose info string opens `adf:`, are claimed (escapable — `spec/flavour.md`) — and - one gap: a CommonMark image fits only as its own title-less paragraph; mid-text and titled images - are error results, save an image inside another's description, which flattens into the alt text. - Converting back yields the library's canonical spelling, which round-trips byte-identically — - where it converts back at all: a parse succeeding is no promise of that, so keep the source until - the way back succeeds. ``` ` `` ` ``` reads cleanly and then refuses. + and a code fence whose info string opens `adf:`, are claimed (each can be kept literal — + `spec/flavour.md`) — and one gap: a CommonMark image fits only as its own title-less paragraph; + mid-text and titled images are error results, save an image inside another's description, which + flattens into the alt text. Converting back yields the library's canonical spelling, which + round-trips byte-identically — where it converts back at all: a parse succeeding is no promise of + that, so keep the source until the way back succeeds. ``` ` `` ` ``` reads cleanly and then + refuses. - Four CommonMark spellings parse without an error and build a document the reference implementation renders differently: `[](/url)` and `[]()` stay literal text against CommonMark's empty link, a list continuing past a marker change stays one list against CommonMark's two, a diff --git a/spec/flavour.md b/spec/flavour.md index a3170d6..9e7396b 100644 --- a/spec/flavour.md +++ b/spec/flavour.md @@ -4,7 +4,7 @@ The grammar of the extended markdown `adfToMarkdown` emits and `markdownToAdf` p CommonMark is a subset apart from raw HTML (below), with four carve-outs: literal text that matches directive syntax below or reads as a pipe table is claimed by the flavour, a matched `~~` pair spells `strike` (escape the `!adf:`, `|` or `~` to keep it literal), and a code fence whose info -string opens `adf:` is the opaque carry (wrap it as a bare fence in +string opens `adf:` is the opaque carry (drop the info string and wrap the fence in `!adf:codeBlock {language="adf:…"}` to keep it code) — and one gap: a CommonMark image fits only as its own title-less paragraph — mid-text and titled images are named errors. The emitted form is contract (`docs/decisions.md` §The formats are API). Per-node syntaxes build on this grammar in the