415 lines
36 KiB
Markdown
415 lines
36 KiB
Markdown
# Todo history
|
|
|
|
The done `todo.md` items in full, as they were written. `todo.md` keeps a one-line summary of each.
|
|
|
|
## Milestones
|
|
|
|
- [x] **0 — Scaffold.** `package.json` per §6, `tsconfig.json`, `.npmrc` (`save-exact=true`), the
|
|
Docker tooling, `renovate.json` (§9), and `.gitea/workflows/ci.yml` gating branches:
|
|
`runs-on: docker-host`, actions pinned to semver tags.
|
|
- [x] **1a — The directive grammar** (`spec/flavour.md`): inline/block/leaf directive forms,
|
|
attributes, escaping, nesting, canonical form, the opaque-carry spelling, the raw-HTML
|
|
input policy.
|
|
- [x] **1b — Block node syntaxes** in `spec/flavour.md`: panel, expand/nestedExpand, the media
|
|
family, the pipe-vs-directive table rule and the directive table form, task and decision
|
|
lists, layout, extensions, syncBlock.
|
|
- [x] **1c — Inline node syntaxes and marks** in `spec/flavour.md`: mention, emoji, status, date,
|
|
inlineCard, mediaInline; underline, subsup, textColor, border; the spelling for text nodes
|
|
whose whitespace CommonMark cannot hold (literal newlines, leading or trailing spaces) —
|
|
escape-based, never literal, since pipe cells trim and pad. At `mediaInline`, check real
|
|
payloads for external-URL support — if it exists, revisit the media section's
|
|
mid-text-image error and its "no slot" ground.
|
|
- [x] **1d — Corpus start** (§10): checked-in fixtures per spec'd node, in `corpus/`, one
|
|
directory per contract kind (`corpus/README.md`).
|
|
**Settled** (the maintainer, 2026-08-26): the nodes CommonMark spells get directive sections
|
|
of their own, rather than riding the opaque carry. Per `@atlaskit/adf-schema` 57.1.0 every
|
|
block node it spells — `blockquote`, `bulletList`, `codeBlock`, `heading`, `listItem`,
|
|
`orderedList`, `paragraph`, `rule` — carries a `localId` with no spelling, `codeBlock` also
|
|
`hideLineNumbers`, `uniqueId` and `wrap`, `blockquote` also marks, and `hardBreak` `text`
|
|
and `localId`; 2f gives each a place, and the plain spelling stays wherever the attributes
|
|
are absent. That hands the two collision sites in `corpus/unspellable/` the second spelling
|
|
they lacked, so each takes the directive form as a `media` with an empty `alt` already does
|
|
(`spec/flavour.md`, the CommonMark image): a `codeBlock` whose info string is empty, and an
|
|
`orderedList` starting at 1.
|
|
**Also blocked**: the link rule covers destination spaces only, so two shapes
|
|
have no spelling and are refused meanwhile — href `https://example.com/a)b` and title
|
|
`He said "hi"`, both in `corpus/unspellable/`. Two defensible spellings each — angle
|
|
brackets or a backslash escape, and for titles `'…'` or `(…)` besides — so §8 leaves
|
|
the pick here. **Also blocked**: block separation is unstated for a CommonMark block beside a
|
|
directive block in a container body — an `expand` whose content is `paragraph` "A" then a
|
|
`panel` (`panelType` `warning`) holding "B" spells `A` and `:::panel warning` either on
|
|
consecutive lines or with a blank line between. Two defensible spellings, so §8 leaves the
|
|
pick here; the answer governs every unknown node type too, the block carry counting as a
|
|
CommonMark block since its spelling is a fenced code block.
|
|
`unspelled-block-separation` refuses the pair meanwhile, an empty paragraph's
|
|
`::paragraph` beside a CommonMark block included — and, since a `mediaSingle`'s spelling now
|
|
follows whether CommonMark can spell its URL, two sibling images differing only by an
|
|
`&` land in the same refusal.
|
|
- [x] **1d1 — The CommonMark subset**: blockquote, bulletList, codeBlock, heading, orderedList,
|
|
paragraph, rule, listItem, hardBreak, text, code spans, and the `code`, `em`, `link`,
|
|
`strike` and `strong` marks — one mark per text node; nesting is 1d3's.
|
|
- [x] **1d2 — Block nodes**: panel, expand/nestedExpand, the media family and the CommonMark
|
|
image shape, both table forms, task and decision lists, layout, extensions, syncBlock —
|
|
with the reserved `marks` attribute and the fence lengths nesting forces.
|
|
- [x] **1d3 — Inline nodes and marks**: date, emoji, inlineCard, mediaInline, mention, status;
|
|
border, subsup, textColor, underline; the content slot's `text` attribute and the
|
|
`:text{text="…"}` whitespace spelling.
|
|
- [x] **2 — `adfToMarkdown`.** First real code. Each sub-item turns one corpus directory green;
|
|
the two that have no fixtures yet write them in the same chunk, tests first (§10).
|
|
- [x] **2a — The runner and the CommonMark subset.** The corpus runner: walk
|
|
`corpus/round-trip/`, assert `adfToMarkdown` emits each `.md` byte for byte. Decide here
|
|
where §10's coverage check lives, and gate that every `corpus/**/*.json` re-serializes to
|
|
itself under the library's own canonical serializer — one implementation, keys sorted, two
|
|
spellings: two-space indent for the corpus files and the block carry's body, compact for
|
|
the inline carry. `commonmark-subset/` green.
|
|
- [x] **2b — Block nodes.** `block-nodes/` green. A nested list that cannot interrupt the block
|
|
above it is refused meanwhile, not spelled: the maintainer's answer on tight-versus-blank
|
|
separation turns that refusal into an emission. The test is broader than the name it
|
|
carries — `interruptsParagraph` reads the next list alone, so a list after a block no
|
|
paragraph continues, a code block say, is refused too — and the same answer narrows it.
|
|
Block separation becomes
|
|
`separationBetween(previous, next, container)` here — a boolean cannot hold the third case
|
|
`spec/flavour.md` states for two directive blocks in a container body, and the maintainer's
|
|
answer on a CommonMark block beside a directive block (1d) drops into the same seam. Give
|
|
the emitter's refusals a corpus home while the directories grow: `corpus/unspellable/`,
|
|
a `.json` beside the `ConvertErrorCode` it must return, the emitter half of `corpus/errors/`.
|
|
- [x] **2c — Inline nodes and marks.** `inline-nodes/` green. `InlineSegment` splits into its
|
|
two axes — escapability (`backslash`, `bracketed`, `none`) and the emphasis role. A lone
|
|
surrogate in a text node emits verbatim and becomes U+FFFD on any UTF-8 encode, a §2 break
|
|
plain text still holds open — attribute values already escape it. The pipe form's fallback
|
|
reads the emitted segments rather than naming the nodes whose attribute values spell a pipe
|
|
as syntax, so 2e's `\u007c` narrows it in one place.
|
|
- [x] **2d — The opaque carry** (§3). Fixtures and emitter together, into
|
|
`corpus/round-trip/opaque-carry/`: an unknown node in both positions, the reserved `adf`
|
|
info string, and the `codeBlock` whose language is `adf` — carried whole ahead of the
|
|
attribute fallback 2e owes, since the reservation leaves that node no other spelling
|
|
whatever 1d decides for its `localId`.
|
|
- [x] **2e — Carve-outs and combinations.** Fixtures and emitter together, into
|
|
`corpus/round-trip/combinations/`.
|
|
- [x] **2e1 — The carve-outs and the claimed line.** The three carve-outs
|
|
and their escapes, and a paragraph line inside a container body shaped like a closing
|
|
fence (`:::`, `::: x`). Guard `fenceNestingFault`'s bare-run pop here too — a run shorter
|
|
than the open fence is a fault, not a close — which today's emitter cannot reach.
|
|
- [x] **2e2 — Mark runs and the runs a carry breaks.** The longest-run rule, attributes
|
|
included, and a mark spelling that cannot open where it sits (`un**-real**istic`; the spec
|
|
owes the carry a trigger). One mark vocabulary lands here, before 2e3 changes the
|
|
attribute spelling: `emphasisSpellings`, `linkAttributes` and the `code`/`link` names join
|
|
the mark table (3a parted it across `adf/mark-attributes.ts` and
|
|
`markdown/mark-spellings.ts`), which holds four of the nine marks while the rest are
|
|
branch literals in the emitter — and the parser (3) needs every name to make `:em[x]`
|
|
the named error `spec/flavour.md` promises.
|
|
- [x] **2e3 — Attribute canonicalization and the quoted value's escape.**
|
|
**Settled** (the maintainer, 2026-08-26): a quoted attribute value escapes `` ` ``, `&`,
|
|
`<` and `|` as `\u0060`, `\u0026`, `\u003c` and `\u007c`, in every directive, block and
|
|
inline alike — the constructs those four open all bind at or before a directive does, and
|
|
nothing else reaches into `{attrs}`. Emitted attributes being inert leaves 3 free to keep
|
|
CommonMark's own precedence between a directive and a code span, and collapsed
|
|
`escaping: 'attribute'` into `none`. The escaper's link-opener scan skips emitted syntax
|
|
to match: a `](` inside a directive escapes no text `[`.
|
|
- [x] **2e4 — The carry's fallback triggers.** `spec/flavour.md` carries a node its section
|
|
cannot spell — an attrs key no section lists, a value that is not the section's type, an
|
|
arg slot holding no bare token, marks no nesting spells — where the emitter still refuses,
|
|
which leaves the refusals a container's own spelling owns. The flanking trigger 2e2
|
|
added to that list is the odd one out: `unspellableMark` finds it after assembly and
|
|
names a mark type against the line's path, so the failing run needs identifying before
|
|
the carry can replace the refusal `mark-inside-word` pinned.
|
|
- [x] **2e5 — Combined documents and the collision property.** Documents combining nodes rather
|
|
than isolating one, and the gate's collision property: no two corpus documents may emit
|
|
the same bytes — one spelling for two documents is a round-trip break no parser can undo,
|
|
and it is provable without one.
|
|
**Settled** (the maintainer, 2026-08-27): the approximation this item inherited — flanking
|
|
exact, CommonMark's *matching* unmodelled — had two round-trip breaks reachable by hand,
|
|
so the emitter now models the matching. `process_emphasis` runs over the runs the emitter
|
|
wrote (`emphasis-matching.ts`) and a pair it hands to another delimiter rides the carry,
|
|
which is what the multiple-of-3 rule did to the em in `un*a**b*****c**istic`. A delimiter
|
|
run in text now escapes wherever CommonMark could open or close with it, not only open:
|
|
one that could only close stole the spelling around it (`un*a* b*istic`), and escaping
|
|
both ways keeps every delimiter the emitter did not write out of the matching. The
|
|
canonical form gained a backslash where a run only closes — `\*not emphasis\*`, and
|
|
2e1's `carve-out-strike` a third and fourth.
|
|
- [x] **2f — The attributes CommonMark cannot hold.** 1d's settled answer: the block nodes
|
|
CommonMark spells — `blockquote`, `bulletList`, `codeBlock`, `heading`, `listItem`,
|
|
`orderedList`, `paragraph`, `rule` — get directive sections in `spec/flavour.md` carrying
|
|
`localId`, `codeBlock`'s `hideLineNumbers`, `uniqueId` and `wrap`, and `blockquote`'s
|
|
marks, while `hardBreak`'s `text` and `localId` join the inline directive it already has.
|
|
The plain spelling stays wherever those attributes are absent, so only a node that carries
|
|
one takes the directive form — which is what keeps a real payload readable rather than a
|
|
page of carried JSON. Fixtures and emitter together, and the three documents the answer
|
|
settles leave `corpus/unspellable/` as round-trip pairs: `block-local-id`,
|
|
`code-block-empty-language`, `ordered-list-start-one`.
|
|
**Settled** (the maintainer, 2026-08-27): the `codeBlock` directive's body is one fenced
|
|
code block, the language staying on the fence line so every renderer still highlights it;
|
|
a language no info string holds — empty, a backtick, edge whitespace, an entity reference
|
|
or the reserved `adf` — rides the `language` attribute with the fence bare, which retires
|
|
2d's carry for the reserved name along with the premise that left it no other spelling.
|
|
The plain spelling gives way wherever it cannot render what the node carries rather than
|
|
only where it has no place for it, so a heading level absent or outside 1-6 and an order
|
|
whose markers would run past 999999999 take the directive form too, and
|
|
`ambiguous-attribute-spelling`, `unspellable-code-block-language`,
|
|
`unspellable-list-marker`, `unspelled-block-marks` and `unsupported-heading-level` leave
|
|
`ConvertErrorCode`; content and placement refusals stay, which leaves the directive form
|
|
spelling an empty list or a non-`listItem` child that the plain form refuses. `order` is
|
|
the first marker, so `order: 1` keeps the plain `1.` — what a real payload carries — and a
|
|
list carrying no `order` has no number to take and takes the directive form.
|
|
2f raises what 1d's unspelled block separation costs: a single `localId` on a paragraph
|
|
beside a plain one now refuses every container body that is a directive's — a panel, an
|
|
expand, a table cell — where before 2f the attribute refused the document anyway.
|
|
|
|
Under **3 — `markdownToAdf` (`0.1.0`)**:
|
|
|
|
- [x] **3a — The hierarchy.** Mechanical, ahead of the first parser file: `src/adf/` and
|
|
`src/markdown/` (`html/` arrives with its first file, 6-7), the grammar module shared
|
|
inside `markdown/`, and `emphasis-matching.ts` beside it — the parser reuses it whole,
|
|
`delimiterFlags` and `matchEmphasis` taking CommonMark's own run vocabulary rather than
|
|
the emitter's, so no second `process_emphasis` exists to drift from the first.
|
|
`block-directives.ts` and `inline-directives.ts` each part by file, a node table
|
|
milestones 6-7 need in `adf/` beside a markdown spelling that belongs in `markdown/`.
|
|
`directive-attributes.ts` cannot: `vocabularyPairs` walks the vocabulary and spells the
|
|
value in one pass, the type check living inside `spellAttributeValue`, so the check comes
|
|
out as its own predicate and goes to `adf/` with the walk while the spelling stays in
|
|
`markdown/`, `isBareToken` with it — only spelling calls it. `markSpellings` is the one
|
|
table whose keys part rather than its file, so key the markdown half off the ADF half's
|
|
type: a mark named in one and not the other is then a compile error instead of a false
|
|
refusal. `spellDestination`, `spellTitle` and `balanced` leave `markdown-inline.ts` here
|
|
too — CommonMark destination spelling `emitLink` and `tryImageLine` share, and the six
|
|
concerns that file carries are one fewer for it. `AttributeKind` and `AttributeVocabulary`
|
|
follow the walk into `adf/`, the vocabulary a string-typed attribute grammar needs and
|
|
HTML will want too, not a markdown spelling.
|
|
**Settled** (the maintainer, 2026-08-27): `markdown/` parts here as well, into `emit/` and
|
|
`parse/` with the shared set at the root — the grammar module, emphasis matching,
|
|
the tables' markdown halves — and `parse/` arriving with 3b's first
|
|
file, the rule `html/` already follows. And the node tables, a second copy of
|
|
`spec/flavour.md`'s prose whose mistyped attribute name degrades into a false refusal no
|
|
test catches, get their guard: a test reads the spec's node sections, takes each
|
|
`name (type)` list and asserts it equals the table, leaving the spec the source a human
|
|
writes with no build step and no generated file. It is built at 3g, where a wrong entry
|
|
starts refusing documents.
|
|
- [x] **3b — The leaf blocks.** The line walk that opens and closes a block, ahead of any inline
|
|
parsing: paragraph, ATX and setext heading, thematic break, fenced and indented code
|
|
block, the HTML block whose lines it swallows whether or not the construct then errors,
|
|
the link reference definitions a closing paragraph gives up, and the blank lines between
|
|
them. The openers are `commonmark-grammar.ts`'s — one table answers both directions, or
|
|
the emitter under-escapes a line the parser reads as a block and §2 breaks in silence —
|
|
the HTML block's start conditions excepted, which are new here since the emitter writes
|
|
none. Block-level claiming lands here too: a colon run or an unescaped leading `|` is
|
|
claimed, the parse behind it 3f's and 3h's, a claim with nothing yet to parse it the named
|
|
error the claim promises meanwhile. The runner's parse half comes with it, and the first
|
|
`normalization/` fixtures, holding inline-trivial content so 3d and 3e add beside them
|
|
rather than editing them.
|
|
- [x] **3c — The container blocks.** Blockquote, bullet and ordered list: the continuation a
|
|
marker's width sets, lazy continuation, and the tightness ADF does not record — `> `
|
|
repeated being two bytes a level, so this is the cheapest way to reach §11's 500. 3b's leaf
|
|
readers scan the physical line themselves, so a container re-cuts the walk rather than adding
|
|
to it: the open containers' prefix comes off the line first and the readers take one line at a
|
|
time, `LeafBlock` renamed with the union they join and `blockNode`'s chain gaining their
|
|
branches.
|
|
**Settled** (the maintainer, 2026-08-27): a claimed line ends lazy continuation, so a
|
|
closing fence on the line after a blockquote's open paragraph closes its container instead
|
|
of continuing the paragraph CommonMark would fold it into. Claiming at block level is
|
|
already absolute, and this binds input alone — 2e1's `closing-fence-line` orders the
|
|
emitter's blockquote away from the edge either way — so `spec/flavour.md`'s claiming
|
|
paragraph gains the case here. And 2b's tight-versus-blank, one answer for both
|
|
directions: the tight spelling stays wherever it parses back, a blank line going in only
|
|
where the nested list would be swallowed — `interruptsParagraph` inverted from a refusal
|
|
into the separation it names, and `spec/flavour.md`'s "none between a nested list and a
|
|
CommonMark block above it" gaining that exception. Every fixture spelled tight today keeps
|
|
its bytes, and `nested-list-tight` becomes the round-trip pair `nested-list-separation`.
|
|
- [x] **3d — Inline text.** The inline scanner over a block's content: backslash escapes, entity
|
|
references decoding to their characters, code spans and the literal they hold — directive
|
|
syntax and `~~` included — CommonMark's own hard breaks, a trailing backslash and two
|
|
trailing spaces alike, a soft line break as one space, the fenced info string's own decoding
|
|
the block walk leaves raw, and the raw inline tag, comment and processing instruction
|
|
refused by name, recognized by the `commonmark-grammar.ts` predicates the emitter already
|
|
escapes against, under 3b's one-table rule.
|
|
**Settled** (the maintainer, 2026-08-30): entity references decode against HTML5's whole
|
|
named table, checked in packed (§5) — a curated subset leaves 3k an exception class and a
|
|
cutoff line nobody can defend. And the escape superset the emitter reads for raw HTML
|
|
tightens into one precise CommonMark inline reader both directions share, the email
|
|
autolink parting off as 3e's own predicate: refusing on the superset would refuse
|
|
`1 <b 2`, a fourth carve-out §4 and the README do not list. `holdsEntityReference` reads
|
|
the table for the same reason, so `¬areference;` is emitted bare.
|
|
- [x] **3e — Emphasis and links.** `_`, `*` and `~~` runs through `matchEmphasis` to the `em`,
|
|
`strong` and `strike` marks; links inline and reference, 3b's definitions resolved here,
|
|
autolinks, and the image gap's named errors — a titled image, and one amid other text.
|
|
`spec/flavour.md` does not yet pin `~`'s `can_open`/`can_close`, which is transcription
|
|
rather than a decision: `delimiterFlags` already gives it CommonMark flanking, as for `*`,
|
|
and §8 fixed that the moment the emitter shipped.
|
|
**Settled** (the maintainer, 2026-08-27): 1d's deferred pair takes the backslash inside
|
|
the delimiters it already has — `[a](https://example.com/a\)b)` and
|
|
`[a](/url "He said \"hi\"")`. `<…>` stays reserved for the destination holding a space,
|
|
where nothing else works, so each construct keeps one spelling and a destination holding
|
|
both composes. `link-destination-parenthesis` and `link-title-quote` become round-trip
|
|
pairs.
|
|
**Settled** (the maintainer, 2026-08-31): the destination escapes only the parenthesis it
|
|
leaves unbalanced, so `/wiki/Foo_(bar)` keeps its bytes, and the backslash stays refused in
|
|
both the destination and the title — which leaves `[a](/a\b)` a §2 hole 3k's exception list
|
|
answers, as `<http://x?a=1&b=2>` is, autolinks decoding neither escapes nor references.
|
|
The image gap mints `unmappable-image`, mirroring `unmappable-html` — a construct in input
|
|
no ADF node carries. And the CommonMark image shape lands here rather than at 3h: once
|
|
`[…](…)` reads, a lone `` would otherwise misparse as text plus a link, so 3h
|
|
keeps the rest of the media family and loses only that line.
|
|
**Settled** (the maintainer, 2026-08-31, on the review): an empty link text — `[](/u)` —
|
|
leaves the brackets the text they are rather than minting a refusal or dropping the
|
|
destination, giving the label back the way an unresolved pair does, so the shortcut behind
|
|
`[][r]` still reads. A description holding an image flattens to that image's own alt, which
|
|
is what alt text means and what keeps the documented gap to mid-text and titled images; a
|
|
break of either kind inside one reads as a space. And a destination or title whose entity
|
|
reference decodes to a control character — `[a](/x y)` — joins 3k's exception list
|
|
beside the two above: the reader takes cmark's reading, the emitter has no spelling for it.
|
|
- [x] **3f — The directive grammar.** The three forms — inline `:name[content]{attrs}`,
|
|
container `:::name arg {attrs}`, leaf `::name arg {attrs}` — the attribute grammar with
|
|
its quoting and escapes, the fence-length and nesting rules, and the malformed list
|
|
`spec/flavour.md` spells, each a named error. `corpus.test.ts`'s `fenceNestingFault` stays a
|
|
second reading of the fence rule over emitted bytes: the double entry is the check.
|
|
**Settled** (the maintainer, 2026-08-27): the code span, the entity and raw HTML bind
|
|
first in input, as 2e3 already assumed of the emitted side — a raw `` ` ``, `&`, `<` or
|
|
`|` inside `{attrs}` breaks the directive and is a named error, the author writing the
|
|
`\u0060` the emitter writes. One precedence covers both directions, and CommonMark's own
|
|
ordering stays untouched.
|
|
**Settled** (the maintainer, 2026-09-01): a directive whose name reads back to no node
|
|
takes its own code, `unknown-directive-name` — a well-formed spelling the vocabulary does
|
|
not hold is not a malformed one, and §8's "erroring input gaining meaning later is MINOR"
|
|
is what a consumer switches the two apart for. And input reads canonical spacing only: one
|
|
space parting the name, the argument, `{attrs}` and each attribute pair, no padding inside
|
|
the braces, trailing whitespace on a directive block line tolerated — §8 makes loosening a
|
|
MINOR, so strict is the reversible direction. `directive-attributes.ts` becomes
|
|
`directive-syntax.ts` with the readers in it: the whole directive grammar, both
|
|
directions, beside the escaping regexes and the spellings it must not drift from. And the
|
|
500-level guards compose here for the first time — a recursive reader stacked on the block
|
|
walk — so `nesting-depth-composed` pins both axes now rather than waiting for 3i's third.
|
|
A closing fence closes the innermost open container however long its run, which
|
|
`spec/flavour.md`'s closing-fence sentence now says: a run reaching past the innermost
|
|
leaves the fence it did not close a named error, which §2 prefers to closing more than the
|
|
author wrote.
|
|
- [x] **3g — The node tables read backwards.** `commonmark-subset/` reads back, the first
|
|
directory to. A parsed directive becomes its node: the name to the type and an unknown one
|
|
to a named error, the arg to the attribute it names, each value to the type its section
|
|
assigns, the body to `content`, the reserved `marks` key to the marks array. 3a's drift
|
|
guard is built here if the answer there was yes.
|
|
3f leaves two here: `Read<T>` moves to `src/result.ts` once a second reader takes it, and
|
|
the reserved `adf` name in block position needs an error of its own — 3f reports it as
|
|
`unknown-directive-name`, which §8 makes the signal that a later MINOR may give the name
|
|
meaning, and `adf` never will.
|
|
**Settled** (the maintainer, 2026-09-01): the reserved `adf` name in block position is a
|
|
`malformed-directive` — the grammar section states the reservation, so it is that spelling
|
|
the name breaks — and a well-formed directive the tables refuse is `unsupported-node-shape`,
|
|
the emitter's code for the same mismatch read the other way; AGENTS.md §8 carries the
|
|
split. And input reads canonical `{attrs}` alone, keys in order and every value spelled as
|
|
the emitter spells it, the error naming the spelling to write instead: §8 makes loosening a
|
|
MINOR, so strict is the reversible direction, as 3f already settled for spacing.
|
|
**Settled** (the maintainer, 2026-09-01, on the review): 2f's plain-versus-directive
|
|
choice is read back here rather than at 3h — a directive spelling a node CommonMark holds
|
|
is refused, so `::rule` and `:::blockquote` are errors while `::rule {localId=…}` is not.
|
|
The parser asks `spellsCommonMark`, the emitter's own choice, rather than restating the
|
|
per-node conditions: a copy would refuse the list whose first item reads back as a
|
|
thematic break, which the emitter does spell as a directive, and §2 breaks in silence.
|
|
Two refusals land here for a later chunk to lift, on the same rule: the inline `[content]`
|
|
slot, which 3i opens for `emoji`, `mention` and `status`, and the `codeBlock` content
|
|
model's fenced body, 3h's. `Read<T>` stays where 3f left it — the node reader knows its
|
|
path and returns `Result`, so no second reader took it. The drift guard earned itself on
|
|
the way in: the spec's `text` attribute was missing from three inline table entries, which
|
|
the content slot spells and the vocabulary walk already passes over.
|
|
- [x] **3h — The block nodes.** `block-nodes/` reads back: the `codeBlock` directive's fenced
|
|
body and the `language` attribute a bare fence leaves it; the media family's composition;
|
|
and both table forms, the pipe table's cell split and its named errors. `fenceInfo` is a
|
|
rule both directions answer alike and moves to the `markdown/` root with the language
|
|
attribute.
|
|
**Settled** (the maintainer, 2026-08-27): 1d's last pick, the one
|
|
`container-block-separation` holds — a CommonMark block and a directive block sit adjacent
|
|
in a container body with no blank line between them. That reduces the three cases to one
|
|
rule, separation only where its absence would merge the blocks: the `:::` fence is
|
|
separation already, and 3c's claim ends the lazy continuation that would otherwise swallow
|
|
it. The fixture becomes a round-trip pair, and with `nested-list-separation` and 3e's pair
|
|
that empties `corpus/unspellable/`: this chunk settles the directory's own guard in
|
|
`corpus.test.ts` too, and `unspelled-block-separation`, which loses its only cause here.
|
|
The emitter's other refusals survive on causes no fixture in that directory covers, so
|
|
3k's one-list pass is where they get fixtures or the directory goes.
|
|
**Settled** (the maintainer, 2026-09-01): losing that cause closed one of the shapes input
|
|
accepted and emit refused, not the last. Two adjacent lists of a kind are what
|
|
`adfToMarkdown` refuses and one `- ` spelling cannot hold apart, and the walk reached them
|
|
two ways — a marker change, which CommonMark opens a second list on, and an empty last item,
|
|
whose blank line pops the container the list's identity hung from. The parser opens no list
|
|
beside one of its own kind instead, the way it already drops the blank lines between items;
|
|
3k owes the CommonMark suite an exception where the reference HTML holds two `<ul>`. The
|
|
`normalization/` arm emits each document and reads it back from here, so the population that
|
|
class lives in is checked rather than read. The README's canonical-fixpoint sentence still
|
|
claims more than the parser keeps — 3e's three shapes — which stays milestone 5's to
|
|
narrow.
|
|
- [x] **3i — The inline nodes and the marks.** `inline-nodes/` reads back: the content slot's
|
|
`text` attribute and the error a slot holding anything but one unmarked text node is; the
|
|
`:text{text="…"}` whitespace spelling; the four directive marks and their nesting order,
|
|
outermost first; and `:em[x]` as the error `spec/flavour.md` promises. Editor-normal's
|
|
merging half lands here, `text-whitespace` being the first fixture that forces it, and 4's
|
|
`toEditorNormal` is built on it.
|
|
3g's shape leaves three: `readInlineDirectiveNode` takes the name, the attributes and the
|
|
slot's parsed text rather than the span, since `inline-content.ts` already imports it and
|
|
parsing the slot inside it is a cycle; the four directive marks get `parse/directive-marks.ts`
|
|
that `inline-content.ts` tries ahead of the node reader, as `mark-spellings.ts` sits apart
|
|
from `emit/inline-directive-spelling.ts`; and the five markdown-spelled mark names in inline
|
|
directive position take `unsupported-node-shape` rather than a code of their own — §8
|
|
already answers a well-formed directive the node tables refuse, and the message names the
|
|
spelling to use (`*x*`), while `unknown-directive-name`'s "a later MINOR may give the name
|
|
meaning" stays the wrong signal, as it was for `adf`. `corpus/errors/directive-content-slot` goes when the slot opens.
|
|
The marks a spelling wraps answer the same question 3g settled for a block's form: only the
|
|
nesting the emitter writes parses back.
|
|
**Settled** (the maintainer, 2026-09-01): `:text` reads back what the emitter writes and
|
|
nothing else — one run of spaces and tabs, or one run of newlines. A mixed run, and text
|
|
CommonMark carries plainly, are named errors, as 3g refuses the directive form of a node
|
|
CommonMark spells. The reader takes the slot's parsed nodes rather than its text, so the
|
|
rule refusing anything but one unmarked text node sits beside the node tables that own the
|
|
slot. Only `text`, read ahead of the slot, names a refusal before the slot's own: a
|
|
doubly-broken span reports what its content holds, `:date[<div>]{timestamp=1}` being
|
|
`unmappable-html` rather than `date takes no content`, which the maintainer pinned with an
|
|
assertion rather than reordering the readers. `directive-content-slot` stays with the
|
|
fixtures, its cause now a marked slot rather than a slot at all. The slot's own whitespace
|
|
answers the rule the spelling does: `:text{text="\n"}` and ` ` alike reach a slot the
|
|
emitter refuses a line ending in, so one function answers both directions.
|
|
**Settled** (the maintainer, 2026-09-01): a name the other position spells names that
|
|
spelling rather than reading as unknown — `:::em` and `::date` take
|
|
`unsupported-node-shape` naming the inline form, `:paragraph[a]` the block one — leaving
|
|
`unknown-directive-name` for a name no table holds, which is the meaning §8 gives it. The
|
|
two readers lean on the tables being disjoint, so that is a test beside the spec drift
|
|
guard now.
|
|
The same read found the hole the other way: `attemptLine` refused a line edged with a
|
|
vertical tab or a form feed, where CommonMark strips spaces and tabs alone, so valid
|
|
CommonMark parsed to a document `adfToMarkdown` then refused. The edges that check covered
|
|
are carried before the line is assembled, so narrowing it to spaces and tabs left it no
|
|
cause and it goes with them.
|
|
- [x] **3j — The carry and the combinations.** `opaque-carry/` and `combinations/` read back:
|
|
the `adf` fence and `:adf{json="…"}` restoring a deep-equal node, invalid JSON in either a
|
|
named error, a carry inside a mark spelling another, and the three carve-outs' escapes
|
|
reading as the literal text they hold. 3g refuses the `adf` fence rather than reading a
|
|
`codeBlock` from it; the refusal goes when the carry reads it. 3i left the slot parse
|
|
contextless, so the refusal a carry inside a mark spelling earns needs a channel — a reader
|
|
context in place of `parseInline`'s `strip` flag, or a return arm from the slot — and
|
|
`directiveNodes` takes its fourth reader beside it.
|
|
`index.ts` gains `markdownToAdf` here, and the README's status line with it: this is the
|
|
last parser chunk, so `parsingDirectories` becomes `emittingDirectories` and the whole
|
|
corpus round-trips both ways — `0.1.0`'s proof, which 4 widens rather than replaces.
|
|
- [x] **5a — Rename to `@larvit/adf-codec` (`0.1.0`).** Before the first publish, the name being
|
|
the published identity: `package.json` `name` and `repository`, the Gitea repo and its
|
|
remote, the README title, §6's published-as line, the checkout directory.
|
|
**Settled** (the maintainer, 2026-09-01): ADF's own `A` is "Atlassian", and "converter" is
|
|
the one-way lossy tool §2 exists to replace, where a codec is both directions. It names the
|
|
hub, not the formats around it.
|
|
- [x] **5b1 — The error's source position.** A parse error names an ADF path into a document the
|
|
caller does not hold yet — `unmappable-html` at `["content", 5]` for a `<span>` on line
|
|
12 — and no coordinate into the markdown string it passed in. `ConvertError` gains an
|
|
optional `position` the parser carries to every parse-side mint, and the README's published
|
|
shape gains it.
|
|
**Settled** (the maintainer, 2026-09-03): the position is the parse side's alone — an
|
|
emitter has no source string to point into, so emit-side errors keep `path` unchanged. The
|
|
representation and where the position is captured are implementation judgment.
|
|
The block walk mints it and the node walk attaches it as results return — at `blockNodes`,
|
|
and at the inline body a directive holds — so the innermost block wins and the emitter's
|
|
own refusals, which the parser re-enters for the CommonMark spelling, get an input
|
|
coordinate too. `markdownToAdf` wraps the walk once more, which is what turns the wide
|
|
`Result<T>` into the `Result<T, ParseError>` its signature promises rather than guarding
|
|
anything: the depth guard under it cannot fire at depth 0. A paragraph names the line its
|
|
kept text starts on, never a link reference definition it gave up. Line endings stay as
|
|
the input spells them, so an offset indexes the string the caller passed rather than a
|
|
normalized copy of it. §8 records the framings the review settled beside it:
|
|
`unsupported-node-shape` stays one code across the two directions, `unmappable-html` names
|
|
the version rather than the element, and a direction that reads a source returns the
|
|
narrowed error type.
|