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 🟡; say which reader skips what, reorder the migration's keep-the-mark advice, file the decision title as a question

This commit is contained in:
2026-10-14 20:57:30 +02:00
committed by Lillem4n
parent 5886f76f96
commit e56291eb90
8 changed files with 44 additions and 36 deletions
+3 -3
View File
@@ -9,9 +9,9 @@
breaks alone still reads as before. `adfToLosslessMarkdown` writes a node holding a mark its type
cannot take as `!adf:carry{json="…"}`. `MIGRATION.md` says how to convert stored markdown.
- **Breaking:** every reader gives code text no mark but `link` and `annotation`, as Atlassian's
schema requires: ``**`npm ci`**`` now reads as plain code, and a directive mark around only code
spans is `unsupported-node-shape`. `adfToLosslessMarkdown` writes code text holding another mark
as `!adf:carry{json="…"}`, and `adfToPortableMarkdown` drops that mark.
schema requires: ``**`npm ci`**`` now reads as plain code, and a directive mark other than a link
around only code spans is `unsupported-node-shape`. `adfToLosslessMarkdown` writes code text
holding another mark as `!adf:carry{json="…"}`, and `adfToPortableMarkdown` drops that mark.
- `losslessMarkdownToAdf` names the CommonMark spelling to write when it refuses a directive whose
node CommonMark spells, such as `[![alt](url)](href)` for a linked `!adf:mediaSingle`.
- The README compares the package with seven other ADF converters, each checked against its
+10 -11
View File
@@ -2,23 +2,22 @@
## From `0.2.0` to `0.3.0`
A mark spelling skips a node Atlassian's schema withholds the mark from, in `losslessMarkdownToAdf`, and
skips code text in every reader:
Every reader skips code text when it spells a mark other than a link, and `losslessMarkdownToAdf`
also skips every other node Atlassian's schema withholds the mark from:
| Input | `0.2.0` | `0.3.0` |
| --- | --- | --- |
| a `date`, `emoji`, `inlineCard`, `mention`, `status` or `mediaInline` inside a mark's spelling (`**!adf:mention[@A]{id=x}**`) | the node holds the mark | the node holds only marks its type takes; text beside it keeps the mark |
| a link around only nodes that cannot take it, hard breaks aside, or a directive mark around only such nodes or code spans (`[!adf:status[Done]{color=green}](/u)`) | the nodes hold the mark | `unsupported-node-shape`; write the mark around text beside them |
| a code span inside any mark's spelling but a link's (``**`npm ci`**``), in every reader | the code text holds the mark | the code text holds only `code` and the link |
| a code span inside any mark's spelling but a link's (``**`npm ci`**``) | the code text holds the mark | the code text holds only `code`, and a link where one wraps it |
`0.2.0` wrote these spellings for a node holding such a mark; `0.3.0` writes the node as
`!adf:carry{json="…"}`. `0.3.0` reads markdown stored from such a document without the mark, or
refuses it where the mark wrapped such nodes alone. Written back, a space beside such a node inside
the mark's spelling becomes `!adf:text{text=" "}`: ``**Run `npm ci` first**`` writes back as
``**Run!adf:text{text=" "}**`npm ci`**!adf:text{text=" "}first**``. A kept mark is one Atlassian's
schema withholds, so a site may refuse the document: keep one only in markdown written from ADF that
held it. To keep the mark, read the markdown with `0.2.0` and write it with `0.3.0`, installed side
by side:
`0.3.0`'s `adfToLosslessMarkdown` writes a node holding a withheld mark as `!adf:carry{json="…"}`,
where `0.2.0` wrote these spellings. `0.3.0` reads markdown stored from such a document without the
mark, and refuses it where the mark wrapped only those nodes. Written back, a space beside the node
inside the mark's spelling becomes `!adf:text{text=" "}`: ``**Run `npm ci` first**`` writes back as
``**Run!adf:text{text=" "}**`npm ci`**!adf:text{text=" "}first**``. Keep the mark only in markdown
written from ADF that held it: Atlassian's schema withholds it, so a site may refuse the document.
To keep it, read the markdown with `0.2.0` and write it with `0.3.0`, installed side by side:
```sh
npm install @larvit/adf-codec@0.3.0 adf-codec-0.2@npm:@larvit/adf-codec@0.2.0
+3 -3
View File
@@ -391,7 +391,7 @@ saving what this pair read replaces mentions, attachments and macros with text.
`_(synced block not included)_` for a `syncBlock`.
- `backgroundColor`, `code`, `em`, `link`, `strike` and `strong` stay; every other mark drops,
keeping its text, and so does a mark the flavour cannot spell where it stands. Code text keeps
only `link`.
no mark but `code` and `link`.
- A newline in text is a hard break and in an expand's title a space, edge whitespace outside a
link or code span is trimmed, carriage returns and null characters are removed, and an empty
paragraph drops.
@@ -536,8 +536,8 @@ Serves [Goals](#goals) 1, 4 and 5.
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.
- A code span inside emphasis reads as code alone, since ADF gives code text no mark but a link
or an annotation: ``**`npm ci`**`` reads as `` `npm ci` ``.
- 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. 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
+3 -3
View File
@@ -259,11 +259,11 @@ Their CommonMark suite exceptions are `node-model` (§The CommonMark suite check
## A mark skips a node that cannot hold it, and a mark wrapping only such nodes is refused, but for a link around hard breaks alone
2026-10-14, two reader panels of the viewer/editor app, the LLM pipeline and a support agent, 3 of
3 each. Goals 1, 4 and 6. Valid while Atlassian's schema withholds marks from inline nodes. Code
text follows by this entry's rule, unpanelled, 2026-10-14.
3 each. Goals 1, 4 and 6. Valid while Atlassian's schema withholds marks from inline nodes. Extended
to code text under this rule, without a panel, 2026-10-14.
A mark spelling skips a node that takes none of its type, code text included, which takes only
`link` and `annotation`. So `**Ping !adf:mention[@A]{id=x} now**` reads as bold text around a plain
`code`, `link` and `annotation`. So `**Ping !adf:mention[@A]{id=x} now**` reads as bold text around a plain
mention, and ``**`npm ci`**`` as plain code. A link whose text holds something besides hard breaks,
but no node that takes a link, is `unsupported-node-shape` in `losslessMarkdownToAdf`: the panel
refused to have `[!adf:status[Done]{color=green}](/u)` read as `/u` linked beside the status. A link
+3 -3
View File
@@ -549,9 +549,9 @@ A block's `marks` key also holds these, which no inline spelling wraps:
- `fragment` — Attributes: `localId` (string), `name` (string).
- `indentation` — Attributes: `level` (number, 1–6).
A spelling adds its mark to every text node it wraps but one marked `code`, which takes only
`link`, and to every other inline node Atlassian's schema gives that mark: ``**`npm ci`**`` reads
as plain code. Of the marks a spelling makes, `date`, `emoji`, `hardBreak`, `inlineCard`, `mention`
A spelling adds its mark to every inline node it wraps that Atlassian's schema gives the mark. Text
takes every mark, except text marked `code`, which takes only `link`: ``**`npm ci`**`` reads as
plain code. Of the marks a spelling makes, `date`, `emoji`, `hardBreak`, `inlineCard`, `mention`
and `status` take none, and `mediaInline` takes only `border` and `link`. Nesting is the
marks array in order, outermost first: `_!adf:underline[x]_` gives marks `[em, underline]`,
`!adf:underline[_x_]` the reverse. `adfToLosslessMarkdown` nests in the order the array holds rather than sorting it — `docs/decisions.md` §Equality is deep restores
+1 -1
View File
@@ -37,7 +37,7 @@ export function inlineNodeModel(type: string): InlineNodeModel | undefined {
return Object.hasOwn(inlineNodes, type) ? inlineNodes[type] : undefined
}
// Undefined where the schema withholds no mark: the table lists no such type, text among them, but code text takes only `codeTextMarkTypes`.
// Undefined means every mark: the table lists only types the schema restricts, and it restricts text only when marked code.
export function takenMarkTypes(node: AdfNode): readonly string[] | undefined {
if (node.type === 'text') return nodeMarks(node).some((mark) => mark.type === 'code') ? codeTextMarkTypes : undefined
return inlineNodeModel(node.type)?.markTypes
+1 -1
View File
@@ -99,7 +99,7 @@ function emitLine(nodes: readonly AdfNode[], container: LineContainer, path: Con
}
}
// Seeds the withheld-mark carries once per line, so no run at any depth reads a node's marks whole.
// Seeds the withheld-mark carries once per line, so the run at each mark depth does not rescan every node's marks.
function freshFallbacks(nodes: readonly AdfNode[], flavour: WrittenFlavour): LineFallbacks {
const carried = new Set<number>()
for (const [index, node] of nodes.entries()) {
+20 -11
View File
@@ -6,7 +6,7 @@
`Bar = 9`
`Next ID = 116`
`Next ID = 117`
| Goal | W |
|---|---|
@@ -27,7 +27,7 @@
|---|---|---|---|---|---|---|---|---|---|---|
| 73 | 0.3.0 | | **Announce the package where someone needing an ADF converter already reads: JRACLOUD-77436, the Atlassian developer community and Stack Overflow's ADF-to-markdown questions.** | 1 | 2 | 1 | 6 | 8 | 3 | 23.0 |
| 99 | 0.3.0 | defect | **Write a linked external image standing inline or in a table cell in the spelling a reader panel picks, in `adfToPortableMarkdown`.** | 3 | 3 | 5 | 6 | 7 | 7 | 12.9 |
| 115 | 0.3.0 | defect | **Build text holding no `border` mark in every reader, and have `adfToLosslessMarkdown` carry text holding one.** | 3 | 3 | 3 | 4 | 6 | 4 | 12.3 |
| 115 | 0.3.0 | defect | **Build text holding no `border` mark in `losslessMarkdownToAdf`, and have `adfToLosslessMarkdown` carry text holding one.** | 3 | 3 | 3 | 4 | 6 | 4 | 12.3 |
| 100 | 0.3.0 | 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 | 3 | 4 | 5 | 4, 6 | 10.9 |
| 60 | 0.3.0 | decision | **Collect the questions `parse/` asks `emit/` into one named module.** | 3 | 4 | 4 | 2 | 6 | 2 | 10.7 |
| 59 | 0.3.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 | 3 | 2 | 6 | 2 | 10.4 |
@@ -37,6 +37,7 @@
| 33 | 0.3.0 | defect | **Emit a line in time linear in its mark runs, in `adfToLosslessMarkdown` and `adfToPortableMarkdown`.** | 4 | 5 | 8 | 6 | 9 | 9 | 8.7 |
| 98 | 0.3.0 | defect | **Read blank lines inside deeply nested list items in time linear in the markdown.** | 3 | 3 | 7 | 5 | 9 | 9 | 8.2 |
| 102 | 0.3.0 | decision | **Place a block a portable task list refuses at its path in the returned document without replaying `splitTaskList` on stand-in nodes.** | 3 | 4 | 7 | 2 | 4 | 1 | 8.0 |
| 116 | 0.3.0 | question | **Retitle `docs/decisions.md` §A mark skips a node that cannot hold it to say only a link or directive mark wrapping only such nodes is refused.** | 1 | 1 | 1 | 2 | 3 | 6 | 5.4 |
| 109 | 0.3.0 | defect | **Spell nested lists and quotes in time linear in the markdown.** | 3 | 5 | 8 | 4 | 7 | 9 | 5.3 |
| 7 | 0.4.0 | decision | **Ship HTML: `adfToHtml`, `htmlToAdf`, and markdown to and from HTML composed through ADF, each call named by its flavour.** | 6 | 9 | 9 | 9 | 9 | 2, 4 | 25.8 |
| 45 | 0.4.0 | | **Replace `isAdfDocument` with a reader returning `Result<AdfDocument>`.** | 2 | 3 | 4 | 6 | 8 | 1 | 25.2 |
@@ -49,7 +50,7 @@
| 112 | 0.4.0 | | **Pad a GFM pipe table's short rows with empty cells, read its body rows holding no pipe, and read a one-column table without leading pipes in `portableMarkdownToAdf`.** | 3 | 3 | 5 | 6 | 6 | 4 | 16.3 |
| 46 | 0.4.0 | | **Publish the bundle size in the README, failing the release pipeline when it drifts.** | 2 | 4 | 3 | 5 | 5 | 3, 8 | 14.8 |
| 72 | 0.4.0 | decision | **Re-create `gitea.larvit.se/larvit/adf-codec` as a read-only pull mirror of GitHub, its issues and Actions off and its description pointing at GitHub.** | 2 | 1 | 1 | 3 | 6 | 3 | 13.1 |
| 91 | 0.4.0 | | **Spell an emphasis or link once across the code spans, hard breaks, dates, emoji, inline cards, mentions and statuses between two nodes it marks, and an emphasis across inline media, in both writers.** | 5 | 5 | 8 | 6 | 6 | 6 | 11.0 |
| 91 | 0.4.0 | | **Spell an emphasis once across the code spans, an emphasis or link once across the hard breaks, dates, emoji, inline cards, mentions and statuses between two nodes it marks, and an emphasis across inline media, in both writers.** | 5 | 5 | 8 | 6 | 6 | 6 | 11.0 |
| 107 | 0.4.0 | decision | **Spell the block carry's JSON so its length stays linear in the node's JSON.** | 4 | 5 | 7 | 3 | 5 | 1 | 10.8 |
| 51 | 0.4.0 | | **Match a reference label to its definition under Unicode case folding.** | 2 | 4 | 5 | 2 | 7 | 4, 5 | 10.4 |
| 95 | 0.4.0 | decision | **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.** | 5 | 6 | 9 | 2 | 6 | 1 | 9.5 |
@@ -85,13 +86,13 @@ there reads back as `[alt](href)`, breaking the byte-for-byte rewrite, so only o
`href` can survive; a reader panel picks which. If it picks `url`, the README names the dropped
`href` where it says link targets stay.
### 115. Build text holding no `border` mark in every reader, and have `adfToLosslessMarkdown` carry text holding one.
### 115. Build text holding no `border` mark in `losslessMarkdownToAdf`, and have `adfToLosslessMarkdown` carry text holding one.
`!adf:border[x]{color="#091e42" size=2}` builds text holding `border`, which
`formatted_text_inline_node` (`spec/adf-schema/full.json`) withholds from text: only `media` and
`mediaInline` take it, so a site may reject the document. `takesMark` (`src/adf/inline-nodes.ts`)
gives text every mark but code text's. It changes how stored markdown reads, so it ships in a minor.
Found while building item 114, 2026-10-14.
`mediaInline` take it, so a site may reject the document. `takenMarkTypes`
(`src/adf/inline-nodes.ts`) restricts text only when it is marked `code`. It changes how stored
markdown reads, so it ships in a minor. Found while building item 114, 2026-10-14.
### 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.
@@ -164,6 +165,14 @@ the fix.
`taskOfParagraphs` keeps the original child objects. Both paired-ruling seats on 2026-10-11 named it
the reason the project read worse.
### 116. Retitle `docs/decisions.md` §A mark skips a node that cannot hold it to say only a link or directive mark wrapping only such nodes is refused.
The title says "a mark wrapping only such nodes is refused", but emphasis wrapping only a mention or
a code span reads as the plain node, as the entry's body says. Proposed title: "A mark skips a node
that cannot hold it, and a link or directive mark wrapping only such nodes is refused, but for a link
around hard breaks alone", with `AGENTS.md`'s index line. Found by the prose review of item 114,
2026-10-14.
### 109. Spell nested lists and quotes in time linear in the markdown.
Each level of a nested list or quote re-indents its whole text, so the work is the depth times the
@@ -271,14 +280,14 @@ Gitea copy holds no issues or releases to move. Actions stay off on the mirror:
otherwise run there. When this lands, the `docs/decisions.md` entry "GitHub is canonical" loses its
interim sentence.
### 91. Spell an emphasis or link once across the code spans, hard breaks, dates, emoji, inline cards, mentions and statuses between two nodes it marks, and an emphasis across inline media, in both writers.
### 91. Spell an emphasis once across the code spans, an emphasis or link once across the hard breaks, dates, emoji, inline cards, mentions and statuses between two nodes it marks, and an emphasis across inline media, in both writers.
Lands after item 95, which lets the joined spelling be tried and kept only where it reads back. No
reader marks a hard break, so `*One\` then `Two*` writes back as `_One_\` then `_Two_`, and a link
typed across a line as two links. No reader gives those other nodes a mark Atlassian's schema
withholds from them, and none gives code text a mark but a link, so `**Ping !adf:mention[@A]{id=x}
now**` and ``**Run `npm ci` first**`` write back with the space beside the mention or the code in
each strong run spelled `!adf:text{text=" "}`. Joining the run across the break in
withholds from them, and none gives code text a mark but a link. So `**Ping !adf:mention[@A]{id=x}
now**` and ``**Run `npm ci` first**`` each write back as two strong runs, with the space beside the
mention or code spelled `!adf:text{text=" "}`. Joining the run across the break in
`inline-line.ts` ruled worse with two comprehension panels (PR #22): a joined run that cannot be spelled needs its own fallback,
first as a set of parted breaks, then as a flag the first fallback clears.