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

Answer prose round 1: 0 🔴, 8 🟡; split the image-in-link ruling out, place the (image) plainly, credit only the same-URL rule to the portable writer

This commit is contained in:
2026-10-23 03:39:36 +02:00
parent ad6a4da94e
commit 8e7da0dc8c
7 changed files with 34 additions and 32 deletions
+1 -1
View File
@@ -13,7 +13,7 @@
around only code spans is `unsupported-node-shape`. `adfToLosslessMarkdown` writes code text
holding another mark as `!adf:carry{json="…"}`, and `adfToPortableMarkdown` drops that mark.
- Every reader keeps the URL and title of an image inside a link's text, where it dropped them:
`(image)` follows the link, linked to the image, so
after the link comes `(image)`, which links to the image, so
`[look at ![the moon](moon.png "Full") tonight](/moon)` reads as
`[look at the moon tonight](/moon) ([image](moon.png "Full"))`. A link to the image's own URL
takes no `(image)`, unless the image's title is one the link lacks.
+2 -2
View File
@@ -554,8 +554,8 @@ Serves [Goals](#goals) 1, 4 and 5.
blank line between it and any other line.
- A code span inside emphasis reads as code without the emphasis, since ADF gives code text no
mark but a link or an annotation: ``**`npm ci`**`` reads as `` `npm ci` ``.
- Inside a link, an image reads as plain text the link marks, and `(image)` follows the link,
linked to the image: `[look at ![the moon](moon.png) tonight](/moon)` reads as
- Inside a link, an image reads as plain text the link marks. After the link comes `(image)`,
which links to the image: `[look at ![the moon](moon.png) tonight](/moon)` reads as
`[look at the moon tonight](/moon) ([image](moon.png))`. A link to the image's own URL takes
no `(image)`, unless the image's title is one the link lacks. A link alone in its paragraph
whose whole text is one image reads as that image, with the link marking it.
+13 -11
View File
@@ -243,15 +243,17 @@ reads as the URL, and an empty link text as its destination. A link text holding
reads as its destination, then those breaks: four of seven readers expected that over dropping them.
`[]()` 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 ` (image)` follows the link,
`image` linked to the image's URL and titled by its title, so Goal 7 keeps both. For a link whose
text is the image alone, the bot, the LLM pipeline and a support agent picked this 3 of 3. For a
link holding words beside the image they split on where `(image)` stands, and four more readers, the
bot, the LLM pipeline, a product manager and an engineer, made it 5 of 7 for after the whole link
over right after the alt text. Every reader rejected linking the alt text to the image: the words
lose the link they were written in. A link to the image's own URL takes no `(image)`, unless the
image holds a title the link does not: it would repeat the link, as `adfToPortableMarkdown` never
writes it (§A linked image standing inline in portable markdown).
Inside any other link, the alt text is plain text the link marks. After the link comes ` (image)`,
where `image` links to the image's URL and carries its title, so Goal 7 keeps both. Three readers,
the bot, the LLM pipeline and a support agent, picked this 3 of 3 for a link whose text is the image
alone. For a link with words beside the image, the three split on where `(image)` goes. Four fresh
readers joined: a second bot, a second LLM pipeline, a product manager and an engineer. Five of the
seven put `(image)` after the whole link rather than right after the alt text. Every reader rejected
linking the alt text to the image, since the words would lose the link they were written in.
A link to the image's own URL takes no `(image)`, since the link already reaches the image. The
exception is an image whose title the link lacks: there `(image)` keeps the title.
In `portableMarkdownToAdf`, a `[!WORD]` marker with no `+` or `-` stands apart from the lines after
it, and so does a `+` or `-` callout's title line, so an image alone on the marker line or after the
@@ -315,8 +317,8 @@ linked text.
Where `adfToPortableMarkdown` writes an external image inline, as in a table cell, and a link to
another target marks it, the link keeps its target and the image follows as a link reading `image`.
With no alt text, the link text is the link's target, which is how the reader reads `[](href)`, so
the rewrite holds. Every seat first wrote `[![alt](url)](href)`, which reads back as linked text
written in another spelling, and so breaks the byte-for-byte rewrite. Each then picked this over
the rewrite holds. Every seat first wrote `[![alt](url)](href)`, but the reader reads that as
`[alt](href) ([image](url))`, so writing it breaks the byte-for-byte rewrite. Each then picked this over
`[alt](url)` and `[alt](href)`, which drop a target.
## Ids stay site-local
+7 -6
View File
@@ -328,12 +328,13 @@ its alt text linked to its destination, the link titled by the image's title: be
content, in a heading, a table cell or an `!adf:paragraph`, and in the paragraph a portable task
marker opens. A `[!WORD]` marker with no `+` or `-` stands apart from the lines after it, and so
does a `+` or `-` callout's title line, so an image alone on the marker line or after the title
reads as the image. An empty alt text reads as the destination, as an empty link text does. Inside
a link's text, an `!adf:link`'s included, the alt text is plain text the link marks, and after the
link stands ` (image)`, `image` linked to the image's destination and titled by its title, one for
each image in the order they stand. An image whose destination is the link's own takes none, unless
it holds a non-empty title the link does not (`docs/decisions.md` §An image reads as an image only
alone in its paragraph).
reads as the image. 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. This holds for an `!adf:link`'s
text too. After the link comes ` (image)` for each image, in the order the images stand. Each
`image` links to its image's destination and carries the image's title. An image whose destination
is the link's own takes no ` (image)`, unless the image has a non-empty title the link lacks
(`docs/decisions.md` §An image reads as an image only alone in its paragraph).
### Tables
+1 -1
View File
@@ -13,7 +13,7 @@ export function externalMedia(alt: string, url: string, link?: LinkDefinition):
return link === undefined ? media : { ...media, marks: [linkMark(link)] }
}
// The ` (image)` after a link for an image inside it, as the readers build it (spec/flavour.md, The CommonMark image) and `adfToPortableMarkdown` writes it; `link` builds `image`.
// The ` (image)` after a link for an image inside it, as the readers build it (spec/flavour.md, The CommonMark image) and `adfToPortableMarkdown` writes it; `link` builds the linked word `image`.
export function imageAside(link: (text: string) => AdfNode[]): AdfNode[] {
return [{ text: ' (', type: 'text' }, ...link('image'), { text: ')', type: 'text' }]
}
+6 -6
View File
@@ -34,7 +34,7 @@ type OwnMarks = { kind: 'ownMarks'; node: AdfNode }
type Inline = AdfNode | OwnMarks | TextBreak
// `imageLinks` are the (image)s an `!adf:link` slot's link stands after itself.
// `imageLinks` are the (image)s an `!adf:link` slot holds, which the link places after itself.
type Scanned = { image?: AdfNode; imageLinks: ImageLink[]; pieces: Resolved[] }
export type LinkDefinitions = ReadonlyMap<string, LinkDefinition>
@@ -44,7 +44,7 @@ type Bracket = { active: boolean; image: boolean; kind: 'open'; start: number }
// `link` is the link whose text is this image alone.
type Image = { alt: string; definition: LinkDefinition; kind: 'image'; link?: LinkDefinition }
// The (image) after a link for one image its text holds: running text reads it, alt text drops it.
// The (image) after a link for one image its text holds: running text keeps it, alt text drops it.
type ImageLink = { image: LinkDefinition; kind: 'imageLink'; nodes: Inline[] }
type Nodes = { kind: 'nodes'; nodes: Inline[] }
@@ -74,7 +74,7 @@ type Scan = {
// Pieces below this have been walked for openers to deactivate: an image close folds the link-marked piece into alt text, leaving this the only record that the brackets around it are doomed.
deactivatedBefore: number
definitions: LinkDefinitions
// Whether an `!adf:link` slot holds this content through mark slots alone, where an image reads as its plain alt text and the link stands its (image) after itself.
// Whether an `!adf:link` slot holds this content through mark slots alone, where an image reads as its plain alt text and the link places its (image) after itself.
inLink: boolean
openingSpellableLink: boolean
path: ConvertErrorPath
@@ -270,7 +270,7 @@ function assemble(scan: Scan): Result<Scanned> {
return success(scan.pieces.length === 1 && only?.kind === 'image' ? { ...resolved, image: mediaSingle(only) } : resolved)
}
// A content slot is read before the scan holding it knows whether an image description holds it, so each (image) stays a piece of its own.
// A slot is read before its holder knows whether it sits in an image description, which drops every (image), so each (image) stays a piece of its own.
function resolvedPieces(pieces: readonly Piece[], nodes: readonly Inline[][]): Resolved[] {
const resolved: Resolved[] = []
let run: Inline[] = []
@@ -292,7 +292,7 @@ function contentNodes(pieces: readonly Resolved[]): Inline[] {
return mergeReadText(pieces.flatMap((piece) => piece.nodes))
}
// spec/flavour.md, The CommonMark image: an image no paragraph holds alone reads as its alt text, linked, and inside a link as the link's text, then (image) linked to it.
// spec/flavour.md, The CommonMark image: an image no paragraph holds alone reads as its alt text, linked, and inside a link as the link's text, then (image) linked to the image.
function linkedAlt(piece: Piece, inLink: boolean): Piece[] {
if (inLink && piece.kind === 'imageLink') return []
if (piece.kind !== 'image' || (inLink && piece.link === undefined)) return [piece]
@@ -300,7 +300,7 @@ function linkedAlt(piece: Piece, inLink: boolean): Piece[] {
return piece.link === undefined ? [alt] : [alt, ...besideLink([imageLink(piece.definition)], piece.link)]
}
// As adfToPortableMarkdown writes it: a link to the image itself needs no (image), unless the image holds a title the link does not.
// A link to the image itself needs no (image), as `adfToPortableMarkdown` writes none; one stays to keep an image title the link lacks.
function besideLink(imageLinks: readonly ImageLink[], link: LinkDefinition): ImageLink[] {
return imageLinks.filter(({ image }) => image.destination !== link.destination || (image.title !== undefined && image.title !== '' && image.title !== link.title))
}
+4 -5
View File
@@ -64,12 +64,11 @@ operations (§Cost fixes are measured, never timed), 4× per doubling: about 255
- `markPairings`, called through `markedNodes`, re-marks every piece each emphasis pairing spans:
`'*'.repeat(n) + '[b a](/v)'.repeat(n) + '*'.repeat(n)`. Pair by range instead, outermost
pairing first, marking each piece once.
- `lastBracket`, called by `closeBracket`, walks back over every piece a bracket that went literal
left: `'['.repeat(n) + '[b a](/v)' + '](/w)'.repeat(n)`. Drop such a bracket from the search, as
CommonMark's bracket stack does.
- `lastBracket`, called by `closeBracket`, walks back over the pieces each literal bracket leaves:
`'['.repeat(n) + '[b a](/v)' + '](/w)'.repeat(n)`. Drop a bracket from the search once it goes
literal, as CommonMark's bracket stack does.
The seats found `closeBracket` and `resolveNodes` hard to read; leave both easier to read. Found by
the stability review of item 122, 2026-10-23.
The seats found `closeBracket` and `resolveNodes` hard to read; leave both easier to read.
### 95. Find the code-span closers an escape forms, the mark runs a line cannot spell and the text `[` that opens link text by reading the line back, in place of `escapeClosedRuns`, `unspellableRuns` and `lastLinkClose` predicting them.