diff --git a/AGENTS.md b/AGENTS.md index ba7aa61..3749487 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -27,6 +27,7 @@ In `docs/decisions.md`: - Tables - Links - An image reads as an image only alone in its paragraph, else as its alt text linked; a link text holding no node that takes marks reads as its destination, then its hard breaks +- A linked image is written `[![alt](url)](href)`, a captioned image as its directive - Ids stay site-local - Portable task ids come from position - A block a task item cannot hold stands after its task list diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a375be..605fd51 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -60,8 +60,8 @@ list's own indentation, ends the list, as CommonMark reads it; `0.1.0` read one list. See `MIGRATION.md`. - **Breaking:** `adfToLosslessMarkdown` writes a centered, uncaptioned external image whose one - mark is a link holding only `href` and `title` as `[![alt](url)](href "title")`. Convert stored - markdown per `MIGRATION.md`. + mark is a link holding only `href` and `title` as `[![alt](url)](href "title")`, where it wrote the + `mediaSingle` directive. Convert stored markdown per `MIGRATION.md`. - Fix the time `adfToLosslessMarkdown` and `adfToPortableMarkdown` take on text holding a long run of `*`, `_`, `~` or backticks, or a heading ending in a long run of `#`: the time is linear in the run, and a run of 20000 `*` takes milliseconds where it took seconds. diff --git a/MIGRATION.md b/MIGRATION.md index a5ff8f6..345b1e2 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -60,7 +60,7 @@ function migrateMarkdown(stored: string) { | --- | --- | | `:::panel info` … `:::`, the fence longer per nesting level | `!adf:panel info` … `!adf:/panel` at any depth | | `::media {id=a type=file}` | `!adf:media {id=a type=file}` | -| `:::mediaSingle {layout=center}`, `::media {alt=a marks="[{\"attrs\":{\"href\":\"/v\"},\"type\":\"link\"}]" type=external url=u}`, `:::` | `[![a](u)](/v)` | +| `:::mediaSingle {layout=center}` … `:::` around `::media {alt=a marks="[{\"attrs\":{\"href\":\"/v\"},\"type\":\"link\"}]" type=external url=u}` | `[![a](u)](/v)` | | `::taskItem TODO {localId=i}`: an empty `caption`, `decisionItem`, `paragraph` or `taskItem`, or an empty `heading` carrying `localId` | `!adf:taskItem TODO {localId=i}` then `!adf:/taskItem` | | `:mention[@Mikael]{id=5b10a2}` | `!adf:mention[@Mikael]{id=5b10a2}` | | the `adf` code fence and `:adf{json="…"}` | the `adf:` code fence, its JSON without `type`, and `!adf:carry{json="…"}` | diff --git a/docs/decisions.md b/docs/decisions.md index 6eb09fe..ec850b4 100644 --- a/docs/decisions.md +++ b/docs/decisions.md @@ -241,9 +241,15 @@ The same panel confirmed the other readings on 2026-10-09: a title as the captio text as linked alt text, and an empty link text as its URL, each 3 of 3; `[]()` as nothing, 5 of 7. Their CommonMark suite exceptions are `node-model` (§The CommonMark suite checks three ways). -Writing, a writer panel of a product manager, an engineer and a support agent on 2026-10-11, 3 of -3: `adfToLosslessMarkdown` writes an image only a link marks as `[![alt](url)](href)`, and a -captioned image as the `!adf:caption` directive, since a title reads as hover text, not a caption. +## A linked image is written `[![alt](url)](href)`, a captioned image as its directive + +2026-10-11, a writer panel of a product manager, an engineer and a support agent, 3 of 3 on each. +Goal 6. Valid while the audience reads a title as hover text. + +`adfToLosslessMarkdown` writes a `media` whose only mark is a link as `[![alt](url)](href)`, which +each reader expected before seeing the options. A captioned image keeps the `!adf:caption` +directive: each reader read `![alt](url "title")` as hover text, not a caption. Every reader still +reads that title as the caption; `todo.md` item 100 puts that reading back to a reader panel. ## Ids stay site-local diff --git a/spec/flavour.md b/spec/flavour.md index 15dfaa3..30298e6 100644 --- a/spec/flavour.md +++ b/spec/flavour.md @@ -310,12 +310,12 @@ 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 only for the -bare shape — those attrs and no others, no marks on the `mediaSingle`, no caption, and a -`media` carrying nothing beyond `alt`, `type` and `url` and no mark but a link holding nothing -beyond `href` and `title`, spelled `[![alt](url)](href "title")` — and only where CommonMark spells -it: a destination, a title or a description the image form cannot hold, an empty `alt` included, -takes the directive form instead. +`[![alt](url)](href)`, marks the `media`. `adfToLosslessMarkdown` writes `![alt](url)` only for this +shape: a `mediaSingle` with those attrs and no others, no marks and no caption, holding a `media` +with no attrs beyond `alt`, `type` and `url`. The `media` may carry one mark, a link with no attrs +beyond `href` and `title`, written `[![alt](url)](href "title")`. Where CommonMark cannot spell the +image's alt text or URL, or the link's `href` or title, the directive form is written instead; an +empty `alt` counts as one CommonMark cannot spell. `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 diff --git a/todo.md b/todo.md index 9b931a9..940d065 100644 --- a/todo.md +++ b/todo.md @@ -26,8 +26,8 @@ | ID | Release | Exempt | Item | R | S | A | G | Goals | Score | |---|---|---|---|---|---|---|---|---|---| | 89 | 0.2.1 | | **Build a `date`, `emoji`, `inlineCard`, `mention` or `status` holding no mark Atlassian's schema withholds from it, in every reader.** | 4 | 4 | 5 | 6 | 4 | 13.0 | -| 99 | 0.2.1 | defect | **Keep the link target of a linked external image standing inline or in a table cell, in the spelling a reader panel picks, in `adfToPortableMarkdown`.** | 3 | 3 | 6 | 7 | 7 | 12.9 | -| 100 | 0.2.1 | question | **Put the reading of an image's title as its caption back to a reader panel, now that a writer panel reads a title as hover text.** | 3 | 3 | 4 | 5 | 4, 6 | 10.9 | +| 99 | 0.2.1 | defect | **Write a linked external image standing inline or in a table cell in the spelling a reader panel picks, in `adfToPortableMarkdown`.** | 3 | 3 | 6 | 7 | 7 | 12.9 | +| 100 | 0.2.1 | decision | **Put the reading of an image's title as its caption back to a reader panel, now that a writer panel reads a title as hover text.** | 3 | 3 | 4 | 5 | 4, 6 | 10.9 | | 60 | 0.2.1 | decision | **Collect the questions `parse/` asks `emit/` into one named module.** | 3 | 4 | 2 | 6 | 2 | 10.7 | | 59 | 0.2.1 | decision | **Group the directive grammar into `src/markdown/directive/`, move `Read` to `result.ts`, and move `Flavour` and `flavourClaims` to `markdown/flavour.ts`.** | 3 | 5 | 2 | 6 | 2 | 10.4 | | 62 | 0.2.1 | decision | **Move the portable flavour's reading out of `parse/` and its writing out of `emit/` into `markdown/portable/`, so `emit/` no longer imports `portable/`.** | 4 | 5 | 2 | 6 | 2 | 9.4 | @@ -77,12 +77,13 @@ may reject the document. Replace `InlineNodeModel.takesMarks` (`src/adf/inline-nodes.ts`) with the mark types a node takes, and have the emitter carry a node holding another. -### 99. Keep the link target of a linked external image standing inline or in a table cell, in the spelling a reader panel picks, in `adfToPortableMarkdown`. +### 99. Write a linked external image standing inline or in a table cell in the spelling a reader panel picks, in `adfToPortableMarkdown`. `mediaLeaves` (`src/markdown/portable/inline-reduction.ts`) writes such an image `[alt](url)`, dropping the link's `href`; README §Portable markdown says link targets stay (Goal 7). `[![alt](url)](href)` -there reads back as `[alt](href)`, so it breaks the byte-for-byte rewrite: keeping one target is the -panel's pick. +there reads back as `[alt](href)`, breaking the byte-for-byte rewrite, so only one of `url` and +`href` can survive; a reader panel picks which. If it picks `url`, the README names the dropped +`href` where it says link targets stay. ### 100. Put the reading of an image's title as its caption back to a reader panel, now that a writer panel reads a title as hover text. @@ -124,15 +125,16 @@ commonmark.js avoids the backtick cost by caching backtick runs by length. ### 101. Name the CommonMark spelling to write in the refusal of a directive whose node CommonMark spells. `directiveNode` (`src/markdown/parse/markdown-to-adf.ts`) refuses `!adf:mediaSingle` holding a linked -image with "mediaSingle takes the CommonMark spelling, not the directive form"; the README tells the -reader to write the spelling the message names, and for `[![alt](url)](href "title")` none is named. +image as `unsupported-node-shape`: "mediaSingle takes the CommonMark spelling, not the directive +form". README §The errors says to fix what the message names, and the message names no spelling, +here `[![alt](url)](href "title")`. ### 92. Lower the reading cost of the files a chunk reads most before its first commit. The last five chunks' median is 168135 new input tokens before their first commit, the five before -them 97329. Files read most over the last five chunks, counting opens and searches: `docs/decisions.md` 28, `README.md` -27, `todo.md` 26, `CHANGELOG.md` 20, `src/markdown/parse/blocks.ts` 15, -`src/markdown/portable/adf-to-portable-markdown.ts` 12, `spec/flavour.md` 10, +them 97329. Files read most over the last five chunks, counting opens and searches: +`docs/decisions.md` 28, `README.md` 27, `todo.md` 26, `CHANGELOG.md` 20, +`src/markdown/parse/blocks.ts` 15, `src/markdown/portable/adf-to-portable-markdown.ts` 12, `spec/flavour.md` 10, `src/markdown/parse/portable-markdown-to-adf.test.ts` 10, `src/markdown/emit/line-escaping.ts` 9, `src/markdown/parse/markdown-to-adf.test.ts` 8.