and degrades every other shape; reading refuses what `markdownToAdf` refuses. A read node
carries no `localId`, save a task node's position id (10f, the maintainer, 2026-09-26). Reading also takes other tools' spellings — type words in any case,
Obsidian's aliases, `[X]` — since it reads their output and never writes those spellings.
- A `panel` is an alert: the marker alone on the quote's first line, a blank `>`, then the body
(`> [!WARNING]`), in GitHub's five words by colour — info `NOTE`, note `IMPORTANT`, tip and
success `TIP`, warning `WARNING`, error `CAUTION`, custom `NOTE`. Reading takes those words
back (`NOTE` info, `IMPORTANT` note, `TIP` tip, `WARNING` warning, `CAUTION` error) and
Obsidian's by meaning (hint tip; success, check and done success; attention warning; danger,
failure, fail, missing, bug and error error — `error` by a panel, 3 of 3, 2026-09-25; any
other word info). Text after a marker on its line is the panel's first body paragraph, and
the lines after it open the body (35a).
- An `expand` or `nestedExpand` is Obsidian's folded callout, `> [!NOTE]- Title`, a blank `>`,
then the body. Reading takes a fold sign (`-` or `+`) as an expand whatever the word, the
rest of the marker's line as its title and the lines after it as the body (35a), and an
expand inside an expand as a `nestedExpand`.
- A `taskList` is a bullet list whose items lead with `[x]` or `[ ]` (`- [x] Write the spec`).
Reading takes a list whose every item is so marked back as a `taskList` — a `blockTaskItem`
where an item holds more than one block, a nested task list moved beside its item — and
leaves mixed and ordered lists plain. A `decisionList` is a plain bullet list.
- `backgroundColor` is `==text==`, and reading gives `==text==` the Atlassian editor's default
highlight colour where whitespace, punctuation or a line edge bounds each `==` outside, so
`a==b and c==d` stays text (a panel, 3 of 3, 2026-09-25).
`plainMarkdownToAdf(adfToPlainMarkdown(doc))` keeps a literal `==x==`, a quote opening `[!NOTE]`
`0.2.0`;
and a list whose items all open `[x] ` as text. A highlighted `=` (today `=====`) and `a==b`
8, 9 → TBD; 5e last.
(today `==a==b==`, highlighting `a` alone) come back highlighted whole, or lose the highlight
The numbering is the order the work was planned in, not the order it ships. Everything known and
where no spelling holds them; 10c's byte-for-byte property misses both, since the wrong document
shaped ships in one release rather than a string of them: nothing waits on a version, and no
re-spells to the same bytes. The reduction keeps only degrading what the flavour cannot spell.
consumer is served by the churn (the maintainer, 2026-09-18). So `0.2.0` completes HTML, and
- **10f — Give task nodes read from plain markdown position ids.** `plainMarkdownToAdf` gives each
`0.2.1` and `0.3.0` are gone. `8` and `9` stay out as the two goals nothing has shaped yet. `0.2.0`'s order is settled (the maintainer, 2026-09-13, extended 2026-09-18): 11 makes the
`taskList`, `taskItem` and `blockTaskItem` a deterministic `localId` from its position in document
tables 4 generates from answer to Atlassian's schema, 4 proves 12, 13 spells 11's gaps in 12's
order, so a site that rejects a missing `localId` takes the document and the same markdown reads
grammar, and 12 rewrites code 4b and 4c change; then 14 moves the files 15, 16 and 10 edit and HTML
to the same ids every run; README §Plain markdown's `localId` bullet says so (the maintainer,
is written against that layout, 4d marks the gate legs before 17 adds one, 17 puts the size ratchet
2026-09-26). The id spelling — unique within the document, no host API — is part of the chunk.
under the largest body of new code, and 5f and 5g read last because 7 is what changes the
- **6 — Specify the HTML dialect.** Element-by-element mapping, the `data-*` fidelity scheme, the
bundle size and the tagline.
opaque-carry form, and the documented foreign-element set `htmlToAdf` accepts — the set
19 to 27 come from a comprehension panel — nine readers across four experience levels, none of
`markdownToAdf` shares (`spec/flavour.md` §Raw HTML in input; 29).
them able to see this file, reporting what defeated them and whether the project's shape fits in a
head (2026-09-20). They read ahead of 6, 7 and 10 because every one of them is cheaper before the
HTML format lands than after: 19 and 20 because HTML has no answer without them, 21 to 24 because
HTML doubles the importers and the file count they touch, and 25 to 27 because they are what the
panel says the next reader pays for.
29 and 30 come from 17's prose pass (2026-09-20). 29 reads first because every goal is what a later
ask is settled against, 19's included; 30 sits beside 25, the other chunk rereading AGENTS.md.
33 comes from 10a and 34 from 10b (2026-09-25); both read beside 31.
35 comes from Goal 2's rewrite (2026-09-27) and reads first of what is left.
31 comes from 20's gate runs (2026-09-21) and reads beside 5f, the other chunk putting a measured
number under the pipeline. 32 comes from 21's review (2026-09-21) and reads beside 22, the other
chunk clearing a §11 seam.
- [ ]**31 — The branch figure the floor is read against is stable (`0.2.0`).** Three Node test
legs over one unchanged tree reported `emit/inline-line.ts` at 95.83%, 96.23% and 96.23%,
and the total at 98.80%, 98.84% and 98.84% (2026-09-21). `--experimental-test-coverage`
counts branches off V8's own coverage, which the runner's parallel files and V8's
optimization make run-dependent, so the number the floor is read against is not the code's
alone. The floor of 98 holds today on 0.8 points of slack and §10 says it only ever moves
upward, so the first raise to the measured figure reddens a run that changed nothing. Make
the measurement repeatable, or state the number the floor may be raised to and why it is not
the measured one.
- [ ]**33 — A carried mark run costs the line one re-emit (`0.2.0`).**`adfToMarkdown` spends 23 s
on one paragraph of 2000 ×`un` plus `**-r**`: each run its flanking cannot spell re-emits the
whole line before riding the carry, quadratic in the runs (§11 Bounds), and the plain
reduction's `spellableLine` drops one mark per re-emit the same way. Make both linear.
- [ ]**34 — Emphasis flanking reads a whole character (`0.2.0`).** Check whether
`line-escaping.ts`'s `charAt` and the parser's flanking read one UTF-16 unit beside an astral
symbol — a lone surrogate is neither punctuation nor symbol, where CommonMark reads `😀` as
punctuation — and, where they do, read the code point, with a fixture per direction.
- [x]**24 — The conformance gates have a directory (`0.2.0`).**
- [x]**25 — AGENTS.md §8 and §11 are findable (`0.2.0`).**
- [x]**26 — The two mutable structures say what they guarantee (`0.2.0`).**
- [x]**27 — The dead `headroom` write goes (`0.2.0`).**
- [x]**0 — Scaffold.**
- [x]**1a — The directive grammar.**
- [x]**1b — Block node syntaxes.**
- [x]**1c — Inline node syntaxes and marks.**
- [x]**1d — Corpus start.**
- [x]**1d1 — The CommonMark subset.**
- [x]**1d2 — Block nodes.**
- [x]**1d3 — Inline nodes and marks.**
- [x]**2 — `adfToMarkdown`.**
- [x]**2a — The runner and the CommonMark subset.**
- [x]**2b — Block nodes.**
- [x]**2c — Inline nodes and marks.**
- [x]**2d — The opaque carry.**
- [x]**2e — Carve-outs and combinations.**
- [x]**2e1 — The carve-outs and the claimed line.**
- [x]**2e2 — Mark runs and the runs a carry breaks.**
- [x]**2e3 — Attribute canonicalization and the quoted value's escape.**
- [x]**2e4 — The carry's fallback triggers.**
- [x]**2e5 — Combined documents and the collision property.**
- [x]**2f — The attributes CommonMark cannot hold.**
- [x]**3 — `markdownToAdf`.**
- [x]**3a — The hierarchy.**
- [x]**3b — The leaf blocks.**
- [x]**3c — The container blocks.**
- [x]**3d — Inline text.**
- [x]**3e — Emphasis and links.**
- [x]**3f — The directive grammar.**
- [x]**3g — The node tables read backwards.**
- [x]**3h — The block nodes.**
- [x]**3i — The inline nodes and the marks.**
- [x]**3j — The carry and the combinations.**
- [x]**3k — The CommonMark spec suite.**
- [x]**4 — Round-trip property tests.**
- [x]**4.1 — Editor-normal and the node accessors.**
- [x]**4.2 — The ADF property.**
- [x]**4.3 — The markdown property.**
- [x]**4.4 — The real payloads.**
- [x]**4b — The block walk's retry (`0.2.0`).**
- [x]**4c — The scanning rule's remaining sites (`0.2.0`).**
- [x]**4d — What the gate says while it runs (`0.2.0`).**
- [x]**5 — Ship `0.1.0`.**
- [ ]**5e — The publish token's deadline.**`0.1.0` published only once the npm
token carried **Bypass 2FA**: the account requiring no 2FA on writes was not enough, and npm
answered `EOTP` until the token itself bypassed. npm retires bypass-2FA tokens for direct
publishing around January 2027, leaving them `npm stage publish`, which a maintainer
approves with 2FA; its replacement — trusted publishing over OIDC — supports GitHub-hosted
Actions, GitLab.com's shared runners and CircleCI's cloud, self-hosted runners planned
without a date. So the release path has an expiry date and no drop-in successor yet. Revisit:
whether npm has added Gitea or self-hosted OIDC, and otherwise whether the
release moves to the staged publish — which fits badly with publish-on-merge,
and is the trade to weigh rather than discover on a red release run.
**Settled** (the maintainer, 2026-09-13): last of the known work, clear of `0.2.0`, placed
there knowing the cutoff may land before `0.2.0` ships.
- [ ]**5f — Publish the bundle size (`0.2.0`).** Measure the shipped artifact and put the number in the
README, kept honest by the release pipeline rather than by a human re-reading it. The
quantity is what a consumer downloads and loads: the tarball `npm pack` produces, its
unpacked `dist`, and the built JavaScript minified + gzipped — the figure the competitors
advertise (marklassian's "12kb") and the only apple-to-apple one, since ours ships tsc's
unminified output and no minifier yet (decide here whether to minify for the build or report
the unminified gzip). A publish/pipeline leg measures it and fails when the README figure
drifts, so the number can't rot; the figure lands in README §The package beside the
through ADF.** CommonMark spec suite runs against `markdownToHtml` from here (§10). The README's
- [ ]**7 — HTML (`0.2.0`).**`adfToHtml`, `htmlToAdf`, the composed
tagline and `package.json`'s `description` regain HTML (5g).
`markdownToHtml` / `htmlToMarkdown`. CommonMark spec suite runs against `markdownToHtml` from
- **31 — Make the branch figure the coverage floor is read against repeatable.** Three Node test
here (§10). The README's tagline and `package.json`'s `description` regain HTML (5g).
legs over one unchanged tree reported `emit/inline-line.ts` at 95.83%, 96.23% and 96.23%, and the
- [ ]**8 — CLI.** A later goal, shaped around the personas once the library exists.
total at 98.80%, 98.84% and 98.84% (2026-09-21). `--experimental-test-coverage` counts branches
- [ ]**9 — The online sandbox.** A web page with two textboxes converting back and forth between ADF and markdown, powered by the library's browser build.
off V8's own coverage, which the runner's parallel files and V8's optimization make run-dependent,
- [ ]**35 — Read and write plain markdown as a flavour of the markdown grammar (`0.2.0`).** Per Goal 2 and
so the number the floor is read against is not the code's alone. The floor of 98 holds today on
AGENTS.md §1, `plainMarkdownToAdf` is `markdownToAdf`'s parser and `adfToPlainMarkdown`
0.8 points of slack and §10 says it only ever moves upward, so the first raise to the measured
`adfToMarkdown`'s writer, each with the plain flavour set; 10's rows are read and written
figure reddens a run that changed nothing. Make the measurement repeatable, or state the number
there, and the lift goes (the maintainer, 2026-09-27). The exports, their refusals and 10's
the floor may be raised to and why it is not the measured one.
rows stay as they are.
- **33 — Make a carried mark run cost the line one re-emit.** `adfToMarkdown` spends 23 s on one
- [ ]**35a — Read the plain flavour in the parser and delete the lift.** 10's rows are read while parsing, and
paragraph of 2000 × `un` plus `**-r**`: each run its flanking cannot spell re-emits the whole line
`plain-lift.ts` is deleted, its tests reading through `plainMarkdownToAdf`. `> [!faq]- Why?`
before riding the carry, quadratic in the runs (§11 Bounds), and the plain reduction's
with the body on the next `>` line reads to an expand titled `Why?` whose body keeps the
`spellableLine` drops one mark per re-emit the same way. Make both linear.
next lines' link targets and marks, and `> [!tip] Title` then `> body` to a panel whose
- **34 — Read emphasis flanking by the whole character beside an astral symbol.** Check whether
paragraphs are `Title` and `body`: the rest of the marker's line is the title (an expand)
`line-escaping.ts`'s `charAt` and the parser's flanking read one UTF-16 unit beside an astral
or the first body paragraph (a panel). A CommonMark backslash keeps a marker literal —
symbol — a lone surrogate is neither punctuation nor symbol, where CommonMark reads `😀` as
`\==x==`, `> \[!NOTE]`, `- \[x]`.
punctuation — and, where they do, read the code point, with a fixture per direction.
- [ ]**35b — Spell the plain flavour in the writer.** Panels, expands, task lists and highlights
- **5f — Publish the bundle size, after 7 changes it.** Measure the shipped artifact and put the
are written by the writer, which escapes text that would read back as one, so
number in the README, kept honest by the release pipeline rather than by a human re-reading it.
`plainMarkdownToAdf(adfToPlainMarkdown(doc))` keeps a literal `==x==`, a quote opening
The quantity is what a consumer downloads and loads: the tarball `npm pack` produces, its unpacked
`[!NOTE]` and a list whose items all open `[x] ` as text. A highlighted `=` (today
`dist`, and the built JavaScript minified + gzipped — the figure the competitors advertise
`=====`) and `a==b` (today `==a==b==`, highlighting `a` alone) come back highlighted
(marklassian's "12kb") and the only apple-to-apple one, since ours ships tsc's unminified output
whole, or lose the highlight where no spelling holds them; 10c's byte-for-byte property
and no minifier yet (decide here whether to minify for the build or report the unminified gzip). A
misses both, since the wrong document re-spells to the same bytes. The reduction keeps
publish/pipeline leg measures it and fails when the README figure drifts, so the number can't rot;
only degrading what the flavour cannot spell.
the figure lands in README §The package beside the "no runtime dependencies" claim. Measured
- [ ]**10 — Lossy conversion (`0.2.0`).** Markdown other tools render readably, to and from ADF,
exported sits near the bottom. The HTML directions were to stay an aside until a later release
and degrades every other shape; reading refuses what `markdownToAdf` refuses. A read node
shipped them; 7 now ships in this one and reads ahead of this item, so the README documents HTML
carries no `localId`, save a task node's position id (10f, the maintainer, 2026-09-26). Reading also takes other tools' spellings — type words in any case,
as it documents markdown, the tagline and `description` naming both (the maintainer, 2026-09-13,
Obsidian's aliases, `[X]` — since it reads their output and never writes those spellings.
revised 2026-09-18). They name the lossy pair too, and the flavours it writes and reads by name —
- A `panel` is an alert: the marker alone on the quote's first line, a blank `>`, then the body
GitHub Flavored Markdown's alerts and task lists, Obsidian Flavored Markdown's callouts — so a
(`> [!WARNING]`), in GitHub's five words by colour — info `NOTE`, note `IMPORTANT`, tip and
search for either finds the package (the maintainer, 2026-09-26).
success `TIP`, warning `WARNING`, error `CAUTION`, custom `NOTE`. Reading takes those words
back (`NOTE` info, `IMPORTANT` note, `TIP` tip, `WARNING` warning, `CAUTION` error) and
Obsidian's by meaning (hint tip; success, check and done success; attention warning; danger,
failure, fail, missing, bug and error error — `error` by a panel, 3 of 3, 2026-09-25; any
other word info). Text after a marker on its line is the panel's first body paragraph, and
the lines after it open the body (35a).
- An `expand` or `nestedExpand` is Obsidian's folded callout, `> [!NOTE]- Title`, a blank `>`,
then the body. Reading takes a fold sign (`-` or `+`) as an expand whatever the word, the
rest of the marker's line as its title and the lines after it as the body (35a), and an
expand inside an expand as a `nestedExpand`.
- A `taskList` is a bullet list whose items lead with `[x]` or `[ ]` (`- [x] Write the spec`).
Reading takes a list whose every item is so marked back as a `taskList` — a `blockTaskItem`
where an item holds more than one block, a nested task list moved beside its item — and
leaves mixed and ordered lists plain. A `decisionList` is a plain bullet list.
-`backgroundColor` is `==text==`, and reading gives `==text==` the Atlassian editor's default
highlight colour where whitespace, punctuation or a line edge bounds each `==` outside, so
`a==b and c==d` stays text (a panel, 3 of 3, 2026-09-25).
`paragraph`, `rule`, `listItem`, `hardBreak`, `text`, and the `code`, `em`, `link` and `strong`
marks; `strike` is the flavour's `~~` carve-out. Everything else is what the lossless flavour is for.
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.