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

Answer prose round 1: one sentence per idea for the image readings, a decision title that states the rule, item 86 names its shapes

This commit is contained in:
2026-10-09 03:58:35 +02:00
committed by Lillem4n
parent 446efded6c
commit ee3086ad1e
12 changed files with 58 additions and 58 deletions
+1 -1
View File
@@ -25,7 +25,7 @@ In `docs/decisions.md`:
- CommonMark is a subset
- Tables
- Links
- Every CommonMark image and empty link reads: the image where it stands alone, else a link, `[]()` as nothing
- An image reads as an image only alone in its paragraph, else as its alt text linked; an empty link text reads as its destination
- Ids stay site-local
- Portable task ids come from position
- A callout title keeps its link targets
+5 -5
View File
@@ -57,11 +57,11 @@
`collection`, `id` and `occurrenceKey` directly where they rode the opaque carry.
- Fix an image inside another image's description: it flattens into the alt text, where it was
refused.
- **Breaking:** `unmappable-image` leaves `ConvertErrorCode`, and every image reads. One anywhere
but alone in a plain paragraph reads as its alt text linked to its URL, or inside a link as plain
text the link marks. A titled one alone in a plain paragraph reads as the image captioned with
its title, and a link alone in a plain paragraph whose whole text is an image as the image the
link marks. To keep an image an image, give it a paragraph of its own. See `MIGRATION.md`.
- **Breaking:** `unmappable-image` leaves `ConvertErrorCode`, and every image reads. An image reads
as an image only alone in its paragraph, and its title becomes the caption. Anywhere else it
reads as its alt text linked to its URL; inside a link, as plain text the link marks. A link
alone in its paragraph whose whole text is one image reads as that image, with the link marking
it. To keep an image an image, give it a paragraph of its own. See `MIGRATION.md`.
- **Breaking:** an empty link text reads as its destination, `[](/url)` as `/url` linked, and
`[]()` as nothing, where both spellings stayed literal text. See `MIGRATION.md`.
- Fix a JSON key holding an escape, in a carried node or a JSON attribute such as `parameters`:
+4 -4
View File
@@ -74,14 +74,14 @@ Markdown the spelling table leaves alone, which `0.2.0` reads as a different doc
`unmappable-image`, `unspellable-character`, `unspellable-line-start` and `unspellable-link` leave
`ConvertErrorCode` and `not-a-string` joins it: a `switch` naming one of the four stops compiling,
and one naming every code needs the new case. A document `adfToMarkdown` refused with one of the
last three now converts in `adfToLosslessMarkdown`; markdown the parser refused with one of the
four now reads, or refuses with the code the table gives.
three `unspellable-` codes now converts in `adfToLosslessMarkdown`; markdown the parser refused
with one of the four now reads, or refuses with the code the table gives.
| Input | `0.1.0` | `0.2.0` |
| --- | --- | --- |
| an image anywhere but alone in a plain paragraph (`See ![a](u).`, `# ![a](u)`), on parse | `unmappable-image` | its alt text linked to its URL, or, inside a link, plain text the link marks; give the image a paragraph of its own, with a blank line between it and any text, to keep it an image |
| an image not alone in its paragraph (`See ![a](u).`, `# ![a](u)`), on parse | `unmappable-image` | its alt text linked to its URL, or, inside a link, plain text the link marks; give the image a paragraph of its own, a blank line between it and any other line, to keep it an image |
| a titled image alone in its paragraph (`![a](u "t")`), on parse | `unmappable-image` | the image, captioned with its title |
| an image that is a link's whole text, alone in its paragraph (`[![a](u)](/v)`), on parse | `unmappable-image` | the image, its `media` marked with the link |
| a link alone in its paragraph whose whole text is an image (`[![a](u)](/v)`), on parse | `unmappable-image` | the image, its `media` marked with the link |
| a link whose `href` or `title` no CommonMark escape spells, on emit | `unspellable-link` | spells `!adf:link[text]{attrs}` |
| a carriage return in text, on emit | `unspellable-character` | spells `
` |
| a null character in text, on emit | `unspellable-character` | spells `!adf:text{text="\u0000"}` |
+12 -12
View File
@@ -13,8 +13,7 @@ products store as ADF.
- **Markdown in:** `portableMarkdownToAdf` reads a bot's or a model's markdown, including `~~` and
pipe tables whose every row opens with a pipe. `commonMarkToAdf` reads strict CommonMark as its
spec says: nothing becomes a table, task or panel. Both refuse raw HTML and nesting past 500
levels, and `portableMarkdownToAdf` a claimed shape that does not complete
([The errors](#the-errors)).
levels; `portableMarkdownToAdf` also refuses a malformed pipe table ([The errors](#the-errors)).
- **Portable markdown, lossy:** `adfToPortableMarkdown` and `portableMarkdownToAdf` convert to and
from the markdown that GitHub, GitLab and Obsidian render — GitHub Flavored Markdown's alerts,
task lists and pipe tables, Obsidian Flavored Markdown's callouts and highlights.
@@ -239,16 +238,17 @@ 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
implementation renders differently. Three because ADF holds no node for what it renders:
- 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.
- An image anywhere but alone in a plain paragraph reads as its alt text linked to its URL,
`See ![the moon](moon.png).` as `See [the moon](moon.png).`, and inside a link as plain text
the link marks — unless the link's whole text is the image and the link stands alone in a
plain paragraph, which reads as the image the link marks. To keep an image an image, give it
a paragraph of its own: nothing else on its line, and a blank line between it and any text
above or below. Images on consecutive lines share a paragraph.
- An image reads as an image only alone in its paragraph. Anywhere else — beside other text, in
a heading or a table cell, or in the paragraph an alert or task marker opens — it reads as its
alt text linked to its URL: `See ![the moon](moon.png).` reads as
`See [the moon](moon.png).`. To keep an image an image, give it a paragraph of its own: a
blank line between it and any other line.
- Inside a link, an image reads as plain text the link marks. A link alone in its paragraph
whose whole text is one image reads as that image, with the link marking it.
- 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.
@@ -256,8 +256,8 @@ Serves Goals 1, 4 and 5.
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 three `node-model` and the next
two `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 next two `pending`; the suite holds no example of the last.
- 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
+1 -2
View File
@@ -17,8 +17,7 @@ One directory per contract kind:
`exceptions.json` pins each known divergence by `check`, `example`, `kind` and the exact
`divergence`, with a `reason`. `kind` is `mark-model` (the permanent count divergence from ADF's
mark-per-node model), `node-model` (a permanent divergence where ADF holds no node for what
CommonMark renders: an empty link text, an image amid text, an image's title) or `pending` (a
parser gap).
CommonMark renders) or `pending` (a parser gap).
JSON is two-space indent, keys sorted, and a document read back must deep-equal the fixture's
(`docs/decisions.md` §Equality is deep). `spec.json` is the vendored, upstream machine-readable
+12 -11
View File
@@ -202,19 +202,20 @@ link reference definition — and a directive link CommonMark could spell is ref
link — the bracket form goes literal, the directive form refused — which is CommonMark's prose
where its reference implementation nests one `<a>` in another.
## Every CommonMark image and empty link reads: the image where it stands alone, else a link, `[]()` as nothing
## An image reads as an image only alone in its paragraph, else as its alt text linked; an empty link text reads as its destination
2026-10-04, the maintainer and a writer panel, 3 of 3; `[]()` 2026-10-09, settled by Goals 4 and 7
as the maintainer's plan item directed. Goals 4, 5, 6 and 7. Valid while `mediaInline` carries no
URL and ADF holds no empty text node.
2026-10-04, the maintainer and a writer panel, 3 of 3; `[]()` 2026-10-09, by Goals 4 and 7, as the
maintainer's plan item asked. Goals 4, 5, 6 and 7. Valid while `mediaInline` carries no URL and ADF
holds no empty text node.
Every reader reads every CommonMark image and empty link. An image anywhere but alone in a plain
paragraph reads as its alt text linked to its URL, titled by its title; an empty alt text reads as
the URL, and an empty link text as its destination. `[]()` reads as nothing: it renders nothing, so
Goal 7 loses no content. A titled image alone in a plain paragraph reads as the image captioned with
its title. A link alone in a plain paragraph whose whole text is an image reads as the image with
the link marking its `media`. Inside a link otherwise, the alt text is plain text the link marks and
the image's URL and title drop: the panel chose this by Goal 6, which outranks Goal 7.
Every reader reads every CommonMark image and empty link, as `spec/flavour.md` §The CommonMark
image and §Marks spell out. An image alone in its paragraph reads as the image, its title as the caption.
Anywhere else it reads as its alt text linked to its URL, titled by its title. An empty alt text
reads as the URL, and an empty link text as its destination. `[]()` reads as nothing: it renders
nothing, so Goal 7 loses no content. A link alone in its paragraph whose whole text is one image
reads as that image, with the link marking its `media`. Inside any other link, the alt text is plain
text the link marks, and the image's URL and title drop: the panel chose this by Goal 6, which
outranks Goal 7.
## Ids stay site-local
+8 -8
View File
@@ -314,18 +314,18 @@ the destination, `alt` the description's plain-text content when non-empty — a
it contributing its own text, a node spelling its text in the content slot contributing that text,
and a break of either kind a space. A non-empty title, `![alt](url "title")`, adds a `caption`
holding the title as one text node, and a link whose entire text is the image,
`[![alt](url)](href)`, marks the `media`. `adfToLosslessMarkdown` emits the image form for exactly
the shape with neither — those attrs and no others, no marks on either node, no caption, and a
`[![alt](url)](href)`, marks the `media`. `adfToLosslessMarkdown` emits the image form only for the
bare shape — those attrs and no others, no marks on either node, no caption, and a
`media` carrying nothing beyond `alt`, `type` and `url` — and only where CommonMark spells the pair:
a destination or a description the image form cannot hold, an empty `alt` included, takes the
directive form instead.
Anywhere else — beside other content, in a heading, a table cell or an `!adf:paragraph` — an image
reads as its alt text linked to its destination, the link titled by the image's title, since
`mediaInline` carries a media `collection` + `id`, never a URL. An empty alt text reads as the
destination, as an empty link text does. Inside a link's text, the alt text is plain text the link
marks, and the image's destination and title drop (`docs/decisions.md` §Every CommonMark image and
empty link reads).
`mediaInline` carries a media `collection` + `id`, never a URL, so anywhere else an image reads as
its alt text linked to its destination, the link titled by the image's title: beside other
content, in a heading, a table cell or an `!adf:paragraph`, and in the paragraph a portable alert,
callout or task marker opens. An empty alt text reads as the destination, as an empty link text
does. Inside a link's text, the alt text is plain text the link marks, and the image's destination
and title drop (`docs/decisions.md` §An image reads as an image only alone in its paragraph).
### Tables
+1 -1
View File
@@ -92,7 +92,7 @@ test('the refusal list is unique per example and names real examples', () => {
for (const example of exampleToRefusal.keys()) assert.ok(spec.some((entry) => entry.example === example), `refusal ${example} names no example in the suite`)
})
// A mark is counted once per node it touches (docs/decisions.md §No schema validation).
// A mark counts once per node carrying it: ADF marks each node where HTML nests one element (docs/decisions.md §No schema validation).
const countKeys = ['a', 'blockquote', 'br', 'code', 'em', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'img', 'li', 'ol', 'pre', 'strong', 'ul']
const nodeElement: Record<string, string> = {
blockquote: 'blockquote',
+1 -1
View File
@@ -1,6 +1,6 @@
import type { AdfNode } from '../adf/document.ts'
// spec/flavour.md, The CommonMark image: `![alt](url)` reads to `centeredImage([externalMedia(alt, url)])`, the only shape written back as it.
// spec/flavour.md, The CommonMark image: `![alt](url)` reads to `centeredImage([externalMedia(alt, url)])`, the only shape `adfToLosslessMarkdown` writes as `![alt](url)`.
export function centeredImage(content: AdfNode[]): AdfNode {
return { attrs: { layout: 'center' }, content, type: 'mediaSingle' }
}
+1 -1
View File
@@ -22,7 +22,7 @@ import { readDirectiveMark } from './directive-marks.ts'
import { readInlineDirectiveNode } from './directive-nodes.ts'
import { readTextDirective } from '../text-directive.ts'
// `image` is the `mediaSingle` a lone image builds, for the one container that holds it: a plain paragraph. `nodes` reads it as its linked alt text.
// Only a `paragraph` block takes `image`, the `mediaSingle` a lone image builds; every other container takes `nodes`, where the image is its linked alt text.
export type InlineContent = { image?: AdfNode; nodes: AdfNode[] }
// The text break leaf: it holds no marks, and stands between the nodes it parts until the outermost content checks and drops it.
+1 -1
View File
@@ -318,7 +318,7 @@ function codeBlockNode(language: string, text: string, reading: Reading, path: C
return success(text === '' ? node : { ...node, content: [{ text, type: 'text' }] })
}
// spec/flavour.md, The CommonMark image: only a plain paragraph gives a lone image the block it needs.
// spec/flavour.md, The CommonMark image: only a `paragraph` block gives a lone image the block it needs.
function paragraphNode(text: string, reading: Reading, path: ConvertErrorPath): Result<AdfNode> {
const content = parseInlineContent(text, reading.definitions, path, 'paragraph', reading.claims)
if (!content.ok) return content
+11 -11
View File
@@ -28,7 +28,7 @@
| 87 | 0.2.0 | defect | **Build a `hardBreak` carrying no marks where emphasis or a link spans a line break, in every reader.** | 4 | 3 | 6 | 7 | 4 | 16.6 |
| 49 | 0.2.0 | | **Read a list whose bullet or ordered delimiter changes as two lists, in every reader, and retire `!adf:listBreak`.** | 4 | 5 | 5 | 8 | 4, 5 | 15.5 |
| 61 | 0.2.0 | decision | **Have the emitter ask the inline reader how a line reads back, in place of `line-escaping.ts` predicting it.** | 6 | 8 | 3 | 8 | 1 | 14.0 |
| 86 | 0.2.0 | | **Write an image a link alone marks as `[![alt](url)](href)`, and one captioned with plain text as `![alt](url "caption")`, in `adfToLosslessMarkdown`.** | 4 | 4 | 5 | 6 | 4, 6 | 13.0 |
| 86 | 0.2.0 | | **Write an image whose `media` only a link marks, and one captioned with plain text, in the CommonMark spelling a writer panel picks, in `adfToLosslessMarkdown`.** | 4 | 4 | 5 | 6 | 4, 6 | 13.0 |
| 85 | 0.2.0 | defect | **Write an external image a link marks as `[![alt](url)](href)` in `adfToPortableMarkdown`, keeping the link target.** | 3 | 3 | 6 | 7 | 7 | 12.9 |
| 60 | 0.2.0 | decision | **Collect the questions `parse/` asks `emit/` into one named module.** | 3 | 4 | 2 | 6 | 2 | 10.7 |
| 59 | 0.2.0 | decision | **Group the directive grammar into `src/markdown/directive/`, move `Read<T>` to `result.ts`, and move `Flavour` and `flavourClaims` to `markdown/flavour.ts`.** | 3 | 5 | 2 | 6 | 2 | 10.4 |
@@ -81,7 +81,7 @@ changing the marker, so `adfToLosslessMarkdown` and `adfToPortableMarkdown` alte
between adjacent bullet lists and `.` and `)` between adjacent ordered lists, and `!adf:listBreak`
retires. `*` is the maintainer's pick (2026-10-04). Breaking: `MIGRATION.md`'s Readings and
Spellings tables gain their rows. Examples 301 and 302 lose their `pending` exceptions, and the
spelling leaves the README's "Six CommonMark spellings" bullet, which counts one fewer.
spelling leaves the README's "Seven CommonMark spellings" bullet, which counts one fewer.
### 61. Have the emitter ask the inline reader how a line reads back, in place of `line-escaping.ts` predicting it.
@@ -91,11 +91,12 @@ only the property tests catch drift. It caps the panel's Locality score. The rep
time linear in the line: today `mergesWithSyntax`, `touchesSyntax` and `closesHeading` rescan a run
of one character from each of its characters.
### 86. Write an image a link alone marks as `[![alt](url)](href)`, and one captioned with plain text as `![alt](url "caption")`, in `adfToLosslessMarkdown`.
### 86. Write an image whose `media` only a link marks, and one captioned with plain text, in the CommonMark spelling a writer panel picks, in `adfToLosslessMarkdown`.
Every reader reads both spellings so; `adfToLosslessMarkdown` writes the `!adf:mediaSingle`
directive for them. The emitted form is API (`docs/decisions.md` §The formats are API), so it
changes before `0.2.0` ships. A writer panel settles the spelling.
Every reader reads `[![alt](url)](href)` and `![alt](url "caption")` as these shapes, and
`adfToLosslessMarkdown` writes the `!adf:mediaSingle` directive for both. The emitted form is API
(`docs/decisions.md` §The formats are API): changing it inside `0.2.0` spares consumers a second
breaking change to stored markdown.
### 85. Write an external image a link marks as `[![alt](url)](href)` in `adfToPortableMarkdown`, keeping the link target.
@@ -248,8 +249,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 "Six CommonMark
spellings" bullet, which counts one fewer.
gains its row. Its `pending` exceptions go, and its spelling leaves the README's "Seven
CommonMark spellings" bullet, which counts one fewer.
### 53. Put a block's `marks` spelling to the writer panel and adopt its pick.
@@ -324,9 +325,8 @@ functions write `pending`, `pieces`, `deactivatedBefore` and `openingSpellableLi
`takeFallback` (`emit/inline-line.ts`), which writes the record the next `lineSegments` pass reads;
`writeUnpaired`, `markPairings` and `markHighlights` (`inline-content.ts`), whose order changes the
output; and `nestIn` (`portable/adf-to-portable-markdown.ts`), which pushes into its caller's array.
Once `Scan`'s pieces have owners, the bracket resolution — `closeBracket` through `closeImage`,
`linkPiece`, `linkTo`, `linkedAlt`, `mediaSingle`, `linkMark`, `holdsLink`, `imageAlt` and
`altText` — can leave `inline-content.ts`, about 690 lines over five concerns, for a module of its own.
Once `Scan`'s pieces have owners, the bracket resolution, `closeBracket` through `altText`, can
leave `inline-content.ts` (about 690 lines) for a module of its own.
### 76. State the files the package does not ship once, so the build, the lint ceiling and the coverage exclusions cannot drift apart.