3
0
mirror of https://github.com/larvit/adf-codec.git synced 2026-11-12 11:48:29 +01:00

Answer prose round 1: the decision says right after and keeps only its reason, the README counts the link-definition exception, and the comments and spec say which list alternates

This commit is contained in:
2026-10-09 15:23:05 +02:00
parent 994c450490
commit ca3d703061
10 changed files with 35 additions and 40 deletions
+1 -1
View File
@@ -13,7 +13,7 @@ In `docs/decisions.md`:
- Markdown in is a canonical fixpoint
- Equality is deep
- `!adf:textBreak{}` parts text CommonMark would join
- A list beside one of its kind takes the other marker
- A list right after one of its kind takes the other marker
- An empty key spells `empty`
- `-0` is spelled `-0`
- Empty markdown is a document of no blocks
+1 -1
View File
@@ -5,7 +5,7 @@
- **Breaking:** a list whose bullet character or ordered delimiter changes reads as two lists, as
CommonMark reads it: `- a` then `* b`, `1. a` then `1) b`. `adfToLosslessMarkdown` gives a list
right after one of its kind the other marker, `* ` or `1)`, in place of the `::listBreak`
separator, and `adfToPortableMarkdown` does the same. Convert stored markdown per `MIGRATION.md`.
separator. Convert stored markdown per `MIGRATION.md`.
- **Breaking:** each call names the markdown it reads or writes. `adfToMarkdown` and `markdownToAdf`
are now `adfToLosslessMarkdown` and `losslessMarkdownToAdf`. A bot's or a model's markdown reads
in `portableMarkdownToAdf`, and markdown known to be strict CommonMark in the new
+1 -1
View File
@@ -65,7 +65,7 @@ function migrateMarkdown(stored: string) {
| the `adf` code fence and `:adf{json="…"}` | the `adf:<type>` code fence, its JSON without `type`, and `!adf:carry{json="…"}` |
| `\:` keeps a directive literal | `\!adf:` keeps a directive literal |
| `:adf{json="…"}` carrying a link for its `collection`, `id` or `occurrenceKey` | `!adf:link[text]{attrs}` |
| `::listBreak` between two lists of a kind | no separator, the second list's marker changed: `* ` for `- `, `1)` for `1.`, alternating |
| `::listBreak` between two lists of a kind | no separator: adjacent lists of a kind alternate `- ` and `* `, or `1.` and `1)` |
A colon run and `:name[` are plain text now, and `adf` an ordinary code block language; text
holding an unescaped `!adf:` and a code fence whose info string opens `adf:` are claimed instead.
+9 -8
View File
@@ -155,8 +155,8 @@ saving what this pair read replaces mentions, attachments and macros with text.
- A task list holding a block other than a task is a bullet list keeping its states as text:
`- \[x] Done`.
- Text that would read as a marker takes a backslash: `==` wherever it could open or close a
highlight, `[!…]` opening a quote, asterisks that would read as a rule after a `* ` marker,
`* \**`, and `[x]` or `[ ]` opening any list item, since GitHub reads that marker per item.
highlight, `[!…]` opening a quote, asterisks that would read as a rule after a `* ` marker
(`* \**`), and `[x]` or `[ ]` opening any list item, since GitHub reads that marker per item.
- A node read back carries no `localId` except a `taskList`, `taskItem` or `blockTaskItem`, which
Atlassian's schema requires one on: each gets a UUID v4 hashed from the whole markdown and its
position, the same on every read. Join markdown bound for one document and read it once: the same
@@ -239,7 +239,7 @@ Serves Goals 1, 4 and 5.
without a delimiter row, is an error result; each can be kept literal (`spec/flavour.md`).
- A document any reader built, written back with `adfToLosslessMarkdown`, takes the library's
canonical spelling, which round-trips byte-identically.
- Six CommonMark spellings parse without an error and build a document the reference
- Seven CommonMark spellings parse without an error and build a document the reference
implementation renders differently. Four because ADF holds no node for what it renders:
- An empty link text reads as its destination, `[](/url)` as `/url` linked to `/url`, and `[]()`
as nothing. A link text holding only hard breaks reads the same, followed by its hard breaks.
@@ -253,11 +253,12 @@ Serves Goals 1, 4 and 5.
- An image's title becomes its caption when the image stays an image, and its link's title when
it becomes linked alt text. Inside another link, the title is dropped.
And two more: a shortcut reference matching its definition only under Unicode case folding stays
unresolved, and a link whose text holds an autolink keeps the inner link and leaves the outer
brackets literal text, which the spec requires and the reference itself breaks, nesting one `<a>`
in the other. `corpus/commonmark-spec/exceptions.json` pins the first four `node-model` where the
suite reaches them, and the next `pending`; the suite holds no example of the last.
And three more: a shortcut reference matching its definition only under Unicode case folding
stays unresolved, two lists a link reference definition parts stay one list against CommonMark's
two, and a link whose text holds an autolink keeps the inner link and leaves the outer brackets
literal text, which the spec requires and the reference itself breaks, nesting one `<a>` in the
other. `corpus/commonmark-spec/exceptions.json` pins the first four `node-model` where the suite
reaches them and the fifth `pending`; the suite holds no example of the last two.
- Not every document converts back: `adfToLosslessMarkdown` refuses a document nesting past 500
levels, and the node shapes the `unsupported-node-shape` row lists. Show the refusal and keep the
document read-only; saving markdown you could not produce is the loss the round-trip exists to
+6 -9
View File
@@ -72,15 +72,14 @@ Two adjacent text nodes CommonMark would read back as one are parted by the rese
A code span holds no directive, so the spans close and reopen around the leaf. The grammar:
`spec/flavour.md` §Inline nodes, **Adjacent text nodes**.
## A list beside one of its kind takes the other marker
## A list right after one of its kind takes the other marker
2026-10-04, the maintainer. Goals 4 and 5. Valid while CommonMark starts a new list where the
bullet character or the ordered delimiter changes.
Every reader parts lists where the marker changes, as CommonMark does. Both writers give a bullet or
task list right after a bullet or task list `* `, and an ordered list right after an ordered one
`1)`; the list after that takes `- ` or `1.` again. CommonMark already spells two adjacent lists, so
no directive parts them. The grammar: `spec/flavour.md` §Canonical form.
Every reader parts lists where the marker changes, as CommonMark does, so both writers alternate the
marker between adjacent lists of a kind and no directive parts them. `*` is the maintainer's pick.
The grammar: `spec/flavour.md` §Canonical form.
## An empty key spells `empty`
@@ -289,9 +288,7 @@ renderer confirming each shape. Reader panels settled `error` as an error panel,
(3 of 3) and a Han, Hangul, kana, Thai, Lao, Khmer or Myanmar character on either side bounding a
delimiter, so `は==日本語==で` (3 of 3), `==한국어==에서만` and `iPhone==専用==` (6 of 7) highlight,
the external image's two forms (6 of 7), a rule opening a list item dropping and the omission
notes (3 of 3), and a list's numbering overflowing into bullets (3 of 3). A 5 of 7 verdict joining
adjacent ordered lists whose numbering breaks into one such list lapsed on 2026-10-09, when a
delimiter change began parting them.
notes (3 of 3), and a list's numbering overflowing into bullets (3 of 3).
An omission note reads as the converter's, never as the author's.
Reading takes other tools' spellings, since it reads their output and writes none of them.
Rejected: `~sub~` and `^sup^` (`~2~` is a strike on GitHub, so `subsup` drops), underline and colour
@@ -702,7 +699,7 @@ two cannot disagree — which is what makes the kept value a memo rather than a
per level above it otherwise.
The parse and the portable reduction keep each node's readable spelling in a memo, so the
`commonMarkSpelling` ask stops spelling a node once per level above it. The spelled texts and
`commonMarkSpelling` ask stops spelling a node once per level above it. `text`, `alternateText` and
`spelling` carry no depth and `headroom` is affine in it, so a read at or above the depth that
filled the entry rebases; a read below re-spells, because a hit skips the depth guards the walk it
replaces runs and an ordered list past the marker cap gives way, spending two emitter levels where
+10 -12
View File
@@ -18,18 +18,16 @@ normalizes to it through the round-trip.
cannot parse (intra-word). Strike is GFM strikethrough narrowed to exactly two tildes — a
single tilde or a run of three or more is literal text — and block structure resolves before
inline, so a `~~~` line opens a CommonMark tilde code fence.
- Bullet lists `- `; ordered lists incrementing `1.` `2.` `3.`, the first number the node's
`order` attribute. A bullet list right after a bullet list takes `* `, and an ordered list
right after an ordered one `1)`, since CommonMark starts a new list where the
bullet character or the delimiter changes; the list after that takes `- ` or `1.` again. An item
text of asterisks and spaces that would read as a rule after `* ` opens with a backslash,
`* \**`. Continuation lines align with the first character
after the marker (two spaces for `- `, three for `1. `); blank lines inside an item are empty
lines, none between a nested list and a CommonMark block above it — one wherever the nested
list's own marker cannot interrupt a paragraph (an ordered list whose first number is not 1, or
a list whose first item is empty), whatever block sits above it. Blank lines between items
normalize away. A list whose item holds a line of spaces or tabs alone, which a list item reads
back empty, takes the directive form.
- Bullet lists `- `; ordered lists incrementing `1.` `2.` `3.`, the first number the node's `order`
attribute. CommonMark starts a new list where the bullet character or the delimiter changes, so
adjacent lists of a kind alternate markers, `- ` `* ` `- ` and `1.` `1)` `1.`. An item whose first
line would read as a rule after `* ` opens with a backslash, `* \**`. Continuation lines align
with the first character after the marker (two spaces for `- `, three for `1. `); blank lines
inside an item are empty lines, none between a nested list and a CommonMark block above it — one
wherever the nested list's own marker cannot interrupt a paragraph (an ordered list whose first
number is not 1, or a list whose first item is empty), whatever block sits above it. Blank lines
between items normalize away. A list whose item holds a line of spaces or tabs alone, which a list
item reads back empty, takes the directive form.
- Blockquotes prefix lines with `> `; a blank line inside a blockquote is a bare `>`.
- ATX headings (`#` … `######`); setext input normalizes to ATX.
- Code fences ``` with the node's language as info string, the fence lengthened past any backtick
+2 -3
View File
@@ -18,14 +18,13 @@ import { tryImage } from './image.ts'
import { tryPipeTable } from './pipe-table.ts'
type BlockContainer = 'directive' | 'document' | 'list-item'
// The usual delimiter, then the one a list beside another of its kind takes.
type Delimiters = readonly [string, string]
// headroom: the nesting levels the deepest node below may still spend before `largestNesting` refuses it.
type EmittedBlock = SpelledBlock & { headroom: number }
type KeptSpelling = { block: EmittedBlock | undefined; depth: number }
type PlacedBlock = SpelledBlock & { node: AdfNode }
type PlacedBlocks = { blocks: readonly PlacedBlock[]; headroom: number }
// CommonMark starts a new list where the marker changes, so a list beside one of its kind takes `alternateText`.
// CommonMark starts a new list where the marker changes, so every second list in a run of one kind takes `alternateText`.
type SpelledBlock = { spelling: 'commonmark' | 'directive'; text: string } | { alternateText: string; spelling: 'list'; text: string }
// Keyed by reference: only a caller building one object per position (the parse, the portable reduction) passes one; a consumer's document may share a node.
export type SpellingMemo = Map<AdfNode, KeptSpelling>
@@ -367,7 +366,7 @@ function tryListItemLines(inner: string, marker: string): string | undefined {
const lines = body.map((line, index) => (index === 0 ? `${marker}${line}` : line === '' ? '' : `${indent}${line}`))
const first = lines[0] ?? ''
if (!isThematicBreak(first)) return lines.join('\n')
// Only text opens an item with `*`, a rule being spelled `---`; where `* ` before it reads as a rule, a backslash keeps it text.
// An item opens with `*` only as text, a list there taking `- ` and a rule `---`; a backslash keeps a first line reading as a rule text.
return inner.startsWith('*') ? [`${marker}\\${first.slice(marker.length)}`, ...lines.slice(1)].join('\n') : undefined
}
+1 -1
View File
@@ -246,7 +246,7 @@ function openContainer(walk: Walk, start: ContainerStart): void {
walk.stack.push({ blocks, indentation: start.indentation, kind: 'item', list })
}
// An item after a closed one, such as an empty item a blank line ends, continues the list beside it.
// An item opening after its list's last item closed, as an empty item a blank line ends does, continues that list.
function openedList(walk: Walk, start: Extract<ContainerStart, { kind: 'item' }>): ListBlock {
if (!start.fresh) return start.list
const blocks = currentBlocks(walk)
@@ -125,7 +125,7 @@ function portableSequence(blocks: readonly AdfNode[], reduction: Reduction): Res
sequence.push(block)
continue
}
// Read next, since a replacement may take the directive form itself.
// Back on `pending`: a replacement may take the directive form itself.
const replacement = block === next ? nodeContent(block).flatMap(nodeContent) : [block]
for (const node of replacement.reverse()) pending.push(node)
}
+3 -3
View File
@@ -83,7 +83,7 @@ of one character from each of its characters.
no block stands between, so `- a\n\n[r]: /u\n\n- b` reads as one list; CommonMark reads two,
the definition closing the first. The join exists for an empty item a blank line ends, which
continues its list. `markdown-to-adf.test.ts` asserts the one list. Breaking: `MIGRATION.md`'s
Readings table gains its row.
Readings table gains its row, and the README's guarantees drop the exception and count one fewer.
### 93. Report a refusal's `path` where the node would stand after `portableMarkdownToAdf` moves it out of a task item.
@@ -275,8 +275,8 @@ A writer panel chose it on 2026-10-03, 5 of 7, over today's `colwidth="[340,420]
`link-syntax.ts` normalizes a label with `toLowerCase`, so `[ẞ]` misses its `[SS]` definition (spec
example 540); lowercasing and then uppercasing folds it. Breaking: `MIGRATION.md`'s Readings table
gains its row. Its `pending` exceptions go, and its spelling leaves the README's bullet counting
the CommonMark spellings, which counts one fewer.
gains its row. Its `pending` exceptions go, and the README's guarantees drop the case-folding
exception and count one fewer.
### 53. Put a block's `marks` spelling to the writer panel and adopt its pick.