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:
@@ -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
@@ -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
@@ -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 .`, `# `), 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 .`, `# `), 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 (``), on parse | `unmappable-image` | the image, captioned with its title |
|
||||
| an image that is a link's whole text, alone in its paragraph (`[](/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 (`[](/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"}` |
|
||||
|
||||
@@ -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 .` 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 .` 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
@@ -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
@@ -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
@@ -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, ``, adds a `caption`
|
||||
holding the title as one text node, and a link whose entire text is the image,
|
||||
`[](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
|
||||
`[](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
|
||||
|
||||
|
||||
@@ -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,6 +1,6 @@
|
||||
import type { AdfNode } from '../adf/document.ts'
|
||||
|
||||
// spec/flavour.md, The CommonMark image: `` reads to `centeredImage([externalMedia(alt, url)])`, the only shape written back as it.
|
||||
// spec/flavour.md, The CommonMark image: `` reads to `centeredImage([externalMedia(alt, url)])`, the only shape `adfToLosslessMarkdown` writes as ``.
|
||||
export function centeredImage(content: AdfNode[]): AdfNode {
|
||||
return { attrs: { layout: 'center' }, content, type: 'mediaSingle' }
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 `[](href)`, and one captioned with plain text as ``, 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 `[](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 `[](href)`, and one captioned with plain text as ``, 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 `[](href)` and `` 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 `[](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.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user