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:
@@ -13,7 +13,7 @@ In `docs/decisions.md`:
|
|||||||
- Markdown in is a canonical fixpoint
|
- Markdown in is a canonical fixpoint
|
||||||
- Equality is deep
|
- Equality is deep
|
||||||
- `!adf:textBreak{}` parts text CommonMark would join
|
- `!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`
|
- An empty key spells `empty`
|
||||||
- `-0` is spelled `-0`
|
- `-0` is spelled `-0`
|
||||||
- Empty markdown is a document of no blocks
|
- Empty markdown is a document of no blocks
|
||||||
|
|||||||
+1
-1
@@ -5,7 +5,7 @@
|
|||||||
- **Breaking:** a list whose bullet character or ordered delimiter changes reads as two lists, as
|
- **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
|
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`
|
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`
|
- **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
|
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
|
in `portableMarkdownToAdf`, and markdown known to be strict CommonMark in the new
|
||||||
|
|||||||
+1
-1
@@ -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="…"}` |
|
| 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 |
|
| `\:` keeps a directive literal | `\!adf:` keeps a directive literal |
|
||||||
| `:adf{json="…"}` carrying a link for its `collection`, `id` or `occurrenceKey` | `!adf:link[text]{attrs}` |
|
| `: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
|
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.
|
holding an unescaped `!adf:` and a code fence whose info string opens `adf:` are claimed instead.
|
||||||
|
|||||||
@@ -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:
|
- A task list holding a block other than a task is a bullet list keeping its states as text:
|
||||||
`- \[x] Done`.
|
`- \[x] Done`.
|
||||||
- Text that would read as a marker takes a backslash: `==` wherever it could open or close a
|
- 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,
|
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.
|
(`* \**`), 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
|
- 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
|
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
|
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`).
|
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
|
- A document any reader built, written back with `adfToLosslessMarkdown`, takes the library's
|
||||||
canonical spelling, which round-trips byte-identically.
|
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:
|
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 `[]()`
|
- 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.
|
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
|
- 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.
|
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
|
And three more: a shortcut reference matching its definition only under Unicode case folding
|
||||||
unresolved, and a link whose text holds an autolink keeps the inner link and leaves the outer
|
stays unresolved, two lists a link reference definition parts stay one list against CommonMark's
|
||||||
brackets literal text, which the spec requires and the reference itself breaks, nesting one `<a>`
|
two, and a link whose text holds an autolink keeps the inner link and leaves the outer brackets
|
||||||
in the other. `corpus/commonmark-spec/exceptions.json` pins the first four `node-model` where the
|
literal text, which the spec requires and the reference itself breaks, nesting one `<a>` in the
|
||||||
suite reaches them, and the next `pending`; the suite holds no example of the last.
|
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
|
- 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
|
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
|
document read-only; saving markdown you could not produce is the loss the round-trip exists to
|
||||||
|
|||||||
+6
-9
@@ -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:
|
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**.
|
`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
|
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.
|
bullet character or the ordered delimiter changes.
|
||||||
|
|
||||||
Every reader parts lists where the marker changes, as CommonMark does. Both writers give a bullet or
|
Every reader parts lists where the marker changes, as CommonMark does, so both writers alternate the
|
||||||
task list right after a bullet or task list `* `, and an ordered list right after an ordered one
|
marker between adjacent lists of a kind and no directive parts them. `*` is the maintainer's pick.
|
||||||
`1)`; the list after that takes `- ` or `1.` again. CommonMark already spells two adjacent lists, so
|
The grammar: `spec/flavour.md` §Canonical form.
|
||||||
no directive parts them. The grammar: `spec/flavour.md` §Canonical form.
|
|
||||||
|
|
||||||
## An empty key spells `empty`
|
## 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
|
(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,
|
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
|
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
|
notes (3 of 3), and a list's numbering overflowing into bullets (3 of 3).
|
||||||
adjacent ordered lists whose numbering breaks into one such list lapsed on 2026-10-09, when a
|
|
||||||
delimiter change began parting them.
|
|
||||||
An omission note reads as the converter's, never as the author's.
|
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.
|
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
|
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.
|
per level above it otherwise.
|
||||||
|
|
||||||
The parse and the portable reduction keep each node's readable spelling in a memo, so the
|
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
|
`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
|
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
|
replaces runs and an ordered list past the marker cap gives way, spending two emitter levels where
|
||||||
|
|||||||
+10
-12
@@ -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
|
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
|
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.
|
inline, so a `~~~` line opens a CommonMark tilde code fence.
|
||||||
- Bullet lists `- `; ordered lists incrementing `1.` `2.` `3.`, the first number the node's
|
- Bullet lists `- `; ordered lists incrementing `1.` `2.` `3.`, the first number the node's `order`
|
||||||
`order` attribute. A bullet list right after a bullet list takes `* `, and an ordered list
|
attribute. CommonMark starts a new list where the bullet character or the delimiter changes, so
|
||||||
right after an ordered one `1)`, since CommonMark starts a new list where the
|
adjacent lists of a kind alternate markers, `- ` `* ` `- ` and `1.` `1)` `1.`. An item whose first
|
||||||
bullet character or the delimiter changes; the list after that takes `- ` or `1.` again. An item
|
line would read as a rule after `* ` opens with a backslash, `* \**`. Continuation lines align
|
||||||
text of asterisks and spaces that would read as a rule after `* ` opens with a backslash,
|
with the first character after the marker (two spaces for `- `, three for `1. `); blank lines
|
||||||
`* \**`. Continuation lines align with the first character
|
inside an item are empty lines, none between a nested list and a CommonMark block above it — one
|
||||||
after the marker (two spaces for `- `, three for `1. `); blank lines inside an item are empty
|
wherever the nested list's own marker cannot interrupt a paragraph (an ordered list whose first
|
||||||
lines, none between a nested list and a CommonMark block above it — one wherever the nested
|
number is not 1, or a list whose first item is empty), whatever block sits above it. Blank lines
|
||||||
list's own marker cannot interrupt a paragraph (an ordered list whose first number is not 1, or
|
between items normalize away. A list whose item holds a line of spaces or tabs alone, which a list
|
||||||
a list whose first item is empty), whatever block sits above it. Blank lines between items
|
item reads back empty, takes the directive form.
|
||||||
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 `>`.
|
- Blockquotes prefix lines with `> `; a blank line inside a blockquote is a bare `>`.
|
||||||
- ATX headings (`#` … `######`); setext input normalizes to ATX.
|
- ATX headings (`#` … `######`); setext input normalizes to ATX.
|
||||||
- Code fences ``` with the node's language as info string, the fence lengthened past any backtick
|
- Code fences ``` with the node's language as info string, the fence lengthened past any backtick
|
||||||
|
|||||||
@@ -18,14 +18,13 @@ import { tryImage } from './image.ts'
|
|||||||
import { tryPipeTable } from './pipe-table.ts'
|
import { tryPipeTable } from './pipe-table.ts'
|
||||||
|
|
||||||
type BlockContainer = 'directive' | 'document' | 'list-item'
|
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]
|
type Delimiters = readonly [string, string]
|
||||||
// headroom: the nesting levels the deepest node below may still spend before `largestNesting` refuses it.
|
// headroom: the nesting levels the deepest node below may still spend before `largestNesting` refuses it.
|
||||||
type EmittedBlock = SpelledBlock & { headroom: number }
|
type EmittedBlock = SpelledBlock & { headroom: number }
|
||||||
type KeptSpelling = { block: EmittedBlock | undefined; depth: number }
|
type KeptSpelling = { block: EmittedBlock | undefined; depth: number }
|
||||||
type PlacedBlock = SpelledBlock & { node: AdfNode }
|
type PlacedBlock = SpelledBlock & { node: AdfNode }
|
||||||
type PlacedBlocks = { blocks: readonly PlacedBlock[]; headroom: number }
|
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 }
|
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.
|
// 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>
|
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 lines = body.map((line, index) => (index === 0 ? `${marker}${line}` : line === '' ? '' : `${indent}${line}`))
|
||||||
const first = lines[0] ?? ''
|
const first = lines[0] ?? ''
|
||||||
if (!isThematicBreak(first)) return lines.join('\n')
|
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
|
return inner.startsWith('*') ? [`${marker}\\${first.slice(marker.length)}`, ...lines.slice(1)].join('\n') : undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -246,7 +246,7 @@ function openContainer(walk: Walk, start: ContainerStart): void {
|
|||||||
walk.stack.push({ blocks, indentation: start.indentation, kind: 'item', list })
|
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 {
|
function openedList(walk: Walk, start: Extract<ContainerStart, { kind: 'item' }>): ListBlock {
|
||||||
if (!start.fresh) return start.list
|
if (!start.fresh) return start.list
|
||||||
const blocks = currentBlocks(walk)
|
const blocks = currentBlocks(walk)
|
||||||
|
|||||||
@@ -125,7 +125,7 @@ function portableSequence(blocks: readonly AdfNode[], reduction: Reduction): Res
|
|||||||
sequence.push(block)
|
sequence.push(block)
|
||||||
continue
|
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]
|
const replacement = block === next ? nodeContent(block).flatMap(nodeContent) : [block]
|
||||||
for (const node of replacement.reverse()) pending.push(node)
|
for (const node of replacement.reverse()) pending.push(node)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
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
|
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
|
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.
|
### 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
|
`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
|
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
|
gains its row. Its `pending` exceptions go, and the README's guarantees drop the case-folding
|
||||||
the CommonMark spellings, which counts one fewer.
|
exception and count one fewer.
|
||||||
|
|
||||||
### 53. Put a block's `marks` spelling to the writer panel and adopt its pick.
|
### 53. Put a block's `marks` spelling to the writer panel and adopt its pick.
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user