# Row 154 (#1648) phase 1: candidate report Researcher, 2026-10-18 (figures re-checked against the result files at 15:20Z). Brief: `docs/plans/2026-10-16_q168-wysiwyg-editor.md` (blob 9d01d989c187). Search table and the rejected candidates: `LIBRARY.md`. This report is for Sage to take to Mos for the decision 123 go. **No editor code is in the repository and nothing was installed there.** Phase 2 has not started. ## Recommendation Candidate A: **ProseMirror + prosemirror-markdown + markdown-it**, built here from tagged source into one unminified bundle with `esbuild`, with a **block-splice save** so an unedited file saves the same bytes. All MIT except one BSD-2-Clause package (`entities`). No separate sanitizer: markdown parsing runs with `html:false`, and the editor schema is the whitelist. The schema needs two overrides (links, images) that I measured as necessary, below. This is a recommendation, not a go. Five points need a ruling from Mos (end of this report). ## 1. Project, origin, pins Twenty packages are bundled. Every pin is a tag in the project's own repository; each tagged tree was compared with the npm package of the same version and every file present in both is byte-identical (`results/pins.txt`; `punycode.js` differs only in `package.json`'s `name`). The ProseMirror tags exist on the project's own forge, not on the GitHub mirrors (which lack the current tags), so the provenance file should record that URL. | Package | Version | License | Commit | Tree | Origin | |---|---|---|---|---|---| | prosemirror-model | 1.25.12 | MIT | 4211aeeaa3d738454a5d1428a95c1a210e86d787 | 30600a5e07c4ec5c63f1fb1c8ab392e2bc019b9f | code.haverbeke.berlin/prosemirror/prosemirror-model | | prosemirror-state | 1.4.4 | MIT | d6fdcd19c4f7f68206b0a8d49649860365672585 | b781a85e73fb7c3f37bc669e7dc27e7d16f592d6 | …/prosemirror-state | | prosemirror-view | 1.42.6 | MIT | f5a5b93a78f6f723c1fbf1a2eec1556a022cd0ee | d73328db75884503bf74157722236efe4a6ef867 | …/prosemirror-view | | prosemirror-transform | 1.12.2 | MIT | a93e228a10b76698e0878df2e1f881389e3a94ec | a0aaeb587f5fa15efa9fadaab03e7fcb26bbe64e | …/prosemirror-transform | | prosemirror-inputrules | 1.5.1 | MIT | 089aa3852a55d551211af90aae2560c554ccf476 | 056aa2801460da409048c2eef074f6e513bb1233 | …/prosemirror-inputrules | | prosemirror-markdown | 1.13.8 | MIT | e3a5926aa3236af2a463d824f4f22455460b008f | 1467a53e37b554d0f0329a5017781db89ecc3da1 | …/prosemirror-markdown | | prosemirror-commands | 1.7.2 | MIT | 59fee8d89be2d033b28c2bac68e6736e2989bf9d | b5e1865a2d532f1424f409dc5be1d56ac077b855 | …/prosemirror-commands | | prosemirror-history | 1.5.1 | MIT | 44956b6a1929d7bc99aca9a8ec01c790260fa45f | 8c1e6d7ec040a87886e7f39fbf3029b0c2961ca9 | …/prosemirror-history | | prosemirror-keymap | 1.2.3 | MIT | 081f24f65b6a394174a4bdf07b9a55a00361f1b7 | 9031f125a6b53618b55b12b9272573a5aba1677c | …/prosemirror-keymap | | prosemirror-schema-basic | 1.2.5 | MIT | 531f1cad1b8059616dbe68407ab4d446b6f882f4 | c7b80fc1ee25b75fa506bd400d2d479a16b33a9c | …/prosemirror-schema-basic | | prosemirror-schema-list | 1.5.1 | MIT | e65a144f2285178a20cbdea5b0ee16925be7d6c5 | 12f3dd9c2735f86f54af2442e2600a14dc14f61f | …/prosemirror-schema-list | | markdown-it | 14.3.3 | MIT | d372f0d8a68bef0b0acaa68868ce8ae8ba85b384 | add6fa27b88402728a549324407204b5ca0b9799 | github.com/markdown-it/markdown-it | | linkify-it | 5.0.2 | MIT | 50a0c914f834b201cab25ff4faefd1f832b37332 | c8b101da7bbcff3a3de72621fc7e9abf31903eab | github.com/markdown-it/linkify-it | | uc.micro | 2.1.0 | MIT | d91cec10974f125be7accd4dd870900fd18fdec7 | 085c2a8b6c255efbd5ccd1fb13c4fc012a3521da | github.com/markdown-it/uc.micro | | mdurl | 2.1.0 | MIT | dd913f6290a4d8772240a401aab993eb65c5786d | 36d7a3131965a4e77531f549b89ecb73eede5080 | github.com/markdown-it/mdurl | | punycode.js | 2.3.1 (tag v2.3.1) | MIT | 9e1b2cda98d215d3a73fcbfe93c62e021f4ba768 | f95d544642e1599cd27de41416654b7b7a950031 | github.com/mathiasbynens/punycode.js | | entities | 4.5.0 (tag v4.5.0) | BSD-2-Clause | 61afd4701eaa736978b13c7351cd3de9a96b04bc | 23f047805a33dc7ce35dbdb137c947e5526d8d5c | github.com/fb55/entities | | orderedmap | 2.1.1 | MIT | 6a30ebbbecd30fed8bda884c7acf4ca9c6a02405 | 49c032b36195f4f3eb5bdf3aa9a0a7b70b0d523c | github.com/marijnh/orderedmap | | rope-sequence | 1.3.4 | MIT | 11db1ba5aa2abf31ff8040bdf01fabdb9db34c7a | 4006f2f2db7ababcbe84527ccc71afc6b69c19b3 | github.com/marijnh/rope-sequence | | w3c-keyname | 2.2.8 | MIT | 5ea93b04bb62230c7de4165e3a9b5c840f05cd44 | d6851bd96544a49c9ffd99a0b0eae920656b098d | github.com/marijnh/w3c-keyname | `prosemirror-markdown` 1.13.8 depends on `markdown-it ^14`, so 14.3.3 (what the npm lock resolves under it) is the parser, not markdown-it 15. `argparse` (Python-2.0 license) is in the npm lock only for markdown-it's CLI and is not bundled. `@types/*` packages are in the lock for typings and are not bundled. ## 2. Licenses `licenses/` holds 10 license files (8 distinct by checksum: `orderedmap` = `w3c-keyname`, and `punycode.js` = `uc.micro`). I copied one file for the ProseMirror group (`prosemirror-model.license`) and did **not** compare the other ten ProseMirror packages' license files with it; the provenance step in phase 2 should copy each package's own file. Holders, from the copied files: - Marijn Haverbeke and others: `prosemirror-model` (copied), assumed the same for the other 10 ProseMirror packages (not checked), `orderedmap`, `rope-sequence`, `w3c-keyname` (MIT). - Vitaly Puzrin (and Alex Kocharin on `markdown-it`, `mdurl`): `markdown-it`, `linkify-it`, `mdurl` (MIT). - Mathias Bynens: `uc.micro`, `punycode.js` (MIT; the two files are byte-identical). - Felix Böhm: `entities` (BSD-2-Clause: keep the notice in source and in binary form, with the disclaimer). All are permissive: the obligation is to keep the notice and the disclaimer with the code, including the built bundle (`--legal-comments=inline` keeps the license comments the packages carry; phase 2 should also ship a `NOTICE`/`LICENSE` file next to the imported source, as the brief asks). **The repository has no root `LICENSE`, and `package.json` says `"license": "UNLICENSED"`.** MIT and BSD-2 allow that: they impose notice-retention, not a license on our code. I am not a lawyer and this is not a legal opinion; Mos should know the repository's own license position is unstated. Among the other candidates in LIBRARY.md, only E (BlockNote core, MPL-2.0) and the transitive dual-licensed `dompurify` in B and F are outside MIT/BSD/ISC. ## 3. Sanitizer, and the schema overrides this design needs There is **no DOMPurify** in this closure; nothing in the bundle sanitizes HTML strings because nothing parses HTML into the page. - **Load path:** Markdown is parsed by markdown-it with `html:false` (raw HTML in a file becomes text), then mapped into the schema by `prosemirror-markdown`. The editor DOM is rendered by `prosemirror-view` through the schema's `toDOM`. - **Paste path** (the only HTML sink): `prosemirror-view` parses clipboard HTML in a detached document created with `document.implementation.createHTMLDocument`, which does not run scripts or load images, and maps it through the schema's `parseDOM` rules, so only schema nodes and marks survive. This is the same mechanism as the sanitizer: the schema is the allow-list. - **Measured with the pinned bundle** (jsdom; `results/hostile-html.txt`; 12 hostile inputs through `DOMParser` and `DOMSerializer` with the stock schema): - Dropped: `onclick`, `onmouseover`, `style` attributes, `