Rank discoverability third among the goals, plan the move to GitHub and the outreach, and drop a text node's content in plain #161
@@ -169,7 +169,7 @@ reading is the ask. Never ask "A or B?": state the gap, the earlier entries of i
|
|||||||
nearest text, a candidate entry in that file's voice, and the instance it yields. An entry that
|
nearest text, a candidate entry in that file's voice, and the instance it yields. An entry that
|
||||||
keeps collecting instances is wrong: rewrite it.
|
keeps collecting instances is wrong: rewrite it.
|
||||||
|
|
||||||
Which output the audience expects — README goal 5 — is settled by a reader panel rather than
|
Which output the audience expects — README goal 6 — is settled by a reader panel rather than
|
||||||
asked: three fresh-context readers, one per README persona the question serves, each given only
|
asked: three fresh-context readers, one per README persona the question serves, each given only
|
||||||
`## Audience` and the input, writing what they expect before picking among outputs the goals
|
`## Audience` and the input, writing what they expect before picking among outputs the goals
|
||||||
allow, rendered, shuffled, with no rationale and nothing saying what is implemented. Three agreeing
|
allow, rendered, shuffled, with no rationale and nothing saying what is implemented. Three agreeing
|
||||||
|
|||||||
@@ -28,13 +28,13 @@ The most useful ADF conversion library available, judged by these goals, in prio
|
|||||||
|
|
||||||
1. **Lossless, and every call returns a result, never a throw.**
|
1. **Lossless, and every call returns a result, never a throw.**
|
||||||
2. **ADF is the hub.**
|
2. **ADF is the hub.**
|
||||||
3. **Each format reads and writes as its standard says.**
|
3. **Easy to find, and clear at a glance what it does.**
|
||||||
4. **Our markdown is CommonMark, extended only where CommonMark has no spelling.**
|
4. **Each format reads and writes as its standard says.**
|
||||||
5. **No surprises: output reads and edits the way its audience expects.**
|
5. **Our markdown is CommonMark, extended only where CommonMark has no spelling.**
|
||||||
6. **Lossy conversion drops form, never content.**
|
6. **No surprises: output reads and edits the way its audience expects.**
|
||||||
7. **Runs in any JavaScript engine, with no runtime dependencies and nothing to configure or connect.**
|
7. **Lossy conversion drops form, never content.**
|
||||||
8. **Fast, and linear in the document's size as JSON.**
|
8. **Runs in any JavaScript engine, with no runtime dependencies and nothing to configure or connect.**
|
||||||
9. **Easy to find, and clear at a glance what it does.**
|
9. **Fast, and linear in the document's size as JSON.**
|
||||||
|
|
||||||
## Audience
|
## Audience
|
||||||
|
|
||||||
@@ -74,7 +74,7 @@ if (result.ok) {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Serves Goals 1, 2 and 7. Pure functions, each taking a whole document and returning a whole
|
Serves Goals 1, 2 and 8. Pure functions, each taking a whole document and returning a whole
|
||||||
result; no I/O, no configuration. `markdownToHtml` and `htmlToMarkdown` convert through ADF:
|
result; no I/O, no configuration. `markdownToHtml` and `htmlToMarkdown` convert through ADF:
|
||||||
they keep only what ADF holds, and refuse what `markdownToAdf` or `htmlToAdf` refuses.
|
they keep only what ADF holds, and refuse what `markdownToAdf` or `htmlToAdf` refuses.
|
||||||
|
|
||||||
@@ -96,7 +96,7 @@ htmlToMarkdown(html: string): Result<string> // 0.2.0
|
|||||||
|
|
||||||
## Plain markdown
|
## Plain markdown
|
||||||
|
|
||||||
Serves Goal 6. Plain markdown is a second flavour of the same grammar. `adfToPlainMarkdown` writes
|
Serves Goal 7. Plain markdown is a second flavour of the same grammar. `adfToPlainMarkdown` writes
|
||||||
markdown other tools render — GitHub, GitLab, Obsidian and the like — keeping the content and
|
markdown other tools render — GitHub, GitLab, Obsidian and the like — keeping the content and
|
||||||
dropping the rest: attributes, colours, layout, identity. Content is what a reader of the rendered
|
dropping the rest: attributes, colours, layout, identity. Content is what a reader of the rendered
|
||||||
document sees or follows: its text, images and link targets. It refuses only
|
document sees or follows: its text, images and link targets. It refuses only
|
||||||
@@ -136,6 +136,7 @@ read replaces mentions, attachments and macros with text.
|
|||||||
- A newline in text is a hard break and in an expand's title a space, edge whitespace outside a
|
- A newline in text is a hard break and in an expand's title a space, edge whitespace outside a
|
||||||
link or code span is trimmed, carriage returns and null characters are removed, and an empty
|
link or code span is trimmed, carriage returns and null characters are removed, and an empty
|
||||||
paragraph drops.
|
paragraph drops.
|
||||||
|
- A text node's `content`, which Atlassian's schema forbids, drops.
|
||||||
- An ordered list numbered past `999999999`, or adjacent ordered lists whose numbering does not
|
- An ordered list numbered past `999999999`, or adjacent ordered lists whose numbering does not
|
||||||
continue, is one bullet list keeping its numbers as text.
|
continue, is one bullet list keeping its numbers as text.
|
||||||
- A task list beside a bullet or decision list, or holding a block other than a task, joins one
|
- A task list beside a bullet or decision list, or holding a block other than a task, joins one
|
||||||
@@ -200,7 +201,7 @@ emit refuses:
|
|||||||
|
|
||||||
## The guarantees
|
## The guarantees
|
||||||
|
|
||||||
Serves Goals 1, 3 and 4.
|
Serves Goals 1, 4 and 5.
|
||||||
|
|
||||||
- `markdownToAdf(adfToMarkdown(doc))` deep-equals `doc` as JSON, for a document of plain objects
|
- `markdownToAdf(adfToMarkdown(doc))` deep-equals `doc` as JSON, for a document of plain objects
|
||||||
as `JSON.parse` builds them — every key and value as `doc` holds it, adjacent text nodes, an
|
as `JSON.parse` builds them — every key and value as `doc` holds it, adjacent text nodes, an
|
||||||
@@ -244,7 +245,7 @@ Serves Goals 1, 3 and 4.
|
|||||||
|
|
||||||
## The package
|
## The package
|
||||||
|
|
||||||
Serves Goal 7. ESM only, no runtime dependencies, public npm. Built JavaScript with `.d.ts`
|
Serves Goal 8. ESM only, no runtime dependencies, public npm. Built JavaScript with `.d.ts`
|
||||||
beside it. Pure ECMAScript at an ES2022 baseline, reaching for no host API; the test suite runs
|
beside it. Pure ECMAScript at an ES2022 baseline, reaching for no host API; the test suite runs
|
||||||
under Node, Deno and Bun, and a headless Firefox converts the corpus through the built entrypoint.
|
under Node, Deno and Bun, and a headless Firefox converts the corpus through the built entrypoint.
|
||||||
Contract: [`docs/decisions.md`](https://gitea.larvit.se/larvit/adf-codec/src/branch/main/docs/decisions.md#any-es2022-engine), §Any
|
Contract: [`docs/decisions.md`](https://gitea.larvit.se/larvit/adf-codec/src/branch/main/docs/decisions.md#any-es2022-engine), §Any
|
||||||
|
|||||||
+34
-34
@@ -24,7 +24,7 @@ sanitized and a mention keeps the test user's real account id.
|
|||||||
## Markdown in is a canonical fixpoint
|
## Markdown in is a canonical fixpoint
|
||||||
|
|
||||||
2026-08-23, the maintainer; every parsed document has converted back since 2026-10-04. Goals 1 and
|
2026-08-23, the maintainer; every parsed document has converted back since 2026-10-04. Goals 1 and
|
||||||
4. Valid while markdown input may be written by hand.
|
5. Valid while markdown input may be written by hand.
|
||||||
|
|
||||||
The other direction is a canonical fixpoint, not byte-identity: human markdown normalizes, the way
|
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.
|
back yields the library's canonical spelling, and that spelling round-trips byte-identically.
|
||||||
@@ -54,7 +54,7 @@ around the leaf. The grammar: `spec/flavour.md` §Inline nodes, **Adjacent text
|
|||||||
|
|
||||||
## An empty key spells `empty`
|
## An empty key spells `empty`
|
||||||
|
|
||||||
2026-10-03, a writer panel and the maintainer. Goals 1 and 5. Valid while no attribute value
|
2026-10-03, a writer panel and the maintainer. Goals 1 and 6. Valid while no attribute value
|
||||||
spells an empty object or array.
|
spells an empty object or array.
|
||||||
|
|
||||||
An `attrs`, `content` or `marks` key holding an empty object or array is the reserved key with the
|
An `attrs`, `content` or `marks` key holding an empty object or array is the reserved key with the
|
||||||
@@ -71,7 +71,7 @@ reads it back as `-0`. The grammar: `spec/flavour.md` §Block nodes and §The Co
|
|||||||
|
|
||||||
## Empty markdown is a document of no blocks
|
## Empty markdown is a document of no blocks
|
||||||
|
|
||||||
2026-10-03, a writer panel and the maintainer. Goals 1 and 5. Valid while ADF's schema requires
|
2026-10-03, a writer panel and the maintainer. Goals 1 and 6. Valid while ADF's schema requires
|
||||||
`content` on `doc`.
|
`content` on `doc`.
|
||||||
|
|
||||||
Markdown holding no block reads as `{ content: [], type: 'doc', version: 1 }`, the document
|
Markdown holding no block reads as `{ content: [], type: 'doc', version: 1 }`, the document
|
||||||
@@ -94,7 +94,7 @@ checking its position, and refusing loses a document ADF itself keeps in an `uns
|
|||||||
|
|
||||||
## The carry fence names the node type
|
## The carry fence names the node type
|
||||||
|
|
||||||
2026-10-03, the maintainer. Goals 1 and 5. Valid while a code fence's info string reads back
|
2026-10-03, the maintainer. Goals 1 and 6. Valid while a code fence's info string reads back
|
||||||
verbatim.
|
verbatim.
|
||||||
|
|
||||||
The block carry is a code fence whose info string `adf:<type>` names the node's type, its body the
|
The block carry is a code fence whose info string `adf:<type>` names the node's type, its body the
|
||||||
@@ -118,7 +118,7 @@ language, and none is empty beside another, since a text node holds text. The gr
|
|||||||
|
|
||||||
## Foreign HTML sorts three ways
|
## Foreign HTML sorts three ways
|
||||||
|
|
||||||
2026-08-23, the sort 2026-09-20, the maintainer. Goals 1 and 4. Valid while ADF holds no node
|
2026-08-23, the sort 2026-09-20, the maintainer. Goals 1 and 5. Valid while ADF holds no node
|
||||||
for a bare container, a comment or a script. Lands with `todo.md` item 6.
|
for a bare container, a comment or a script. Lands with `todo.md` item 6.
|
||||||
|
|
||||||
Every foreign element `htmlToAdf` and `markdownToAdf` read sorts one of three ways, never a silent
|
Every foreign element `htmlToAdf` and `markdownToAdf` read sorts one of three ways, never a silent
|
||||||
@@ -139,14 +139,14 @@ than they buy.
|
|||||||
|
|
||||||
## Names stay text
|
## Names stay text
|
||||||
|
|
||||||
2026-08-23, the maintainer. Goal 7. Valid while resolving a name to an id needs I/O.
|
2026-08-23, the maintainer. Goal 8. Valid while resolving a name to an id needs I/O.
|
||||||
|
|
||||||
A bare `@name` or `:smile:` in typed text stays a text node. Only directives produce
|
A bare `@name` or `:smile:` in typed text stays a text node. Only directives produce
|
||||||
mention/emoji/media nodes; resolving names to ids is the consumer's job.
|
mention/emoji/media nodes; resolving names to ids is the consumer's job.
|
||||||
|
|
||||||
## Directives under `!adf:`
|
## Directives under `!adf:`
|
||||||
|
|
||||||
2026-08-23, prefixed `!adf:` 2026-09-16, the maintainer. Goals 4 and 5. Valid while prose does not
|
2026-08-23, prefixed `!adf:` 2026-09-16, the maintainer. Goals 5 and 6. Valid while prose does not
|
||||||
write `!adf:`.
|
write `!adf:`.
|
||||||
|
|
||||||
Directives are one grammar for everything markdown lacks, namespaced under `!adf:`:
|
Directives are one grammar for everything markdown lacks, namespaced under `!adf:`:
|
||||||
@@ -157,7 +157,7 @@ opener nests by itself and leaf versus container falls out of the node's content
|
|||||||
|
|
||||||
## CommonMark is a subset
|
## CommonMark is a subset
|
||||||
|
|
||||||
2026-08-23, the maintainer. Goal 4. Valid while prose rarely writes the shapes the carve-outs claim.
|
2026-08-23, the maintainer. Goal 5. Valid while prose rarely writes the shapes the carve-outs claim.
|
||||||
|
|
||||||
Plain CommonMark is a subset, with carve-outs (`spec/flavour.md`): literal text shaped like a
|
Plain CommonMark is a subset, with carve-outs (`spec/flavour.md`): literal text shaped like a
|
||||||
directive, a pipe table or a `~~` pair is claimed, and so is a code fence whose info string opens
|
directive, a pipe table or a `~~` pair is claimed, and so is a code fence whose info string opens
|
||||||
@@ -166,14 +166,14 @@ claims, giving CommonMark its own reader, and `todo.md` item 65 ends the image g
|
|||||||
|
|
||||||
## Tables
|
## Tables
|
||||||
|
|
||||||
2026-08-23, the maintainer. Goal 5. Valid while a pipe table holds only one header row and inline
|
2026-08-23, the maintainer. Goal 6. Valid while a pipe table holds only one header row and inline
|
||||||
cells.
|
cells.
|
||||||
|
|
||||||
One header row plus plain inline cells → pipe table; anything richer → directive form.
|
One header row plus plain inline cells → pipe table; anything richer → directive form.
|
||||||
|
|
||||||
## Links
|
## Links
|
||||||
|
|
||||||
2026-09-13, nesting 2026-09-17, the maintainer. Goals 1 and 5. Valid while CommonMark's link
|
2026-09-13, nesting 2026-09-17, the maintainer. Goals 1 and 6. Valid while CommonMark's link
|
||||||
syntax is what readers edit.
|
syntax is what readers edit.
|
||||||
|
|
||||||
`[text](url "title")`, or `<url>` for a bare autolink-shaped text, wherever CommonMark spells the
|
`[text](url "title")`, or `<url>` for a bare autolink-shaped text, wherever CommonMark spells the
|
||||||
@@ -192,7 +192,7 @@ accepted.
|
|||||||
|
|
||||||
## Plain task ids come from position
|
## Plain task ids come from position
|
||||||
|
|
||||||
2026-09-26, spelling 2026-09-29, the maintainer. Goals 6 and 7. Valid while a site rejects a task
|
2026-09-26, spelling 2026-09-29, the maintainer. Goals 7 and 8. Valid while a site rejects a task
|
||||||
node with no `localId`.
|
node with no `localId`.
|
||||||
|
|
||||||
`plainMarkdownToAdf` gives each `taskList`, `taskItem` and `blockTaskItem` lacking one a `localId`
|
`plainMarkdownToAdf` gives each `taskList`, `taskItem` and `blockTaskItem` lacking one a `localId`
|
||||||
@@ -204,7 +204,7 @@ ids are only skipped.
|
|||||||
|
|
||||||
## A callout title keeps its link targets
|
## A callout title keeps its link targets
|
||||||
|
|
||||||
2026-09-29, the maintainer. Goals 5 and 6. Valid while an expand's `title` is a string.
|
2026-09-29, the maintainer. Goals 6 and 7. Valid while an expand's `title` is a string.
|
||||||
|
|
||||||
`plainMarkdownToAdf` writes a link in a folded callout's title as its text and its target in
|
`plainMarkdownToAdf` writes a link in a folded callout's title as its text and its target in
|
||||||
parentheses: `> [!faq]- See [x](http://y)` reads to the title `See x (http://y)`. A link whose text
|
parentheses: `> [!faq]- See [x](http://y)` reads to the title `See x (http://y)`. A link whose text
|
||||||
@@ -213,7 +213,7 @@ is its target, with or without `mailto:`, keeps its text alone: `<http://y>` tit
|
|||||||
|
|
||||||
## The plain flavour's spellings
|
## The plain flavour's spellings
|
||||||
|
|
||||||
2026-09-14, panels 2026-09-25 and 2026-09-29, the maintainer. Goals 5 and 6. Valid while GitHub's
|
2026-09-14, panels 2026-09-25 and 2026-09-29, the maintainer. Goals 6 and 7. Valid while GitHub's
|
||||||
renderer is the one the audience's markdown is read in.
|
renderer is the one the audience's markdown is read in.
|
||||||
|
|
||||||
README §Plain markdown's rows come from a survey of GitHub, GitLab, Gitea, Obsidian, Pandoc,
|
README §Plain markdown's rows come from a survey of GitHub, GitLab, Gitea, Obsidian, Pandoc,
|
||||||
@@ -232,7 +232,7 @@ past `[x]`/`[ ]`, and lifting bare URLs, `@name`, `:shortcode:` or ISO dates int
|
|||||||
|
|
||||||
## The HTML dialect
|
## The HTML dialect
|
||||||
|
|
||||||
2026-08-23, the maintainer. Goals 5 and 7. Valid while HTML output is read by consumers styling it
|
2026-08-23, the maintainer. Goals 6 and 8. Valid while HTML output is read by consumers styling it
|
||||||
themselves.
|
themselves.
|
||||||
|
|
||||||
The HTML dialect mirrors the markdown flavour: semantic elements, stable `adf-*` classes, `data-*`
|
The HTML dialect mirrors the markdown flavour: semantic elements, stable `adf-*` classes, `data-*`
|
||||||
@@ -240,7 +240,7 @@ for what HTML cannot express, text always escaped. No stylesheet ships.
|
|||||||
|
|
||||||
## No runtime dependencies
|
## No runtime dependencies
|
||||||
|
|
||||||
2026-08-23, the maintainer. Goal 7. Valid while ~20 lines of own code, or a vendored table, do each
|
2026-08-23, the maintainer. Goal 8. Valid while ~20 lines of own code, or a vendored table, do each
|
||||||
job a dependency would.
|
job a dependency would.
|
||||||
|
|
||||||
`dependencies` is empty. A runtime dependency enters only through an entry here stating why ~20
|
`dependencies` is empty. A runtime dependency enters only through an entry here stating why ~20
|
||||||
@@ -250,7 +250,7 @@ and HTML parsers are written in this repo.
|
|||||||
## Standards ship as data
|
## Standards ship as data
|
||||||
|
|
||||||
2026-08-30, the CommonMark suite 2026-09-05 and ADF's schemas 2026-09-13, the maintainer. Goals 1,
|
2026-08-30, the CommonMark suite 2026-09-05 and ADF's schemas 2026-09-13, the maintainer. Goals 1,
|
||||||
4 and 7. Valid while each table is fixed data a dependency would only wrap.
|
5 and 8. Valid while each table is fixed data a dependency would only wrap.
|
||||||
|
|
||||||
A table a standard fixes is data rather than a dependency: HTML5's 2125 semicolon-terminated
|
A table a standard fixes is data rather than a dependency: HTML5's 2125 semicolon-terminated
|
||||||
character references ship packed in their own module, so entity decoding is complete without one.
|
character references ship packed in their own module, so entity decoding is complete without one.
|
||||||
@@ -273,7 +273,7 @@ nodes that break it.
|
|||||||
|
|
||||||
## Any ES2022 engine
|
## Any ES2022 engine
|
||||||
|
|
||||||
2026-09-01, the maintainer. Goal 7. Valid while ES2022 is the floor browsers and servers share.
|
2026-09-01, the maintainer. Goal 8. Valid while ES2022 is the floor browsers and servers share.
|
||||||
|
|
||||||
The library runs on any ES2022 engine, not only Node — a browser as readily as a server. The
|
The library runs on any ES2022 engine, not only Node — a browser as readily as a server. The
|
||||||
shipped source is ECMAScript and nothing else: no host import, no host global, no DOM.
|
shipped source is ECMAScript and nothing else: no host import, no host global, no DOM.
|
||||||
@@ -287,13 +287,13 @@ never the higher one those repo-only tools want.
|
|||||||
|
|
||||||
## ESM only
|
## ESM only
|
||||||
|
|
||||||
2026-08-23, the maintainer. Goal 7. Valid while the audience's toolchains all import ES modules.
|
2026-08-23, the maintainer. Goal 8. Valid while the audience's toolchains all import ES modules.
|
||||||
|
|
||||||
No CommonJS build, no dual-package hazard.
|
No CommonJS build, no dual-package hazard.
|
||||||
|
|
||||||
## One built entrypoint
|
## One built entrypoint
|
||||||
|
|
||||||
2026-08-23, the maintainer. Goal 7. Valid while Node refuses to type-strip under `node_modules`.
|
2026-08-23, the maintainer. Goal 8. Valid while Node refuses to type-strip under `node_modules`.
|
||||||
|
|
||||||
Built JavaScript, `.d.ts` beside it. Do not add a TypeScript-source entrypoint — Node refuses to
|
Built JavaScript, `.d.ts` beside it. Do not add a TypeScript-source entrypoint — Node refuses to
|
||||||
type-strip under `node_modules` (`ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING`), so it cannot serve
|
type-strip under `node_modules` (`ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING`), so it cannot serve
|
||||||
@@ -301,7 +301,7 @@ an npm consumer.
|
|||||||
|
|
||||||
## Public on npm
|
## Public on npm
|
||||||
|
|
||||||
2026-08-23, the name 2026-09-01, the maintainer. Goals 2 and 7. Valid while the package's source
|
2026-08-23, the name 2026-09-01, the maintainer. Goals 2 and 8. Valid while the package's source
|
||||||
stays public beside it.
|
stays public beside it.
|
||||||
|
|
||||||
Published to public npm as `@larvit/adf-codec`. Public source: the Gitea repo goes public,
|
Published to public npm as `@larvit/adf-codec`. Public source: the Gitea repo goes public,
|
||||||
@@ -375,7 +375,7 @@ handles one cause alike whichever node, attribute or direction raised it.
|
|||||||
|
|
||||||
## `message` and `path`
|
## `message` and `path`
|
||||||
|
|
||||||
2026-09-03, the path 2026-09-23, the maintainer. Goals 1 and 4. Valid while a person fixing the
|
2026-09-03, the path 2026-09-23, the maintainer. Goals 1 and 5. Valid while a person fixing the
|
||||||
input reads `message`.
|
input reads `message`.
|
||||||
|
|
||||||
- A message names the violation, not the rule alone — a rule by itself states a truth the reader
|
- A message names the violation, not the rule alone — a rule by itself states a truth the reader
|
||||||
@@ -389,7 +389,7 @@ input reads `message`.
|
|||||||
|
|
||||||
## Publish on a version bump
|
## Publish on a version bump
|
||||||
|
|
||||||
2026-08-23, converging 2026-09-03, the maintainer. Goal 7. Valid while CI on `main` holds the npm
|
2026-08-23, converging 2026-09-03, the maintainer. Goal 8. Valid while CI on `main` holds the npm
|
||||||
token.
|
token.
|
||||||
|
|
||||||
`package.json` version on `main` is the source of truth. CI on `main`: tests green and the version
|
`package.json` version on `main` is the source of truth. CI on `main`: tests green and the version
|
||||||
@@ -405,7 +405,7 @@ depend on a store that the gate would then have to keep.
|
|||||||
|
|
||||||
## Docs describe the release being built
|
## Docs describe the release being built
|
||||||
|
|
||||||
2026-09-16, the maintainer. Goal 7. Valid while a bump on `main` publishes.
|
2026-09-16, the maintainer. Goal 8. Valid while a bump on `main` publishes.
|
||||||
|
|
||||||
Docs on `main` describe the release being built rather than the version npm holds, so they match it
|
Docs on `main` describe the release being built rather than the version npm holds, so they match it
|
||||||
the moment the bump publishes; add no interim note marking the gap.
|
the moment the bump publishes; add no interim note marking the gap.
|
||||||
@@ -421,7 +421,7 @@ nesting a spelling inside its own kind (`*(*a*)*`) names that mark once.
|
|||||||
|
|
||||||
## The gate runs on Deno and Bun
|
## The gate runs on Deno and Bun
|
||||||
|
|
||||||
2026-09-01, Deno's reason 2026-09-28, the maintainer. Goals 3 and 7. Valid while the library claims
|
2026-09-01, Deno's reason 2026-09-28, the maintainer. Goals 4 and 8. Valid while the library claims
|
||||||
any ES2022 engine.
|
any ES2022 engine.
|
||||||
|
|
||||||
The gate runs the suite under Deno and Bun as well as Node. Bun runs JavaScriptCore, the one engine
|
The gate runs the suite under Deno and Bun as well as Node. Bun runs JavaScriptCore, the one engine
|
||||||
@@ -433,7 +433,7 @@ over the corpus rather than over a smoke import.
|
|||||||
|
|
||||||
## The gate installs the tarball
|
## The gate installs the tarball
|
||||||
|
|
||||||
2026-09-03, the maintainer. Goal 7. Valid while consumers install the packed package.
|
2026-09-03, the maintainer. Goal 8. Valid while consumers install the packed package.
|
||||||
|
|
||||||
The gate packs the build and installs the tarball under `package-tests/`, so `files`, `exports`
|
The gate packs the build and installs the tarball under `package-tests/`, so `files`, `exports`
|
||||||
and `types` are proved on the artifact that ships rather than on the source tree a self-reference
|
and `types` are proved on the artifact that ships rather than on the source tree a self-reference
|
||||||
@@ -445,7 +445,7 @@ resolver maps them, under `NodeNext` alone; a `.d.ts` reader that is not `tsc` s
|
|||||||
|
|
||||||
## Firefox reads the build
|
## Firefox reads the build
|
||||||
|
|
||||||
2026-09-04, the maintainer. Goal 7. Valid while the library claims a browser and no other leg runs
|
2026-09-04, the maintainer. Goal 8. Valid while the library claims a browser and no other leg runs
|
||||||
SpiderMonkey.
|
SpiderMonkey.
|
||||||
|
|
||||||
A headless Firefox loads `dist/index.js` over HTTP and converts the round-trip, normalization and
|
A headless Firefox loads `dist/index.js` over HTTP and converts the round-trip, normalization and
|
||||||
@@ -517,7 +517,7 @@ markdown, on a fixed seed in the gate; a counterexample found becomes a round-tr
|
|||||||
|
|
||||||
## The CommonMark suite checks three ways
|
## The CommonMark suite checks three ways
|
||||||
|
|
||||||
2026-08-27, the maintainer. Goals 1 and 3. Valid while the suite's answers are HTML ADF cannot be
|
2026-08-27, the maintainer. Goals 1 and 4. Valid while the suite's answers are HTML ADF cannot be
|
||||||
compared against.
|
compared against.
|
||||||
|
|
||||||
Each example is a named error or markdown that parses and emits to itself byte for byte; its
|
Each example is a named error or markdown that parses and emits to itself byte for byte; its
|
||||||
@@ -564,12 +564,12 @@ once doubled the parser's frames per level.
|
|||||||
|
|
||||||
## No value `JSON.parse` or `structuredClone` builds loops a call
|
## No value `JSON.parse` or `structuredClone` builds loops a call
|
||||||
|
|
||||||
2026-10-04, the maintainer. Goals 1 and 8. Valid while every persona hands in plain data, as
|
2026-10-04, the maintainer. Goals 1 and 9. Valid while every persona hands in plain data, as
|
||||||
`JSON.parse` or `structuredClone` builds it.
|
`JSON.parse` or `structuredClone` builds it.
|
||||||
|
|
||||||
The guards walk a value by `Object.values` and refuse a cycle in it. An inherited or non-enumerable
|
The guards walk a value by `Object.values` and refuse a cycle in it. An inherited or non-enumerable
|
||||||
key, a getter, a Proxy or a custom iterator is the caller's to rule out: no persona builds one.
|
key, a getter, a Proxy or a custom iterator is the caller's to rule out: no persona builds one.
|
||||||
Goal 8's size counts ADF as JSON, so a shared object costs once per place it is held.
|
Goal 9's size counts ADF as JSON, so a shared object costs once per place it is held.
|
||||||
|
|
||||||
## Nothing spreads an unbounded array
|
## Nothing spreads an unbounded array
|
||||||
|
|
||||||
@@ -589,7 +589,7 @@ termination is the loop's own check.
|
|||||||
|
|
||||||
## Readers scan by index
|
## Readers scan by index
|
||||||
|
|
||||||
2026-08-30, the kept scan 2026-09-18, the maintainer. Goal 8. Valid while the pipeline persona
|
2026-08-30, the kept scan 2026-09-18, the maintainer. Goal 9. Valid while the pipeline persona
|
||||||
feeds documents nobody typed.
|
feeds documents nobody typed.
|
||||||
|
|
||||||
A reader takes the text and an index — a sticky regex whose `lastIndex` the caller sets on the line
|
A reader takes the text and an index — a sticky regex whose `lastIndex` the caller sets on the line
|
||||||
@@ -601,7 +601,7 @@ two cannot disagree — which is what makes the kept value a memo rather than a
|
|||||||
|
|
||||||
## The spelling memo
|
## The spelling memo
|
||||||
|
|
||||||
2026-09-19, the maintainer. Goal 8. Valid while the `commonMarkSpelling` ask spells a node once
|
2026-09-19, the maintainer. Goal 9. Valid while the `commonMarkSpelling` ask spells a node once
|
||||||
per level above it otherwise.
|
per level above it otherwise.
|
||||||
|
|
||||||
The parse and the plain reduction keep each node's readable spelling in a memo, so the
|
The parse and the plain reduction keep each node's readable spelling in a memo, so the
|
||||||
@@ -613,7 +613,7 @@ one. Only what succeeded is kept, so no path minted at another position is ever
|
|||||||
|
|
||||||
## Cost fixes are measured, never timed
|
## Cost fixes are measured, never timed
|
||||||
|
|
||||||
2026-09-18, the maintainer and the stability-reviewer. Goal 8. Valid while Goal 8 promises growth
|
2026-09-18, the maintainer and the stability-reviewer. Goal 9. Valid while Goal 9 promises growth
|
||||||
rather than a figure.
|
rather than a figure.
|
||||||
|
|
||||||
A cost fix that changes no behaviour lands on the suite staying green with no fixture output
|
A cost fix that changes no behaviour lands on the suite staying green with no fixture output
|
||||||
@@ -632,7 +632,7 @@ refuses it — which is how the whitespace carry finds a line edge.
|
|||||||
|
|
||||||
## Emphasis follows CommonMark's matching
|
## Emphasis follows CommonMark's matching
|
||||||
|
|
||||||
2026-08-27, the maintainer. Goals 1 and 3. Valid while CommonMark's emphasis rules are the
|
2026-08-27, the maintainer. Goals 1 and 4. Valid while CommonMark's emphasis rules are the
|
||||||
reader's.
|
reader's.
|
||||||
|
|
||||||
Emphasis is spelled against CommonMark's matching, never flanking alone: a delimiter run in text
|
Emphasis is spelled against CommonMark's matching, never flanking alone: a delimiter run in text
|
||||||
@@ -641,7 +641,7 @@ only ones in play, and a pair that matching hands to another delimiter rides the
|
|||||||
|
|
||||||
## Readable spellings take the `try` prefix
|
## Readable spellings take the `try` prefix
|
||||||
|
|
||||||
2026-09-21, the maintainer. Goals 1 and 5. Valid while a readable spelling's refusal would cost a
|
2026-09-21, the maintainer. Goals 1 and 6. Valid while a readable spelling's refusal would cost a
|
||||||
document the general form spells.
|
document the general form spells.
|
||||||
|
|
||||||
A readable spelling tried ahead of a general one takes the `try` prefix and fails only where the
|
A readable spelling tried ahead of a general one takes the `try` prefix and fails only where the
|
||||||
|
|||||||
@@ -314,6 +314,7 @@ test('breaks a line at a newline and trims whitespace at every edge CommonMark s
|
|||||||
assert.equal(plain(node('heading', { level: 1 }, text(' h\ni '))), '# h i\n')
|
assert.equal(plain(node('heading', { level: 1 }, text(' h\ni '))), '# h i\n')
|
||||||
assert.equal(plain(paragraph(text('a\r\u0000b'))), 'ab\n')
|
assert.equal(plain(paragraph(text('a\r\u0000b'))), 'ab\n')
|
||||||
assert.equal(plain(paragraph(text('x', link('/u', 't\r\u0000')))), '[x](/u "t")\n')
|
assert.equal(plain(paragraph(text('x', link('/u', 't\r\u0000')))), '[x](/u "t")\n')
|
||||||
|
assert.equal(plain(paragraph({ content: [text('dropped')], text: 'a', type: 'text' }, text('b'))), 'ab\n')
|
||||||
})
|
})
|
||||||
|
|
||||||
test('keeps a code span opening a line, whose backticks open no fence', () => {
|
test('keeps a code span opening a line, whose backticks open no fence', () => {
|
||||||
|
|||||||
@@ -77,9 +77,9 @@ test('normalizes blocks and mark attributes nesting far past the levels a recurs
|
|||||||
assert.deepEqual(merged.content?.[0]?.content?.map((text) => text.text), ['ab'])
|
assert.deepEqual(merged.content?.[0]?.content?.map((text) => text.text), ['ab'])
|
||||||
})
|
})
|
||||||
|
|
||||||
test('joins a text node holding content to no neighbour, so its content is kept', () => {
|
test('drops the content a text node holds, and joins the node as any other', () => {
|
||||||
const holding: AdfNode = { content: [{ text: 'kept', type: 'text' }], text: 'a', type: 'text' }
|
const holding: AdfNode = { content: [{ text: 'dropped', type: 'text' }], text: 'a', type: 'text' }
|
||||||
for (const content of [[holding, { text: 'b', type: 'text' }], [{ text: 'b', type: 'text' }, holding]]) {
|
const normal = (content: AdfNode[]): AdfNode[] | undefined => toEditorNormal({ content: [{ content, type: 'paragraph' }], type: 'doc', version: 1 }).content
|
||||||
assert.deepEqual(toEditorNormal({ content: [{ content, type: 'paragraph' }], type: 'doc', version: 1 }).content, [{ content, type: 'paragraph' }])
|
assert.deepEqual(normal([holding, { text: 'b', type: 'text' }]), [{ content: [{ text: 'ab', type: 'text' }], type: 'paragraph' }])
|
||||||
}
|
assert.deepEqual(normal([{ text: 'b', type: 'text' }, holding]), [{ content: [{ text: 'ba', type: 'text' }], type: 'paragraph' }])
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ type JsonContainer = JsonValue[] | { [key: string]: JsonValue }
|
|||||||
|
|
||||||
type NodeHolder = { content?: AdfNode[] }
|
type NodeHolder = { content?: AdfNode[] }
|
||||||
|
|
||||||
// The editor's rule is the reader's over the pair's editor-normal forms; a text node holding content never joins, so its content is kept.
|
// The editor's rule is the reader's over the pair's editor-normal forms.
|
||||||
export function joinsWhenEditorNormal(previous: AdfNode, node: AdfNode): boolean {
|
export function joinsWhenEditorNormal(previous: AdfNode, node: AdfNode): boolean {
|
||||||
if (previous.type !== 'text' || node.type !== 'text' || nodeContent(previous).length > 0 || nodeContent(node).length > 0) return false
|
if (previous.type !== 'text' || node.type !== 'text') return false
|
||||||
return joinsWhenRead(normalNode(previous), normalNode(node))
|
return joinsWhenRead(normalNode(previous), normalNode(node))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -25,7 +25,8 @@ export function toEditorNormal(document: AdfDocument): AdfDocument {
|
|||||||
if (content.length === 0) continue
|
if (content.length === 0) continue
|
||||||
entry.holder.content = content.map((source) => {
|
entry.holder.content = content.map((source) => {
|
||||||
const holder = normalNode(source)
|
const holder = normalNode(source)
|
||||||
pending.push({ holder, source })
|
// Atlassian's schema allows a text node no content, so the editor keeps none.
|
||||||
|
if (source.type !== 'text') pending.push({ holder, source })
|
||||||
return holder
|
return holder
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
`Bar = 9`
|
`Bar = 9`
|
||||||
|
|
||||||
`Next ID = 70`
|
`Next ID = 74`
|
||||||
|
|
||||||
| Goal | W |
|
| Goal | W |
|
||||||
|---|---|
|
|---|---|
|
||||||
@@ -24,38 +24,40 @@
|
|||||||
|
|
||||||
| ID | Release | Exempt | Item | R | S | A | G | Goals | Score |
|
| ID | Release | Exempt | Item | R | S | A | G | Goals | Score |
|
||||||
|---|---|---|---|---|---|---|---|---|---|
|
|---|---|---|---|---|---|---|---|---|---|
|
||||||
| 7 | 0.2.0 | | **Ship HTML: `adfToHtml`, `htmlToAdf`, and `markdownToHtml` / `htmlToMarkdown` composed through ADF.** | 6 | 9 | 9 | 9 | 2, 3 | 25.8 |
|
| 7 | 0.2.0 | | **Ship HTML: `adfToHtml`, `htmlToAdf`, and `markdownToHtml` / `htmlToMarkdown` composed through ADF.** | 6 | 9 | 9 | 9 | 2, 4 | 25.8 |
|
||||||
| 45 | 0.2.0 | | **Replace `isAdfDocument` with a reader returning `Result<AdfDocument>`.** | 2 | 3 | 6 | 8 | 1 | 25.2 |
|
| 45 | 0.2.0 | | **Replace `isAdfDocument` with a reader returning `Result<AdfDocument>`.** | 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 |
|
| 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, 3 | 24.7 |
|
| 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, 7 | 23.0 |
|
| 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 |
|
||||||
| 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 |
|
| 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 |
|
||||||
| 43 | 0.2.0 | decision | **Give each markdown input its own reader, strict to its own standard.** | 6 | 7 | 8 | 9 | 3, 4 | 22.3 |
|
| 71 | 0.2.0 | | **Compare the package with the other ADF converters in the README, each claim checked against that package's own docs or behaviour.** | 1 | 3 | 6 | 7 | 3 | 21.2 |
|
||||||
| 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 | 3, 4, 6 | 18.7 |
|
| 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 |
|
| 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 |
|
||||||
| 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 | 3, 4 | 17.2 |
|
| 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 |
|
||||||
|
| 72 | 0.2.0 | | **Make GitHub the canonical repository, with `gitea.larvit.se/larvit/adf-codec` a read-only pull mirror of it.** | 5 | 6 | 5 | 8 | 3 | 16.0 |
|
||||||
|
| 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 |
|
||||||
| 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 |
|
| 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 |
|
||||||
| 52 | 0.2.0 | | **Spell `colwidth` as a comma list, `colwidth="340,420"`.** | 3 | 3 | 5 | 6 | 5 | 13.0 |
|
| 52 | 0.2.0 | | **Spell `colwidth` as a comma list, `colwidth="340,420"`.** | 3 | 3 | 5 | 6 | 6 | 11.5 |
|
||||||
| 51 | 0.2.0 | | **Match a reference label to its definition under Unicode case folding.** | 2 | 2 | 2 | 7 | 3, 4 | 12.4 |
|
| 51 | 0.2.0 | | **Match a reference label to its definition under Unicode case folding.** | 2 | 2 | 2 | 7 | 4, 5 | 10.9 |
|
||||||
| 53 | 0.2.0 | | **Put a block's `marks` spelling to the writer panel and adopt its pick.** | 4 | 5 | 5 | 6 | 5 | 11.5 |
|
|
||||||
| 60 | 0.2.0 | decision | **Collect the questions `parse/` asks `emit/` into one named module.** | 3 | 4 | 2 | 6 | 2 | 10.7 |
|
| 60 | 0.2.0 | decision | **Collect the questions `parse/` asks `emit/` into one named module.** | 3 | 4 | 2 | 6 | 2 | 10.7 |
|
||||||
| 59 | 0.2.0 | decision | **Group the directive grammar into `src/markdown/directive/`, move `Read<T>` to `result.ts`, and move `Flavour` to `markdown/flavour.ts`.** | 3 | 5 | 2 | 6 | 2 | 10.4 |
|
| 59 | 0.2.0 | decision | **Group the directive grammar into `src/markdown/directive/`, move `Read<T>` to `result.ts`, and move `Flavour` to `markdown/flavour.ts`.** | 3 | 5 | 2 | 6 | 2 | 10.4 |
|
||||||
| 50 | 0.2.0 | | **Read `[](/url)` and `[]()` as CommonMark's empty link.** | 4 | 4 | 3 | 6 | 3, 4, 6 | 10.4 |
|
| 53 | 0.2.0 | | **Put a block's `marks` spelling to the writer panel and adopt its pick.** | 4 | 5 | 5 | 6 | 6 | 10.0 |
|
||||||
| 62 | 0.2.0 | decision | **Move the plain flavour's reading out of `parse/` and its writing out of `emit/` into `markdown/plain/`, so `emit/` no longer imports `plain/`.** | 4 | 5 | 2 | 6 | 2 | 9.4 |
|
| 62 | 0.2.0 | decision | **Move the plain flavour's reading out of `parse/` and its writing out of `emit/` into `markdown/plain/`, so `emit/` no longer imports `plain/`.** | 4 | 5 | 2 | 6 | 2 | 9.4 |
|
||||||
|
| 50 | 0.2.0 | | **Read `[](/url)` and `[]()` as CommonMark's empty link.** | 4 | 4 | 3 | 6 | 4, 5, 7 | 9.0 |
|
||||||
|
| 47 | 0.3.0 | | **Open the README with what the package is, what it does and for whom.** | 1 | 4 | 7 | 9 | 3 | 26.0 |
|
||||||
|
| 73 | 0.3.0 | | **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.** | 1 | 2 | 6 | 8 | 3 | 23.0 |
|
||||||
|
| 9 | 0.3.0 | | **Ship an online sandbox: a web page with two textboxes converting between ADF and markdown on the library's browser build.** | 2 | 6 | 6 | 8 | 3 | 21.0 |
|
||||||
| 38 | 0.3.0 | | **Spell a lone surrogate in a text node so it survives a UTF-8 encode.** | 2 | 2 | 4 | 7 | 1 | 19.5 |
|
| 38 | 0.3.0 | | **Spell a lone surrogate in a text node so it survives a UTF-8 encode.** | 2 | 2 | 4 | 7 | 1 | 19.5 |
|
||||||
| 47 | 0.3.0 | | **Open the README with what the package is, what it does and for whom.** | 1 | 4 | 7 | 9 | 9 | 14.0 |
|
| 48 | 0.3.0 | | **Keep the release path publishing past npm's bypass-2FA token retirement.** | 4 | 4 | 8 | 3 | 3 | 15.7 |
|
||||||
| 34 | 0.3.0 | | **Read emphasis flanking by the whole character beside an astral symbol.** | 2 | 3 | 3 | 6 | 3, 4 | 12.6 |
|
| 46 | 0.3.0 | | **Publish the bundle size in the README, failing the release pipeline when it drifts.** | 2 | 4 | 5 | 5 | 3, 8 | 14.8 |
|
||||||
| 42 | 0.3.0 | | **Trim a text leaf's trailing blanks in linear time.** | 1 | 2 | 5 | 9 | 8 | 12.5 |
|
| 34 | 0.3.0 | | **Read emphasis flanking by the whole character beside an astral symbol.** | 2 | 3 | 3 | 6 | 4, 5 | 11.3 |
|
||||||
| 48 | 0.3.0 | | **Keep the release path publishing past npm's bypass-2FA token retirement.** | 4 | 4 | 8 | 3 | 9 | 11.7 |
|
|
||||||
| 31 | 0.3.0 | | **Make the branch-coverage figure repeat across runs of an unchanged tree.** | 2 | 3 | 3 | 4 | 1 | 11.2 |
|
| 31 | 0.3.0 | | **Make the branch-coverage figure repeat across runs of an unchanged tree.** | 2 | 3 | 3 | 4 | 1 | 11.2 |
|
||||||
| 33 | 0.3.0 | | **Emit a line in time linear in its mark runs, in `adfToMarkdown` and `adfToPlainMarkdown`.** | 4 | 5 | 6 | 9 | 8 | 10.7 |
|
| 42 | 0.3.0 | | **Trim a text leaf's trailing blanks in linear time.** | 1 | 2 | 5 | 9 | 9 | 10.5 |
|
||||||
| 46 | 0.3.0 | | **Publish the bundle size in the README, failing the release pipeline when it drifts.** | 2 | 4 | 5 | 5 | 7, 9 | 10.3 |
|
|
||||||
| 9 | 0.3.0 | | **Ship an online sandbox: a web page with two textboxes converting between ADF and markdown on the library's browser build.** | 2 | 6 | 6 | 8 | 9 | 10.3 |
|
|
||||||
| 69 | 0.3.0 | question | **Decide whether `adfToPlainMarkdown` keeps a text node's `content`, and keep it or name the drop in the README.** | 2 | 2 | 3 | 5 | 6 | 7.9 |
|
|
||||||
| 56 | 0.3.0 | principle | **Give each piece of `blocks.ts`'s block-walk state and `inline-content.ts`'s `Scan` one owner that returns what it changes.** | 4 | 5 | 2 | 4 | 1 | 6.8 |
|
| 56 | 0.3.0 | principle | **Give each piece of `blocks.ts`'s block-walk state and `inline-content.ts`'s `Scan` one owner that returns what it changes.** | 4 | 5 | 2 | 4 | 1 | 6.8 |
|
||||||
| 57 | 0.3.0 | principle | **Make each `ci.sh` leg build what it reads, so one leg run alone tests the current tree.** | 2 | 3 | 1 | 3 | 7 | 1.2 |
|
| 57 | 0.3.0 | principle | **Make each `ci.sh` leg build what it reads, so one leg run alone tests the current tree.** | 2 | 3 | 1 | 3 | 8 | 0.6 |
|
||||||
| 58 | 0.3.0 | principle | **Port `ci.sh`, `publish.sh` and `docker-runner.sh` to standalone Python scripts.** | 4 | 6 | 1 | 2 | 7 | -2.2 |
|
| 58 | 0.3.0 | principle | **Port `ci.sh`, `publish.sh` and `docker-runner.sh` to standalone Python scripts.** | 4 | 6 | 1 | 2 | 8 | -2.6 |
|
||||||
| 8 | 0.4.0 | | **Ship a CLI.** | 3 | 7 | 7 | 6 | 9 | 10.6 |
|
| 8 | 0.4.0 | | **Ship a CLI.** | 3 | 7 | 7 | 6 | 3 | 18.6 |
|
||||||
|
|
||||||
## Details
|
## Details
|
||||||
|
|
||||||
@@ -99,6 +101,14 @@ 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
|
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.
|
`Object.prototype` or `null`, and an array. Found by the stability review of item 63, 2026-10-04.
|
||||||
|
|
||||||
|
### 71. Compare the package with the other ADF converters in the README, each claim checked against that package's own docs or behaviour.
|
||||||
|
|
||||||
|
Lands after item 7, whose HTML the comparison names. A table: 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, since a
|
||||||
|
reader relies on it. Found while planning discoverability, 2026-10-04.
|
||||||
|
|
||||||
### 43. Give each markdown input its own reader, strict to its own standard.
|
### 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
|
Today `markdownToAdf` reads CommonMark and the lossless flavour as one input: text shaped like a
|
||||||
@@ -107,19 +117,6 @@ a code fence whose info string opens `adf:` becomes the block carry where Common
|
|||||||
caller names the markdown it hands in: CommonMark, read as its spec says, or the lossless flavour,
|
caller names the markdown it hands in: CommonMark, read as its spec says, or the lossless flavour,
|
||||||
read as `spec/flavour.md` says. Breaking: `MIGRATION.md` says which call a caller takes.
|
read as `spec/flavour.md` says. Breaking: `MIGRATION.md` says which call a caller takes.
|
||||||
|
|
||||||
### 65. 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.
|
|
||||||
|
|
||||||
Both are refused today with `unmappable-image`: 14 examples in
|
|
||||||
`corpus/commonmark-spec/refusals.json`, a bot's `See  here` among them. `mediaInline`
|
|
||||||
takes only Atlassian media ids, never a URL. An image not alone in its paragraph reads as its alt
|
|
||||||
text, linked to its URL. Its title becomes the link's `title`. An empty alt text reads as the URL,
|
|
||||||
as item 50 reads an empty link. A titled image alone in its paragraph reads as the image, with its
|
|
||||||
title as a `caption` (the maintainer, 2026-10-04). By Goal 4, the lossless flavour reads both as
|
|
||||||
CommonMark does. An image inside a link: `[](/uri)` (spec example 517). Alone in
|
|
||||||
its paragraph, it reads as the image with a `link` mark to `/uri`, which ADF's `media` takes. Not
|
|
||||||
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 5, which outranks Goal 6.
|
|
||||||
|
|
||||||
### 68. Carry a non-text inline node holding `content` or `text`, a block holding `text`, and a leaf block holding `content`.
|
### 68. Carry a non-text inline node holding `content` or `text`, a block holding `text`, and a leaf block holding `content`.
|
||||||
|
|
||||||
`refuseContentAndText` (`emit/inline-line.ts`) and `emitDirectiveBlock` (`emit/adf-to-markdown.ts`)
|
`refuseContentAndText` (`emit/inline-line.ts`) and `emitDirectiveBlock` (`emit/adf-to-markdown.ts`)
|
||||||
@@ -128,10 +125,41 @@ round-trips them; `docs/decisions.md` §The code list gives a cause the carry an
|
|||||||
them from the emitting half of the README's `unsupported-node-shape` row. Found by the README-goals
|
them from the emitting half of the README's `unsupported-node-shape` row. Found by the README-goals
|
||||||
audit, 2026-10-04.
|
audit, 2026-10-04.
|
||||||
|
|
||||||
|
### 65. 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.
|
||||||
|
|
||||||
|
Both are refused today with `unmappable-image`: 14 examples in
|
||||||
|
`corpus/commonmark-spec/refusals.json`, a bot's `See  here` among them. `mediaInline`
|
||||||
|
takes only Atlassian media ids, never a URL. An image not alone in its paragraph reads as its alt
|
||||||
|
text, linked to its URL. Its title becomes the link's `title`. An empty alt text reads as the URL,
|
||||||
|
as item 50 reads an empty link. A titled image alone in its paragraph reads as the image, with its
|
||||||
|
title as a `caption` (the maintainer, 2026-10-04). By Goal 5, the lossless flavour reads both as
|
||||||
|
CommonMark does. An image inside a link: `[](/uri)` (spec example 517). Alone in
|
||||||
|
its paragraph, it reads as the image with a `link` mark to `/uri`, which ADF's `media` takes. Not
|
||||||
|
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 terms the other ADF converters on
|
||||||
|
npm list and the names the README uses — ADF, Atlassian Document Format, markdown, CommonMark, HTML
|
||||||
|
— so a search for any of them can reach the package. Found while planning discoverability,
|
||||||
|
2026-10-04.
|
||||||
|
|
||||||
|
### 72. Make GitHub the canonical repository, with `gitea.larvit.se/larvit/adf-codec` a read-only pull mirror of it.
|
||||||
|
|
||||||
|
The maintainer's call, 2026-10-04: GitHub is where an ADF converter is searched for, and the Gitea
|
||||||
|
copy keeps the move back open. The workflows move to `.github/workflows/` with `ci.sh` and
|
||||||
|
`publish.sh` unchanged; `package.json`'s `repository`, `bugs` and `homepage`, the README's,
|
||||||
|
CHANGELOG's and MIGRATION's links, and AGENTS.md's forge commands and merge style follow.
|
||||||
|
`docs/decisions.md` gains the entry "GitHub is canonical, and Gitea a pull mirror of it" (Goal 3),
|
||||||
|
and every entry naming Gitea Actions is revised. Creating the GitHub repository, its `NPM_TOKEN`
|
||||||
|
secret, and re-creating the Gitea copy as a mirror — Gitea converts a repository to a mirror only
|
||||||
|
that way — are the maintainer's.
|
||||||
|
|
||||||
### 49. Read a list whose bullet or ordered delimiter changes as two lists, in both readers, and retire `!adf:listBreak`.
|
### 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
|
Lands after item 43. Today `- a` then `+ b`, or `1.` then `1)`, reads as one list; CommonMark reads
|
||||||
two (spec examples 301 and 302). Goal 4 settles it for the lossless flavour too: CommonMark spells
|
two (spec examples 301 and 302). Goal 5 settles it for the lossless flavour too: CommonMark spells
|
||||||
adjacent lists by changing the marker, so `adfToMarkdown` and `adfToPlainMarkdown` alternate `-` and
|
adjacent lists by changing the marker, so `adfToMarkdown` and `adfToPlainMarkdown` alternate `-` and
|
||||||
`*` between adjacent bullet lists and `.` and `)` between adjacent ordered lists, and
|
`*` between adjacent bullet lists and `.` and `)` between adjacent ordered lists, and
|
||||||
`!adf:listBreak` retires. `*` is the maintainer's pick (2026-10-04). Breaking, so it ships beside
|
`!adf:listBreak` retires. `*` is the maintainer's pick (2026-10-04). Breaking, so it ships beside
|
||||||
@@ -159,12 +187,6 @@ example 540); lowercasing and then uppercasing folds it. Breaking, so it ships b
|
|||||||
`MIGRATION.md`'s Readings table gains its row. Its `pending` exceptions go, and its spelling leaves
|
`MIGRATION.md`'s Readings table gains its row. Its `pending` exceptions go, and its spelling leaves
|
||||||
the README's "Four CommonMark spellings" bullet, which counts one fewer.
|
the README's "Four CommonMark spellings" bullet, which counts one fewer.
|
||||||
|
|
||||||
### 53. Put a block's `marks` spelling to the writer panel and adopt its pick.
|
|
||||||
|
|
||||||
Today `marks="[{\"attrs\":{\"mode\":\"wide\"},\"type\":\"breakout\"}]"`, the marks array as
|
|
||||||
escaped JSON. Breaking where the panel picks another spelling: `MIGRATION.md`'s Spellings table
|
|
||||||
gains its row.
|
|
||||||
|
|
||||||
### 60. Collect the questions `parse/` asks `emit/` into one named module.
|
### 60. Collect the questions `parse/` asks `emit/` into one named module.
|
||||||
|
|
||||||
`parse/` asks `emit/` through `commonMarkSpelling` and `openingLinkTakesDirective`, each imported
|
`parse/` asks `emit/` through `commonMarkSpelling` and `openingLinkTakesDirective`, each imported
|
||||||
@@ -177,14 +199,11 @@ Eight directive files sit across three directories, and the `markdown/` root hol
|
|||||||
needs `Read<T>` and `Flavour` out of the plain flavour and `markdown/`; it needs the carry and the
|
needs `Read<T>` and `Flavour` out of the plain flavour and `markdown/`; it needs the carry and the
|
||||||
mark spellings too, which item 7 moves where it learns what HTML shares.
|
mark spellings too, which item 7 moves where it learns what HTML shares.
|
||||||
|
|
||||||
### 50. Read `[](/url)` and `[]()` as CommonMark's empty link.
|
### 53. Put a block's `marks` spelling to the writer panel and adopt its pick.
|
||||||
|
|
||||||
Both stay literal text today (spec examples 484 and 487). ADF holds no empty text node to carry a
|
Today `marks="[{\"attrs\":{\"mode\":\"wide\"},\"type\":\"breakout\"}]"`, the marks array as
|
||||||
link mark, so a link whose text is empty takes its URL as its text (the maintainer, 2026-10-04):
|
escaped JSON. Breaking where the panel picks another spelling: `MIGRATION.md`'s Spellings table
|
||||||
`[](/url)` reads as `/url` linked to `/url`. `[]()` has no URL to show; the chunk settles it by Goals
|
gains its row.
|
||||||
3 and 6. Breaking, so it ships beside item 43: `MIGRATION.md`'s Readings table gains its row. Its
|
|
||||||
`pending` exceptions go, and its spelling leaves the README's "Four CommonMark spellings" bullet,
|
|
||||||
which counts one fewer.
|
|
||||||
|
|
||||||
### 62. Move the plain flavour's reading out of `parse/` and its writing out of `emit/` into `markdown/plain/`, so `emit/` no longer imports `plain/`.
|
### 62. Move the plain flavour's reading out of `parse/` and its writing out of `emit/` into `markdown/plain/`, so `emit/` no longer imports `plain/`.
|
||||||
|
|
||||||
@@ -194,10 +213,14 @@ the `inlineLeaves` use. Writing: `spellPlainBlock`, `quotedUnder`, `tryTaskList`
|
|||||||
`plain/conventions.ts`, which `emit/` imports them from; item 59 moves `Flavour`. Every
|
`plain/conventions.ts`, which `emit/` imports them from; item 59 moves `Flavour`. Every
|
||||||
comprehension reader on 2026-10-03 named the plain flavour's spread across three directories.
|
comprehension reader on 2026-10-03 named the plain flavour's spread across three directories.
|
||||||
|
|
||||||
### 38. Spell a lone surrogate in a text node so it survives a UTF-8 encode.
|
### 50. Read `[](/url)` and `[]()` as CommonMark's empty link.
|
||||||
|
|
||||||
`adfToMarkdown` emits it verbatim, so markdown stored as UTF-8 reads back U+FFFD; attribute values
|
Both stay literal text today (spec examples 484 and 487). ADF holds no empty text node to carry a
|
||||||
already escape it.
|
link mark, so a link whose text is empty takes its URL as its text (the maintainer, 2026-10-04):
|
||||||
|
`[](/url)` reads as `/url` linked to `/url`. `[]()` has no URL to show; the chunk settles it by Goals
|
||||||
|
4 and 7. Breaking, so it ships beside item 43: `MIGRATION.md`'s Readings table gains its row. Its
|
||||||
|
`pending` exceptions go, and its spelling leaves the README's "Four CommonMark spellings" bullet,
|
||||||
|
which counts one fewer.
|
||||||
|
|
||||||
### 47. Open the README with what the package is, what it does and for whom.
|
### 47. Open the README with what the package is, what it does and for whom.
|
||||||
|
|
||||||
@@ -208,17 +231,16 @@ pair, `adfToPlainMarkdown` and `plainMarkdownToAdf`, and the flavours it writes
|
|||||||
Flavored Markdown's alerts and task lists, Obsidian Flavored Markdown's callouts — so a search for
|
Flavored Markdown's alerts and task lists, Obsidian Flavored Markdown's callouts — so a search for
|
||||||
any of these names finds the package.
|
any of these names finds the package.
|
||||||
|
|
||||||
### 34. Read emphasis flanking by the whole character beside an astral symbol.
|
### 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.
|
||||||
|
|
||||||
Check whether `line-escaping.ts`'s `charAt` and the parser's flanking read one UTF-16 unit beside an
|
Lands after `0.2.0` is published and items 71 and 72 land, so each post links the GitHub
|
||||||
astral symbol — a lone surrogate is neither punctuation nor symbol, where CommonMark reads `😀` as
|
repository and the comparison. We draft each post for the place it goes; the maintainer posts
|
||||||
punctuation — and, where they do, read the code point, with a fixture per direction.
|
them. Found while planning discoverability, 2026-10-04.
|
||||||
|
|
||||||
### 42. Trim a text leaf's trailing blanks in linear time.
|
### 38. Spell a lone surrogate in a text node so it survives a UTF-8 encode.
|
||||||
|
|
||||||
`plain/inline-reduction.ts`'s `leafEdges` finds the trail with an unanchored `/[ \t]*$/`, quadratic
|
`adfToMarkdown` emits it verbatim, so markdown stored as UTF-8 reads back U+FFFD; attribute values
|
||||||
in a run of blanks inside one leaf: a paragraph of `a`, 80 000 spaces, `b` takes 6.5 s in
|
already escape it.
|
||||||
`adfToPlainMarkdown`. Scan backward, as the expand title's trim does.
|
|
||||||
|
|
||||||
### 48. Keep the release path publishing past npm's bypass-2FA token retirement.
|
### 48. Keep the release path publishing past npm's bypass-2FA token retirement.
|
||||||
|
|
||||||
@@ -234,6 +256,22 @@ added Gitea or self-hosted OIDC, and otherwise whether the release moves to the
|
|||||||
which fits badly with publish-on-merge, and is the maintainer's trade to weigh. The Goals and G
|
which fits badly with publish-on-merge, and is the maintainer's trade to weigh. The Goals and G
|
||||||
cells are provisional: no README goal covers the release path.
|
cells are provisional: no README goal covers the release path.
|
||||||
|
|
||||||
|
### 46. Publish the bundle size in the README, failing the release pipeline when it drifts.
|
||||||
|
|
||||||
|
Lands after item 7, which changes 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 apples-to-apples 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). The figure lands in README §The package beside the "no runtime
|
||||||
|
dependencies" claim. Measured today, unminified: tarball 60.4 kB, unpacked 221.5 kB, JS gzipped 45.6
|
||||||
|
kB.
|
||||||
|
|
||||||
|
### 34. Read emphasis flanking by the whole character beside an astral symbol.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
### 31. Make the branch-coverage figure repeat across runs of an unchanged tree.
|
### 31. Make the branch-coverage figure repeat across runs of an unchanged tree.
|
||||||
|
|
||||||
Three Node test legs over one unchanged tree reported `emit/inline-line.ts` at 95.83%, 96.23% and
|
Three Node test legs over one unchanged tree reported `emit/inline-line.ts` at 95.83%, 96.23% and
|
||||||
@@ -245,33 +283,11 @@ moves upward, so the first raise to the measured figure reddens a run that chang
|
|||||||
measurement repeatable, or state the number the floor may be raised to and why it is not the
|
measurement repeatable, or state the number the floor may be raised to and why it is not the
|
||||||
measured one.
|
measured one.
|
||||||
|
|
||||||
### 33. Emit a line in time linear in its mark runs, in `adfToMarkdown` and `adfToPlainMarkdown`.
|
### 42. Trim a text leaf's trailing blanks in linear time.
|
||||||
|
|
||||||
`adfToMarkdown` spends 23 s on one paragraph of 2000 × `un` plus `**-r**`: each run its flanking
|
`plain/inline-reduction.ts`'s `leafEdges` finds the trail with an unanchored `/[ \t]*$/`, quadratic
|
||||||
cannot spell re-emits the whole line before riding the carry, quadratic in the runs, and the plain
|
in a run of blanks inside one leaf: a paragraph of `a`, 80 000 spaces, `b` takes 6.5 s in
|
||||||
reduction's `spellableLine` drops one mark per re-emit the same way. An inline node whose
|
`adfToPlainMarkdown`. Scan backward, as the expand title's trim does.
|
||||||
attributes no spelling writes re-emits the line the same way before riding the carry. Make all
|
|
||||||
three linear.
|
|
||||||
|
|
||||||
### 46. Publish the bundle size in the README, failing the release pipeline when it drifts.
|
|
||||||
|
|
||||||
Lands after item 7, which changes 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 apples-to-apples 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). The figure lands in README §The package beside the "no runtime
|
|
||||||
dependencies" claim. Measured today, unminified: tarball 60.4 kB, unpacked 221.5 kB, JS gzipped 45.6
|
|
||||||
kB.
|
|
||||||
|
|
||||||
### 69. Decide whether `adfToPlainMarkdown` keeps a text node's `content`, and keep it or name the drop in the README.
|
|
||||||
|
|
||||||
`nodeLeaves` (`plain/inline-reduction.ts`) keeps only a text node's `text`, so
|
|
||||||
`{ text: 'a', content: [{ text: 'kept', type: 'text' }] }` writes `a`, though `toEditorNormal`
|
|
||||||
keeps that content (`plain/editor-normal.ts`) and an unknown node's children keep their text.
|
|
||||||
Atlassian's schema allows no `content` on a text node, so no Atlassian reader saw it; an
|
|
||||||
export/indexing tool fed a looser producer loses searchable text. Goal 6 reads two ways here: keep
|
|
||||||
the children's text as an unknown node's, or name the drop in the README's plain section. Found by
|
|
||||||
the product-owner review, 2026-10-04.
|
|
||||||
|
|
||||||
### 56. Give each piece of `blocks.ts`'s block-walk state and `inline-content.ts`'s `Scan` one owner that returns what it changes.
|
### 56. Give each piece of `blocks.ts`'s block-walk state and `inline-content.ts`'s `Scan` one owner that returns what it changes.
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user