Settle goals, guarantees, HTML scope and release automation in the spec docs

This commit is contained in:
2026-08-23 23:09:06 +02:00
parent 07ec80efc5
commit f8d43075a2
3 changed files with 172 additions and 99 deletions
+53 -23
View File
@@ -1,42 +1,72 @@
# @larvit/atlassian-adf-converter
Lossless conversion between **Atlassian Document Format** (ADF) and an extended markdown flavour
that can carry the nodes plain markdown has no syntax for.
Lossless conversion between **Atlassian Document Format** (ADF), an extended markdown flavour, and
an HTML dialect.
**Status: specification only. No code is implemented yet.** `todo.md` holds the plan and the design
questions still open; `AGENTS.md` holds the decisions already made.
**Status: specification only. No code is implemented yet.** `todo.md` holds the plan; `AGENTS.md`
holds the decisions already made.
## What it is for
Jira Cloud's REST v3 API hands out issue descriptions and comment bodies as ADF — a JSON node tree,
ProseMirror-shaped and takes them back the same way. There is no Atlassian endpoint that converts
it: `pf-editor-service/convert` was decommissioned and
Atlassian Cloud REST APIs (Jira v3, Confluence) hand out rich text — issue descriptions, comments,
pages — as ADF, a JSON node tree, ProseMirror-shaped, and take it back the same way. There is no
Atlassian endpoint that converts it: `pf-editor-service/convert` was decommissioned and
[JRACLOUD-77436](https://jira.atlassian.com/browse/JRACLOUD-77436) is still an open request. The npm
ecosystem covers one direction each, drops what markdown cannot express, and none of it round-trips.
A client that shows a ticket and lets someone edit it needs both directions, and needs them
A consumer that shows a document and lets someone edit it needs both directions, and needs them
lossless — otherwise saving an edit silently destroys the panels, mentions and attachments that were
in someone else's ticket. That is what this library is.
in someone else's document. That is what this library is.
## The intended shape
## The shape
Two pure functions and their types. No I/O, no network, no configuration:
Pure functions and their types. No I/O, no network, no configuration. ADF is the hub: the
markdown↔HTML directions compose through it.
```ts
adfToMarkdown(document: AdfDocument): string
markdownToAdf(markdown: string): AdfDocument
adfToMarkdown(doc: AdfDocument): Result<string>
markdownToAdf(markdown: string): Result<AdfDocument>
adfToHtml(doc: AdfDocument): Result<string>
htmlToAdf(html: string): Result<AdfDocument>
markdownToHtml(markdown: string): Result<string> // via ADF
htmlToMarkdown(html: string): Result<string> // via ADF
isAdfDocument(v: unknown): v is AdfDocument
```
The published package is ESM only, has **no runtime dependencies**, and offers two entrypoints — the
built JavaScript for ordinary consumers, and the TypeScript source for consumers that run TypeScript
directly (Node's type stripping), with exported types either way. `AGENTS.md` §4 has the contract.
`Result<T>` is `{ ok: true; value: T } | { ok: false; error: ConvertError }` — nothing throws.
## The first consumer
## The guarantees
[`plainpages-plugin-fastjira`](https://gitea.larvit.se/larvit/plainpages-plugin-fastjira) — a
server-rendered Jira client. Its read-only ticket view shows the markdown this library produces
verbatim, with no HTML rendering anywhere; its later write paths post back what this library
converts the other way. **That view is blocked on `0.1.0`,** and it needs `adfToMarkdown` first.
- **`markdownToAdf(adfToMarkdown(doc))` equals `doc`** — including node types the library has never
seen, which are carried opaquely (AGENTS.md §3).
- **`htmlToAdf(adfToHtml(doc))` equals `doc`** — fidelity HTML cannot express rides `data-*`
attributes.
- **Plain CommonMark is valid input** to `markdownToAdf`: any ordinary markdown a human types
converts per the CommonMark spec. Converting back yields the library's canonical spelling, which
then round-trips byte-identically.
- **Foreign HTML** (not emitted by this library) maps a documented element set; an unmappable
element is an error result, never a silent drop. The parser takes well-formed HTML, not
WHATWG tag-soup recovery.
- **The emitted formats are semver surface** (AGENTS.md §8): after 1.0, output an old version
emitted always parses under a newer one within the same major.
The library knows nothing about that consumer. No Jira, no HTTP, no REST shapes, no plainpages — a
document tree in, a string out, and the reverse.
## Who it is for
No actual consumer is named here or anywhere in this repo (AGENTS.md §7). The personas the design
serves:
- **A viewer/editor app** — shows a document as markdown or HTML, lets a human edit, posts the
result back. Needs losslessness above all.
- **A bot posting content** — generates ordinary markdown (templates, LLM output) and converts it
to ADF. Needs the CommonMark input promise; never reads ADF back.
- **An export/indexing tool** — bulk-converts ADF to markdown or HTML for archives, search, static
sites. Read-only; needs readable output.
- **An LLM/agent pipeline** — feeds documents to a model as markdown, converts the model's edits
back. Needs the round-trip plus markdown that stays legible to a reader that half-knows the
flavour.
## The package
ESM only, **no runtime dependencies**, published to public npmjs. Two entrypoints — built
JavaScript for ordinary consumers, TypeScript source for consumers running Node's type stripping —
with exported types either way. `AGENTS.md` §56 have the contract.