Extend prose-minimum rule to all markdown files and apply it to the docs
This commit is contained in:
@@ -1,158 +1,125 @@
|
||||
# Working in this repo
|
||||
|
||||
Decisions a reader would otherwise relitigate, and the rules every collaborator — human or agent —
|
||||
works under. Everything about *using* the library is in `README.md`; what is still to build is in
|
||||
`todo.md`.
|
||||
Decisions a reader would otherwise relitigate, and the rules for every collaborator, human or
|
||||
agent. Using the library: `README.md`. What is still to build: `todo.md`.
|
||||
|
||||
## 1. Three formats, ADF is the hub
|
||||
|
||||
ADF, one markdown flavour, one HTML dialect. Six directions are exposed, but markdown↔HTML compose
|
||||
through ADF, so four conversions exist to keep correct — never write a fifth. A fourth format
|
||||
(wiki markup, anything else) is refused: each one doubles the directions again.
|
||||
ADF, one markdown flavour, one HTML dialect. Six directions exposed, but markdown↔HTML compose
|
||||
through ADF: four conversions exist to keep correct — never write a fifth. No fourth format, ever;
|
||||
each one doubles the directions.
|
||||
|
||||
## 2. The round-trip is the product
|
||||
|
||||
`markdownToAdf(adfToMarkdown(doc))` and `htmlToAdf(adfToHtml(doc))` must equal `doc`. Anything less
|
||||
and a consumer that lets someone edit a document destroys what it could not represent — a panel, a
|
||||
mention, an attachment — in a document it did not author.
|
||||
`markdownToAdf(adfToMarkdown(doc))` and `htmlToAdf(adfToHtml(doc))` must equal `doc` — anything
|
||||
less silently destroys content an editor could not represent, in a document it did not author.
|
||||
When losslessness and readability conflict, losslessness wins.
|
||||
|
||||
When losslessness and readability conflict, losslessness wins. A rare node may look ugly; nothing
|
||||
is ever dropped for looks.
|
||||
The other direction is a canonical fixpoint, not byte-identity: human markdown normalizes, the way
|
||||
back yields the library's canonical spelling, and that spelling round-trips byte-identically.
|
||||
|
||||
The other direction is a **canonical fixpoint**, not byte-identity: human markdown normalizes
|
||||
(`*em*` and `_em_` become one ADF doc), converting back yields the library's canonical spelling,
|
||||
and that spelling round-trips byte-identically. Byte-identity both ways would mean storing the
|
||||
author's formatting choices in ADF, which has no place for them.
|
||||
|
||||
Round-trip equality is a property to test over a corpus, not a claim to make in prose.
|
||||
Round-trip equality is a property tested over a corpus, not a claim made in prose.
|
||||
|
||||
## 3. Unknown input policy
|
||||
|
||||
- **Unknown ADF node** (Atlassian ships undocumented types and adds more): carried opaquely — the
|
||||
raw JSON rides a dedicated syntax in markdown and HTML, and converts back byte-for-byte. The
|
||||
round-trip holds even for documents newer than the library.
|
||||
- **Unmappable foreign HTML element**: error result naming the element. Arbitrary HTML can express
|
||||
what ADF cannot hold, so import maps a documented element set and refuses the rest — never a
|
||||
silent drop.
|
||||
- **Bare `@name` / `:smile:` typed as plain text**: stays a text node. Only the directive syntax
|
||||
produces mention/emoji/media nodes — resolving names to ids needs I/O, which is the consumer's
|
||||
job (e.g. an autocomplete that inserts the directive).
|
||||
- Unknown ADF node: carried opaquely — raw JSON rides a dedicated syntax in both formats, restored
|
||||
byte-for-byte. The round-trip holds for documents newer than the library.
|
||||
- Unmappable foreign HTML element: error result naming the element — never a silent drop.
|
||||
- Bare `@name` / `:smile:` in typed text: stays a text node. Only directives produce
|
||||
mention/emoji/media nodes; resolving names to ids needs I/O, which is the consumer's job.
|
||||
|
||||
## 4. The flavour
|
||||
|
||||
- **Directives**, one grammar for everything markdown lacks: `:::panel info` … `:::` for blocks,
|
||||
`:mention[@Mikael]{id=5b10a2}` style inline. Prior art: CommonMark's generic-directives proposal.
|
||||
- **Plain CommonMark is a subset.** Anything a human types converts per the spec; the flavour only
|
||||
adds syntax, never changes CommonMark meaning.
|
||||
- **Tables**: a simple table (one header row, plain inline cells — no spans, widths, colours, block
|
||||
content) emits a pipe table; anything richer emits the directive form.
|
||||
- **Identity-bearing nodes** (`mention`, `media`, `emoji`) carry their ids in attributes. A
|
||||
converted document is therefore only portable within the site it came from — accepted.
|
||||
|
||||
The HTML dialect mirrors this: semantic elements, stable `adf-*` class names, `data-*` attributes
|
||||
for what HTML cannot express, text content always escaped. No stylesheet ships — styling is the
|
||||
consumer's.
|
||||
- Directives, one grammar for everything markdown lacks: `:::panel info` … `:::` blocks,
|
||||
`:mention[@Mikael]{id=5b10a2}` inline. Prior art: CommonMark's generic-directives proposal.
|
||||
- Plain CommonMark is a subset: the flavour adds syntax, never changes CommonMark meaning.
|
||||
- Tables: one header row plus plain inline cells → pipe table; anything richer → directive form.
|
||||
- Identity-bearing nodes carry their ids in attributes; a document is only portable within its
|
||||
site — accepted.
|
||||
- The HTML dialect mirrors this: semantic elements, stable `adf-*` classes, `data-*` for what HTML
|
||||
cannot express, text always escaped. No stylesheet ships.
|
||||
|
||||
## 5. Dependencies
|
||||
|
||||
`dependencies` is empty. A runtime dependency may enter only through a decision entry here stating
|
||||
why ~20 lines of our own code cannot do the job, who maintains it, and what auditing it costs;
|
||||
until that entry exists the answer is no. So the CommonMark and HTML parsers are written in this
|
||||
repo — a general parser would have to be extended into the flavour anyway.
|
||||
|
||||
`devDependencies`: few, each earning its keep. Tooling that genuinely helps (a property-test
|
||||
generator) is welcome; it never reaches a consumer.
|
||||
`dependencies` is empty. A runtime dependency enters only through a decision entry here stating
|
||||
why ~20 lines of own code cannot do the job, who maintains it, and what auditing it costs. So the
|
||||
CommonMark and HTML parsers are written in this repo. `devDependencies`: few, each earning its
|
||||
keep; they never reach a consumer.
|
||||
|
||||
## 6. The package contract
|
||||
|
||||
- **ESM only.** No CommonJS build, no dual-package hazard.
|
||||
- **Two entrypoints.** Built JavaScript for ordinary consumers; TypeScript source for consumers
|
||||
running TypeScript directly through Node's type stripping.
|
||||
- **Types for both.** The JavaScript entrypoint ships `.d.ts` beside it; the TypeScript entrypoint
|
||||
is its own types.
|
||||
- **Published to public npmjs as `@larvit/atlassian-adf-converter`**, matching `@larvit/log`.
|
||||
Public means the source is public: the Gitea repo starts private, and going public — LICENSE in
|
||||
place — is a step before the first publish, not after it.
|
||||
- **Exact versions.** `save-exact=true` in `.npmrc`, as in every other repo here.
|
||||
- ESM only — no CommonJS build, no dual-package hazard.
|
||||
- Two entrypoints: built JavaScript, and TypeScript source for Node's type stripping. Types for
|
||||
both (`.d.ts` beside the JavaScript).
|
||||
- Published to public npmjs as `@larvit/atlassian-adf-converter`. Public source: the Gitea repo
|
||||
goes public, LICENSE in place, before the first publish.
|
||||
- Exact versions: `save-exact=true` in `.npmrc`.
|
||||
|
||||
## 7. Nothing about any consumer
|
||||
|
||||
No Jira client, no HTTP, no REST response shapes, no issue keys — and no actual consumer named
|
||||
anywhere in this repo. Design against the README's personas, not against a product. A consumer's
|
||||
concern that leaks in here is a seam nobody declared.
|
||||
No Jira client, no HTTP, no REST shapes, no issue keys, no actual consumer named anywhere. Design
|
||||
against the README's personas.
|
||||
|
||||
## 8. Semver: the formats are API
|
||||
|
||||
The emitted markdown and HTML are contracts, not just the TypeScript surface. After 1.0: a change
|
||||
that makes previously-emitted output parse differently, or stop parsing, is MAJOR; teaching the
|
||||
parsers new syntax while old output still round-trips is MINOR. Documents in flight — stored,
|
||||
cached, mid-edit — survive upgrades. Pre-1.0, normal 0.x rules apply.
|
||||
The emitted markdown and HTML are contracts. After 1.0: previously-emitted output parsing
|
||||
differently, or not at all, is MAJOR; new syntax while old output still round-trips is MINOR.
|
||||
Pre-1.0, normal 0.x rules.
|
||||
|
||||
## 9. Release automation
|
||||
|
||||
- `package.json` version on `main` is the source of truth. CI on every `main` push: tests green
|
||||
and version differs from npm's latest → publish and tag `vX.Y.Z`. A merge without a bump (docs,
|
||||
CI, dep bumps) deploys nothing; the bump in each shipping PR is a deliberate semver judgment.
|
||||
- **Renovate** watches npm devDependencies, Docker image pins and action tags, and automerges
|
||||
every update — majors included — when CI is green.
|
||||
- Docker images pin the **full patch version** (`node:24.19.0-alpine3.24`, never `node:24`);
|
||||
actions pin semver tags. Renovate raises the bumps as reviewable PRs.
|
||||
- `package.json` version on `main` is the source of truth. CI on `main`: tests green and version
|
||||
differs from npm → publish and tag `vX.Y.Z`. No bump, no deploy; the bump is each shipping PR's
|
||||
deliberate semver judgment.
|
||||
- Renovate watches devDependencies, Docker pins and action tags; automerges everything on green CI.
|
||||
- Docker images pin the full patch version (`node:24.19.0-alpine3.24`, never `node:24`); actions
|
||||
pin semver tags.
|
||||
|
||||
## 10. Tests first, in Docker
|
||||
|
||||
Write the test for the behaviour wanted, then implement until it passes. `node --test`, beside the
|
||||
code. Node, tsc and npm never run on the host — a compose service or `docker run` against a pinned
|
||||
image (§9). Tests are independent: none passes because another ran first. Coverage does not
|
||||
decline. Tear down test containers after a run.
|
||||
Test for the behaviour wanted first, then implement until green. `node --test`, beside the code.
|
||||
Node, tsc and npm never run on the host — only via the pinned images (§9). Tests are independent,
|
||||
coverage does not decline, containers are torn down after a run.
|
||||
|
||||
The corpus, all checked in: hand-built fixtures per node and combination; real (sanitized) ADF
|
||||
captured from live Atlassian APIs, including undocumented nodes; property-based generated ADF trees
|
||||
(generator is a devDependency); the official CommonMark spec suite, run against `markdownToAdf`
|
||||
(mapped) and `markdownToHtml`.
|
||||
The corpus, all checked in: hand-built fixtures per node and combination; real sanitized ADF from
|
||||
live Atlassian APIs; property-generated ADF trees; the CommonMark spec suite against
|
||||
`markdownToAdf` and `markdownToHtml`.
|
||||
|
||||
## 11. Code rules
|
||||
|
||||
- Two-space indent, strict TypeScript, English everywhere. Alphabetical order for object keys,
|
||||
lists and file lists wherever order carries no meaning.
|
||||
- **Failures are values.** Every conversion returns
|
||||
`Result<T>` — `{ ok: true; value } | { ok: false; error: ConvertError }` — and nothing throws.
|
||||
- Two-space indent, strict TypeScript, English everywhere. Alphabetical order wherever order
|
||||
carries no meaning.
|
||||
- Failures are values: everything returns
|
||||
`Result<T>` — `{ ok: true; value } | { ok: false; error: ConvertError }` — nothing throws.
|
||||
`try/catch` only wrapped tightly around a call that genuinely throws, converted to a result on
|
||||
the spot; never for control flow.
|
||||
- **No casts.** `as`, `as unknown as` and non-null `!` are banned — they silence the compiler
|
||||
exactly where it is needed. A boundary owes a type guard validating the fields it claims (that
|
||||
is what `isAdfDocument` is); past it everything is typed. Make invalid states unrepresentable:
|
||||
encode "one of these, never both" in the types, not in checks.
|
||||
- Explicit over implicit. Descriptive names (`panelDirectiveParser`, not `helper`); no catch-all
|
||||
files or folders (`utils`, `helpers`, `misc`, `common`, `lib`).
|
||||
- **Reuse before adding.** Extend an existing test or function before writing a new one; the
|
||||
smallest sufficient diff is the benchmark. No speculative generality: an interface with one
|
||||
implementation, an option with one used value, or a wrapper that only forwards waits for its
|
||||
second consumer — or goes.
|
||||
the spot.
|
||||
- No casts: `as`, `as unknown as`, non-null `!`. A boundary owes a type guard validating the
|
||||
fields it claims (`isAdfDocument`); past it everything is typed. Make invalid states
|
||||
unrepresentable.
|
||||
- Explicit over implicit; descriptive names; no catch-all files (`utils`, `helpers`, `misc`).
|
||||
- Reuse before adding; the smallest sufficient diff is the benchmark; no speculative generality —
|
||||
a second consumer, or it goes.
|
||||
|
||||
## 12. Prose to a minimum
|
||||
|
||||
Stricter here than most repos. Applies to comments, docs and PR text alike.
|
||||
Applies everywhere: comments, every markdown file in this repo (this one included), PR text.
|
||||
|
||||
- **Default is no comment.** One earns its single line only by naming an invariant, footgun or
|
||||
external constraint the code cannot show. Never a restatement, never history ("we used to…"),
|
||||
never why something is absent, never how a file is arranged. A second line means it belongs in
|
||||
the commit message or a decision entry here.
|
||||
- Every prose comment in a diff is a review question, and the reviewer's default answer is delete.
|
||||
- A doc paragraph clears the same bar: it says what the repo cannot say for itself, or it goes —
|
||||
and the fix for a redundant one is deletion, not trimming. A false claim in any doc is a bug,
|
||||
fixed in the change that finds it.
|
||||
- Published text — the npm README, exported error messages, API docs — never references internal
|
||||
systems, tickets or repos.
|
||||
- Default is no comment. One earns its single line only by naming an invariant, footgun or
|
||||
external constraint the code cannot show — never restatement, history, absence or arrangement.
|
||||
A second line belongs in the commit message or a decision entry here.
|
||||
- Every prose comment in a diff is a review question; the default answer is delete.
|
||||
- A doc paragraph says what the repo cannot say for itself, or it goes. The fix for a redundant
|
||||
one is deletion, not trimming. A false claim in any doc is a bug, fixed where found.
|
||||
- Published text — npm README, error messages, API docs — never references internal systems,
|
||||
tickets or repos.
|
||||
|
||||
## 13. Commits and PRs
|
||||
|
||||
One-line commit messages and PR titles; short PR summaries. No AI-attribution markers —
|
||||
`Co-Authored-By` bots, "Generated with …" footers — in commits, PRs or comments, ever.
|
||||
One-line commit messages and PR titles; short PR summaries. No AI-attribution markers, ever.
|
||||
|
||||
## 14. Non-goals
|
||||
|
||||
Stated so nobody builds them in: no wiki markup (§1), no network or filesystem I/O, no name→id
|
||||
resolution (§3), no ADF schema validation or exported validator — whether Atlassian accepts a tree
|
||||
is Atlassian's call — no shipped CSS (§4), no streaming APIs and no performance budget: conversions
|
||||
are O(n) with no pathological blowups, and real documents are kilobytes. A CLI is a later goal
|
||||
(`todo.md`), not a non-goal.
|
||||
No wiki markup (§1), no network or filesystem I/O, no name→id resolution (§3), no ADF schema
|
||||
validation or exported validator, no shipped CSS (§4), no streaming APIs, no performance budget —
|
||||
conversions are O(n), real documents are kilobytes. A CLI is a later goal (`todo.md`), not a
|
||||
non-goal.
|
||||
|
||||
@@ -3,25 +3,21 @@
|
||||
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; `AGENTS.md`
|
||||
holds the decisions already made.
|
||||
**Status: specification only, no code yet.** Plan: `todo.md`. Decisions: `AGENTS.md`.
|
||||
|
||||
## What it is for
|
||||
|
||||
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 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 document. That is what this library is.
|
||||
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.
|
||||
|
||||
## The shape
|
||||
|
||||
Pure functions and their types. No I/O, no network, no configuration. ADF is the hub: the
|
||||
markdown↔HTML directions compose through it.
|
||||
Pure functions, no I/O, no configuration. ADF is the hub: markdown↔HTML compose through it.
|
||||
|
||||
```ts
|
||||
adfToMarkdown(doc: AdfDocument): Result<string>
|
||||
@@ -37,36 +33,27 @@ isAdfDocument(v: unknown): v is AdfDocument
|
||||
|
||||
## The guarantees
|
||||
|
||||
- **`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-*`
|
||||
- `markdownToAdf(adfToMarkdown(doc))` equals `doc` — unknown node types included, 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.
|
||||
- Plain CommonMark is valid input to `markdownToAdf`; converting back yields the library's
|
||||
canonical spelling, which round-trips byte-identically.
|
||||
- Foreign HTML maps a documented element set; an unmappable element is an error, never a silent
|
||||
drop. Well-formed HTML only — no tag-soup recovery.
|
||||
- The emitted formats are semver surface (AGENTS.md §8).
|
||||
|
||||
## Who it is for
|
||||
|
||||
No actual consumer is named here or anywhere in this repo (AGENTS.md §7). The personas the design
|
||||
serves:
|
||||
Personas, never named consumers (AGENTS.md §7):
|
||||
|
||||
- **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.
|
||||
- **Viewer/editor app** — shows a document, lets a human edit, posts back. Losslessness above all.
|
||||
- **Bot posting content** — converts generated markdown to ADF; needs the CommonMark promise.
|
||||
- **Export/indexing tool** — bulk ADF→markdown/HTML; needs readable output.
|
||||
- **LLM/agent pipeline** — documents to a model as markdown, edits back; needs the round-trip and
|
||||
markdown 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` §5–6 have the contract.
|
||||
ESM only, no runtime dependencies, public npmjs. Two entrypoints — built JavaScript, and
|
||||
TypeScript source for Node's type stripping — with types either way. Contract: `AGENTS.md` §5–6.
|
||||
|
||||
@@ -1,43 +1,38 @@
|
||||
# Todo
|
||||
|
||||
The plan, in order. Nothing here is built yet. The design questions that used to be open are
|
||||
settled in `AGENTS.md`; what remains open below is spec detail, settled at its own milestone.
|
||||
The plan, in order. Nothing is built. Design questions are settled in `AGENTS.md`; remaining spec
|
||||
detail is settled at its own milestone.
|
||||
|
||||
## Milestones
|
||||
|
||||
- [ ] **0 — Scaffold.** `package.json` with the §6 contract, `tsconfig.json`, `.npmrc`
|
||||
(`save-exact=true`), the Docker tooling setup, `renovate.json` (automerge-on-green, §9), and
|
||||
`.gitea/workflows/ci.yml` gating branches: `runs-on: docker-host`, actions pinned to semver
|
||||
tags.
|
||||
- [ ] **1 — The flavour spec.** Write the markdown flavour down as this repo's specification before
|
||||
implementing it: the exact directive grammar (attributes, escaping, nesting), each node's
|
||||
syntax from the inventory below, the opaque-carry spelling for unknown nodes, and the
|
||||
pipe-vs-directive table rule. Open detail to settle here: what CommonMark's raw-HTML
|
||||
constructs become in ADF, which has no raw-HTML node — likely the §3 element mapping, error
|
||||
otherwise. Start the corpus (§10) from this spec.
|
||||
- [ ] **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.
|
||||
- [ ] **1 — The flavour spec.** The markdown flavour written as this repo's specification before
|
||||
any implementation: the directive grammar (attributes, escaping, nesting), each node's
|
||||
syntax from the inventory below, the opaque-carry spelling, the pipe-vs-directive table
|
||||
rule, and what CommonMark's raw-HTML constructs become in ADF, which has no raw-HTML node —
|
||||
likely the §3 element mapping, error otherwise. Start the corpus (§10) from this spec.
|
||||
- [ ] **2 — `adfToMarkdown`.**
|
||||
- [ ] **3 — `markdownToAdf`.** The CommonMark parser is the largest single component.
|
||||
- [ ] **4 — Round-trip property tests** over the corpus, both ways. Not a milestone that follows
|
||||
2 and 3 so much as the thing that proves them.
|
||||
- [ ] **5 — Release pipeline, ship `0.1.0`.** Publish-on-version-change on `main` (§9),
|
||||
`NPM_TOKEN` secret, the repo made public first (§6). `0.1.0` is the markdown round-trip:
|
||||
both markdown directions, the types, `isAdfDocument`.
|
||||
- [ ] **4 — Round-trip property tests** over the corpus, both ways — the thing that proves 2 and 3.
|
||||
- [ ] **5 — Release pipeline, ship `0.1.0`.** Publish-on-version-change (§9), `NPM_TOKEN` secret,
|
||||
the repo made public first (§6). `0.1.0` is the markdown round-trip: both markdown
|
||||
directions, the types, `isAdfDocument`.
|
||||
- [ ] **6 — The HTML dialect spec.** Element-by-element mapping, the `data-*` fidelity scheme, the
|
||||
opaque-carry form, and the documented foreign-element set `htmlToAdf` accepts.
|
||||
- [ ] **7 — HTML, ship `0.2.0`.** `adfToHtml`, `htmlToAdf`, and the composed `markdownToHtml` /
|
||||
- [ ] **7 — HTML, ship `0.2.0`.** `adfToHtml`, `htmlToAdf`, the composed `markdownToHtml` /
|
||||
`htmlToMarkdown`. CommonMark spec suite runs against `markdownToHtml` from here (§10).
|
||||
- [ ] **8 — CLI.** A later goal, deliberately unspecified until the library exists: shape it
|
||||
around the personas then.
|
||||
- [ ] **8 — CLI.** A later goal, shaped around the personas once the library exists.
|
||||
|
||||
## The ADF inventory to cover
|
||||
|
||||
From Atlassian's [structure
|
||||
reference](https://developer.atlassian.com/cloud/jira/platform/apis/document/structure/). **It is
|
||||
not the whole schema** — real payloads also carry `taskList`/`taskItem`, `decisionList`/`decisionItem`,
|
||||
reference](https://developer.atlassian.com/cloud/jira/platform/apis/document/structure/) — not the
|
||||
whole schema: real payloads also carry `taskList`/`taskItem`, `decisionList`/`decisionItem`,
|
||||
`layoutSection`/`layoutColumn`, `blockCard`/`embedCard`, `extension`/`bodiedExtension`/`inlineExtension`
|
||||
and `placeholder`, none of which are documented there. Treat the documented set as the floor, not the
|
||||
ceiling — the floor gets designed syntax, everything else rides the opaque carry (§3) until it too
|
||||
gets syntax.
|
||||
and `placeholder`, none documented there. The documented set is the floor: the floor gets designed
|
||||
syntax, the rest rides the opaque carry (§3) until it does too.
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
@@ -46,6 +41,6 @@ gets syntax.
|
||||
| Inline | `date` `emoji` `hardBreak` `inlineCard` `mediaInline` `mention` `status` `text` |
|
||||
| Marks | `border` `code` `em` `link` `strike` `strong` `subsup` `textColor` `underline` |
|
||||
|
||||
Plain markdown already covers `blockquote`, `bulletList`, `codeBlock`, `heading`, `orderedList`,
|
||||
Plain markdown covers `blockquote`, `bulletList`, `codeBlock`, `heading`, `orderedList`,
|
||||
`paragraph`, `rule`, `listItem`, `hardBreak`, `text`, and the `code`, `em`, `link`, `strike` and
|
||||
`strong` marks. Everything else is what the flavour is for.
|
||||
|
||||
Reference in New Issue
Block a user