36e - todo-history.md's decisions move to docs/decisions.md and it goes #141

Merged
lilleman merged 1 commits from 36e into main 2026-09-28 21:05:08 +02:00
4 changed files with 71 additions and 1196 deletions
+5 -3
View File
@@ -1,8 +1,7 @@
# Working in this repo # Working in this repo
The rules for every collaborator, human or agent, and an index of the decisions a reader would The rules for every collaborator, human or agent, and an index of the decisions a reader would
otherwise relitigate. Using the library: `README.md`. What is still to build: `todo.md`; a bare otherwise relitigate. Using the library: `README.md`. What is still to build: `todo.md`.
`(28)` cites that item's entry in `todo-history.md`.
## Decisions ## Decisions
@@ -21,6 +20,7 @@ In `docs/decisions.md`:
- Links - Links
- Ids stay site-local - Ids stay site-local
- Plain task ids come from position - Plain task ids come from position
- The plain flavour's spellings
- The HTML dialect - The HTML dialect
- No runtime dependencies - No runtime dependencies
- Standards ship as data - Standards ship as data
@@ -42,6 +42,7 @@ In `docs/decisions.md`:
- The coverage floors - The coverage floors
- The size ratchet - The size ratchet
- Properties on a fixed seed - Properties on a fixed seed
- The CommonMark suite checks three ways
- The flavour spec is read as a source - The flavour spec is read as a source
- The node tables answer to Atlassian's schema - The node tables answer to Atlassian's schema
- Nothing recurses unbounded - Nothing recurses unbounded
@@ -49,6 +50,7 @@ In `docs/decisions.md`:
- A retry loop checks its own termination - A retry loop checks its own termination
- Readers scan by index - Readers scan by index
- The spelling memo - The spelling memo
- Cost fixes are measured, never timed
- Only the hard break holds a raw newline - Only the hard break holds a raw newline
- Emphasis follows CommonMark's matching - Emphasis follows CommonMark's matching
- Readable spellings take the `try` prefix - Readable spellings take the `try` prefix
@@ -82,7 +84,7 @@ a hang. A leg added later owes the same marker, and a function a leg reaches cha
with `&&`, because the `||` that captures the leg's status suspends `set -e` for everything it with `&&`, because the `||` that captures the leg's status suspends `set -e` for everything it
calls. A leg whose output is both streamed and grepped keeps the copy in a `mktemp` calls. A leg whose output is both streamed and grepped keeps the copy in a `mktemp`
file: `tee /dev/stderr` reopens fd 2, and under `./ci.sh > log 2>&1` the two offsets punch NUL file: `tee /dev/stderr` reopens fd 2, and under `./ci.sh > log 2>&1` the two offsets punch NUL
holes through each other's lines (4d). holes through each other's lines.
`PROPERTY_RUNS=<runs>` raises the property runs and randomizes the seed for local digging. The `PROPERTY_RUNS=<runs>` raises the property runs and randomizes the seed for local digging. The
generators and run parameters properties share live in `src/conformance/property-harness.ts`, generators and run parameters properties share live in `src/conformance/property-harness.ts`,
+55 -8
View File
@@ -11,12 +11,15 @@ a backslash reach them intact; what the flavour cannot spell reduces ADF→ADF a
## The round-trip is the product ## The round-trip is the product
2026-08-23, the maintainer. Goal 1. Valid while a consumer saves back through the lossless pair. 2026-08-23, real payloads 2026-09-15, 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 `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. 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 When losslessness and readability conflict, losslessness wins. Round-trip equality is a property
tested over a checked-in corpus (`corpus/README.md`), not a claim made in prose. tested over a checked-in corpus (`corpus/README.md`), not a claim made in prose. Its real payloads
are invented content written in Atlassian's editor on the maintainer's test site, so none is
sanitized and a mention keeps the test user's real account id.
## Markdown in is a canonical fixpoint ## Markdown in is a canonical fixpoint
@@ -131,6 +134,22 @@ position in document order, unique within the document and minted with no host A
rejecting a missing `localId` takes the document and the same markdown reads to the same ids every rejecting a missing `localId` takes the document and the same markdown reads to the same ids every
run. run.
## The plain flavour's spellings
2026-09-14, panels 2026-09-25, the maintainer. Goal 5. Valid while GitHub's 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,
MkDocs, Docusaurus, Typora, Joplin, Logseq, Bear, Notion, Azure DevOps and Discord, GitHub's
renderer confirming each shape. Reader panels settled `error` as an error panel and the `==`
bounds (3 of 3), the external image's two forms (6 of 7), the omission notes and a rule opening a
list item dropping (3 of 3), and a list's numbering overflowing into bullets (3 of 3, 5 of 7).
Reading takes other tools' spellings, since it reads their output and writes none of them.
Rejected: `~sub~` and `^sup^` (`~2~` is a strike on GitHub, so `subsup` drops), underline and colour
spellings, raw HTML (`<details>`, `<mark>`), MkDocs `!!!` and the `:::` admonition family,
footnotes, definition lists, wikilinks, embeds, tags, comments, TOC tokens, spoilers, task states
past `[x]`/`[ ]`, and lifting bare URLs, `@name`, `:shortcode:` or ISO dates into nodes.
## The HTML dialect ## The HTML dialect
2026-08-23, the maintainer. Goals 4 and 7. Valid while HTML output is read by consumers styling it 2026-08-23, the maintainer. Goals 4 and 7. Valid while HTML output is read by consumers styling it
@@ -202,21 +221,24 @@ an npm consumer.
## Public on npm ## Public on npm
2026-08-23, the maintainer. Goal 7. Valid while the package's source stays public beside it. 2026-08-23, the name 2026-09-01, the maintainer. Goals 2 and 7. Valid while the package's source
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,
LICENSE in place, before the first publish. LICENSE in place, before the first publish. A codec, since it converts both directions, and named
for the hub rather than the formats around it.
## The formats are API ## The formats are API
2026-08-23, content models 2026-09-16, the maintainer. Goals 1 and 6. Valid while consumers store 2026-08-23, strict input 2026-09-01, content models 2026-09-16, the maintainer. Goals 1 and 6.
what the library emits. Valid while consumers store what the library emits.
The emitted markdown and HTML are contracts. After 1.0: previously-emitted output parsing The emitted markdown and HTML are contracts. After 1.0: previously-emitted output parsing
differently, or not at all, is MAJOR; new syntax while old output still round-trips is MINOR. differently, or not at all, is MAJOR; new syntax while old output still round-trips is MINOR.
Pre-1.0, normal 0.x rules. A spelled node's content model is part of that contract — leaf or Pre-1.0, normal 0.x rules. A spelled node's content model is part of that contract — leaf or
container is the model, not the syntax — so giving a spelled node's model content it had not, or container is the model, not the syntax — so giving a spelled node's model content it had not, or
taking it away, is MAJOR whatever ADF's own schema does. taking it away, is MAJOR whatever ADF's own schema does. Input reads the canonical directive
spelling alone — spacing, key order, each value's spelling — since loosening it later is MINOR.
The error surface is a contract too; `README.md` §The errors states it to the consumer, and the The error surface is a contract too; `README.md` §The errors states it to the consumer, and the
types in `src/result.ts` hold its shape. types in `src/result.ts` hold its shape.
@@ -354,7 +376,9 @@ is the other's `127.0.0.1`; `--headless --screenshot` has no such channel, and l
`dist/index.js` in a globals-stripped realm buys one by not running a browser. The leg re-checks `dist/index.js` in a globals-stripped realm buys one by not running a browser. The leg re-checks
the conversions and nothing else — each fixture's emitted markdown, its parsed document, its error the conversions and nothing else — each fixture's emitted markdown, its parsed document, its error
code — leaving the corpus's pairing, uniqueness, source positions and byte-level equality to the code — leaving the corpus's pairing, uniqueness, source positions and byte-level equality to the
Node suite that owns them. Node suite that owns them. `selenium/standalone-firefox` runs it over the smaller
`instrumentisto/geckodriver`: the leg is worth a current SpiderMonkey, and that image fell four
Firefox majors behind.
## The coverage floors ## The coverage floors
@@ -390,6 +414,18 @@ exits 0 on.
Beside the corpus, properties run over documents generated from the node tables and over generated Beside the corpus, properties run over documents generated from the node tables and over generated
markdown, on a fixed seed in the gate; a counterexample found becomes a round-trip fixture. markdown, on a fixed seed in the gate; a counterexample found becomes a round-trip fixture.
## The CommonMark suite checks three ways
2026-08-27, the maintainer. Goals 1 and 8. Valid while the suite's answers are HTML ADF cannot be
compared against.
Each example is a named error or markdown that parses and emits to itself byte for byte; its
reference HTML's text, tags stripped and entities decoded, equals the parsed document's; and its
elements count the marks and nodes they map to. The fixpoint alone passes a parser returning the
empty document, the text alone one dropping every emphasis. An exception is the maintainer's to
add, and valid CommonMark parsing to a document `adfToMarkdown` refuses where a spelling could exist
is a bug to fix, never an exception.
## The flavour spec is read as a source ## The flavour spec is read as a source
2026-09-01, the maintainer. Goal 1. Valid while `spec/flavour.md` restates the node tables in 2026-09-01, the maintainer. Goal 1. Valid while `spec/flavour.md` restates the node tables in
@@ -465,6 +501,17 @@ rebases; a read below re-spells, because a hit skips the depth guards the walk i
an ordered list past the marker cap gives way, spending two emitter levels where the parser spent an ordered list past the marker cap gives way, spending two emitter levels where the parser spent
one. Only what succeeded is kept, so no path minted at another position is ever read. one. Only what succeeded is kept, so no path minted at another position is ever read.
## Cost fixes are measured, never timed
2026-09-18, the maintainer and the stability-reviewer. Goal 9. Valid while Goal 9 promises growth
rather than a figure.
A cost fix that changes no behaviour lands on the suite staying green with no fixture output
changed, and a before-and-after figure in its PR; the gate times nothing. Measured and kept:
`adfDocumentFault`'s shape and depth walks stay two — the parting gives depth its own code — at
52 ms for a 9 MB document the emit takes 314 ms over; and `continuesContainer`'s re-scan per item
level stays, linear in the lines and bounded in depth by the 500-level guard.
## Only the hard break holds a raw newline ## Only the hard break holds a raw newline
2026-08-26, the maintainer. Goal 1. Valid while the whitespace carry finds a line edge by its raw 2026-08-26, the maintainer. Goal 1. Valid while the whitespace carry finds a line edge by its raw
-1172
View File
File diff suppressed because it is too large Load Diff
+11 -13
View File
@@ -2,17 +2,11 @@
## 0.2.0 ## 0.2.0
- **36 — Move every decision into `docs/decisions.md`, indexed from `AGENTS.md`.** Each entry states
the decision, its date, who made it, the README goal it serves and the premise it is valid while;
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
nothing cites it. Split by `AGENTS.md` section where one chunk is too big.
- **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
`docs/decisions.md` §Plain markdown is a flavour of the grammar, 10's rows are read by `docs/decisions.md` §Plain markdown is a flavour of the grammar, README §Plain markdown's rows are
`markdownToAdf`'s parser and written by `adfToMarkdown`'s writer, and the lift goes. The exports, read by `markdownToAdf`'s parser and written by `adfToMarkdown`'s writer, and the lift goes. The
their refusals and 10's rows stay as they are. exports, their refusals and those 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.** The 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
keeps the next lines' link targets and marks, and `> [!tip] Title` then `> body` to a panel keeps the next lines' link targets and marks, and `> [!tip] Title` then `> body` to a panel
@@ -24,14 +18,15 @@
`plainMarkdownToAdf(adfToPlainMarkdown(doc))` keeps a literal `==x==`, a quote opening `[!NOTE]` `plainMarkdownToAdf(adfToPlainMarkdown(doc))` keeps a literal `==x==`, a quote opening `[!NOTE]`
and a list whose items all open `[x] ` as text. A highlighted `=` (today `=====`) and `a==b` and a list whose items all open `[x] ` as text. A highlighted `=` (today `=====`) and `a==b`
(today `==a==b==`, highlighting `a` alone) come back highlighted whole, or lose the highlight (today `==a==b==`, highlighting `a` alone) come back highlighted whole, or lose the highlight
where no spelling holds them; 10c's byte-for-byte property misses both, since the wrong document where no spelling holds them; the plain pair's byte-for-byte property misses both, since the
re-spells to the same bytes. The reduction keeps only degrading what the flavour cannot spell. wrong document re-spells to the same bytes. The reduction keeps only degrading what the flavour
cannot spell.
- **10f — Give task nodes read from plain markdown position ids.** Per `docs/decisions.md` §Plain - **10f — Give task nodes read from plain markdown position ids.** Per `docs/decisions.md` §Plain
task ids come from position, README §Plain markdown's `localId` bullet saying so. The id spelling task ids come from position, README §Plain markdown's `localId` bullet saying so. The id spelling
is part of the chunk. is part of the chunk.
- **6 — Specify the HTML dialect.** Element-by-element mapping, the `data-*` fidelity scheme, the - **6 — Specify the HTML dialect.** Element-by-element mapping, the `data-*` fidelity scheme, the
opaque-carry form, and the documented foreign-element set `htmlToAdf` accepts — the set opaque-carry form, and the documented foreign-element set `htmlToAdf` accepts — the set
`markdownToAdf` shares (`spec/flavour.md` §Raw HTML in input; 29). `markdownToAdf` shares (`spec/flavour.md` §Raw HTML in input).
The set sorts per `docs/decisions.md` §Foreign HTML sorts three ways. The set sorts per `docs/decisions.md` §Foreign HTML sorts three ways.
- **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. The README's through ADF.** CommonMark spec suite runs against `markdownToHtml` from here. The README's
@@ -53,6 +48,9 @@
`line-escaping.ts`'s `charAt` and the parser's flanking read one UTF-16 unit beside an astral `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 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. punctuation — and, where they do, read the code point, with a fixture per direction.
- **38 — Spell a lone surrogate in a text node so it survives a UTF-8 encode.** `adfToMarkdown`
emits it verbatim, so markdown stored as UTF-8 reads back U+FFFD; attribute values already escape
it.
- **5f — Publish the bundle size, after 7 changes it.** Measure the shipped artifact and put the - **5f — Publish the bundle size, after 7 changes it.** Measure the shipped artifact and put the
number in the README, kept honest by the release pipeline rather than by a human re-reading it. number in the README, kept honest by the release pipeline rather than by a human re-reading it.
The quantity is what a consumer downloads and loads: the tarball `npm pack` produces, its unpacked The quantity is what a consumer downloads and loads: the tarball `npm pack` produces, its unpacked