From cb98ca99ded2f4bff9a9fb1f3b27dc1d75ae3fb2 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 29 Sep 2026 08:06:08 +0000 Subject: [PATCH] docs: README as a library landing page; history in CHANGELOG.md The README had become a log of every phase. It now says what the engine is, what it guarantees (byte-exact identity, stable spans, local reparse, one dialect), its status and how to install it pinned to a tag. The phase history moves to CHANGELOG.md, one entry per tag from v0.1.0 to v0.1.19, in Keep a Changelog form; that includes the Phase 6 breaking change. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_011Zsubk1jzejiCfkbLeWVQd --- CHANGELOG.md | 162 +++++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 129 +++++++++++++--------------------------- 2 files changed, 203 insertions(+), 88 deletions(-) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..461a161 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,162 @@ +# Changelog + +All notable changes to `@roobli/md`. The format follows +[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and versions follow +[Semantic Versioning](https://semver.org/). Before 1.0 a minor release may +change the API; each entry says so when it does. + +Phases refer to [`docs/design/roadmap.md`](docs/design/roadmap.md). + +## [0.1.19] - 2026-09-29 + +### Fixed +- Lazy continuation needs an open paragraph: empty quotes and marker-only empty + list items no longer absorb unindented lines. A complete link definition + absorbs at most one indented title line. Ordered lists that start at a + number other than 1 no longer interrupt a paragraph. (Phase 22, CommonMark + and micromark parity.) + +## [0.1.18] - 2026-09-27 + +### Fixed +- Pipe-less delimiter rows such as `- | -` or `1. | ---` are list items, not + tables. (Phase 21.) + +## [0.1.17] - 2026-09-27 + +### Fixed +- Marker-only empty list items are lists; an empty item followed by a blank + line and an indented block leaves that block outside the list. (Phase 20.) + +## [0.1.16] - 2026-09-23 + +### Fixed +- Sibling lists at the same indent with a different bullet (`-` `+` `*`) or + ordered delimiter (`.` `)`) start a new span. (Phase 19.) + +## [0.1.15] - 2026-09-22 + +### Added +- `md serve `: a read-only local folder browser over HTTP, previewing + Markdown through `parseBlocks`. The root is pinned and resolved with + `realpath`, so symlinks cannot escape it. (Phase 18.) + +## [0.1.14] - 2026-09-21 + +### Fixed +- A GFM table needs as many delimiter cells as header cells; otherwise it + stays a paragraph. (Phase 17.) + +## [0.1.13] - 2026-09-19 + +### Fixed +- Nested lists that mix markers stay one span when indented to the parent + item's content column. (Phase 16.) + +## [0.1.12] - 2026-09-18 + +### Fixed +- `text` followed by a continuous `---` is a setext level-2 heading, not a + thematic break. (Phase 15.) + +## [0.1.11] - 2026-09-17 + +### Fixed +- Adjacent definitions each start a block. + +## [0.1.10] - 2026-09-17 + +### Fixed +- Nesting and interruption parity: definition lazy continuations, GFM tables + interrupting paragraphs, and indented blocks after a blank line staying + inside a list. (Phase 14.) + +## [0.1.9] - 2026-09-17 + +### Fixed +- CommonMark lazy continuation: unprefixed paragraph lines stay inside quotes + and list items. (Phase 13.) + +## [0.1.8] - 2026-09-15 + +### Fixed +- CJK emphasis: `renderMarkdown` keeps Typora-shaped `**注意:**…` without + escaping the Chinese flanking characters. (Phase 12.) + +## [0.1.7] - 2026-09-15 + +### Added +- `sourceEditBetween` and `reparseFromText`, so a host holding a previous + split and a full new buffer can reparse incrementally. (Phase 11.) + +## [0.1.6] - 2026-09-15 + +### Fixed +- Up to three leading spaces before a block marker are counted as leading + whitespace or gap, as micromark does. (Phase 10.) + +## [0.1.5] - 2026-09-15 + +### Changed +- Table delimiters are widened to the three-dash style + (`| --- | :--- | :---: |`); cell content stays unpadded. (Phase 9.) + +## [0.1.4] - 2026-09-14 + +### Changed +- Serialize keeps verbatim runs (wiki links, alerts, footnotes, `[TOC]`, + snake_case identifiers) and bare http(s) autolinks as written. (Phase 8.) + +## [0.1.3] - 2026-09-14 + +### Changed +- Serialize writes a hard break as two trailing spaces and keeps each list's + marker and ordered delimiter. (Phase 7.) + +## [0.1.2] - 2026-09-11 + +### Added +- Native indented code blocks. + +## [0.1.1] - 2026-09-11 + +### Fixed +- Tightly adjacent quotes and callouts split the way micromark splits them. + +## [0.1.0] - 2026-09-11 + +### Added +- A native block scanner with exact byte offsets and preserved gaps: + headings, paragraphs, lists, task lists, fenced code, quotes, thematic + breaks, GFM tables, display math, YAML frontmatter, HTML blocks, and link + and footnote definitions. (Phases 1–3.) +- Incremental reparse of a dirty window with `reparseBlocks`. (Phase 4.) +- Byte-exact saves: `serializeDocument`, `identityUnits` and `joinSplit` + slice untouched blocks from the source rather than re-emitting them. + (Phase 5.) + +### Changed +- micromark is off the hot path and available only from + `@roobli/md/legacy-micromark`. (Phase 6; breaking for callers that relied + on the fallback.) + +[0.1.19]: https://github.com/roobli/md/compare/v0.1.18...v0.1.19 +[0.1.18]: https://github.com/roobli/md/compare/v0.1.17...v0.1.18 +[0.1.17]: https://github.com/roobli/md/compare/v0.1.16...v0.1.17 +[0.1.16]: https://github.com/roobli/md/compare/v0.1.15...v0.1.16 +[0.1.15]: https://github.com/roobli/md/compare/v0.1.14...v0.1.15 +[0.1.14]: https://github.com/roobli/md/compare/v0.1.13...v0.1.14 +[0.1.13]: https://github.com/roobli/md/compare/v0.1.12...v0.1.13 +[0.1.12]: https://github.com/roobli/md/compare/v0.1.11...v0.1.12 +[0.1.11]: https://github.com/roobli/md/compare/v0.1.10...v0.1.11 +[0.1.10]: https://github.com/roobli/md/compare/v0.1.9...v0.1.10 +[0.1.9]: https://github.com/roobli/md/compare/v0.1.8...v0.1.9 +[0.1.8]: https://github.com/roobli/md/compare/v0.1.7...v0.1.8 +[0.1.7]: https://github.com/roobli/md/compare/v0.1.6...v0.1.7 +[0.1.6]: https://github.com/roobli/md/compare/v0.1.5...v0.1.6 +[0.1.5]: https://github.com/roobli/md/compare/v0.1.4...v0.1.5 +[0.1.4]: https://github.com/roobli/md/compare/v0.1.3...v0.1.4 +[0.1.3]: https://github.com/roobli/md/compare/v0.1.2...v0.1.3 +[0.1.2]: https://github.com/roobli/md/compare/v0.1.1...v0.1.2 +[0.1.1]: https://github.com/roobli/md/compare/v0.1.0...v0.1.1 +[0.1.0]: https://github.com/roobli/md/releases/tag/v0.1.0 diff --git a/README.md b/README.md index 0a83906..5df7557 100644 --- a/README.md +++ b/README.md @@ -1,86 +1,56 @@ # @roobli/md -WYSIWYG-first markdown engine for [Noto](https://github.com/roobli/Noto). +A Markdown engine for editors that must not rewrite the file. + +`@roobli/md` splits a document into top-level blocks with exact byte offsets +and keeps the gaps between them. A host can then re-parse only the part that +changed, and save by slicing every untouched block straight from the original +source instead of re-serializing it. It is the engine under +[Noto](https://github.com/roobli/Noto), and it has no dependency on any editor +or UI. + +MIT licensed, so a host that is not Noto can use it without taking on Noto's +AGPL. + +## What it guarantees + +- **Byte-exact identity.** Parse a document, serialize it with every block + untouched, and you get the same bytes back: line endings, BOM, trailing + whitespace and odd gaps included. Noto's docs site checks this on every page + it publishes. +- **Stable spans.** Every block carries its `start` and `end` in the source, + its kind, and the literal gap before it. +- **Local reparse.** `reparseBlocks` and `reparseFromText` rebuild only the + window around an edit; blocks outside it keep their identity. +- **One dialect.** CommonMark plus GFM tables, task lists and strikethrough, + `$`/`$$` math, YAML frontmatter, footnotes, callouts, wiki links and + CJK-friendly emphasis. Behaviour is checked against micromark for parity. -The goal is a parser built around **real editing habits**, not around dumping a -full AST once and hoping the editor can live with it: block-oriented spans with -byte offsets, gaps preserved for untouched regions, dialect coverage Noto -already needs (CommonMark + GFM tables/strikethrough/task lists + math + YAML -frontmatter + CJK-friendly emphasis), and open times that beat the micromark -baseline Noto measures today (~570 ms medium / ~2.3 s large on the Linux -corpus). +## Status -Noto itself is AGPL-3.0. This package is **MIT**, same as -[`@roobli/canvas`](https://github.com/roobli/canvas), so hosts that are not Noto -can depend on it without inheriting that copyleft. +`0.1.x`: in production as Noto's default engine since Noto +`v0.0.2-alpha.112`. The API may still change before 1.0; every change is in +the [changelog](CHANGELOG.md). Published to npm once the contract reaches v1; +until then, install from a tag. -## Status +On a synthetic corpus, the native scanner splits a medium document in about +4.5 ms and a large one in about 14.5 ms, against about 304 ms and 1.5 s for +micromark ([bench](docs/design/bench.md)). -**Phase 22 shipped** — lazy continuation only with an open paragraph: empty -quotes / marker-only empty list items do not absorb unindented lazy lines; -complete link-definitions absorb at most one indented title line; ordered lists -with start ≠ 1 do not interrupt paragraphs (CommonMark / micromark). Footnote -lazy (Phase 14) unchanged. **Phase 21 shipped** — micromark list-steal pipe-less delimiters (`- | -`) are not tables. **Phase 20 shipped** — marker-only empty list items + blank put following structural outside (micromark). **Phase 19 shipped** — same-indent sibling lists with a different bullet -(`-`/`+`/`*`) or ordered delimiter (`.`/`)`) open a new span (CommonMark / -micromark parity); Phase 16 indented mixed nests unchanged. **Phase 18** — -`md serve ` thin local read-only folder browser (localhost HTTP shell: -tree + markdown/text/image preview via `parseBlocks` → HTML; root pinned; -path resolved with `realpath` so symlink escapes outside the served root are -rejected). **Phase 17** — GFM table header/delimiter column-count parity vs -micromark (`looksLikeTable` only when counts match; mismatched → paragraph; -ragged body with matching header/delim still table). -**Phase 16** — mixed-marker nested lists stay one span when indented to the -parent item content column (Noto intentional golden gap #2). **Phase 15** — -CommonMark setext level-2 (`text` + continuous `---`) vs thematic-break parity -(Noto intentional golden gap #1). **Phase 14** — nest / -interrupt parity vs micromark: definition lazy continuations, GFM tables -interrupt paragraphs, and lists keep indented nested blocks after a blank. -**Phase 13** — CommonMark lazy continuation in the native -split: unprefixed paragraph lines stay inside quotes and list items. **Phase 12** — CJK emphasis / Typora interop lock-in: -`renderMarkdown` keeps Typora-shaped `**注意:**…` without numeric-escaping -Chinese flanking. **Phase 11** — `sourceEditBetween` + `reparseFromText` so -hosts with a prior split and a full next buffer can incremental-reparse without -inventing ordinals. **Phase 10** — line-prefix offset alignment (0–3 leading ASCII spaces -before a block marker → leading/gaps, micromark parity). **Phase 9** — table -delimiter widening to vault three-dash style -(`| --- | :--- | :---: | ---: |`) while keeping `tablePipeAlign: false` -(content cells unpadded). **Phase 8** — serialize dialect verbatim runs -(wiki links, alerts, footnotes, `[TOC]`, snake_case / metrics) and bare -http(s) autolinks, matching Noto’s host handlers. **Phase 7** — hard-break -(two trailing spaces) and list marker / ordered delimiter from `node.data`. -**Phase 6** — micromark quarantined from the hot path; legacy entry -`@roobli/md/legacy-micromark`. Phase 5 `serializeDocument` / hardened -`joinSplit` implement Noto-aligned byte-exact saves. Phase 3 native scanner -covers heading / paragraph / list / fenced code / **indented code** / quote / -thematic / GFM tables / task lists / **display math** / **YAML frontmatter** / -**HTML blocks** / **link + footnote definitions**. -Synthetic medium/large A/B vs micromark (via legacy entry): native ~4.5 ms / -~14.5 ms vs micromark ~304 ms / ~1.5 s (see -[`docs/design/bench.md`](docs/design/bench.md)). -**v0.1.19** — Phase 22 lazy empty-container / link-def title / ordered start≠1. **v0.1.18** — Phase 21 list-steal pipe-less `- | -` delimiters. **v0.1.17** — Phase 20 empty list item + blank + structural outside. **v0.1.16** — Phase 19 same-indent list marker/delimiter split. **v0.1.15** — Phase 18 `md serve` + symlink-escape harden. **v0.1.14** — GFM table header/delimiter column-count parity. **v0.1.13** — mixed-marker nested lists. **v0.1.12** — setext-`---` vs hr. **v0.1.11** — adjacent defs as block starts. -**v0.1.10** — nest / interrupt parity. **v0.1.9** — lazy continuation. -**v0.1.8** — CJK / Typora-shaped strong lock-in. **v0.1.7** — -`sourceEditBetween` / `reparseFromText`. **v0.1.6** line-prefix offset -alignment. **v0.1.5** table delimiter widening. **v0.1.4** verbatim runs + bare -autolink. **v0.1.3** hard-break + list-marker; **v0.1.2** native indented-code; -**v0.1.1** quote/callout split. -Noto may pin `github:roobli/md#v0.1.19` when ready. - -See: - -- [`docs/design/vision.md`](docs/design/vision.md) — product goal -- [`docs/design/roadmap.md`](docs/design/roadmap.md) — phased plan -- [`docs/design/bench.md`](docs/design/bench.md) — native vs micromark numbers -- [`docs/design/typora-notes.md`](docs/design/typora-notes.md) — Typora study (interop research) -- [`docs/design/noto-bridge.md`](docs/design/noto-bridge.md) — how this plugs into Noto v3 -- [`docs/design/contract-v0.md`](docs/design/contract-v0.md) — engine contract sketch +Design notes: [vision](docs/design/vision.md) · +[roadmap](docs/design/roadmap.md) · [contract](docs/design/contract-v0.md) · +[Noto bridge](docs/design/noto-bridge.md) · +[Typora interop notes](docs/design/typora-notes.md) ## Install ``` -pnpm add github:roobli/md +pnpm add github:roobli/md#v0.1.19 ``` +Pin a tag: `main` moves. A host that runs install scripts may need to allow +this package's `prepare` step, which builds `dist/` with `tsc`. + ## Usage ```ts @@ -125,23 +95,6 @@ node dist/serve/cli.js serve ./folder Defaults to `127.0.0.1:4321`. Optional `--port` / explicit `--host 0.0.0.0`. Root is pinned by the CLI argument; the UI cannot change it. -## Breaking change (Phase 6) - -- Default entry **no longer** falls back to micromark. Documents outside the - Phase 1–5 native dialect are still scanned natively (best-effort kinds), not - bounced to micromark. -- Direct dependencies on `micromark-extension-*` and `mdast-util-from-markdown` - moved to **`optionalDependencies`** (needed only for the legacy entry). The - default path still depends on `mdast-util-to-markdown` (+ GFM/math/frontmatter - / CJK to-markdown helpers) for `renderMarkdown` on edited blocks — those pull - some micromark-*util* packages transitively, but the **micromark parser is - not called** on open / reparse / serialize of pristine spans. -- Compatibility import: - -```ts -import { splitWithMicromark } from "@roobli/md/legacy-micromark"; -``` - ## Scripts ```