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
|
||||
- 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
@@ -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
@@ -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.
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user