From 8e7da0dc8c19ef7aef4db921fff97b25d8ac862a Mon Sep 17 00:00:00 2001 From: Lilleman auf Larv Date: Fri, 23 Oct 2026 03:39:36 +0200 Subject: [PATCH] =?UTF-8?q?Answer=20prose=20round=201:=200=20=F0=9F=94=B4,?= =?UTF-8?q?=208=20=F0=9F=9F=A1;=20split=20the=20image-in-link=20ruling=20o?= =?UTF-8?q?ut,=20place=20the=20(image)=20plainly,=20credit=20only=20the=20?= =?UTF-8?q?same-URL=20rule=20to=20the=20portable=20writer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 2 +- README.md | 4 ++-- docs/decisions.md | 24 +++++++++++++----------- spec/flavour.md | 13 +++++++------ src/markdown/external-image.ts | 2 +- src/markdown/parse/inline-content.ts | 12 ++++++------ todo.md | 9 ++++----- 7 files changed, 34 insertions(+), 32 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ae8c708..e4453cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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. diff --git a/README.md b/README.md index aca73c2..a489831 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/docs/decisions.md b/docs/decisions.md index 3eaebec..1411b5a 100644 --- a/docs/decisions.md +++ b/docs/decisions.md @@ -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 diff --git a/spec/flavour.md b/spec/flavour.md index c768117..9221145 100644 --- a/spec/flavour.md +++ b/spec/flavour.md @@ -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 diff --git a/src/markdown/external-image.ts b/src/markdown/external-image.ts index 6b3f2e1..ec74b35 100644 --- a/src/markdown/external-image.ts +++ b/src/markdown/external-image.ts @@ -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' }] } diff --git a/src/markdown/parse/inline-content.ts b/src/markdown/parse/inline-content.ts index 024d12d..8a3ca02 100644 --- a/src/markdown/parse/inline-content.ts +++ b/src/markdown/parse/inline-content.ts @@ -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 @@ -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 { 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)) } diff --git a/todo.md b/todo.md index 5e55543..66d0928 100644 --- a/todo.md +++ b/todo.md @@ -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.