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

provident-editable

v0.2.0

Published

Rich-text HTML → provident tree converter + structural diff, for the provident-ssr framework. A separate, importable, framework-agnostic package.

Readme

provident-editable

Rich-text HTML → provident tree converter + structural diff, for the provident-ssr framework. A separate, importable, framework-agnostic TypeScript library: it parses the innerHTML of a contenteditable element into a provident tree (a root node + its owned subtree) and diffs that tree against an existing subtree. No DOM in the core (plain-tree parsing via parse5), no network egress — pure, deterministic functions over plain data. It is a DATA MODEL producer: it contains no renderer and imports no engine code, and the render-order contract stays the adopter's — but the emitted shape is DESIGNED to render faithfully under provident-ssr >=0.4.0 <1.0.0 (see "Render boundary" below).

Adoption status

There is currently NO adopter of this package. The adjacent consumer repo that originated the requirement (Astrographer) built an in-house equivalent instead and never installed this package: the [email protected] import plan was REPLACED by its own pure decomposeRichHtml module (src/main/rich-decompose.ts), recorded as RICH-TEXT-EDITING-GATE in ../Astrographer/docs/decisions.md (see also its docs/pending.md "contenteditable rich inline editing" row and docs/specs/editing-mode-toggle-review.md:3 — "adopted in design but NOT installed"; the consumer's feature request for an imported converter is RESOLVED / SUPERSEDED 2026-09-08). There is no dependency in either direction — this package does not import the consumer, and the consumer does not depend on this package.

Consequence, stated plainly: the public API is stable but UNEXERCISED by any real adopter — the contract and its tests are green, but no production consumer validates the shape in anger, and the consumer's own model differs where it matters (its inline children are strong/em/a/img with offsets, and a text child is a validation fail-state for it — see the text note below). Treat the emitted shape as this package's contract per docs/specs/html-to-provident-tree.md, not as a proven integration.

UPGRADE NOTE — 0.2.0 is a BREAKING emitted-shape release

[email protected] (2026-09-17) adopts the provident-ssr 0.4 BARE-TEXT-EMIT mechanism and the per-node XOR rule; 0.1.0 consumers must migrate. Three changes:

  1. The text-run union member was renamed span → text — it is now the ENGINE's own text-node type, not a package-introduced stand-in (the union still has 19 members; the 5 exported types are unchanged).
  2. A node WITH children now carries content:'' (the per-node XOR rule): a node carries EITHER its own scalar content OR children, never both — the engine renders escapeText(content) + children, so carrying both duplicated the text. A block with NO inline formatting is now a LEAF carrying its own text and emitting NO run children (it used to get a single span child). Consequence for diff consumers: because an EMITTED node with children stores content:'', a plain block's text edit now reports the block only and no longer the root alongside it (see "Which node gets the update?" below).
  3. A new helper, providentPlainText(node), derives the plain text that the old content field stored — the value did not change, its LOCATION did.

Migration, concretely: rename every type:'span' node to type:'text'; set content:'' on every node that has children; and read the plain-text projection with providentPlainText(node) instead of node.content.

Release

  • Current version: 0.2.0 — release-ready and packed (provident-editable-0.2.0.tgz, 41.2 kB, dist/-only, runtime dependency parse5). The registry's published version is still 0.1.0; npm publish is a deliberate, separate step.
  • Engine compatibility: floor >=0.4.0 <1.0.0 (an OPTIONAL peer range of the same span). The emitted shape requires provident-ssr's bare-text child emit, which upstream documents for >=0.4.0 <1.0.0; the engine is never a runtime dependency of the shipped dist/.
  • Release gate: prepublishOnly runs npm test && npm run typecheck. The engine-backed render oracle is the separate npm run verify:engine leg, kept out of publishing on purpose.
  • Upgrade note: 0.2.0 renames the public union member span → text and emits content:'' on nodes that have children (with providentPlainText as the derived projection) — see the upgrade note below.

Install

npm install provident-editable

Package identity: [email protected], type: module (ESM), main → dist/index.js, types → dist/index.d.ts, files: ["dist"], license AGPL-3.0. Runtime dependency: parse5 only — the engine is a test-only devDependency ([email protected], exact) plus an optional peer range >=0.4.0 <1.0.0; the shipped dist/ imports no engine code. See docs/decisions.md (DECIDED: PACKAGE-IDENTITY, DECIDED: ENGINE-COMPATIBILITY-FLOOR).

Usage

import { htmlToTree, diffTrees, providentPlainText } from 'provident-editable'

/** Parse rich-text HTML into a provident tree. Pure, deterministic, no DOM, no
 *  network. */
declare function htmlToTree(html: string, opts?: ConvertOptions): ProvidentTree

/** Produce a structural diff between two provident trees. Pure, deterministic. */
declare function diffTrees(prev: ProvidentTree, next: ProvidentTree): StructuralDiff

/** Derive the plain-text projection of a node's subtree (SPEC PASS H): the
 *  node's own scalar `content` plus every descendant's, in document order.
 *  Pure, deterministic, total — never throws. */
declare function providentPlainText(node: ProvidentNode): string
const prev = htmlToTree('<p>Some <strong>bold</strong> text</p>')

// The emitted shape is PER-NODE XOR: a node with children carries content:''
// and its text runs are `text` children, in document order.
//   { id:'n-1', type:'div', content:'', children:[
//     { id:'n-2', type:'p', content:'', children:[
//       { id:'n-3', type:'text', content:'Some ' },
//       { id:'n-4', type:'strong', content:'bold' },   // collapsed leaf
//       { id:'n-5', type:'text', content:' text' } ]}]}

providentPlainText(prev.root)   // → 'Some bold text'  (the value the old
                                //    contract stored in the node's `content`)

const next = htmlToTree('<p>Some <strong>bold</strong> words</p>')
const diff = diffTrees(prev, next)

diff.nodeChanges.update
// → [ { prevId: 'n-5', nextId: 'n-5', content: ' words' } ]
//   exactly ONE update — the EDITED RUN NODE.

Which node gets the update? An ancestor is reported only when the ancestor's OWN scalar content changes — not merely because a descendant changed. So an edit inside an INTERLEAVED block (a block with inline formatting, whose content is '') reports the run node only, and an edit to a PLAIN block (which stores its own text and is a childless LEAF) reports the block only — NO ancestor update, because an emitted root with children also stores content:'' on both sides. (A caller-built tree whose root carries its OWN text does report the root as well — that two-update case is pinned separately, spec §5.8 state 56.) A consumer typically applies the reported updates and re-traverses.

htmlToTree returns a ProvidentTree — { root: ProvidentNode }, the root is always a div, and nodes carry id/type/content/props/children (ids are minted deterministically in pre-order: ${idPrefix}-${n}, default idPrefix 'n'; every node counts, including a text run). diffTrees returns a StructuralDiff — { nodeChanges: { add, remove, update }, edgeChanges: { add, remove } }: add/remove are subtree-granular, update is field-granular ({ prevId, nextId, type?, content?, props? }). providentPlainText(node) returns the derived string projection. All three signatures, return shapes and throw patterns are the contract in docs/specs/html-to-provident-tree.md §5.1/§5.2.

text is the engine's own text-run type

A text run (e.g. the "Some " and " text" around a <strong>bold</strong>) is emitted as a type:'text' child node — the provident-ssr framework's OWN text-node type, which the engine renders bare (escaped literal text, no wrapper, positioned by childOrder) since engine 0.4.0. The former package-introduced span stand-in, its drift note and its upstream feature-request framing are gone (the rename landed in 0.2.0; see the UPGRADE NOTE above).

Two consequences worth knowing:

  • A text node is a CONTENT-ONLY LEAF — no children, no props. The engine enforces content-only by filtering silently at emit (its text PROP is the only thing emitted; a css:*/prop:*/on:*/data: mapping on a text node never reaches the adapter and no warning fires at emit — the engine's text-content-only warning is TRANSLATE-time and legacy-JSON-only), and the converter never emits any.
  • text runs are ORDINARY diff-visible nodes: a run can be added/removed (subtree-granular) and a run's text edit is reported as an update ON THE RUN. A run update carries the converter's minted id, and a text child has no rendered element identity upstream — so an adopter maps a run update onto its own materialized tree, not onto a rendered element.

The upstream contract for this emit — SETTLED (UR-1, landed 2026-09-22; residuals cleared 2026-09-23). The bare-text emit BEHAVIOUR shipped in engine 0.4.0; its CONTRACT is now written into the engine's own behaviour specs, version-scoped >=0.4.0 <1.0.0 with the 1.0.0 revisit named (../Preempt-Providence/docs/specs/render.md §3.2b — the owning home; docs/specs/adapters.md §3.1/§3.2/§4.1/§4.3/§4.4/§6; docs/specs/node.md §2.1), and the five upstream docs that used to deny it carry SUPERSEDED/AMENDED annotations. That was our upstream request UR-1 (docs/upstream-request.md, which carries the dated RESPONSE/STATUS section; adjudicated in ../Preempt-Providence/docs/specs/ur-2026-09-17-review.md), and it CLOSED the ENG-GAP-HTML-1 release-evidence blocker (docs/defects.md). The behaviour is ALSO verified here by an engine oracle executed on the exact [email protected] — so the floor is stated in its two halves (CONTRACT documented + EXECUTED on 0.5.1), and no part of our release evidence is blocked; see "Render boundary" below.

Render boundary

This package emits a DATA MODEL. htmlToTree returns a plain-data provident tree; it contains no renderer, imports no engine code in its shipped dist/, and has no option/type that produces a render encoding. The render-order contract belongs to the adopter — the package makes no promise about how an adopter's own transformation renders (DECIDED: ENGINE-RENDER-CONTRACT, docs/decisions.md; full contract: docs/specs/html-to-provident-tree.md §5.7 C1-C6).

What the package DOES now promise (2026-09-17): the DEFAULT emitted shape renders faithfully on the probed surfaces, because a text child is emitted BARE and positioned by childOrder, and because a parent with children carries content:'' (the per-node XOR rule — no duplication):

input HTML          <p>Some <strong>bold</strong> text</p>
emitted tree        div{content:'', children:[p{content:'', children:[text('Some '), strong('bold'), text(' text')]}]}
rendered output     Some <strong>bold</strong> text        — once, in order, no wrapper

Declared engine floor: provident-ssr >=0.4.0 <1.0.0 (DECIDED: ENGINE-COMPATIBILITY-FLOOR). The <1.0.0 upper bound is the announced 1.0.0 XOR major, which CHANGES this shape's render — and the direction is favourable: the adopted per-node XOR shape already moves toward "children win" and emits no bodyRuns (docs/defects.md ENG-1.0.0-XOR-MAJOR — the package's exit-plan trigger). That upper bound now also matches a versioned upstream ruling: the bodyRuns removability ruling (branch (b)) makes the 1.0.0 major the point at which upstream REMOVES bodyRuns, so the declared peer range and the upstream removal point are the same event.

Evidence, honestly labelled (the floor's two halves as of 2026-09-23):

  • CONTRACT (the 0.4.0 lower bound) — DOCUMENTED UPSTREAM: the emit rule is written into the engine's own BEHAVIOUR specs, version-scoped >=0.4.0 <1.0.0 with the 1.0.0 revisit named (../Preempt-Providence/docs/specs/render.md §3.2b — the owning home, BT-EMIT-1..7; docs/specs/adapters.md §3.1/§3.2/§4.1/§4.3/§4.4/§6 + its §10.1/§10.2 tables; docs/specs/node.md §2.1), and the five denial sites are annotated SUPERSEDED/AMENDED. This is UR-1 landed — ENG-GAP-HTML-1 is RESOLVED and the former release-evidence blocker is CLEARED (docs/defects.md; docs/upstream-request.md §"RESPONSE / STATUS (2026-09-23)"). The residual: the lower bound has no EXECUTED 0.4.0 run (we never installed 0.4.0).
  • EXECUTED on [email protected]: the engine render oracle + a recorded render fixture cover the canonical interleaved case, an img inside a run, a plain leaf block beside an interleaved block, a pre leaf, and the duplicated-text control (the same shape with the parent's content retained renders the text TWICE). The oracle lives behind verify:engine, is SKIPPED explicitly when the engine devDependency is absent, and is kept OUT of prepublishOnly — publishing never needs the engine. The oracle asserts the bare-text emit + document order (not a version string), so its render assertions pass UNCHANGED across 0.5.0 → 0.5.1.
  • NOT PROBED (the two remaining limitations — UNCHANGED by the 0.5.1 landing): the placement-routed / path-state root class, and the markdown / lineMap adapter. Both are documented limitations in spec §5.7 C6 and DEFERRED rows in docs/pending.md. (0.5.1's warn-and-drop diagnostic makes a dropped {child} run observable for adopters who author bodyRuns — we author none, and it probes neither of these surfaces.)
  • PROHIBITION: no test outside the engine oracle may assert rendered output. There is no renderCarrier option — ConvertOptions is exactly { idPrefix?: string }; bodyRuns is out of scope entirely (the package emits no {child} runs), and the former FOLD/CONVERT adopter strategies described the superseded shape (they are the OLD-SHAPE UPGRADE NOTE in spec §5.7 C4).

Development

npm test           # vitest — full suite: 304 tests across 7 files
npm run typecheck  # tsc --noEmit
npm run build      # tsc -p tsconfig.build.json — emits dist/
npm run test:engine     # the engine render oracle only (needs [email protected])
npm run verify:engine   # npm run build && npm run test:engine — the full engine leg

The verify:engine leg (2026-09-17; engine pin updated 2026-09-23): it renders the emitted per-node-XOR shape with the real engine and asserts document order + single occurrence per run, against a recorded render fixture. It requires the test-only exact devDependency [email protected], SKIPS explicitly when that dependency is absent (an explicit skip, never a silent pass), and is deliberately NOT part of prepublishOnly — publishing never needs the engine, and the shipped dist/ imports no engine code. This is the test leg that backs the engine floor >=0.4.0 <1.0.0 (spec §5.7 C3; DECIDED: ENGINE-COMPATIBILITY-FLOOR); no other test may assert rendered output. Evidence strength, honestly stated — the floor's TWO HALVES, re-stated 2026-09-23: the emit CONTRACT is documented upstream, version-scoped >=0.4.0 <1.0.0 (UR-1 landed — ENG-GAP-HTML-1 RESOLVED), and the oracle and the recorded fixture are EXECUTED on the installed [email protected] (38/38 at this revision; the oracle is engine-version-agnostic by design — it asserts the bare-text emit and document order, not a version string — so the render assertions pass UNCHANGED from 0.5.0), so the 0.4.0 half is contract-backed and the executed half is on 0.5.1. The one residual: no EXECUTED 0.4.0 run exists (we never installed 0.4.0; its 0.4.1/0.5.0 tag/date provenance is still UNVERIFIED, while 0.5.1 and 0.4.0 both resolve — docs/HANDOFF.md compatibility matrix).

Blind greens verification (needs a current dist/, so run npm run build first — dist/ is gitignored): node scripts/blind-greens.mjs → PASS 313 / FAIL 0 = 313/313 assertion records over 100 documented scenario records (groups A 36 converter + B 19 diff + C 32 contract + D 2 projection + E 11 trim-endpoint/discriminator/helper-domain); the assertion records split A 52 / B 40 / C 114 / D 43 / E 64.

Test-count note (re-measured 2026-09-17 after the 0.2.0 XOR redirect, at HEAD ff29dbd; re-measured again 2026-09-23 after the concurrent test/fixture re-certification pass landed): the suite is green — 304 passed / 304 across 7 files, with npm run typecheck clean, npm run build emitting an engine-free dist/, and the separate engine leg npm run test:engine at 38/38. Every earlier figure (69, 113, 151, 179, 181, 182, 211, 281, 302) is SUPERSEDED.

The per-file split — **74 tests/html-to-tree.test.ts + 114 tests/diff.test.ts

  • 6 tests/live-edit.test.ts + 25 tests/plain-text.test.ts + 35 tests/engine-render-oracle.test.ts + 34 tests/properties.test.ts + 16 tests/html-to-tree-trim-red.test.ts** (summing to exactly 304) — was measured by counting the leading-whitespace it( declarations per file. The oracle file moved 33 → 35 and the total 302 → 304 when the 2026-09-23 re-certification pass added the two fixture-provenance assertions (S8b the fixture's 0.5.1 identity + pin equality, S8c the previousPin superseded history) and renamed the fixture to tests/fixtures/provident-ssr-0.5.1-render.json; no other file's count moved:
grep -c -E '^[[:space:]]*it\(' tests/*.test.ts   # per-file shape (see note)

(The bare it( pattern overcounts — it also matches describe( lines and one it(-bearing test name — so the anchored ^[[:space:]]*it\( form is the figure to use. A file can additionally run generated cases inside loops, so the anchored grep describes the per-file SHAPE: the authoritative total is the one npx vitest run reports, and at this revision it is 304 across 7 files — which the per-file list above happens to match exactly.)

tests/properties.test.ts is the §5.12 property layer (the PBT gate): fast-check ^4.10.1 as a devDependency, one pinned seed 20260917, 9 register rows at ~44 attempts per row / ≤400 total, stop-after-5 failures, all 9 rows HELD (P-SM1's three clauses, the P-SM4 projection row, and the edge-run / own-text / deep-chain generator strategies). It carries 13 it() blocks — the per-row properties (with the GEN-HTML-BASIC/GEN-HTML-INLINE/GEN-IDS-HOSTILE trio, the JSON-props pair, GEN-PROPS-CYCLIC, GEN-TREE-WELLFORMED and the projection algebra), the U2b GEN-HTML-EDGE-RUN matrix, the GEN-DEPTH-BOUNDARY 512/513 boundary, plus a verdict-summary it(), a dist-freshness guard and a generator self-certification (anti-vacuity) it() (the anchored grep counts this file at 34 because several of those it() blocks run generated cases inside loops). The pre-0.2.0 executed record — 8 rows, 400/400 attempts at 50/row — is HISTORICAL: with the register's ceiling at 9 rows (P-SM1 re-derived to the per-node-XOR three-clause form + the NEW P-SM4) the allocation falls to ≈44/row to keep the ≤400 total (spec §5.12).

The behavior contract is docs/specs/html-to-provident-tree.md (its §5.10 census banner is the authoritative source for every count above); the blind-test greens are docs/specs/html-to-provident-tree-greens.md; the active trackers are docs/decisions.md, docs/next-steps.md, docs/defects.md, docs/pending.md, docs/HANDOFF.md.