mdlib-pure

Built with AI #

mdlib-pure was designed by Michele Polo and written with Claude (Anthropic) through Claude Code. The maintainer sets the direction and makes the calls; agents write the code, the tests and the docs, review the work and try to break it. This page tells how, with facts from the git history and the changelog.

The history has 49 commits, from 28 October 2026 to 2 November 2026; 47 of them carry a Co-Authored-By: Claude trailer.

A contract written for agents #

The first commit already shipped AGENTS.md: rules that each come with the command that verifies them, and recipes for extending the parser. To test it, an agent implemented the ::: container plugin using only that file (5a482ba). Its friction log changed both the core (finalize() now closes a block's open children) and the guide, which gained the container recipe, the ordering rules against GFM table rows and the per-line cost of continue.

From then on, first-party plugins were written by agents from AGENTS.md:

Plugin gzip at the time Tests Commit
mdlib-pure/container 0.81 KB 37 5a482ba
mdlib-pure/math 0.91 KB 65 1c4dd87
mdlib-pure/mark 0.13 KB 37 1c4dd87
mdlib-pure/frontmatter 0.57 KB 38 1c4dd87
mdlib-pure/deflist 1.22 KB 54 53f6b0f

The benchmark suite in bench/ was built by an agent too (cb973e5).

Bugs found by adversarial tests and reviews #

Every new syntax ships with adversarial-input tests, and agents reviewed the code looking for ways to break it. Some of what they found, with the numbers from the commits that fixed it:

  • Inherited ReDoS. The link title regex had overlapping alternatives (exponential backtracking), inherited from the reference implementation, and unanchored trailing-whitespace regexes were quadratic. Rewritten, and AGENTS.md gained its rule on linear regexes (b6fde7c).
  • Prototype lookup. &constructor; resolved to Object.prototype in the fallback entity decoder: now a Map, and a rule (b6fde7c).
  • Quadratic GFM tables. Opening a table split the whole paragraph on every delimiter-like line: 7.5 s on 30k lines, then 110 ms. Unbounded footnote labels: 805 ms, then 82 ms (ea3dfd8).
  • Deep emphasis. Interleaved delimiters nested past MAX_NESTING took 3.8 s at 60k, then 91 ms. Found by an agent review, with three more fixes in the same commit (74de6d1).
  • Quadratic sibling search. Many setext headings or one-row tables took 1.1 s at 100k; now linear (53f6b0f).
  • Unsanitized adapter output. With html: true and sanitize, toVNodes() was not sanitized, so the framework adapter passed raw HTML to dangerouslySetInnerHTML. The regression test fails on the previous version (e440762).
  • Tailwind clash. A real Tailwind CSS v4 build showed that @theme inline { --radius-md } replaced Tailwind's own rounded-md for the whole project (27395d2).
  • CSS source order. A screenshot of the first demo editor revealed that, with zero-specificity rules, source order decides: the first heading kept its top margin and alerts had extra space below. The edge resets moved to the end of the theme (ea3dfd8; the screenshot is below).
  • A timing test that only failed on CI. A 4 MB adversarial input took 2.2 s on the CI runner, over the 2 s limit, while parsing was linear (~130 ms/MB). The first attempted fix made paragraphs quadratic; measuring caught it and it was reverted. The input shrank to 1 MB and every timing limit became budget(ms), tripled on CI (a593dd7).

Web Components, then Blazor #

mdlib-pure/element added two custom elements that work in plain HTML and in any framework: <mdlib-markdown>, a viewer, and <mdlib-editor>, an editor with toolbar and live preview (7f25396). They were written as the base of a Blazor wrapper. Core, every plugin and both elements came to 19.8 KB gzip at the time, within a 26 KB budget; KaTeX and Mermaid come through loaders, fetched only when a document needs them. The commit shipped with 7 Chromium tests.

An agent then built MdlibPure.Blazor on the elements, and the work was reviewed (13c87c3): a Razor class library for .NET 10 with a viewer, an editor bound with @bind-Value, and a C# service for the AST. It has 48 bUnit tests and 20 Playwright end-to-end tests across Static SSR, Interactive Server, WebAssembly and Auto. Version 0.1.0 is attached to a GitHub release; version 0.1.1 (release) adds PodcastEditor. The guide is its README.

Building the wrapper found three problems in the elements, each reproduced in a test before the fix (60c7298):

  • DOM morphing. Blazor enhanced navigation (like Turbo and htmx) empties elements it did not create, and the elements never recovered. A MutationObserver now renders the viewer again and puts back the editor's toolbar and panes, keeping the typed text.
  • A stylesheet that disappeared. The KaTeX stylesheet was a <link> added once, which a morph removed for good. It is now adopted through document.adoptedStyleSheets, with absolute font URLs.
  • Late loaders. Elements defined before their loaders never drew math or diagrams. Every defineElements() call now refreshes the elements already in the page.

Then a rule for the docs (8dcf108): rule 10 of AGENTS.md, checked by npm run check, requires every public entry point to be documented in docs/api.md and the README, and every docs/*.md and bindings/*/README.md to be linked from the README. This site adds the other half: its build fails when one of those guides is not published as a docs page. That is how the Web Components guide and the Blazor README became pages here.

Then Angular #

A second agent built ngx-mdlib-pure on the same elements, in a git worktree of its own so that it could not collide with the agent updating this site (5a551cd). The package has standalone components whose tag is the Web Component (<mdlib-editor [(value)]>, also a form control for ngModel and reactive forms, and <mdlib-markdown>), a provider, and a service for the AST that also runs on the server. It is checked by 29 unit tests in Chromium and 11 end-to-end tests against a server-rendered, hydrated demo. Its README became a docs page here in the same commit, since the site build requires it.

Building and reviewing it found four problems outside the wrapper, each now covered by a test or a check:

  • No disabled state. Inside <fieldset disabled>, the editor still took typing. It now disables its text area and formatting buttons, like a disabled <textarea>.
  • A loader type that rejected KaTeX. The documented katex: () => import('katex/contrib/auto-render').then((m) => m.default) did not compile under strict TypeScript. npm run typecheck now compiles it against KaTeX's own types.
  • Warnings in every build. Bare import "./chunk-*.js" lines in dist/ made each consumer's bundler warn ten times. The build removes them.
  • A stale copy in the cache. Vite kept serving the mdlib-pure it had pre-bundled before a rebuild, so the unit tests ran against old code. The Angular workspace now drops that cache before testing.

Nothing is on a package registry yet. The README, the guides, AGENTS.md and this site say so, and each gives the tarball to install today, attached to a GitHub release.

The maintainer decides #

Agents propose; Michele decides. Some of the decisions in the history:

  • The URL policy: an allowlist of http, https, mailto, tel, sms and relative URLs, with data: only for raster images (9175b67).
  • Heading ids: first prefixed with user-content- by default (9500a6b), then clean by default, with the prefix recommended for untrusted input (6303064).
  • The core size budget raised to 15 KB, to recover 39% of GFM throughput (caf2d63).
  • Pandoc-style math output, so that KaTeX auto-render and MathJax work with no configuration (691260a).
  • Mermaid diagrams: costs and benefits were laid out before any code (Mermaid 12 measured at 215 KB gzip on first load). Chosen: no dependency on Mermaid, lazy loading, theme from the tokens with redraw on light/dark, pre-rendered output only through a render option (287334c).
  • The Blazor wrapper: built on the Web Components, in this repository, for .NET 10, in every render mode, with every plugin and access to the AST, and no server-side rendering of the Markdown for SEO (13c87c3).
  • The repository made public and this site published on GitHub Pages (ba0b3a4).
  • Publishing on npm put on hold, and the Blazor package released only on GitHub. The Angular wrapper followed, built on the same elements, and the docs say plainly that nothing is on a registry yet (5a551cd); the npm tarballs of mdlib-pure and the Angular wrapper are attached to GitHub releases as well.

Checked on every change #

npm run verify runs the type checker, the tests, the invariants (zero dependencies, no HTML sinks) and the size budgets. GitHub Actions runs it on Node 22 and 24, plus a Chromium suite with Playwright: CommonMark through the DOM, renderTo() never assigning innerHTML without raw HTML, hostile Markdown unable to run script, the theme, and a real Tailwind build (297d6af). A blazor job builds, tests and packs the wrapper and runs its end-to-end tests (13c87c3).

This site #

This site was built the same way: an agent in Claude Code wrote the build script, the pages and the playground. The docs are the repository's Markdown rendered by mdlib-pure itself, and the screenshots below were taken with Playwright. Checking the Mermaid sample in a real browser found two problems that unit tests had not: this site switches themes through <meta name="color-scheme">, which the redraw did not watch, and Mermaid's black default edge-label background in dark mode. Both were fixed in the library (287334c). The samples include a live <mdlib-editor> and <mdlib-markdown>, defined from the library's sources with this site's own copies of KaTeX and Mermaid as loaders.

Screenshots

Captured with Playwright in Chromium while the library and this site were built; the captions come from docs/screenshots/README.md, parsed with mdlib-pure.

Commit history

Every commit, oldest first, from git log at build time (times in CET).

  1. a7fc8c0feat: CommonMark 0.31.2 core parser and renderer
  2. b6fde7cfix(security): remove ReDoS and quadratic regexes, prototype lookups
  3. 68836c4feat: GFM plugin, semantic-token theme and Tailwind v4 integration
  4. ea3dfd8fix(gfm): linear-time table header scan and bounded footnote labels; docs, template, demo
  5. 5a482bafeat: container plugin; finalize closes open children; AGENTS.md from dogfooding
  6. 9175b67feat(security): default URL allowlist
  7. 297d6afci: GitHub Actions (verify on Node 22/24, browser tests in Chromium)
  8. 60c60d3feat: highlight option for code blocks
  9. 65ede55feat: headings plugin (GitHub slugs, permalinks, toc)
  10. 27395d2test: verify the Tailwind v4 integration with a real build; fix rounded-md shadowing
  11. 9500a6bfix(security): headings ids prefixed with user-content- by default
  12. 6460289feat: framework adapter for React, Preact and Vue
  13. e440762fix(security): sanitize raw nodes returned by toVNodes()
  14. 6303064feat(headings): clean ids by default (maintainer decision)
  15. 04d2d42feat: block positions and attributes hook; docs/api.md; release prep
  16. 74de6d1fix: issues found by agent review (emphasis nesting, custom runs, BOM, URL autolinks)
  17. 1c4dd87feat: math, mark and frontmatter plugins (built by agents from AGENTS.md)
  18. 53f6b0ffeat: definition lists plugin; fix quadratic sibling search; generic tight paragraphs
  19. cb973e5perf: hot spots from profiling (+7-13% throughput); benchmark suite
  20. 9ae18dbdocs: changelog for the new plugins and performance
  21. caf2d63perf(gfm): recover GFM throughput (+39%); core budget 15 KB
  22. 691260afeat(math): pandoc-style output, works with KaTeX auto-render and MathJax as-is
  23. 47b761ebench: refresh results after the GFM speed recovery
  24. a5feda3docs: development screenshots (demo editor, light and dark)
  25. 72d546ffeat: demo website (home, docs, samples, plugins, playground, story), agents-ready
  26. ba0b3a4ci: publish the demo site to GitHub Pages
  27. 96a8008docs: link the published website from README and package.json
  28. a593dd7test: CI-aware time budgets; shrink an oversized adversarial input
  29. 287334cfeat: Mermaid plugin (no dependency on Mermaid), samples on the site
  30. 7e813b9docs: agents guide and website updated for Mermaid and recent lessons
  31. 0b376f5fix(package): keep screenshots out of the npm package (3.1 MB -> ~140 KB)
  32. 7f25396feat: Web Components <mdlib-markdown> and <mdlib-editor> (mdlib-pure/element)
  33. 60c7298fix(element): survive DOM morphing, adopt the KaTeX sheet, refresh on late loaders
  34. 13c87c3feat: Blazor wrapper MdlibPure.Blazor (bindings/blazor)
  35. 8dcf108docs: Web Components guide, Blazor in the README, docs-coverage invariant
  36. 141baadsite: Web Components and Blazor pages, live editor sample, docs coverage check
  37. 5a551cdfeat: Angular wrapper ngx-mdlib-pure (bindings/angular); editor disabled state
  38. cdf65d3site: the Angular wrapper in the story, with a screenshot of its demo
  39. 59bcab7docs: install from the GitHub releases (mdlib-pure-v0.1.0, angular-v0.1.0)
  40. 2312d9bfix(element, theme): value set before connection; Tailwind preflight; jsdom
  41. d525d87release: mdlib-pure 0.1.1 (GitHub release mdlib-pure-v0.1.1)
  42. 0f9dee9feat(site): add fictional podcast custom-plugin sample
  43. e6743a5feat(blazor): ship configurable podcast editor and episode binding
  44. f6e579adocs: install MdlibPure.Blazor 0.1.1 from the blazor-v0.1.1 release (#3)
  45. a563b96Podcast sample: hosting options for an application with its own rundown server (#4)
  46. 9481e31chore(angular): ngx-mdlib-pure 0.1.1 requires mdlib-pure ^0.1.1 (#5)
  47. 011bab2feat(angular): PodcastEditor in ngx-mdlib-pure/podcast; bindings follow the core (#6)
  48. 4961ab1feat(blazor): OnValueCommit on MarkdownEditor and PodcastEditor (#7)
  49. b1c613bchore(release): ngx-mdlib-pure 0.2.0 and MdlibPure.Blazor 0.2.0 (#8)