From 3fb21b9766e500c73fae5e3ad8a1faf3c9dd164e Mon Sep 17 00:00:00 2001 From: Lilleman auf Larv Date: Mon, 5 Oct 2026 00:24:20 +0200 Subject: [PATCH] Open the README with what the package does and for whom, and give npm search keywords --- CHANGELOG.md | 2 ++ README.md | 58 ++++++++++++++++++++++++++++++++++++---------------- package.json | 14 ++++++++++++- todo.md | 44 +++++++++++---------------------------- 4 files changed, 67 insertions(+), 51 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5cf2b15..345713b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- The README opens with what the package does and for whom, and compares it with the other ADF + converters on npm; `package.json` carries search keywords. - The source moves to [github.com/larvit/adf-codec](https://github.com/larvit/adf-codec), where issues and pull requests go. - **Breaking:** directives, the inline opaque carry among them (now `!adf:carry{json="…"}`), are diff --git a/README.md b/README.md index 3b17b4f..c2dea59 100644 --- a/README.md +++ b/README.md @@ -1,26 +1,31 @@ # @larvit/adf-codec -Lossless conversion between **Atlassian Document Format** (ADF), an extended markdown flavour, and -an HTML dialect. +[![npm](https://img.shields.io/npm/v/@larvit/adf-codec)](https://www.npmjs.com/package/@larvit/adf-codec) +[![CI](https://github.com/larvit/adf-codec/actions/workflows/ci.yml/badge.svg)](https://github.com/larvit/adf-codec/actions/workflows/ci.yml) -**Status: published — the markdown round-trip (`adfToMarkdown`, `markdownToAdf`); HTML at -`0.2.0`.** -Plan: -[`todo.md`](https://github.com/larvit/adf-codec/blob/main/todo.md). Decisions: -[`docs/decisions.md`](https://github.com/larvit/adf-codec/blob/main/docs/decisions.md). Changes: -[`CHANGELOG.md`](https://github.com/larvit/adf-codec/blob/main/CHANGELOG.md). The lossless flavour's grammar: -[`spec/flavour.md`](https://github.com/larvit/adf-codec/blob/main/spec/flavour.md). -Upgrading from `0.1.0`: [convert your markdown first](https://github.com/larvit/adf-codec/blob/main/MIGRATION.md). +Convert Atlassian Document Format (ADF) to and from markdown and HTML, with a lossless round-trip. -## What it is for +For application developers whose app shows, edits, exports or generates the rich text Atlassian +products store as ADF. -Atlassian Cloud REST APIs hand out rich text — issue descriptions, comments, pages — as ADF, a -ProseMirror-shaped JSON tree, and take it back the same way. No Atlassian endpoint converts it -(`pf-editor-service/convert` is decommissioned, -[JRACLOUD-77436](https://jira.atlassian.com/browse/JRACLOUD-77436) open), and the npm ecosystem is -one-directional and lossy. A consumer that shows a document and lets someone edit it needs both -directions lossless — otherwise saving destroys the panels, mentions and attachments it could not -represent. +- **Lossless:** `adfToMarkdown` and `markdownToAdf` give back the document they started from — + panels, mentions, statuses and node types this version does not know included. +- **Plain CommonMark in:** a bot's or a model's markdown is valid input, nothing to learn first. +- **Plain markdown out:** `adfToPlainMarkdown` and `plainMarkdownToAdf` convert to and from what + GitHub, GitLab and Obsidian render — GitHub Flavored Markdown's alerts and task lists, Obsidian + Flavored Markdown's callouts, highlights and pipe tables — keeping the content. +- **No throws:** every call returns a result; a refusal carries a code from a closed list. +- **Runs anywhere:** pure functions with no runtime dependencies, in Node, Deno, Bun and browsers. +- **HTML** at `0.2.0`: `adfToHtml`, `htmlToAdf`, `markdownToHtml`, `htmlToMarkdown`. + +[Goals](#goals) · [Audience](#audience) · [The shape](#the-shape) · +[Other ADF converters](#other-adf-converters) · [Plain markdown](#plain-markdown) · +[The errors](#the-errors) · [The guarantees](#the-guarantees) · [The package](#the-package) · +[Grammar](https://github.com/larvit/adf-codec/blob/main/spec/flavour.md) · +[Changes](https://github.com/larvit/adf-codec/blob/main/CHANGELOG.md) · +[Upgrading from `0.1.0`](https://github.com/larvit/adf-codec/blob/main/MIGRATION.md) · +[Plan](https://github.com/larvit/adf-codec/blob/main/todo.md) · +[Decisions](https://github.com/larvit/adf-codec/blob/main/docs/decisions.md) ## Goals @@ -94,6 +99,23 @@ htmlToMarkdown(html: string): Result // 0.2.0 `Result` is `{ ok: true; value: T } | { ok: false; error: ConvertError }` — nothing throws. +## Other ADF converters + +Serves Goal 3. Each package's latest version on npm, checked 2026-10-05 against its docs and by +converting a document holding a paragraph, a mention, a status, an info panel and an unknown node +type to markdown and back. + +| Package | ADF to markdown | Markdown to ADF | Round-trip | Invalid ADF | Runtime dependencies | +| --- | --- | --- | --- | --- | --- | +| `@larvit/adf-codec` | yes | yes | deep-equal | returns `not-an-adf-document` | 0 | +| `@atlaskit/editor-markdown-transformer` 6.3.1 | no: `encode` throws | yes, through `@atlaskit/editor-json-transformer` | — | — | 5 | +| `marklassian` 1.2.1 | no | yes | — | — | 1 | +| `adf-to-md` 1.2.1 | yes | no | — | throws | 1 | +| `extended-markdown-adf-parser` 2.4.0 | yes | yes | lossy: the mention reads back as a link, the status loses its `localId`, the unknown node as its JSON in a paragraph | writes empty markdown | 9 | +| `md-to-adf` 0.6.4 | no | yes | — | — | 2 | +| `adf-to-markdown` 1.0.1 | yes | no | — | throws on `null`, writes empty markdown for a `doc` without `content` | 0 | +| `adf2markdown` 1.2.0 | yes | no | — | throws on `null`, writes a newline for a `doc` without `content` | 0 | + ## Plain markdown Serves Goal 7. Plain markdown is a second flavour of the same grammar. `adfToPlainMarkdown` writes diff --git a/package.json b/package.json index f5ecc73..7272ef3 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,19 @@ { "name": "@larvit/adf-codec", "version": "0.1.0", - "description": "Lossless conversion between Atlassian Document Format, extended markdown and HTML", + "description": "Convert Atlassian Document Format (ADF) to and from markdown and HTML, with a lossless round-trip.", + "keywords": [ + "adf", + "atlassian", + "atlassian-document-format", + "commonmark", + "confluence", + "converter", + "html", + "jira", + "markdown", + "prosemirror" + ], "license": "MIT", "repository": { "type": "git", diff --git a/todo.md b/todo.md index 86f3cf5..9ec7864 100644 --- a/todo.md +++ b/todo.md @@ -6,7 +6,7 @@ `Bar = 9` -`Next ID = 78` +`Next ID = 79` | Goal | W | |---|---| @@ -24,20 +24,18 @@ | ID | Release | Exempt | Item | R | S | A | G | Goals | Score | |---|---|---|---|---|---|---|---|---|---| -| 47 | 0.2.0 | | **Open the README with what the package is, what it does and for whom.** | 1 | 4 | 7 | 9 | 3 | 26.0 | | 7 | 0.2.0 | | **Ship HTML: `adfToHtml`, `htmlToAdf`, and `markdownToHtml` / `htmlToMarkdown` composed through ADF.** | 6 | 9 | 9 | 9 | 2, 4 | 25.8 | +| 78 | 0.2.0 | defect | **Return a result when `markdownToAdf` or `plainMarkdownToAdf` is handed a value that is not a string.** | 2 | 2 | 5 | 9 | 1 | 25.5 | | 45 | 0.2.0 | | **Replace `isAdfDocument` with a reader returning `Result`.** | 2 | 3 | 6 | 8 | 1 | 25.2 | | 67 | 0.2.0 | defect | **Push an expand title's leaves one at a time, so a title carrying many text nodes returns a result.** | 1 | 1 | 4 | 9 | 1 | 24.8 | | 6 | 0.2.0 | decision | **Specify the HTML dialect.** | 2 | 6 | 7 | 8 | 2, 4 | 24.7 | | 64 | 0.2.0 | defect | **Read back unchanged on V8 every JSON key the emitter writes, with a fixture whose key holds `\`, `"` or a control character.** | 4 | 4 | 6 | 8 | 1, 8 | 23.0 | | 77 | 0.2.0 | decision | **Read the input once into a plain copy, so an accessor property or a throwing Proxy yields `not-an-adf-document` and every call returns a result.** | 4 | 4 | 5 | 9 | 1 | 23.0 | | 66 | 0.2.0 | defect | **Refuse an attribute value that is not a plain object, array or JSON primitive.** | 3 | 2 | 5 | 8 | 1 | 22.5 | -| 71 | 0.2.0 | | **Add a README table comparing the package with the other ADF converters, each cell checked against that package's own docs or behaviour.** | 1 | 3 | 6 | 7 | 3 | 21.2 | | 43 | 0.2.0 | decision | **Give each markdown input its own reader, strict to its own standard.** | 6 | 7 | 8 | 9 | 4, 5 | 20.3 | | 68 | 0.2.0 | | **Carry a non-text inline node holding `content` or `text`, a block holding `text`, and a leaf block holding `content`.** | 3 | 3 | 4 | 7 | 1 | 18.2 | | 75 | 0.2.0 | | **Make the generated-markdown property write block carries as `adf:` fences, canonical and hostile.** | 2 | 2 | 3 | 7 | 1 | 17.5 | | 65 | 0.2.0 | defect | **Read an image not alone in its paragraph as its alt text linked to its URL, and a titled image alone in its paragraph as the image captioned with its title.** | 5 | 5 | 7 | 7 | 4, 5, 7 | 17.1 | -| 70 | 0.2.0 | | **Give `package.json` the `keywords` someone searching npm for an ADF converter types.** | 1 | 1 | 4 | 6 | 3 | 16.1 | | 49 | 0.2.0 | | **Read a list whose bullet or ordered delimiter changes as two lists, in both readers, and retire `!adf:listBreak`.** | 4 | 5 | 5 | 8 | 4, 5 | 15.5 | | 46 | 0.2.0 | | **Publish the bundle size in the README, failing the release pipeline when it drifts.** | 2 | 4 | 5 | 5 | 3, 8 | 14.8 | | 61 | 0.2.0 | decision | **Have the emitter ask the inline reader how a line reads back, in place of `line-escaping.ts` predicting it.** | 6 | 8 | 3 | 8 | 1 | 14.0 | @@ -65,20 +63,18 @@ ## Details -### 47. Open the README with what the package is, what it does and for whom. - -Add badges for npm's version and the GitHub Actions status. Today the README opens with the -pre-launch rationale — Atlassian's REST APIs, `pf-editor-service/convert` being decommissioned, a -link to JRACLOUD-77436. The background goes entirely, no endpoint, ticket or "why" note left. The -README names the lossy pair, `adfToPlainMarkdown` and `plainMarkdownToAdf`, and the flavours it -writes and reads — GitHub Flavored Markdown's alerts and task lists, Obsidian Flavored Markdown's -callouts — so a search for any of these names finds the package. - ### 7. Ship HTML: `adfToHtml`, `htmlToAdf`, and `markdownToHtml` / `htmlToMarkdown` composed through ADF. Lands after items 6, 59, 60, 61 and 62. The CommonMark spec suite also runs against `markdownToHtml`. The README documents HTML as it documents markdown. +### 78. Return a result when `markdownToAdf` or `plainMarkdownToAdf` is handed a value that is not a string. + +Today `null` and `undefined` throw a `TypeError` reading `length`, and `42` reads as an empty +document. A JavaScript caller, or a TypeScript one past an untyped boundary, reaches both. The code +it returns follows `docs/decisions.md` §Which code a cause takes. Found by the competitor probe for +the README's comparison table, 2026-10-05. + ### 45. Replace `isAdfDocument` with a reader returning `Result`. Lands with item 77: the reader returns item 77's copy. Goal 1 has every call return a result; the @@ -129,15 +125,6 @@ typed array in `attrs`, as `structuredClone` keeps it, passes the guard and is w key per element: `ok: true` with content lost. Accept an object whose prototype is `Object.prototype` or `null`, and an array. Found by the stability review of item 63, 2026-10-04. -### 71. Add a README table comparing the package with the other ADF converters, each cell checked against that package's own docs or behaviour. - -Lands after items 7, 46 and 47: the table names HTML, compares item 46's size figure, and sits in -the README item 47 reshapes. Its columns: which directions each converts, whether the round-trip -holds, what an unknown node does, the formats, runtime dependencies and size. Candidates are the -packages an npm search for ADF and markdown returns, marklassian among them. Every cell cites that -package's README or a run against its published version, and the table names each version and the -date it was checked. Found while planning discoverability, 2026-10-04. - ### 43. Give each markdown input its own reader, strict to its own standard. Today `markdownToAdf` reads CommonMark and the lossless flavour as one input: text shaped like a @@ -175,13 +162,6 @@ its paragraph, it reads as the image with a `link` mark to `/uri`, which ADF's ` alone in its paragraph, the image reads as its alt text linked to `/uri`, and its URL drops. A writer panel chose this reading 3 of 3 (2026-10-04), by Goal 6, which outranks Goal 7. -### 70. Give `package.json` the `keywords` someone searching npm for an ADF converter types. - -`package.json` has none, and npm's search ranks on them. Take the GitHub repository's topics, so a -search on npm matches what GitHub already does: adf, atlassian, atlassian-document-format, -commonmark, confluence, converter, html, jira, markdown, typescript. Found while planning -discoverability, 2026-10-04. - ### 49. Read a list whose bullet or ordered delimiter changes as two lists, in both readers, and retire `!adf:listBreak`. Lands after item 43. Today `- a` then `+ b`, or `1.` then `1)`, reads as one list; CommonMark reads @@ -270,9 +250,9 @@ which counts one fewer. ### 73. 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. -Lands after `0.2.0` is published with items 47 and 71, so each post links the GitHub repository, the -comparison and a README that opens with what the package is. We draft each post for the place it -goes; the maintainer posts them. Found while planning discoverability, 2026-10-04. +Lands after `0.2.0` is published, so each post links the GitHub repository, the comparison and a +README that opens with what the package is. We draft each post for the place it goes; the +maintainer posts them. Found while planning discoverability, 2026-10-04. ### 38. Spell a lone surrogate in a text node so it survives a UTF-8 encode.