From c694896090f4722f68fdbb3ff886295d6c0cb65a Mon Sep 17 00:00:00 2001 From: Lilleman auf Larv Date: Mon, 28 Sep 2026 00:39:05 +0200 Subject: [PATCH] 36a - review: README links the decision log, premises that can lapse, citations fixed --- AGENTS.md | 25 +++++++++++++------------ README.md | 10 ++++++---- docs/decisions.md | 15 ++++++++------- spec/flavour.md | 22 +++++++++++----------- 4 files changed, 38 insertions(+), 34 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index f02359d..9a92350 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -172,16 +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. The size ratchet is the other such number, `.oxlintrc.json`'s single rule over the files -`tsconfig.build.json` builds — `oxlint`, since TypeScript 7 is a native compiler publishing no -in-process parser, only the `unstable/` AST surface an out-of-process handshake reaches: a -per-function line ceiling, set at that set's worst and moving only downward. It covers the built -files alone, since one ceiling over the tests too would have to be their worst, loosening the guard -over the shipped code. It guards against drift and never drives a refactor, so no cyclomatic rule -and no second lint rule join it: neither measure picked out what nine readers found hard (the -comprehension panel, 2026-09-20). Three switches guard a silent green: `IIFEs: 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. +`tsconfig.build.json` builds, measured by `oxlint` since TypeScript 7 is a native compiler +publishing no in-process parser, only the `unstable/` AST surface an out-of-process handshake +reaches. It is a per-function line ceiling, set at that set's worst and moving only downward. It +covers the built files alone, since one ceiling over the tests too would have to be their worst, +loosening the guard over the shipped code. It guards against drift and never drives a refactor, so +no cyclomatic rule and no second lint rule join it: neither measure picked out what nine readers +found hard (the comprehension panel, 2026-09-20). Three switches guard a silent green: `IIFEs: +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 editor wrote; the CommonMark spec suite against `markdownToAdf` and `markdownToHtml`. @@ -372,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) - 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 Goals 1 and 2 — an - item it outweighs moves later. A weighing no rule decides is asked as a gap. + in a release, weighed against every item on that release by the personas and `docs/decisions.md` + §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, 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: diff --git a/README.md b/README.md index 82a808d..f6d806b 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,8 @@ an HTML dialect. **Status: published — the markdown round-trip (`adfToMarkdown`, `markdownToAdf`); HTML at `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: [`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). @@ -143,7 +144,7 @@ saving what this pair read replaces mentions, attachments and macros with text. ## The errors An ADF node type this version does not know is not an error: it is carried opaquely and restores -unchanged (`docs/decisions.md §Unknown nodes ride the carry`). +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`, stable across minors and safe to `switch` on exhaustively with no `default`; `message` is free text @@ -195,7 +196,7 @@ emit refuses: ## The guarantees - `markdownToAdf(adfToMarkdown(doc))` equals `doc` — unknown node types included, carried opaquely - (`docs/decisions.md §Unknown nodes ride the carry`). + ([`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` 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 @@ -234,4 +235,5 @@ emit refuses: 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, Deno and Bun, and a headless Firefox converts the corpus through the built entrypoint. -Contract: `docs/decisions.md`. +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. diff --git a/docs/decisions.md b/docs/decisions.md index 98e0a98..84b9a5f 100644 --- a/docs/decisions.md +++ b/docs/decisions.md @@ -40,7 +40,7 @@ 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 -gains node types faster than this library spells them. +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 @@ -76,7 +76,7 @@ opener nests by itself and leaf versus container falls out of the node's content ## CommonMark is a subset -2026-08-23, the maintainer. Goal 3. Valid while the carve-outs stay the flavour's only claims. +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. @@ -117,7 +117,8 @@ for what HTML cannot express, text always escaped. No stylesheet ships. ## No runtime dependencies -2026-08-23, the maintainer. Goal 7. Valid while Goal 7 names 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 @@ -125,8 +126,8 @@ 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 -and 7. Valid while each table's upstream package is CommonJS-only or heavy. +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. @@ -163,7 +164,7 @@ never the higher one those repo-only tools want. ## ESM only -2026-08-23, the maintainer. Goal 7. Valid while every supported engine loads ES modules. +2026-08-23, the maintainer. Goal 7. Valid while the audience's toolchains all import ES modules. No CommonJS build, no dual-package hazard. @@ -177,7 +178,7 @@ an npm consumer. ## Public on npm -2026-08-23, the maintainer. Goal 7 and the Audience. Valid while the audience installs from public +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, diff --git a/spec/flavour.md b/spec/flavour.md index 69091a6..f1ed888 100644 --- a/spec/flavour.md +++ b/spec/flavour.md @@ -156,13 +156,13 @@ naming no open container or a node other than the innermost open one, a leaf giv 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 the round-trip refuses. -## The opaque carry (`docs/decisions.md` §Unknown nodes ride the carry) +## The opaque carry -A node no section spells where it stands — an unknown type, or a known one whose spelling belongs to -the other position — rides as its raw JSON and restores to a deep-equal node. A carry may hold a -node the emitter spells natively: it restores unreinterpreted, and the next emit spells it -canonically (`docs/decisions.md` §The round-trip is the product). Block and inline positions -canonicalize differently, each fitting where it sits: +A node no section spells where it stands (`docs/decisions.md` §Unknown nodes ride the carry) — an +unknown type, or a known one whose spelling belongs to the other position — rides as its raw JSON +and restores to a deep-equal node. A carry may hold a node the emitter spells natively: it restores +unreinterpreted, and the next emit spells it canonically (`docs/decisions.md` §The round-trip is the +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 — two-space indent, object keys sorted. @@ -490,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, outermost first: `_!adf:underline[x]_` gives marks `[em, underline]`, `!adf:underline[_x_]` the -reverse. -`adfToMarkdown` nests in the order the array holds rather than sorting it — §2's equality -restores the array, not a set — and opens each spelling once over the longest run of adjacent -inline nodes carrying an identical mark, attributes included, at that depth. A run breaks at every -node the emitter carries, so no emitted carry sits inside a mark spelling. +reverse. `adfToMarkdown` nests in the order the array holds rather than sorting it — +`docs/decisions.md` §Equality is editor-normal restores the array, not a set — and opens each +spelling once over the longest run of adjacent inline nodes carrying an identical mark, attributes +included, at that depth. A run breaks at every node the emitter carries, so no emitted carry sits +inside a mark spelling. 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