npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

markloom

v0.6.0

Published

The loom for Markdown — rich text with MD isomorphism, reversible Steps, AI write, collab, search/versions/suggest, DOCX I/O

Readme

markloom

The loom for Markdown — rich text that stays honest with MD, and that AI can write into without a second model.

Site: markloom-www.pages.dev · Docs

Most editors pick one: a nice WYSIWYG that mangled MD on export, or a MD source that never becomes a real structured tree. Product teams re-glue paste, IME, undo, and AI cards. Agents stream text into a DOM they don’t own.

markloom is one document model: a schema-constrained tree, reversible Steps, Markdown as an isomorphic projection (not “export later”), and an optional AI transaction on real blocks. Kernel without DOM; view when you need an editor.

npm i markloom
import { Editor } from 'markloom/view'
import 'markloom/view/theme.css'

const ed = new Editor(root, doc, {
  onChange: d => save(d), // or persist with docToMd(d)
})

What you get

1. Markdown as a first-class citizen (lossless where it matters)

  • Tree ↔ Markdown isomorphism via mdast: mdToDoc / docToMd.
  • Dirty-block local re-stringify; clean blocks keep source spans so untouched prose doesn’t get reformatted away.
  • Paste text/plain as MD; text/html as structured blocks — one pipeline, not two half-broken converters.
  • AI and humans can both speak Markdown; the document remains a real tree (headings, lists, code, marks…), not a single string with hope.

This is not “we support export to .md.” The model is built so MD and the editor share the same shape.

2. Real structure, reversible edits

  • Document = ordered tree under schema meta-bounds (depth / type budget) — expressive nested content without unbounded entropy.
  • Every change is a Step: insert/delete text, replace node, splice children.
  • apply ∘ invert = id: undo, history, and AI rollback are the same mechanism — not a parallel “AI history.”
  • Stable block ids + path dual: agents address id, paths move when structure moves.
  • diffById: block-level added / changed / removed / same for accept–reject UIs.

3. Fully controlled editor (DOM is projection)

  • markloom/view: Preact, contentEditable fully owned via beforeinput → Steps.
  • Model is the only truth; DOM cannot drift into a second document.
  • IME handled as a first-class path (composition aligned to the model, not “hope the browser is fine”).
  • Slash menu, bubble format bar, drag handle, gaps around atoms — product surface without forking the kernel.

4. AI write as a document transaction

One options field: /ai, format bar, real draft blocks, accept / undo — same tree and history. Example below; full transports in ai.md.

5. Use the piece you need

markloom           tree, Steps, history, MD, search/versions/suggest pure fns
markloom/view      editor + view/theme.css  (search / versions / suggest / …)
markloom/ai        AI controller + stream helper + ai/theme.css
markloom/collab    realtime OT client (pair with worker/)
markloom/io        DOCX import/export (docToDocx / docxToDoc)
markloom/react     optional React wrapper (peer react, optional)

Kernel only (agents, SSG, no browser):

import { apply, appendText, mdToDoc, docToMd, diffById } from 'markloom'

const doc = mdToDoc(source)
const next = apply(doc, appendText(doc, blockId, token))
await save(docToMd(next))

Editor + AI (one example; proxy / provider / custom → ai.md):

npm i markloom ai @ai-sdk/openai
import { docToMd } from 'markloom'
import { Editor } from 'markloom/view'
import { streamText } from 'ai'
import { createOpenAI } from '@ai-sdk/openai'
import 'markloom/view/theme.css'
import 'markloom/ai/theme.css'

const openai = createOpenAI({ apiKey: process.env.OPENAI_API_KEY! })

new Editor(el, doc, {
  onChange: d => save(docToMd(d)),
  ai: {
    stream: async (input, onToken) => {
      const result = streamText({
        model: openai('gpt-4o-mini'),
        prompt: input.context,
        abortSignal: input.signal,
      })
      for await (const t of result.textStream) onToken(t)
    },
  },
})

Realtime collab (one field; worker + protocol → collab.md):

new Editor(el, doc, {
  collab: { url: 'ws://127.0.0.1:8787', room: 'demo', name: 'Alice' },
})

Enterprise O(1) (import + one option; no assembly manual):

new Editor(el, doc, {
  search: true,      // ⌘F find/replace
  versions: true,    // ⌘⇧S named snapshots (collab syncs when connected)
  suggest: true,     // ⌘⇧G track changes
  collab: { url, room, token: 'optional-shared-secret' },
})

import { docToDocx, docxToDoc } from 'markloom/io'
const bytes = docToDocx(ed.doc)
const back = docxToDoc(bytes)

Why this stack

| Problem | Typical approach | markloom | |---------|------------------|----------| | MD round-trip ruins formatting | Best-effort export | Isomorphic projection + clean-block spans | | AI preview ≠ document | Overlay then paste | Stream into real blocks; accept = commit | | Undo of AI is messy | Multiple history systems | One Step history; AI amend + undo | | Agent can’t address blocks | CSS selectors / offsets | Stable id + pathOfId | | Want headless transforms | Fork the editor | Zero-DOM kernel |

Runtime dependencies

Preact, @floating-ui/dom, unified/mdast GFM (mdast-util-*, micromark-extension-gfm).
No ProseMirror / Tiptap.

| Optional | When | |----------|------| | react ≥ 18 | Only if you use markloom/react |

Limits (honest)

  • Collaboration: client + Cloudflare Worker shipped (collab: { url, room } — see collab.md). TP2 / auth / multi-caret chrome still thin.
  • Tables are atoms (full grid model inside; not free nesting in the prose grammar).

License

MIT