36a - §1–§6's decisions move to docs/decisions.md, indexed from AGENTS.md #134
@@ -1,106 +1,33 @@
|
|||||||
# Working in this repo
|
# Working in this repo
|
||||||
|
|
||||||
Decisions a reader would otherwise relitigate, and the rules for every collaborator, human or
|
The rules for every collaborator, human or agent, and an index of the decisions a reader would
|
||||||
agent. Using the library: `README.md`. What is still to build: `todo.md`; a bare `(28)` cites
|
otherwise relitigate. Using the library: `README.md`. What is still to build: `todo.md`; a bare
|
||||||
that item's entry in `todo-history.md`.
|
`(28)` cites that item's entry in `todo-history.md`.
|
||||||
|
|
||||||
## 1. ADF is the hub
|
## Decisions
|
||||||
|
|
||||||
README Goal 2. The lossy pair is the plain flavour: the markdown grammar's reader and writer with
|
In `docs/decisions.md`:
|
||||||
the flavour set, its spellings — alerts, callouts, task markers, `==` — read and written there, so
|
|
||||||
a marker line and a backslash reach them intact; what the flavour cannot spell reduces ADF→ADF
|
|
||||||
ahead of the writer (the maintainer, 2026-09-27; 35).
|
|
||||||
|
|
||||||
## 2. The round-trip is the product
|
- Plain markdown is a flavour of the grammar
|
||||||
|
- The round-trip is the product
|
||||||
`markdownToAdf(adfToMarkdown(doc))` and `htmlToAdf(adfToHtml(doc))` must equal `doc` — anything
|
- Markdown in is a canonical fixpoint
|
||||||
less silently destroys content an editor could not represent, in a document it did not author.
|
- Equality is editor-normal
|
||||||
When losslessness and readability conflict, losslessness wins.
|
- Unknown nodes ride the carry
|
||||||
|
- Foreign HTML is refused by name
|
||||||
The other direction is a canonical fixpoint, not byte-identity: human markdown normalizes, the way
|
- Names stay text
|
||||||
back yields the library's canonical spelling, and that spelling round-trips byte-identically —
|
- Directives under `!adf:`
|
||||||
where there is a way back. CommonMark spells some things the flavour has no escape for — a
|
- CommonMark is a subset
|
||||||
paragraph opening with a code span whose backticks read back as a fence — so a parse succeeding
|
- Tables
|
||||||
does not imply a spellable document;
|
- Links
|
||||||
`corpus/commonmark-spec/exceptions.json` names those.
|
- Ids stay site-local
|
||||||
|
- The HTML dialect
|
||||||
"Equals" is structural equality over editor-normal ADF — adjacent text nodes with identical marks
|
- No runtime dependencies
|
||||||
and no attributes merged, JSON number semantics, an empty attrs object, marks array or content
|
- Standards ship as data
|
||||||
array the absent key — the only domain markdown can restore.
|
- fast-check
|
||||||
|
- Any ES2022 engine
|
||||||
Round-trip equality is a property tested over a corpus, not a claim made in prose.
|
- ESM only
|
||||||
|
- One built entrypoint
|
||||||
## 3. Unknown input policy
|
- Public on npm
|
||||||
|
|
||||||
- Unknown ADF node: carried opaquely — raw JSON rides a dedicated syntax in both formats and
|
|
||||||
restores to a deep-equal node. The round-trip holds for documents newer than the library. So
|
|
||||||
does a known node no section spells where it stands: a markdown serializer spells a node by type
|
|
||||||
without checking its position, and refusing loses a document ADF itself keeps in an
|
|
||||||
`unsupportedBlock`. Where a container's own spelling cannot hold the child it has — a
|
|
||||||
`bulletList` outside `listItem`, a `codeBlock` outside text — the error result names that
|
|
||||||
instead.
|
|
||||||
- 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, namespaced under `!adf:`: `!adf:panel info`
|
|
||||||
… `!adf:/panel` blocks, `!adf:mention[@Mikael]{id=5b10a2}` inline, `\!adf:` the one escape. Not
|
|
||||||
CommonMark's generic-directives proposal: its `:::` claims a form prose writes, and its
|
|
||||||
fence-length discipline ties a container's opener to its own body, where closing from the opener
|
|
||||||
nests by itself and leaf versus container falls out of the node's content model.
|
|
||||||
- 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 — plus one image gap.
|
|
||||||
- Tables: one header row plus plain inline cells → pipe table; anything richer → directive form.
|
|
||||||
- Links: `[text](url "title")`, or `<url>` for a bare autolink-shaped text, wherever CommonMark
|
|
||||||
spells the mark; `!adf:link[text]{attrs}` where it does not — an attribute CommonMark cannot
|
|
||||||
hold, an `href` or `title` no canonical escape spells, a paragraph opening whose CommonMark
|
|
||||||
spelling would read as a link reference definition — and a directive link CommonMark could spell
|
|
||||||
is refused (the maintainer, 2026-09-13). No link wraps a link — the bracket form goes literal,
|
|
||||||
the directive form refused — which is CommonMark's prose where its reference implementation
|
|
||||||
nests one `<a>` in another (the maintainer, 2026-09-17).
|
|
||||||
- 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 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. 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. The CommonMark spec suite is the same shape of
|
|
||||||
data and ships vendored at `corpus/commonmark-spec/` rather than as the `commonmark-spec` dev
|
|
||||||
dependency — that package is CommonJS-only, and Renovate auto-bumping a spec version would silently
|
|
||||||
point the vendored exception list's example numbers at a renumbered suite. A spec bump is a
|
|
||||||
deliberate re-pin, exceptions re-derived by hand beside it. Atlassian's ADF JSON Schemas ship
|
|
||||||
vendored the same way, at `spec/adf-schema/`, rather than as the `@atlaskit/adf-schema` dev
|
|
||||||
dependency — CommonJS-only, some fifty packages with React among them, and a release most days for
|
|
||||||
Renovate to automerge — re-pinned by hand when a payload or a report shows the need.
|
|
||||||
`devDependencies`: `fast-check` earns its place shrinking a failing generated document to the nodes
|
|
||||||
that break it, `oxlint` measuring §10's size ratchet — TypeScript 7 is a native compiler publishing
|
|
||||||
no in-process parser, only the `unstable/` AST surface an out-of-process handshake reaches.
|
|
||||||
|
|
||||||
## 6. The package contract
|
|
||||||
|
|
||||||
- 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. `tsconfig.build.json` is
|
|
||||||
that gate, typechecking and emitting the shipped files alone, so `node:fs`, `process` and an
|
|
||||||
ES2024 method are compile errors here rather than a consumer's crash there. The standard is the line, never an
|
|
||||||
engine list: one implementing it in part — Hermes is the live doubt, on §10's property escapes
|
|
||||||
and on lookbehind — is out of scope rather than a bug. Node's test runner, the corpus reads and
|
|
||||||
the build are the repo's own,
|
|
||||||
never the library's, and `engines.node` states the floor the shipped JavaScript needs — `>=18` —
|
|
||||||
never the higher one those repo-only tools want.
|
|
||||||
- ESM only — no CommonJS build, no dual-package hazard.
|
|
||||||
- One entrypoint: 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 an npm consumer.
|
|
||||||
- Published to public npmjs as `@larvit/adf-codec`. 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
|
## 7. Nothing about any consumer
|
||||||
|
|
||||||
@@ -191,6 +118,7 @@ types in `src/result.ts` hold its shape.
|
|||||||
than taking the gate's `dist`: the lockfile is committed, the image is patch-pinned and `tsc` is
|
than taking the gate's `dist`: the lockfile is committed, the image is patch-pinned and `tsc` is
|
||||||
deterministic, so the two builds agree, and promoting an artifact would make the release path
|
deterministic, so the two builds agree, and promoting an artifact would make the release path
|
||||||
depend on a store that the gate would then have to keep.
|
depend on a store that the gate would then have to keep.
|
||||||
|
- Exact versions: `save-exact=true` in `.npmrc`.
|
||||||
- Renovate watches devDependencies, Docker pins and action tags; automerges everything on green CI.
|
- 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`), as
|
- Docker images pin the full patch version (`node:24.19.0-alpine3.24`, never `node:24`), as
|
||||||
specific as the publisher tags: `oven/bun:1.4.0-alpine` pins Bun's patch and leaves the base
|
specific as the publisher tags: `oven/bun:1.4.0-alpine` pins Bun's patch and leaves the base
|
||||||
@@ -219,16 +147,16 @@ resolver maps them, under `NodeNext` alone; a `.d.ts` reader that is not `tsc` s
|
|||||||
`node-floor.js` round-trips the installed package under a Node pinned to `engines.node`'s floor.
|
`node-floor.js` round-trips the installed package under a Node pinned to `engines.node`'s floor.
|
||||||
|
|
||||||
A fourth engine reads the build rather than the source: a headless Firefox loads `dist/index.js`
|
A fourth engine reads the build rather than the source: a headless Firefox loads `dist/index.js`
|
||||||
over HTTP and converts the round-trip, normalization and error fixtures and the real payloads —
|
over HTTP and converts the round-trip, normalization and error fixtures and the real payloads — the
|
||||||
the `commonmark-spec` sort is the Node suite's to check — which is §6's browser half and the only
|
`commonmark-spec` sort is the Node suite's to check — which is the browser half of
|
||||||
SpiderMonkey there is — the gate's other three engines are two V8s and a JavaScriptCore that is
|
`docs/decisions.md` §Any ES2022 engine and the only SpiderMonkey there is — the gate's other three
|
||||||
not Safari's.
|
engines are two V8s and a JavaScriptCore that is not Safari's. A WebDriver session is what carries a
|
||||||
A WebDriver session is what carries a verdict back out, the driver and the page's server sharing
|
verdict back out, the driver and the page's server sharing one network namespace so each is the
|
||||||
one network namespace so each is the other's `127.0.0.1`; `--headless --screenshot` has no such
|
other's `127.0.0.1`; `--headless --screenshot` has no such channel, and loading `dist/index.js` in a
|
||||||
channel, and loading `dist/index.js` in a globals-stripped realm buys one by not running a browser.
|
globals-stripped realm buys one by not running a browser. The leg re-checks the conversions and
|
||||||
The leg re-checks the conversions and nothing else — each fixture's emitted markdown, its parsed
|
nothing else — each fixture's emitted markdown, its parsed document, its error code — leaving the
|
||||||
document, its error code — leaving the corpus's pairing, uniqueness, source positions and
|
corpus's pairing, uniqueness, source positions and byte-level equality to the Node suite that owns
|
||||||
byte-level equality to the Node suite that owns them.
|
them.
|
||||||
|
|
||||||
Every leg announces its name and, where a container is in play, the image, before it runs and its
|
Every leg announces its name and, where a container is in play, the image, before it runs and its
|
||||||
elapsed time after, `publish.sh` alongside `ci.sh`, so a long run reads as progress rather than as
|
elapsed time after, `publish.sh` alongside `ci.sh`, so a long run reads as progress rather than as
|
||||||
@@ -244,14 +172,16 @@ functions, and a branch floor that only ever moves upward. It sits below 100 bec
|
|||||||
compared against `undefined` — have a half no valid document reaches.
|
compared against `undefined` — have a half no valid document reaches.
|
||||||
|
|
||||||
The size ratchet is the other such number, `.oxlintrc.json`'s single rule over the files
|
The size ratchet is the other such number, `.oxlintrc.json`'s single rule over the files
|
||||||
`tsconfig.build.json` builds: a per-function line ceiling, set at that set's worst and moving only
|
`tsconfig.build.json` builds, measured by `oxlint` since TypeScript 7 is a native compiler
|
||||||
downward. It covers the built files alone, since one ceiling over the tests too would have to be
|
publishing no in-process parser, only the `unstable/` AST surface an out-of-process handshake
|
||||||
their worst, loosening the guard over the shipped code. It guards against drift and never drives a
|
reaches. It is a per-function line ceiling, set at that set's worst and moving only downward. It
|
||||||
refactor, so no cyclomatic rule and no second lint rule join it: neither measure picked out what
|
covers the built files alone, since one ceiling over the tests too would have to be their worst,
|
||||||
nine readers found hard (the comprehension panel, 2026-09-20). Three switches guard a silent green:
|
loosening the guard over the shipped code. It guards against drift and never drives a refactor, so
|
||||||
`IIFEs: true`, since oxlint exempts an IIFE otherwise; an explicit `-c`, so a config gone missing
|
no cyclomatic rule and no second lint rule join it: neither measure picked out what nine readers
|
||||||
fails the leg instead of falling back to oxlint's own defaults; and `--deny-warnings`, since a rule
|
found hard (the comprehension panel, 2026-09-20). Three switches guard a silent green: `IIFEs:
|
||||||
from a category this config never names arrives as a warning it exits 0 on.
|
true`, since oxlint exempts an IIFE otherwise; an explicit `-c`, so a config gone missing fails the
|
||||||
|
leg instead of falling back to oxlint's own defaults; and `--deny-warnings`, since a rule from a
|
||||||
|
category this config never names arrives as a warning it exits 0 on.
|
||||||
|
|
||||||
The corpus, all checked in: hand-built fixtures per node and combination; real ADF Atlassian's
|
The corpus, all checked in: hand-built fixtures per node and combination; real ADF Atlassian's
|
||||||
editor wrote; the CommonMark spec suite against `markdownToAdf` and `markdownToHtml`.
|
editor wrote; the CommonMark spec suite against `markdownToAdf` and `markdownToHtml`.
|
||||||
@@ -269,11 +199,11 @@ of a bullet; fenced examples are skipped. It guards the attributes alone: nodes
|
|||||||
content model share a bullet, and the argument attribute is spelled ahead of `Attributes: `, so
|
content model share a bullet, and the argument attribute is spelled ahead of `Attributes: `, so
|
||||||
both answer to the round-trip corpus and to nothing else where a node has no fixture.
|
both answer to the round-trip corpus and to nothing else where a node has no fixture.
|
||||||
|
|
||||||
The tables answer to Atlassian's schema too (§5): for every node and mark they spell, the attribute
|
The tables answer to Atlassian's schema too (`docs/decisions.md` §Standards ship as data): for every
|
||||||
names and kinds equal what `full.json` and `stage-0.json` hold between them. Value sets stay
|
node and mark they spell, the attribute names and kinds equal what `full.json` and `stage-0.json`
|
||||||
documentation, since any value round-trips. What the schema holds and the tables do not spell is
|
hold between them. Value sets stay documentation, since any value round-trips. What the schema holds
|
||||||
pinned by name — an attribute as a gap, a type as carried — so a re-pin adding either goes red until
|
and the tables do not spell is pinned by name — an attribute as a gap, a type as carried — so a
|
||||||
someone spells it or pins it.
|
re-pin adding either goes red until someone spells it or pins it.
|
||||||
|
|
||||||
## 11. Code rules
|
## 11. Code rules
|
||||||
|
|
||||||
@@ -388,12 +318,13 @@ One-line commit messages and PR titles; short PR summaries. No AI-attribution ma
|
|||||||
|
|
||||||
## 14. Non-goals
|
## 14. Non-goals
|
||||||
|
|
||||||
No network or filesystem I/O, no name→id resolution (§3), no ADF schema
|
No network or filesystem I/O, no name→id resolution (`docs/decisions.md` §Names stay text), no ADF
|
||||||
validation or exported validator — a refusal that keeps the round-trip is not schema validation,
|
schema validation or exported validator — a refusal that keeps the round-trip is not schema
|
||||||
so the one a spelled node carrying the same mark type twice earns stays, and input nesting a
|
validation, so the one a spelled node carrying the same mark type twice earns stays, and input
|
||||||
spelling inside its own kind (`*(*a*)*`) names that mark once, no shipped CSS (§4), no
|
nesting a spelling inside its own kind (`*(*a*)*`) names that mark once, no shipped CSS
|
||||||
streaming APIs, no performance budget past §11's scanning rule — nothing here is tuned, and no
|
(`docs/decisions.md` §The HTML dialect), no streaming APIs, no performance budget past §11's
|
||||||
figure is promised. A CLI is a later goal (`todo.md`), not a non-goal.
|
scanning rule — nothing here is tuned, and no figure is promised. A CLI is a later goal (`todo.md`),
|
||||||
|
not a non-goal.
|
||||||
|
|
||||||
## 15. The working loop
|
## 15. The working loop
|
||||||
|
|
||||||
@@ -441,8 +372,9 @@ The verdict lands in the item it settles (the maintainer, 2026-09-25).
|
|||||||
### Rules the loop has settled (the maintainer, 2026-09-18)
|
### Rules the loop has settled (the maintainer, 2026-09-18)
|
||||||
|
|
||||||
- A finding inside the chunk's item is fixed in the chunk. Outside it, a new `todo.md` item, always
|
- A finding inside the chunk's item is fixed in the chunk. Outside it, a new `todo.md` item, always
|
||||||
in a release, weighed against every item on that release by the personas and §1–§3 — an item it
|
in a release, weighed against every item on that release by the personas and `docs/decisions.md`
|
||||||
outweighs moves later. A weighing no rule decides is asked as a gap.
|
§Plain markdown is a flavour of the grammar through §Names stay text — an item it outweighs moves
|
||||||
|
later. A weighing no rule decides is asked as a gap.
|
||||||
- A stated number — 500 levels, the branch floor — is kept; a chunk that cannot keep it asks,
|
- A stated number — 500 levels, the branch floor — is kept; a chunk that cannot keep it asks,
|
||||||
naming the number it can reach. A number the code needs and no rule states is a gap.
|
naming the number it can reach. A number the code needs and no rule states is a gap.
|
||||||
- An earliest release with no items left and nothing shipped toward it is planned as the chunk:
|
- An earliest release with no items left and nothing shipped toward it is planned as the chunk:
|
||||||
|
|||||||
@@ -5,7 +5,8 @@ an HTML dialect.
|
|||||||
|
|
||||||
**Status: published — the markdown round-trip (`adfToMarkdown`, `markdownToAdf`); HTML at
|
**Status: published — the markdown round-trip (`adfToMarkdown`, `markdownToAdf`); HTML at
|
||||||
`0.2.0`.**
|
`0.2.0`.**
|
||||||
Plan: `todo.md`. Decisions: `AGENTS.md`. Changes:
|
Plan: `todo.md`. Decisions:
|
||||||
|
[`docs/decisions.md`](https://gitea.larvit.se/larvit/adf-codec/src/branch/main/docs/decisions.md). Changes:
|
||||||
[`CHANGELOG.md`](https://gitea.larvit.se/larvit/adf-codec/src/branch/main/CHANGELOG.md). The lossless flavour's grammar:
|
[`CHANGELOG.md`](https://gitea.larvit.se/larvit/adf-codec/src/branch/main/CHANGELOG.md). The lossless flavour's grammar:
|
||||||
[`spec/flavour.md`](https://gitea.larvit.se/larvit/adf-codec/src/branch/main/spec/flavour.md).
|
[`spec/flavour.md`](https://gitea.larvit.se/larvit/adf-codec/src/branch/main/spec/flavour.md).
|
||||||
Upgrading from `0.1.0`: [convert your markdown first](https://gitea.larvit.se/larvit/adf-codec/src/branch/main/MIGRATION.md).
|
Upgrading from `0.1.0`: [convert your markdown first](https://gitea.larvit.se/larvit/adf-codec/src/branch/main/MIGRATION.md).
|
||||||
@@ -143,7 +144,7 @@ saving what this pair read replaces mentions, attachments and macros with text.
|
|||||||
## The errors
|
## The errors
|
||||||
|
|
||||||
An ADF node type this version does not know is not an error: it is carried opaquely and restores
|
An ADF node type this version does not know is not an error: it is carried opaquely and restores
|
||||||
unchanged (AGENTS.md §3).
|
unchanged ([`docs/decisions.md`](https://gitea.larvit.se/larvit/adf-codec/src/branch/main/docs/decisions.md#unknown-nodes-ride-the-carry)).
|
||||||
|
|
||||||
`ConvertError` is `{ code, message, path, position? }`. `code` is the exported `ConvertErrorCode`,
|
`ConvertError` is `{ code, message, path, position? }`. `code` is the exported `ConvertErrorCode`,
|
||||||
stable across minors and safe to `switch` on exhaustively with no `default`; `message` is free text
|
stable across minors and safe to `switch` on exhaustively with no `default`; `message` is free text
|
||||||
@@ -195,7 +196,7 @@ emit refuses:
|
|||||||
## The guarantees
|
## The guarantees
|
||||||
|
|
||||||
- `markdownToAdf(adfToMarkdown(doc))` equals `doc` — unknown node types included, carried opaquely
|
- `markdownToAdf(adfToMarkdown(doc))` equals `doc` — unknown node types included, carried opaquely
|
||||||
(AGENTS.md §3).
|
([`docs/decisions.md`](https://gitea.larvit.se/larvit/adf-codec/src/branch/main/docs/decisions.md#unknown-nodes-ride-the-carry)).
|
||||||
- Plain CommonMark is valid input to `markdownToAdf` apart from the raw HTML `unmappable-html`
|
- Plain CommonMark is valid input to `markdownToAdf` apart from the raw HTML `unmappable-html`
|
||||||
names, with three carve-outs — literal text matching directive, pipe-table or strikethrough
|
names, with three carve-outs — literal text matching directive, pipe-table or strikethrough
|
||||||
syntax is claimed (escapable — `spec/flavour.md`) — and one gap: a CommonMark image fits only as
|
syntax is claimed (escapable — `spec/flavour.md`) — and one gap: a CommonMark image fits only as
|
||||||
@@ -234,4 +235,5 @@ emit refuses:
|
|||||||
ESM only, no runtime dependencies, public npmjs. Built JavaScript with `.d.ts` beside it.
|
ESM only, no runtime dependencies, public npmjs. Built JavaScript with `.d.ts` beside it.
|
||||||
Pure ECMAScript at an ES2022 baseline, reaching for no host API; the test suite runs under Node,
|
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.
|
Deno and Bun, and a headless Firefox converts the corpus through the built entrypoint.
|
||||||
Contract: `AGENTS.md` §5–6.
|
Contract: [`docs/decisions.md`](https://gitea.larvit.se/larvit/adf-codec/src/branch/main/docs/decisions.md#any-es2022-engine), §Any
|
||||||
|
ES2022 engine to §Public on npm.
|
||||||
|
|||||||
+4
-4
@@ -3,8 +3,8 @@
|
|||||||
One directory per contract kind, each landing with its milestone:
|
One directory per contract kind, each landing with its milestone:
|
||||||
|
|
||||||
- `round-trip/` — `<name>.json` + `<name>.md`: the markdown `adfToMarkdown` must emit for that
|
- `round-trip/` — `<name>.json` + `<name>.md`: the markdown `adfToMarkdown` must emit for that
|
||||||
document, byte for byte, and that `markdownToAdf` must read back to it (AGENTS.md §2). Grouped
|
document, byte for byte, and that `markdownToAdf` must read back to it (`docs/decisions.md` §The
|
||||||
by what the fixture exercises.
|
round-trip is the product). Grouped by what the fixture exercises.
|
||||||
- `normalization/` — `<name>.md` + `<name>.json`: markdown input, and the document
|
- `normalization/` — `<name>.md` + `<name>.json`: markdown input, and the document
|
||||||
`markdownToAdf` must build from it, which must in turn emit and read back to itself. The
|
`markdownToAdf` must build from it, which must in turn emit and read back to itself. The
|
||||||
markdown is not canonical.
|
markdown is not canonical.
|
||||||
@@ -19,8 +19,8 @@ One directory per contract kind, each landing with its milestone:
|
|||||||
model), `unspellable` (parses but the flavour has no spelling) or `pending` (a parser gap a later
|
model), `unspellable` (parses but the flavour has no spelling) or `pending` (a parser gap a later
|
||||||
milestone may close).
|
milestone may close).
|
||||||
|
|
||||||
JSON is editor-normal (AGENTS.md §2), two-space indent, keys sorted. `spec.json` is the vendored,
|
JSON is editor-normal (`docs/decisions.md` §Equality is editor-normal), two-space indent, keys
|
||||||
upstream machine-readable suite, byte-exact from
|
sorted. `spec.json` is the vendored, upstream machine-readable suite, byte-exact from
|
||||||
[spec.commonmark.org](https://spec.commonmark.org/0.31.2/spec.json) (CommonMark 0.31.2, © John
|
[spec.commonmark.org](https://spec.commonmark.org/0.31.2/spec.json) (CommonMark 0.31.2, © John
|
||||||
MacFarlane, [CC-BY-SA-4.0](https://creativecommons.org/licenses/by-sa/4.0/)), and is not
|
MacFarlane, [CC-BY-SA-4.0](https://creativecommons.org/licenses/by-sa/4.0/)), and is not
|
||||||
re-serialized by the corpus gate.
|
re-serialized by the corpus gate.
|
||||||
|
|||||||
@@ -0,0 +1,185 @@
|
|||||||
|
# Decisions
|
||||||
|
|
||||||
|
## Plain markdown is a flavour of the grammar
|
||||||
|
|
||||||
|
2026-09-27, the maintainer. Goal 2. Valid while the plain flavour's spellings are ones the markdown
|
||||||
|
grammar can read and write.
|
||||||
|
|
||||||
|
The lossy pair is the plain flavour: the markdown grammar's reader and writer with the flavour set,
|
||||||
|
its spellings — alerts, callouts, task markers, `==` — read and written there, so a marker line and
|
||||||
|
a backslash reach them intact; what the flavour cannot spell reduces ADF→ADF ahead of the writer.
|
||||||
|
|
||||||
|
## The round-trip is the product
|
||||||
|
|
||||||
|
2026-08-23, the maintainer. Goal 1. Valid while a consumer saves back through the lossless pair.
|
||||||
|
|
||||||
|
`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. Round-trip equality is a property
|
||||||
|
tested over a corpus, not a claim made in prose.
|
||||||
|
|
||||||
|
## Markdown in is a canonical fixpoint
|
||||||
|
|
||||||
|
2026-08-23, the maintainer. Goals 1 and 3. Valid while markdown input may be written by hand.
|
||||||
|
|
||||||
|
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 —
|
||||||
|
where there is a way back. CommonMark spells some things the flavour has no escape for — a
|
||||||
|
paragraph opening with a code span whose backticks read back as a fence — so a parse succeeding
|
||||||
|
does not imply a spellable document; `corpus/commonmark-spec/exceptions.json` names those.
|
||||||
|
|
||||||
|
## Equality is editor-normal
|
||||||
|
|
||||||
|
2026-08-24, the maintainer. Goal 1. Valid while markdown cannot tell apart the ADF shapes this
|
||||||
|
merges.
|
||||||
|
|
||||||
|
"Equals" is structural equality over editor-normal ADF — adjacent text nodes with identical marks
|
||||||
|
and no attributes merged, JSON number semantics, an empty attrs object, marks array or content
|
||||||
|
array the absent key — the only domain markdown can restore.
|
||||||
|
|
||||||
|
## Unknown nodes ride the carry
|
||||||
|
|
||||||
|
2026-08-23, extended to misplaced known nodes 2026-08-26, the maintainer. Goal 1. Valid while ADF
|
||||||
|
holds nodes, or node positions, this library does not spell.
|
||||||
|
|
||||||
|
An unknown ADF node is carried opaquely — raw JSON rides a dedicated syntax in both formats and
|
||||||
|
restores to a deep-equal node. The round-trip holds for documents newer than the library. So does
|
||||||
|
a known node no section spells where it stands: a markdown serializer spells a node by type without
|
||||||
|
checking its position, and refusing loses a document ADF itself keeps in an `unsupportedBlock`.
|
||||||
|
Where a container's own spelling cannot hold the child it has — a `bulletList` outside `listItem`,
|
||||||
|
a `codeBlock` outside text — the error result names that instead.
|
||||||
|
|
||||||
|
## Foreign HTML is refused by name
|
||||||
|
|
||||||
|
2026-08-23, the maintainer. Goals 1 and 6. Valid until the HTML dialect's element set lands
|
||||||
|
(`todo.md`, 6).
|
||||||
|
|
||||||
|
An unmappable foreign HTML element is an error result naming the element — never a silent drop.
|
||||||
|
|
||||||
|
## Names stay text
|
||||||
|
|
||||||
|
2026-08-23, the maintainer. Goal 7. 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
|
||||||
|
mention/emoji/media nodes; resolving names to ids is the consumer's job.
|
||||||
|
|
||||||
|
## Directives under `!adf:`
|
||||||
|
|
||||||
|
2026-08-23, prefixed `!adf:` 2026-09-16, the maintainer. Goals 3 and 4. Valid while prose does not
|
||||||
|
write `!adf:`.
|
||||||
|
|
||||||
|
Directives are one grammar for everything markdown lacks, namespaced under `!adf:`:
|
||||||
|
`!adf:panel info` … `!adf:/panel` blocks, `!adf:mention[@Mikael]{id=5b10a2}` inline, `\!adf:` the
|
||||||
|
one escape. Not CommonMark's generic-directives proposal: its `:::` claims a form prose writes, and
|
||||||
|
its fence-length discipline ties a container's opener to its own body, where closing from the
|
||||||
|
opener nests by itself and leaf versus container falls out of the node's content model.
|
||||||
|
|
||||||
|
## CommonMark is a subset
|
||||||
|
|
||||||
|
2026-08-23, the maintainer. Goal 3. 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
|
||||||
|
directive, a pipe table or a `~~` pair is claimed — plus one image gap.
|
||||||
|
|
||||||
|
## Tables
|
||||||
|
|
||||||
|
2026-08-23, the maintainer. Goal 4. Valid while a pipe table holds only one header row and inline
|
||||||
|
cells.
|
||||||
|
|
||||||
|
One header row plus plain inline cells → pipe table; anything richer → directive form.
|
||||||
|
|
||||||
|
## Links
|
||||||
|
|
||||||
|
2026-09-13, nesting 2026-09-17, the maintainer. Goals 1 and 4. Valid while CommonMark's link
|
||||||
|
syntax is what readers edit.
|
||||||
|
|
||||||
|
`[text](url "title")`, or `<url>` for a bare autolink-shaped text, wherever CommonMark spells the
|
||||||
|
mark; `!adf:link[text]{attrs}` where it does not — an attribute CommonMark cannot hold, an `href` or
|
||||||
|
`title` no canonical escape spells, a paragraph opening whose CommonMark spelling would read as a
|
||||||
|
link reference definition — and a directive link CommonMark could spell is refused. No link wraps a
|
||||||
|
link — the bracket form goes literal, the directive form refused — which is CommonMark's prose
|
||||||
|
where its reference implementation nests one `<a>` in another.
|
||||||
|
|
||||||
|
## Ids stay site-local
|
||||||
|
|
||||||
|
2026-08-23, the maintainer. Goal 1. Valid while ADF ids are minted per site.
|
||||||
|
|
||||||
|
Identity-bearing nodes carry their ids in attributes; a document is only portable within its site —
|
||||||
|
accepted.
|
||||||
|
|
||||||
|
## The HTML dialect
|
||||||
|
|
||||||
|
2026-08-23, the maintainer. Goals 4 and 7. Valid while HTML output is read by consumers styling it
|
||||||
|
themselves.
|
||||||
|
|
||||||
|
The HTML dialect mirrors the markdown flavour: semantic elements, stable `adf-*` classes, `data-*`
|
||||||
|
for what HTML cannot express, text always escaped. No stylesheet ships.
|
||||||
|
|
||||||
|
## No runtime dependencies
|
||||||
|
|
||||||
|
2026-08-23, the maintainer. Goal 7. Valid while ~20 lines of own code, or a vendored table, do each
|
||||||
|
job a dependency would.
|
||||||
|
|
||||||
|
`dependencies` is empty. A runtime dependency enters only through an 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.
|
||||||
|
|
||||||
|
## Standards ship as data
|
||||||
|
|
||||||
|
2026-08-30, the CommonMark suite 2026-09-05 and ADF's schemas 2026-09-13, the maintainer. Goals 1,
|
||||||
|
3 and 7. 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
|
||||||
|
character references ship packed in their own module, so entity decoding is complete without one.
|
||||||
|
The CommonMark spec suite is the same shape of data and ships vendored at `corpus/commonmark-spec/`
|
||||||
|
rather than as the `commonmark-spec` dev dependency — that package is CommonJS-only, and Renovate
|
||||||
|
auto-bumping a spec version would silently point the vendored exception list's example numbers at a
|
||||||
|
renumbered suite. A spec bump is a deliberate re-pin, exceptions re-derived by hand beside it.
|
||||||
|
Atlassian's ADF JSON Schemas ship vendored the same way, at `spec/adf-schema/`, rather than as the
|
||||||
|
`@atlaskit/adf-schema` dev dependency — CommonJS-only, some fifty packages with React among them,
|
||||||
|
and a release most days for Renovate to automerge — re-pinned by hand when a payload or a report
|
||||||
|
shows the need.
|
||||||
|
|
||||||
|
## fast-check
|
||||||
|
|
||||||
|
2026-09-14, the maintainer. Goal 1. Valid while a failing generated document needs shrinking by
|
||||||
|
hand otherwise.
|
||||||
|
|
||||||
|
`fast-check` earns its place as a devDependency shrinking a failing generated document to the
|
||||||
|
nodes that break it.
|
||||||
|
|
||||||
|
## Any ES2022 engine
|
||||||
|
|
||||||
|
2026-09-01, the maintainer. Goal 7. 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
|
||||||
|
shipped source is ECMAScript and nothing else: no host import, no host global, no DOM.
|
||||||
|
`tsconfig.build.json` is that gate, typechecking and emitting the shipped files alone, so
|
||||||
|
`node:fs`, `process` and an ES2024 method are compile errors here rather than a consumer's crash
|
||||||
|
there. The standard is the line, never an engine list: one implementing it in part — Hermes is the
|
||||||
|
live doubt, on the Unicode property escapes emphasis matching leans on and on lookbehind — is out
|
||||||
|
of scope rather than a bug. Node's test runner, the corpus reads and the build are the repo's own,
|
||||||
|
never the library's, and `engines.node` states the floor the shipped JavaScript needs — `>=18` —
|
||||||
|
never the higher one those repo-only tools want.
|
||||||
|
|
||||||
|
## ESM only
|
||||||
|
|
||||||
|
2026-08-23, the maintainer. Goal 7. Valid while the audience's toolchains all import ES modules.
|
||||||
|
|
||||||
|
No CommonJS build, no dual-package hazard.
|
||||||
|
|
||||||
|
## One built entrypoint
|
||||||
|
|
||||||
|
2026-08-23, the maintainer. Goal 7. 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
|
||||||
|
type-strip under `node_modules` (`ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING`), so it cannot serve
|
||||||
|
an npm consumer.
|
||||||
|
|
||||||
|
## Public on npm
|
||||||
|
|
||||||
|
2026-08-23, the maintainer. The Audience. Valid while the audience installs from public
|
||||||
|
npm.
|
||||||
|
|
||||||
|
Published to public npmjs as `@larvit/adf-codec`. Public source: the Gitea repo goes public,
|
||||||
|
LICENSE in place, before the first publish.
|
||||||
+34
-32
@@ -154,15 +154,15 @@ outside code spans and code blocks, `\!adf:` in input yields the literal text.
|
|||||||
naming no open container or a node other than the innermost open one, a leaf given a body, an
|
naming no open container or a node other than the innermost open one, a leaf given a body, an
|
||||||
`!adf:` completing no directive, an inline `[content]` or `{attrs}` left unclosed at end of line,
|
`!adf:` completing no directive, an inline `[content]` or `{attrs}` left unclosed at end of line,
|
||||||
unparseable or duplicate-keyed attrs, invalid JSON in an opaque carry. Never a silent literal-text
|
unparseable or duplicate-keyed attrs, invalid JSON in an opaque carry. Never a silent literal-text
|
||||||
fallback — a typo that reparses as prose is the silent loss §2 refuses.
|
fallback — a typo that reparses as prose is the silent loss the round-trip refuses.
|
||||||
|
|
||||||
## The opaque carry (AGENTS.md §3)
|
## The opaque carry
|
||||||
|
|
||||||
A node no section spells where it stands — an unknown type, or a known one whose spelling belongs
|
A node no section spells where it stands (`docs/decisions.md` §Unknown nodes ride the carry) — an
|
||||||
to the other position — rides as its raw JSON and restores to a deep-equal node. A carry may hold
|
unknown type, or a known one whose spelling belongs to the other position — rides as its raw JSON
|
||||||
a node the emitter spells natively: it restores unreinterpreted, and the next emit spells it
|
and restores to a deep-equal node. A carry may hold a node the emitter spells natively: it restores
|
||||||
canonically (AGENTS.md §2). Block and inline positions canonicalize differently, each fitting
|
unreinterpreted, and the next emit spells it canonically (`docs/decisions.md` §The round-trip is the
|
||||||
where it sits:
|
product). Block and inline positions canonicalize differently, each fitting where it sits:
|
||||||
|
|
||||||
- **Block position**: a fenced code block with info string `carry`, body = the node's JSON —
|
- **Block position**: a fenced code block with info string `carry`, body = the node's JSON —
|
||||||
two-space indent, object keys sorted.
|
two-space indent, object keys sorted.
|
||||||
@@ -177,9 +177,10 @@ In block-directive position `!adf:carry` is a named error — the carry's block
|
|||||||
## Raw HTML in input
|
## Raw HTML in input
|
||||||
|
|
||||||
CommonMark input may contain raw HTML. `markdownToAdf` routes each construct through the foreign
|
CommonMark input may contain raw HTML. `markdownToAdf` routes each construct through the foreign
|
||||||
HTML element mapping (AGENTS.md §3; specified with the HTML dialect, todo.md milestone 6) — ADF
|
HTML element mapping (`docs/decisions.md` §Foreign HTML is refused by name; specified with the HTML
|
||||||
has no raw-HTML node, so a construct without a mapping, comments and processing instructions
|
dialect, todo.md milestone 6) — ADF has no raw-HTML node, so a construct without a mapping, comments
|
||||||
included, is an error result naming it. The flavour never emits raw HTML.
|
and processing instructions included, is an error result naming it. The flavour never emits raw
|
||||||
|
HTML.
|
||||||
|
|
||||||
## Block nodes
|
## Block nodes
|
||||||
|
|
||||||
@@ -192,10 +193,11 @@ have written as CommonMark is a named error.
|
|||||||
Each section lists attributes as `name (type)`. A parenthesized value set documents what real
|
Each section lists attributes as `name (type)`. A parenthesized value set documents what real
|
||||||
payloads hold; the type stays string and any value round-trips verbatim. Values map to attrs by
|
payloads hold; the type stays string and any value round-trips verbatim. Values map to attrs by
|
||||||
type: strings verbatim, numbers and booleans in canonical JSON spelling — quoted where not bare
|
type: strings verbatim, numbers and booleans in canonical JSON spelling — quoted where not bare
|
||||||
(`width="33.33"`) — and `json` values as the inline carry's serialization (compact, keys
|
(`width="33.33"`) — and `json` values as the inline carry's serialization (compact, keys sorted),
|
||||||
sorted), quoted. `markdownToAdf` emits `attrs`, `content` and `marks` keys only when non-empty;
|
quoted. `markdownToAdf` emits `attrs`, `content` and `marks` keys only when non-empty; editor-normal
|
||||||
editor-normal ADF reads an empty attrs object, marks array or content array as the absent key
|
ADF reads an empty attrs object, marks array or content array as the absent key (`docs/decisions.md`
|
||||||
(AGENTS.md §2) — the grammar's empty-`{attrs}` omission already collapses the two spellings.
|
§Equality is editor-normal) — the grammar's empty-`{attrs}` omission already collapses the two
|
||||||
|
spellings.
|
||||||
|
|
||||||
Marks on a block node ride the reserved attribute key `marks` — the node's marks array as a
|
Marks on a block node ride the reserved attribute key `marks` — the node's marks array as a
|
||||||
`json` value: `!adf:layoutSection {marks="[{\"attrs\":{\"mode\":\"wide\"},\"type\":\"breakout\"}]"}`.
|
`json` value: `!adf:layoutSection {marks="[{\"attrs\":{\"mode\":\"wide\"},\"type\":\"breakout\"}]"}`.
|
||||||
@@ -300,10 +302,10 @@ other text, or one carrying a title, is a named error: `mediaInline` carries a m
|
|||||||
### Tables
|
### Tables
|
||||||
|
|
||||||
One header row plus plain inline cells is a pipe table; anything richer is the directive form
|
One header row plus plain inline cells is a pipe table; anything richer is the directive form
|
||||||
(AGENTS.md §4). Precisely: a table emits as a pipe table exactly when the `table`, every row
|
(`docs/decisions.md` §Tables). Precisely: a table emits as a pipe table exactly when the `table`,
|
||||||
and every cell carry no attrs and no marks, the first row is all `tableHeader` and the rest all
|
every row and every cell carry no attrs and no marks, the first row is all `tableHeader` and the
|
||||||
`tableCell`, every row has the header's cell count, and every cell holds exactly one attr-less,
|
rest all `tableCell`, every row has the header's cell count, and every cell holds exactly one
|
||||||
mark-less paragraph — an empty cell holds one empty paragraph — with no `|` anywhere the
|
attr-less, mark-less paragraph — an empty cell holds one empty paragraph — with no `|` anywhere the
|
||||||
inline layer spells as syntax: a code span, an autolink, a link destination or title. A `|` there
|
inline layer spells as syntax: a code span, an autolink, a link destination or title. A `|` there
|
||||||
takes the directive form instead. A pipe table parses back to exactly that shape.
|
takes the directive form instead. A pipe table parses back to exactly that shape.
|
||||||
|
|
||||||
@@ -449,14 +451,14 @@ Shipped !adf:emoji[🎉]{shortName=":tada:"} on !adf:date{timestamp=175608000000
|
|||||||
```
|
```
|
||||||
|
|
||||||
**Whitespace CommonMark cannot hold.** A newline inside a text node, and a space or tab where
|
**Whitespace CommonMark cannot hold.** A newline inside a text node, and a space or tab where
|
||||||
CommonMark strips or refuses one — a block's inline content edges, either side of a line break,
|
CommonMark strips or refuses one — a block's inline content edges, either side of a line break, an
|
||||||
an em, strong or strike spelling's inner edges, a pipe cell's edges — is spelled
|
em, strong or strike spelling's inner edges, a pipe cell's edges — is spelled `!adf:text{text="…"}`,
|
||||||
`!adf:text{text="…"}`, the reserved key carrying the node's text, escaped by the attribute grammar
|
the reserved key carrying the node's text, escaped by the attribute grammar and never literal: pipe
|
||||||
and never literal: pipe cells trim and pad. The emitter wraps the whitespace run alone and leaves
|
cells trim and pad. The emitter wraps the whitespace run alone and leaves the rest plain text;
|
||||||
the rest plain text; `markdownToAdf` merges adjacent text nodes carrying identical marks and no
|
`markdownToAdf` merges adjacent text nodes carrying identical marks and no attributes
|
||||||
attributes (AGENTS.md §2). Input reads that spelling alone: the value is one run of spaces and
|
(`docs/decisions.md` §Equality is editor-normal). Input reads that spelling alone: the value is one
|
||||||
tabs, or one run of newlines, and anything else — a mixed run, or text CommonMark carries plainly —
|
run of spaces and tabs, or one run of newlines, and anything else — a mixed run, or text CommonMark
|
||||||
is a named error.
|
carries plainly — is a named error.
|
||||||
|
|
||||||
```
|
```
|
||||||
!adf:text{text=" "}Two leading spaces held, and one text node split!adf:text{text="\n"}over two lines.
|
!adf:text{text=" "}Two leading spaces held, and one text node split!adf:text{text="\n"}over two lines.
|
||||||
@@ -488,11 +490,11 @@ the directive form, open to no literal reading, is a named error.
|
|||||||
|
|
||||||
A spelling adds its mark to every inline node it wraps, and nesting is the marks array in order,
|
A spelling adds its mark to every inline node it wraps, and nesting is the marks array in order,
|
||||||
outermost first: `_!adf:underline[x]_` gives marks `[em, underline]`, `!adf:underline[_x_]` the
|
outermost first: `_!adf:underline[x]_` gives marks `[em, underline]`, `!adf:underline[_x_]` the
|
||||||
reverse.
|
reverse. `adfToMarkdown` nests in the order the array holds rather than sorting it —
|
||||||
`adfToMarkdown` nests in the order the array holds rather than sorting it — §2's equality
|
`docs/decisions.md` §Equality is editor-normal restores the array, not a set — and opens each
|
||||||
restores the array, not a set — and opens each spelling once over the longest run of adjacent
|
spelling once over the longest run of adjacent inline nodes carrying an identical mark, attributes
|
||||||
inline nodes carrying an identical mark, attributes included, at that depth. A run breaks at every
|
included, at that depth. A run breaks at every node the emitter carries, so no emitted carry sits
|
||||||
node the emitter carries, so no emitted carry sits inside a mark spelling.
|
inside a mark spelling.
|
||||||
|
|
||||||
An inline node whose marks no nesting spells — a mark type not listed here, an attrs key its
|
An inline node whose marks no nesting spells — a mark type not listed here, an attrs key its
|
||||||
spelling does not list, a value that is not the spelling's type, an attribute the spelling needs
|
spelling does not list, a value that is not the spelling's type, an attribute the spelling needs
|
||||||
@@ -502,7 +504,7 @@ close where the run sits (`un**-real**istic`), or one CommonMark's matching pair
|
|||||||
intra-word `*` runs together with a neighbouring `**`, and the multiple-of-3 rule can leave the
|
intra-word `*` runs together with a neighbouring `**`, and the multiple-of-3 rule can leave the
|
||||||
merged run's pairing to another delimiter — rides the inline carry whole. An opaque carry inside a
|
merged run's pairing to another delimiter — rides the inline carry whole. An opaque carry inside a
|
||||||
mark spelling is a named error in input: the carry restores its node exactly, marks included
|
mark spelling is a named error in input: the carry restores its node exactly, marks included
|
||||||
(AGENTS.md §3).
|
(`docs/decisions.md` §Unknown nodes ride the carry).
|
||||||
|
|
||||||
```
|
```
|
||||||
!adf:textColor[**Overdue**]{color="#ae2e24"}, H!adf:subsup[2]{type=sub}O, !adf:underline[signed].
|
!adf:textColor[**Overdue**]{color="#ae2e24"}, H!adf:subsup[2]{type=sub}O, !adf:underline[signed].
|
||||||
|
|||||||
@@ -7,11 +7,19 @@
|
|||||||
one no goal serves is proposed as a goal and asked. Sources: `AGENTS.md`'s body, the settled text
|
one no goal serves is proposed as a goal and asked. Sources: `AGENTS.md`'s body, the settled text
|
||||||
in this file's items, and `todo-history.md`, deleted with the bare `(28)` citations into it once
|
in this file's items, and `todo-history.md`, deleted with the bare `(28)` citations into it once
|
||||||
nothing cites it. Split by `AGENTS.md` section where one chunk is too big.
|
nothing cites it. Split by `AGENTS.md` section where one chunk is too big.
|
||||||
|
- **36b — Move §8, §9 and §14's decisions.** The code list's rules, release automation and the
|
||||||
|
non-goals.
|
||||||
|
- **36c — Move §10 and §11's decisions.** The engine legs, floors, size ratchet, bounds, spellings
|
||||||
|
and layout; the style rules stay working rules.
|
||||||
|
- **36d — Move the settled text in `todo.md`'s items and §15's dated rules, and point §15's
|
||||||
|
"the rule that closes it, landing here" at `docs/decisions.md`.** `AGENTS.md`'s sections are
|
||||||
|
renumbered once only working rules remain, their citations with them.
|
||||||
|
- **36e — Move `todo-history.md`'s decisions, re-point its citations and delete it.**
|
||||||
- **35 — Read and write plain markdown as a flavour of the markdown grammar.** Per Goal 2 and
|
- **35 — Read and write plain markdown as a flavour of the markdown grammar.** Per Goal 2 and
|
||||||
AGENTS.md §1, `plainMarkdownToAdf` is `markdownToAdf`'s parser and `adfToPlainMarkdown`
|
`docs/decisions.md` §Plain markdown is a flavour of the grammar, `plainMarkdownToAdf` is
|
||||||
`adfToMarkdown`'s writer, each with the plain flavour set; 10's rows are read and written there,
|
`markdownToAdf`'s parser and `adfToPlainMarkdown` `adfToMarkdown`'s writer, each with the plain
|
||||||
and the lift goes (the maintainer, 2026-09-27). The exports, their refusals and 10's rows stay as
|
flavour set; 10's rows are read and written there, and the lift goes (the maintainer, 2026-09-27).
|
||||||
they are.
|
The exports, their refusals and 10's rows stay as they are.
|
||||||
- **35a — Read the plain flavour in the parser and delete the lift.** 10's rows are read while
|
- **35a — Read the plain flavour in the parser and delete the lift.** 10's rows are read while
|
||||||
parsing, and `plain-lift.ts` is deleted, its tests reading through `plainMarkdownToAdf`. `>
|
parsing, and `plain-lift.ts` is deleted, its tests reading through `plainMarkdownToAdf`. `>
|
||||||
[!faq]- Why?` with the body on the next `>` line reads to an expand titled `Why?` whose body
|
[!faq]- Why?` with the body on the next `>` line reads to an expand titled `Why?` whose body
|
||||||
@@ -49,13 +57,14 @@
|
|||||||
the document ever saw, so nothing is lost; unwrapping them would put `alert(1)` on the page as
|
the document ever saw, so nothing is lost; unwrapping them would put `alert(1)` on the page as
|
||||||
prose. A `style` attribute is a separate question — `textColor` and `backgroundColor` are the
|
prose. A `style` attribute is a separate question — `textColor` and `backgroundColor` are the
|
||||||
marks it could reach — and is not read at `0.2.0`, the work outweighing what it buys.
|
marks it could reach — and is not read at `0.2.0`, the work outweighing what it buys.
|
||||||
So the set sorts every element three ways, and that is what AGENTS.md §3 gains in place of "error
|
So the set sorts every element three ways, and that is what `docs/decisions.md` §Foreign HTML is
|
||||||
result naming the element": a container around document content unwraps, content ADF cannot hold
|
refused by name becomes in place of "error result naming the element": a container around document
|
||||||
is an error result naming it, and what is not document content at all drops whole. A comment sorts
|
content unwraps, content ADF cannot hold is an error result naming it, and what is not document
|
||||||
into the second rather than the third because a person wrote those words on purpose. 10's
|
content at all drops whole. A comment sorts into the second rather than the third because a person
|
||||||
"Rejected in the survey" line names raw HTML and comments and does not contradict this: it rejects
|
wrote those words on purpose. 10's "Rejected in the survey" line names raw HTML and comments and
|
||||||
them as spellings the lossy pair writes and reads back, where `plainMarkdownToAdf` reads through
|
does not contradict this: it rejects them as spellings the lossy pair writes and reads back, where
|
||||||
`markdownToAdf`'s parser and so inherits whatever this set accepts.
|
`plainMarkdownToAdf` reads through `markdownToAdf`'s parser and so inherits whatever this set
|
||||||
|
accepts.
|
||||||
- **7 — Ship HTML: `adfToHtml`, `htmlToAdf`, and `markdownToHtml` / `htmlToMarkdown` composed
|
- **7 — Ship HTML: `adfToHtml`, `htmlToAdf`, and `markdownToHtml` / `htmlToMarkdown` composed
|
||||||
through ADF.** CommonMark spec suite runs against `markdownToHtml` from here (§10). The README's
|
through ADF.** CommonMark spec suite runs against `markdownToHtml` from here (§10). The README's
|
||||||
tagline and `package.json`'s `description` regain HTML (5g).
|
tagline and `package.json`'s `description` regain HTML (5g).
|
||||||
|
|||||||
Reference in New Issue
Block a user