36c - §10 and §11's decisions move to docs/decisions.md #138

Merged
lilleman merged 5 commits from 36c into main 2026-09-28 11:16:16 +02:00
5 changed files with 54 additions and 63 deletions
Showing only changes of commit fa6df33987 - Show all commits
+9 -8
View File
@@ -40,7 +40,6 @@ In `docs/decisions.md`:
- Firefox reads the build - Firefox reads the build
- The coverage floors - The coverage floors
- The size ratchet - The size ratchet
- The corpus
- Properties on a fixed seed - Properties on a fixed seed
- 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
@@ -88,8 +87,9 @@ holes through each other's lines (4d).
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`,
outside the build and coverage. outside the build and coverage.
Keep prose in `spec/flavour.md`'s `## Block nodes`, `## Inline nodes` and `## Marks` out of a `- ` Each `- ` bullet in `spec/flavour.md`'s `## Block nodes`, `## Inline nodes` and `## Marks` declares
bullet. the nodes named before its first em dash, with the attributes following `Attributes: ` — a
parenthesized value set reading `string`; fenced examples are skipped. Keep prose out of a bullet.
## 11. Code rules ## 11. Code rules
@@ -105,11 +105,12 @@ bullet.
path to name returns `Read<T>`, and the walk attaches the path where it knows it. path to name returns `Read<T>`, and the walk attaches the path where it knows it.
- Reuse before adding; the smallest sufficient diff is the benchmark; no speculative generality — - Reuse before adding; the smallest sufficient diff is the benchmark; no speculative generality —
a second consumer, or it goes. a second consumer, or it goes.
- Explicit over implicit; descriptive names; no catch-all files (`utils`, `helpers`, `misc`); a - Explicit over implicit; descriptive names; no catch-all files (`utils`, `helpers`, `misc`); a file
file does not repeat its directory in its name — `adf/document.ts`, never does not repeat its directory in its name — `adf/document.ts`, never `adf/adf-document.ts`. A name
`adf/adf-document.ts`. A name is the noun `spec/flavour.md` or ADF's schema uses for the is the noun `spec/flavour.md` or ADF's schema uses for the thing; a directory follows a split the
thing; a directory follows a split the spec draws; a placement these rules leave open goes spec draws; a placement neither this section nor `docs/decisions.md` §The source parts by ADF and
beside its only reader, or in what both read where there are two (the maintainer, 2026-09-18). format settles goes beside its only reader, or in what both read where there are two (the
maintainer, 2026-09-18).
## 12. Prose to a minimum ## 12. Prose to a minimum
+2 -2
View File
@@ -55,8 +55,8 @@ In priority order.
document and returns a whole result. document and returns a whole result.
9. **Fast once correct.** Conversion time grows linearly with the document wherever the goals above 9. **Fast once correct.** Conversion time grows linearly with the document wherever the goals above
allow it; a faster path that risks one of them is not taken. allow it; a faster path that risks one of them is not taken.
10. **Source a contributor can hold.** Any one function reads at a sitting, and the gate stops the 10. **Source a contributor can hold.** Any one function reads in one sitting, and no change makes
source drifting longer. the longest one longer.
## Audience ## Audience
+34 -46
View File
@@ -16,7 +16,7 @@ a backslash reach them intact; what the flavour cannot spell reduces ADF→ADF a
`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 corpus, not a claim made in prose. tested over a checked-in corpus (`corpus/README.md`), not a claim made in prose.
## Markdown in is a canonical fixpoint ## Markdown in is a canonical fixpoint
@@ -294,16 +294,15 @@ 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, the maintainer. Goals 7 and 8. Valid while the suite, rather than a smoke import, is 2026-09-01, the maintainer. Goals 7 and 8. Valid while Deno is the only leg refusing an
what proves an engine. extensionless specifier and Bun the only engine that is not V8.
The gate runs the suite under Deno and Bun as well as Node, the three images pinned alike, and The gate runs the suite under Deno and Bun as well as Node, and neither extra leg is Node's proof
neither extra leg is Node's proof twice. Deno refuses an extensionless or directory specifier, so twice. Deno refuses an extensionless or directory specifier, so it holds the module graph to the
it holds the module graph to the fully-spelled form a browser can load; Bun runs JavaScriptCore, fully-spelled form a browser can load; Bun runs JavaScriptCore, the one engine of the three that is
the one engine of the three that is not V8, where the Unicode property escapes emphasis matching not V8, where the Unicode property escapes emphasis matching leans on can disagree. Both refuse a
leans on can disagree. Both refuse a run matching no test, so Node's is the only vacuous-green run matching no test, so Node's is the only vacuous-green guard, and `AGENTS.md` §10's `node:` shims
guard, and a test may reach only for what all three `node:` shims carry — the price of proving rule is the price of proving those engines over the corpus rather than over a smoke import.
those engines over the corpus rather than over a smoke import.
## The gate installs the tarball ## The gate installs the tarball
@@ -319,7 +318,8 @@ 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 no other leg runs SpiderMonkey. 2026-09-04, the maintainer. Goal 7. Valid while the library claims a browser and no other leg runs
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
error fixtures and the real payloads — the `commonmark-spec` sort is the Node suite's to check — error fixtures and the real payloads — the `commonmark-spec` sort is the Node suite's to check —
@@ -345,7 +345,7 @@ compared against `undefined` — have a half no valid document reaches.
## The size ratchet ## The size ratchet
2026-09-20, the maintainer. Goal 10. Valid while no measure picks out what readers find hard 2026-09-20, the maintainer. Goal 10. Valid while no measure picks out what readers find hard
better than a function's length. (the comprehension panel, 2026-09-20).
`.oxlintrc.json`'s single rule, over the files `tsconfig.build.json` builds, is a per-function line `.oxlintrc.json`'s single rule, over the files `tsconfig.build.json` builds, is a per-function line
ceiling, set at that set's worst and moving only downward. It covers the built files alone, since ceiling, set at that set's worst and moving only downward. It covers the built files alone, since
@@ -359,13 +359,6 @@ config gone missing fails the leg instead of falling back to oxlint's own defaul
`--deny-warnings`, since a rule from a category this config never names arrives as a warning it `--deny-warnings`, since a rule from a category this config never names arrives as a warning it
exits 0 on. exits 0 on.
## The corpus
2026-08-23, the maintainer. Goals 1 and 8. Valid while the round-trip is proved by example.
All checked in: hand-built fixtures per node and combination; real ADF Atlassian's editor wrote; the
CommonMark spec suite against `markdownToAdf` and `markdownToHtml`.
## Properties on a fixed seed ## Properties on a fixed seed
2026-09-14, the maintainer. Goal 1. Valid while a red gate must reproduce. 2026-09-14, the maintainer. Goal 1. Valid while a red gate must reproduce.
@@ -375,16 +368,13 @@ markdown, on a fixed seed in the gate; a counterexample found becomes a round-tr
## The flavour spec is read as a source ## The flavour spec is read as a source
2026-09-01, the maintainer. Goals 1 and 6. Valid while `spec/flavour.md` restates the node tables 2026-09-01, the maintainer. Goal 1. Valid while `spec/flavour.md` restates the node tables in
in prose. prose.
`spec/flavour.md` is read as a source, so the node tables cannot drift from the prose they copy: `spec/flavour.md` is read as a source, so the node tables cannot drift from the prose they copy: its
each `- ` bullet in `## Block nodes`, `## Inline nodes` and `## Marks` declares the nodes named node and mark bullets must equal the tables in `adf/`. It guards the attributes alone: nodes that
before its first em dash, with the attributes following `Attributes: ` — a parenthesized value set differ in content model share a bullet, and the argument attribute is spelled ahead of `Attributes:
reading `string` — and must equal the tables in `adf/`. Fenced examples are skipped. It guards the `, so both answer to the round-trip corpus and to nothing else where a node has no fixture.
attributes alone: nodes that differ in 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.
## The node tables answer to Atlassian's schema ## The node tables answer to Atlassian's schema
@@ -417,14 +407,14 @@ once doubled the parser's frames per level.
Nothing spreads an unbounded array into a call — a node's siblings, a code block's held lines, a Nothing spreads an unbounded array into a call — a node's siblings, a code block's held lines, a
mark run's segments: the argument list caps near 125k and throws a `RangeError` where a `Result` is mark run's segments: the argument list caps near 125k and throws a `RangeError` where a `Result` is
owed. A walk pushes one at a time. A literal spread (`[...value]`) is not the same thing and is owed. A walk pushes one at a time. A literal spread (`[...value]`) is not the same thing and is
fine (4c). fine.
## A retry loop checks its own termination ## A retry loop checks its own termination
2026-09-20, the maintainer. Goal 1. Valid while a line retries until a fallback spells it. 2026-09-20, the maintainer. Goal 1. Valid while a fallback can fail to spell what it is handed.
A loop retrying an input until a fallback spells it refuses the pass taking no fallback, so its A loop retrying an input until a fallback spells it refuses the pass taking no fallback, so its
termination is the loop's own check (28). termination is the loop's own check.
## Readers scan by index ## Readers scan by index
@@ -436,7 +426,7 @@ before it reads, `indexOf` — never a fresh slice per character, and a per-char
scan that does not vary with the character: a megabyte through a quadratic walk is a minute rather scan that does not vary with the character: a megabyte through a quadratic walk is a minute rather
than a millisecond. A scan may keep what it read for a later walk of the same text, and the than a millisecond. A scan may keep what it read for a later walk of the same text, and the
fallback where it kept nothing must be the same reader over the same text at the same index, so the fallback where it kept nothing must be the same reader over the same text at the same index, so the
two cannot disagree — which is what makes the kept value a memo rather than a second spelling (4c). two cannot disagree — which is what makes the kept value a memo rather than a second spelling.
## The spelling memo ## The spelling memo
@@ -444,13 +434,11 @@ two cannot disagree — which is what makes the kept value a memo rather than a
per level above it otherwise. per level above it otherwise.
The parse keeps each node's readable spelling in a memo, so the `commonMarkSpelling` ask stops The parse keeps each node's readable spelling in a memo, so the `commonMarkSpelling` ask stops
spelling a node once per level above it (18). The node reference is the key, which holds because spelling a node once per level above it. `text` and `spelling` carry no depth and `headroom` is
the parse builds one object per position; `adfToMarkdown` passes no memo, where a consumer's affine in it, so a read at or above the depth that filled the entry rebases; a read below re-spells,
document may hold one node at two positions (4b). `text` and `spelling` carry no depth and because a hit skips the depth guards the walk it replaces runs and an ordered list past the marker
`headroom` is affine in it, so a read at or above the depth that filled the entry rebases; a read cap gives way, spending two emitter levels where the parser spent one. Only what succeeded is kept,
below re-spells, because a hit skips the depth guards the walk it replaces runs and an ordered list so no path minted at another position is ever read.
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.
## Only the hard break holds a raw newline ## Only the hard break holds a raw newline
@@ -471,15 +459,15 @@ 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 4. Valid while a readable spelling is tried ahead of a 2026-09-21, the maintainer. Goals 1 and 4. Valid while a readable spelling's refusal would cost a
general one. 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
general form fails on the same node (20): refusing there refuses a document the general form general form fails on the same node: refusing there refuses a document the general form spells, so a
spells, so a refusal the general form does not share belongs in the general form or nowhere. A refusal the general form does not share belongs in the general form or nowhere. A readable spelling
readable spelling that must spell its subtree before it can give way — the list, whose that must spell its subtree before it can give way — the list, whose thematic-break first line and
thematic-break first line and blank lines exist only spelled — hands that one walk to the general blank lines exist only spelled — hands that one walk to the general form instead: giving way after
form instead: giving way after the walk walks again at every level, doubling per level (4b). the walk walks again at every level, doubling per level.
## The attribute vocabulary is ADF's ## The attribute vocabulary is ADF's
+1
View File
@@ -20,6 +20,7 @@ type BlockSpelling = 'commonmark' | 'directive' | 'list'
type EmittedBlock = { headroom: number; spelling: BlockSpelling; text: string } type EmittedBlock = { headroom: number; spelling: BlockSpelling; text: string }
type KeptSpelling = { block: EmittedBlock | undefined; depth: number } type KeptSpelling = { block: EmittedBlock | undefined; depth: number }
type PlacedBlock = Omit<EmittedBlock, 'headroom'> & { node: AdfNode } type PlacedBlock = Omit<EmittedBlock, 'headroom'> & { node: AdfNode }
// Keyed by reference: the parse builds one object per position; a consumer's document may share one, so adfToMarkdown passes none.
export type SpellingMemo = Map<AdfNode, KeptSpelling> export type SpellingMemo = Map<AdfNode, KeptSpelling>
type Walk = { blocks: readonly PlacedBlock[]; headroom: number } type Walk = { blocks: readonly PlacedBlock[]; headroom: number }
type WalkedItem = { node: AdfNode; walk: Walk } type WalkedItem = { node: AdfNode; walk: Walk }
+8 -7
View File
@@ -7,9 +7,9 @@
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.
- **36d — Move the settled text in `todo.md`'s items and §15's dated rules, and point §15's - **36d — Move the settled text in `todo.md`'s items and §11 and §15's dated rules, and point
"the rule that closes it, landing here" at `docs/decisions.md`.** `AGENTS.md`'s sections are §15's "the rule that closes it, landing here" at `docs/decisions.md`.** `AGENTS.md`'s sections
renumbered once only working rules remain, their citations with them. 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.** - **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, `plainMarkdownToAdf` is `docs/decisions.md` §Plain markdown is a flavour of the grammar, `plainMarkdownToAdf` is
@@ -62,16 +62,17 @@
`plainMarkdownToAdf` reads through `markdownToAdf`'s parser and so inherits whatever this set `plainMarkdownToAdf` reads through `markdownToAdf`'s parser and so inherits whatever this set
accepts. 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 (`docs/decisions.md` §The corpus). The README's through ADF.** CommonMark spec suite runs against `markdownToHtml` from here. The README's
tagline and `package.json`'s `description` regain HTML (5g). tagline and `package.json`'s `description` regain HTML (5g).
- **31 — Make the branch figure the coverage floor is read against repeatable.** Three Node test - **31 — Make the branch figure the coverage floor is read against repeatable.** Three Node test
legs over one unchanged tree reported `emit/inline-line.ts` at 95.83%, 96.23% and 96.23%, and the legs over one unchanged tree reported `emit/inline-line.ts` at 95.83%, 96.23% and 96.23%, and the
total at 98.80%, 98.84% and 98.84% (2026-09-21). `--experimental-test-coverage` counts branches total at 98.80%, 98.84% and 98.84% (2026-09-21). `--experimental-test-coverage` counts branches
off V8's own coverage, which the runner's parallel files and V8's optimization make run-dependent, off V8's own coverage, which the runner's parallel files and V8's optimization make run-dependent,
so the number the floor is read against is not the code's alone. The floor of 98 holds today on so the number the floor is read against is not the code's alone. The floor of 98 holds today on
0.8 points of slack and `docs/decisions.md` §The coverage floors says it only ever moves upward, so the first raise to the measured 0.8 points of slack and `docs/decisions.md` §The coverage floors says it only ever moves upward,
figure reddens a run that changed nothing. Make the measurement repeatable, or state the number so the first raise to the measured figure reddens a run that changed nothing. Make the
the floor may be raised to and why it is not the measured one. measurement repeatable, or state the number the floor may be raised to and why it is not the
measured one.
- **33 — Make a carried mark run cost the line one re-emit.** `adfToMarkdown` spends 23 s on one - **33 — Make a carried mark run cost the line one re-emit.** `adfToMarkdown` spends 23 s on one
paragraph of 2000 × `un` plus `**-r**`: each run its flanking cannot spell re-emits the whole line paragraph of 2000 × `un` plus `**-r**`: each run its flanking cannot spell re-emits the whole line
before riding the carry, quadratic in the runs (Goal 9), and the plain reduction's before riding the carry, quadratic in the runs (Goal 9), and the plain reduction's