diff --git a/AGENTS.md b/AGENTS.md index bcae506..f29daa4 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -148,7 +148,7 @@ not take — is `unsupported-node-shape`, the emitter's code for the same mismatch read the other way — one code across both directions for good, since the call site knows which direction it called and parting them after `0.1.0` is MAJOR. `unmappable-html` names the version rather than the element: this one -converts no raw HTML, so at `0.3.0` the mapped elements stop erroring and the code stays for what +converts no raw HTML, so at `0.2.0` the mapped elements stop erroring and the code stays for what no ADF node carries. A refusal found before its path is known — the block walk's, a directive reader's — is a `ConvertFault`, the code and message alone; the node walk attaches the path as it descends, so a document reports its first error in document order. `not-an-adf-document` carries @@ -177,7 +177,7 @@ A parse names a position for every refusal it returns, so the type says so rathe `Result`, and a direction reading a source returns `Result` — `ConvertError` with `position` required. An optional field a direction always fills is a branch a consumer cannot take, and the `!` §11 bans is how they take it anyway. -`htmlToAdf` inherits this at `0.3.0`; the composed `markdownToHtml` and `htmlToMarkdown` keep the +`htmlToAdf` inherits this at `0.2.0`; the composed `markdownToHtml` and `htmlToMarkdown` keep the wide `Result`, since half their refusals come from an emit stage that read no source. ## 9. Release automation diff --git a/README.md b/README.md index 299928f..bcb8170 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Lossless conversion between **Atlassian Document Format** (ADF), an extended mar an HTML dialect. **Status: published — the markdown round-trip (`adfToMarkdown`, `markdownToAdf`); HTML at -`0.3.0`.** +`0.2.0`.** Plan: `todo.md`. Decisions: `AGENTS.md`. The 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). @@ -44,10 +44,10 @@ adfToMarkdown(doc: AdfDocument): Result markdownToAdf(markdown: string): Result isAdfDocument(v: unknown): v is AdfDocument -adfToHtml(doc: AdfDocument): Result // 0.3.0 -htmlToAdf(html: string): Result // 0.3.0 -markdownToHtml(markdown: string): Result // 0.3.0, via ADF -htmlToMarkdown(html: string): Result // 0.3.0, via ADF +adfToHtml(doc: AdfDocument): Result // 0.2.0 +htmlToAdf(html: string): Result // 0.2.0 +markdownToHtml(markdown: string): Result // 0.2.0, via ADF +htmlToMarkdown(html: string): Result // 0.2.0, via ADF ``` `Result` is `{ ok: true; value: T } | { ok: false; error: ConvertError }` — nothing throws. @@ -75,17 +75,17 @@ UTF-16 code unit, a JavaScript string index rather than a codepoint or a byte of or before the refusal — currently the start of the line the enclosing block begins on; a later minor may narrow that, never widen it. -Parsing — `markdownToAdf`, and `htmlToAdf` at `0.3.0`: +Parsing — `markdownToAdf`, and `htmlToAdf` at `0.2.0`: | Code | Fires when | What you can do | | --- | --- | --- | | `malformed-directive` | an `!adf:` the grammar cannot read — a prefix completing no directive, an unclosed container, `[content]` or `{attrs}`, a closer with no container of its name open, a leaf given a body, `{attrs}` out of order or duplicated, invalid JSON in a `carry` | write the spelling the message names, or escape the prefix — `\!adf:`, block and inline alike — to keep it literal text | | `malformed-pipe-table` | a pipe row that is no pipe table — a missing or ragged `---` delimiter row, an alignment colon in it, or a row not opening with a pipe | open every row with a pipe and give the delimiter row the header's cell count; to keep the lines literal text instead, escape the leading pipe of every one — escaping a single row leaves the next to open a fresh table and fail the same way | | `unknown-directive-name` | a directive whose name is no node or mark this version spells | check the name in `spec/flavour.md`, or escape the prefix as `\!adf:`; the spelling itself is well formed, so a later minor may give the name meaning | -| `unmappable-html` | the markdown holds a raw HTML tag, comment or processing instruction | remove it or write it in the flavour — ADF holds no raw-HTML node, and the element mapping lands at `0.3.0` | +| `unmappable-html` | the markdown holds a raw HTML tag, comment or processing instruction | remove it or write it in the flavour — ADF holds no raw-HTML node, and the element mapping lands at `0.2.0` | | `unmappable-image` | an image sits inside other content, or carries a title | give the image a paragraph of its own and drop the title | -Emitting — `adfToMarkdown`, and `adfToHtml` at `0.3.0`: +Emitting — `adfToMarkdown`, and `adfToHtml` at `0.2.0`: | Code | Fires when | What you can do | | --- | --- | --- | @@ -121,7 +121,7 @@ emit refuses: reference matching its definition only under Unicode case folding stays unresolved. Each is pinned `pending` in `corpus/commonmark-spec/exceptions.json`. - Raw HTML in markdown input is an error result, never a silent drop — a tag, a comment and a - processing instruction alike. ADF holds no raw-HTML node; the element mapping ships at `0.3.0`. + processing instruction alike. ADF holds no raw-HTML node; the element mapping ships at `0.2.0`. - Not every document converts back: `adfToMarkdown` is partial on valid ADF — a text node holding a carriage return, or a paragraph line beginning with a code span whose backticks read back as a fence. Show the refusal and keep the document read-only; saving markdown you could not produce @@ -134,7 +134,7 @@ emit refuses: is the `taskList` directive. - A document nested deeper than 500 levels is an error result, not a stack overflow. - The emitted formats are semver surface (AGENTS.md §8). -- **`0.3.0`** — `htmlToAdf(adfToHtml(doc))` equals `doc`; fidelity HTML cannot express rides +- **`0.2.0`** — `htmlToAdf(adfToHtml(doc))` equals `doc`; fidelity HTML cannot express rides `data-*` attributes. Foreign HTML maps a documented element set, an unmappable element is an error, and well-formed HTML only — no tag-soup recovery. diff --git a/todo.md b/todo.md index e546482..836c003 100644 --- a/todo.md +++ b/todo.md @@ -16,11 +16,18 @@ Start a session with: `Read AGENTS.md and todo.md, then do what todo.md's "Next ## Milestones -Shipping order: 3h, 3i, 3j, 5a, 5b, 5c, 5d, 5 → `0.1.0` (shipped 2026-09-05); 3k, 11, 4, 12, 13, 4b, 4c, 14, 15, 16, 10, 5g → `0.2.0`; -4d, 5f, 18 → `0.2.1`; 6, 7 → `0.3.0`; 9, 17 → TBD; 5e last. -The numbering is the order the work was planned in, not the order it ships. `0.2.0`'s order is settled -(the maintainer, 2026-09-13): 11 makes the tables 4 generates from answer to Atlassian's schema, 4 -proves 12, 13 spells 11's gaps in 12's grammar, and 12 rewrites code 4b and 4c change. +Shipping order: 3h, 3i, 3j, 5a, 5b, 5c, 5d, 5 → `0.1.0` (shipped 2026-09-05); 3k, 11, 4, 12, 13, 4b, +4c, 14, 15, 16, 18, 4d, 17, 10, 6, 7, 5f, 5g → `0.2.0`; 8, 9 → TBD; 5e last. +The numbering is the order the work was planned in, not the order it ships. Everything known and +shaped ships in one release rather than a string of them: nothing waits on a version, and no +consumer is served by the churn (the maintainer, 2026-09-18). So `0.2.0` completes §1's three +formats, and `0.2.1` and `0.3.0` are gone. `8` and `9` stay out as the two goals nothing has shaped +yet. `0.2.0`'s order is settled (the maintainer, 2026-09-13, extended 2026-09-18): 11 makes the +tables 4 generates from answer to Atlassian's schema, 4 proves 12, 13 spells 11's gaps in 12's +grammar, and 12 rewrites code 4b and 4c change; then 14 moves the files 15, 16 and 10 edit and HTML +is written against that layout, 4d marks the gate legs before 17 adds one, 17 puts the complexity +guardrail under the largest body of new code, and 5f and 5g read last because 7 is what changes the +bundle size and the tagline. - [x] **0 — Scaffold.** - [x] **1a — The directive grammar.** @@ -61,7 +68,7 @@ proves 12, 13 spells 11's gaps in 12's grammar, and 12 rewrites code 4b and 4c c - [x] **4.4 — The real payloads.** - [x] **4b — The block walk's retry (`0.2.0`).** - [x] **4c — The scanning rule's remaining sites (`0.2.0`).** -- [ ] **4d — What the gate says while it runs (`0.2.1`).** `ci.sh` runs nine legs and announces +- [ ] **4d — What the gate says while it runs (`0.2.0`).** `ci.sh` runs nine legs and announces none of them, so five minutes of a Gitea run read as silence and a hang cannot be told from a slow pull — the maintainer hit exactly this on the `0.1.0` release. Three causes, each its own fix. The legs need markers: `plainpages`' `ci.sh` prints a `step()` header per leg and @@ -88,7 +95,7 @@ proves 12, 13 spells 11's gaps in 12's grammar, and 12 rewrites code 4b and 4c c and is the trade to weigh rather than discover on a red release run. **Settled** (the maintainer, 2026-09-13): last of the known work, clear of `0.2.0`, placed there knowing the cutoff may land before `0.2.0` ships. -- [ ] **5f — Publish the bundle size (`0.2.1`).** Measure the shipped artifact and put the number in the +- [ ] **5f — Publish the bundle size (`0.2.0`).** 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. The quantity is what a consumer downloads and loads: the tarball `npm pack` produces, its unpacked `dist`, and the built JavaScript minified + gzipped — the figure the competitors @@ -106,10 +113,10 @@ proves 12, 13 spells 11's gaps in 12's grammar, and 12 rewrites code 4b and 4c c "why" note left. The top follows the package-README order: an npm version badge and the Gitea Actions badge, a tagline that is also `package.json`'s `description`, a feature list and a one-line table of contents, then install and the shortest runnable example; a table of - everything exported sits near the bottom. The HTML directions are one aside line under the API - until `0.3.0` ships them, the `// 0.3.0` signatures and the `0.3.0` guarantee going until then. - The tagline and `description` read "Lossless conversion between Atlassian Document Format and - extended markdown" until 7 restores HTML. + everything exported sits near the bottom. The HTML directions were to stay an aside until a + later release shipped them; 7 now ships in this one and reads ahead of this item, so the + README documents HTML as it documents markdown, the tagline and `description` naming both + (the maintainer, 2026-09-13, revised 2026-09-18). - [x] **5a — Rename to `@larvit/adf-codec`.** - [x] **5b — The consumer's error surface.** - [x] **5b1 — The error's source position.** @@ -118,11 +125,11 @@ proves 12, 13 spells 11's gaps in 12's grammar, and 12 rewrites code 4b and 4c c - [x] **5b4 — The README's consumer surface.** - [x] **5c — The build and the release pipeline.** - [x] **5d — The browser leg.** -- [ ] **6 — The HTML dialect spec (`0.3.0`).** Element-by-element mapping, the `data-*` fidelity +- [ ] **6 — The HTML dialect spec (`0.2.0`).** Element-by-element mapping, the `data-*` fidelity scheme, the opaque-carry form, and the documented foreign-element set `htmlToAdf` accepts. -- [ ] **7 — HTML, ship `0.3.0`.** `adfToHtml`, `htmlToAdf`, the composed `markdownToHtml` / - `htmlToMarkdown`. CommonMark spec suite runs against `markdownToHtml` from here (§10). The - README's tagline and `package.json`'s `description` regain HTML (5g). +- [ ] **7 — HTML, the third format (`0.2.0`).** `adfToHtml`, `htmlToAdf`, the composed + `markdownToHtml` / `htmlToMarkdown`. CommonMark spec suite runs against `markdownToHtml` from + here (§10). The README's tagline and `package.json`'s `description` regain HTML (5g). - [ ] **8 — CLI.** A later goal, shaped around the personas once the library exists. - [ ] **9 — The online sandbox.** A web page with two textboxes converting back and forth between ADF and markdown, powered by the library's browser build. - [ ] **10 — Lossy conversion (`0.2.0`).** Markdown other tools render readably, to and from ADF, @@ -217,11 +224,15 @@ proves 12, 13 spells 11's gaps in 12's grammar, and 12 rewrites code 4b and 4c c brackets stay literal text, CommonMark's rule that no link holds another — rather than dropping the outer link silently as `closeLink`'s `applyMark` does today, with a normalization fixture per shape (the stability-reviewer, 2026-09-16; the maintainer, 2026-09-17). -- [ ] **17 — A machine-enforced size guardrail.** Add a per-function complexity check to the gate — - branch count or size — so the fits-in-your-head guardrail fails the build rather than - waiting for a review to catch it (the systems-architect, 2026-09-16); placed after `0.3.0` - (the maintainer, 2026-09-17). -- [ ] **18 — The subtree the directive spelling asks about (`0.2.1`).** The parser asks +- [ ] **17 — A machine-enforced size guardrail (`0.2.0`).** Add a per-function complexity check to + the gate — branch count or size — so the fits-in-your-head guardrail fails the build rather + than waiting for a review to catch it (the systems-architect, 2026-09-16). It reads ahead of + 6, 7 and 10 so the largest body of new code is written under it, which is also what decides + the threshold: today's worst is `readDirectiveContent`, 27 lines and about 12 decision points + over four concerns in one loop — escape, code span, nested directive, bracket balance — which + 4c left half-split and this item either passes or forces apart (the systems-architect and the + maintainer, 2026-09-18). +- [ ] **18 — The subtree the directive spelling asks about (`0.2.0`).** The parser asks `commonMarkSpelling` at every directive-spelled block and the answer emits the whole subtree below, so a node at depth d is spelled d times: three nested rule-first directive lists cost 18 asks over 10 nodes, and 250 levels parse in 1.2 s at 16.4 kB, 4.9 s at 261 kB with a