@liminis/editor 0.1.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (219) hide show
  1. package/LICENSE +34 -0
  2. package/README.md +376 -0
  3. package/dist/annotations/anchor-align.d.ts +50 -0
  4. package/dist/annotations/anchor-align.js +74 -0
  5. package/dist/annotations/anchor-model.d.ts +81 -0
  6. package/dist/annotations/anchor-model.js +80 -0
  7. package/dist/annotations/anchor-resolver.d.ts +92 -0
  8. package/dist/annotations/anchor-resolver.js +231 -0
  9. package/dist/annotations/block-structure.d.ts +65 -0
  10. package/dist/annotations/block-structure.js +113 -0
  11. package/dist/annotations/types.d.ts +159 -0
  12. package/dist/annotations/types.js +50 -0
  13. package/dist/annotations.d.ts +25 -0
  14. package/dist/annotations.js +27 -0
  15. package/dist/app/App.d.ts +64 -0
  16. package/dist/app/App.js +180 -0
  17. package/dist/app/editor/AmbientCorrectionPlugin.d.ts +18 -0
  18. package/dist/app/editor/AmbientCorrectionPlugin.js +185 -0
  19. package/dist/app/editor/AnchorScrollPlugin.d.ts +16 -0
  20. package/dist/app/editor/AnchorScrollPlugin.js +134 -0
  21. package/dist/app/editor/AnnotationMarkerPlugin.d.ts +24 -0
  22. package/dist/app/editor/AnnotationMarkerPlugin.js +224 -0
  23. package/dist/app/editor/AnnotationPlugin.d.ts +42 -0
  24. package/dist/app/editor/AnnotationPlugin.js +135 -0
  25. package/dist/app/editor/AnnotationSurface.d.ts +34 -0
  26. package/dist/app/editor/AnnotationSurface.js +115 -0
  27. package/dist/app/editor/AssetContext.d.ts +20 -0
  28. package/dist/app/editor/AssetContext.js +81 -0
  29. package/dist/app/editor/BlockClickPlugin.d.ts +8 -0
  30. package/dist/app/editor/BlockClickPlugin.js +126 -0
  31. package/dist/app/editor/CodeBlockPlugin.d.ts +1 -0
  32. package/dist/app/editor/CodeBlockPlugin.js +219 -0
  33. package/dist/app/editor/CorrectionPanelPlugin.d.ts +14 -0
  34. package/dist/app/editor/CorrectionPanelPlugin.js +333 -0
  35. package/dist/app/editor/DragHandlePlugin.d.ts +1 -0
  36. package/dist/app/editor/DragHandlePlugin.js +300 -0
  37. package/dist/app/editor/Editor.d.ts +84 -0
  38. package/dist/app/editor/Editor.js +539 -0
  39. package/dist/app/editor/FrontmatterPlugin.d.ts +19 -0
  40. package/dist/app/editor/FrontmatterPlugin.js +301 -0
  41. package/dist/app/editor/ImageModal.d.ts +13 -0
  42. package/dist/app/editor/ImageModal.js +267 -0
  43. package/dist/app/editor/ImagePlugin.d.ts +3 -0
  44. package/dist/app/editor/ImagePlugin.js +187 -0
  45. package/dist/app/editor/LinkClickPlugin.d.ts +23 -0
  46. package/dist/app/editor/LinkClickPlugin.js +90 -0
  47. package/dist/app/editor/MarkdownShortcutsPlugin.d.ts +49 -0
  48. package/dist/app/editor/MarkdownShortcutsPlugin.js +373 -0
  49. package/dist/app/editor/OrderedTaskListPlugin.d.ts +21 -0
  50. package/dist/app/editor/OrderedTaskListPlugin.js +78 -0
  51. package/dist/app/editor/SearchPlugin.d.ts +1 -0
  52. package/dist/app/editor/SearchPlugin.js +379 -0
  53. package/dist/app/editor/SelectionContextMenuPlugin.d.ts +29 -0
  54. package/dist/app/editor/SelectionContextMenuPlugin.js +160 -0
  55. package/dist/app/editor/SlashMenu.d.ts +11 -0
  56. package/dist/app/editor/SlashMenu.js +494 -0
  57. package/dist/app/editor/SlashMenuPlugin.d.ts +1 -0
  58. package/dist/app/editor/SlashMenuPlugin.js +168 -0
  59. package/dist/app/editor/TableActionsPlugin.d.ts +1 -0
  60. package/dist/app/editor/TableActionsPlugin.js +398 -0
  61. package/dist/app/editor/TogglePlugin.d.ts +1 -0
  62. package/dist/app/editor/TogglePlugin.js +159 -0
  63. package/dist/app/editor/Toolbar.d.ts +10 -0
  64. package/dist/app/editor/Toolbar.js +263 -0
  65. package/dist/app/editor/WikiLinkExistencePlugin.d.ts +17 -0
  66. package/dist/app/editor/WikiLinkExistencePlugin.js +104 -0
  67. package/dist/app/editor/WikiLinkFormatPlugin.d.ts +17 -0
  68. package/dist/app/editor/WikiLinkFormatPlugin.js +203 -0
  69. package/dist/app/editor/annotation-marks.d.ts +177 -0
  70. package/dist/app/editor/annotation-marks.js +750 -0
  71. package/dist/app/editor/annotationCommands.d.ts +13 -0
  72. package/dist/app/editor/annotationCommands.js +11 -0
  73. package/dist/app/editor/c4/C4InteractiveRenderer.d.ts +35 -0
  74. package/dist/app/editor/c4/C4InteractiveRenderer.js +299 -0
  75. package/dist/app/editor/c4/edge-clipping.d.ts +24 -0
  76. package/dist/app/editor/c4/edge-clipping.js +139 -0
  77. package/dist/app/editor/c4/hooks/useC4DiagramDrag.d.ts +38 -0
  78. package/dist/app/editor/c4/hooks/useC4DiagramDrag.js +112 -0
  79. package/dist/app/editor/c4/layout.d.ts +25 -0
  80. package/dist/app/editor/c4/layout.js +839 -0
  81. package/dist/app/editor/c4/parser.d.ts +19 -0
  82. package/dist/app/editor/c4/parser.js +410 -0
  83. package/dist/app/editor/c4/render-to-string.d.ts +24 -0
  84. package/dist/app/editor/c4/render-to-string.js +34 -0
  85. package/dist/app/editor/c4/renderer.d.ts +64 -0
  86. package/dist/app/editor/c4/renderer.js +569 -0
  87. package/dist/app/editor/c4/types.d.ts +203 -0
  88. package/dist/app/editor/c4/types.js +43 -0
  89. package/dist/app/editor/correction-yaml.d.ts +10 -0
  90. package/dist/app/editor/correction-yaml.js +40 -0
  91. package/dist/app/editor/editorNodes.d.ts +9 -0
  92. package/dist/app/editor/editorNodes.js +36 -0
  93. package/dist/app/editor/index.d.ts +1 -0
  94. package/dist/app/editor/index.js +1 -0
  95. package/dist/app/editor/mark-overlap-resolver.d.ts +6 -0
  96. package/dist/app/editor/mark-overlap-resolver.js +45 -0
  97. package/dist/app/editor/nodes/C4Component.d.ts +14 -0
  98. package/dist/app/editor/nodes/C4Component.js +284 -0
  99. package/dist/app/editor/nodes/C4Node.d.ts +32 -0
  100. package/dist/app/editor/nodes/C4Node.js +112 -0
  101. package/dist/app/editor/nodes/CalloutNode.d.ts +23 -0
  102. package/dist/app/editor/nodes/CalloutNode.js +94 -0
  103. package/dist/app/editor/nodes/CustomLinkNode.d.ts +57 -0
  104. package/dist/app/editor/nodes/CustomLinkNode.js +209 -0
  105. package/dist/app/editor/nodes/CustomListItemNode.d.ts +46 -0
  106. package/dist/app/editor/nodes/CustomListItemNode.js +120 -0
  107. package/dist/app/editor/nodes/CustomListNode.d.ts +27 -0
  108. package/dist/app/editor/nodes/CustomListNode.js +54 -0
  109. package/dist/app/editor/nodes/DefinitionListNode.d.ts +44 -0
  110. package/dist/app/editor/nodes/DefinitionListNode.js +151 -0
  111. package/dist/app/editor/nodes/DiagramContextMenu.d.ts +45 -0
  112. package/dist/app/editor/nodes/DiagramContextMenu.js +116 -0
  113. package/dist/app/editor/nodes/EquationComponent.d.ts +17 -0
  114. package/dist/app/editor/nodes/EquationComponent.js +130 -0
  115. package/dist/app/editor/nodes/EquationNode.d.ts +43 -0
  116. package/dist/app/editor/nodes/EquationNode.js +179 -0
  117. package/dist/app/editor/nodes/FootnoteNode.d.ts +40 -0
  118. package/dist/app/editor/nodes/FootnoteNode.js +151 -0
  119. package/dist/app/editor/nodes/FrontmatterNode.d.ts +24 -0
  120. package/dist/app/editor/nodes/FrontmatterNode.js +88 -0
  121. package/dist/app/editor/nodes/HorizontalRuleNode.d.ts +17 -0
  122. package/dist/app/editor/nodes/HorizontalRuleNode.js +57 -0
  123. package/dist/app/editor/nodes/HtmlNode.d.ts +32 -0
  124. package/dist/app/editor/nodes/HtmlNode.js +121 -0
  125. package/dist/app/editor/nodes/ImageComponent.d.ts +11 -0
  126. package/dist/app/editor/nodes/ImageComponent.js +228 -0
  127. package/dist/app/editor/nodes/ImageNode.d.ts +53 -0
  128. package/dist/app/editor/nodes/ImageNode.js +183 -0
  129. package/dist/app/editor/nodes/ListItemParagraphBreakNode.d.ts +42 -0
  130. package/dist/app/editor/nodes/ListItemParagraphBreakNode.js +95 -0
  131. package/dist/app/editor/nodes/MermaidComponent.d.ts +11 -0
  132. package/dist/app/editor/nodes/MermaidComponent.js +216 -0
  133. package/dist/app/editor/nodes/MermaidNode.d.ts +27 -0
  134. package/dist/app/editor/nodes/MermaidNode.js +96 -0
  135. package/dist/app/editor/nodes/ToggleNode.d.ts +56 -0
  136. package/dist/app/editor/nodes/ToggleNode.js +237 -0
  137. package/dist/app/editor/nodes/diagram-context-menu.d.ts +11 -0
  138. package/dist/app/editor/nodes/diagram-context-menu.js +70 -0
  139. package/dist/app/editor/nodes/diagram-utils.d.ts +13 -0
  140. package/dist/app/editor/nodes/diagram-utils.js +29 -0
  141. package/dist/app/editor/nodes/index.d.ts +30 -0
  142. package/dist/app/editor/nodes/index.js +17 -0
  143. package/dist/app/editor/utils.d.ts +2 -0
  144. package/dist/app/editor/utils.js +17 -0
  145. package/dist/app/mapper/index.d.ts +3 -0
  146. package/dist/app/mapper/index.js +2 -0
  147. package/dist/app/mapper/lexicalToMdast.d.ts +62 -0
  148. package/dist/app/mapper/lexicalToMdast.js +1937 -0
  149. package/dist/app/mapper/mdastToLexical.d.ts +31 -0
  150. package/dist/app/mapper/mdastToLexical.js +1075 -0
  151. package/dist/contract.d.ts +8 -0
  152. package/dist/contract.js +8 -0
  153. package/dist/headless.d.ts +40 -0
  154. package/dist/headless.js +45 -0
  155. package/dist/host/context.d.ts +20 -0
  156. package/dist/host/context.js +24 -0
  157. package/dist/host/defaults.d.ts +21 -0
  158. package/dist/host/defaults.js +47 -0
  159. package/dist/host/messages.d.ts +26 -0
  160. package/dist/host/messages.js +47 -0
  161. package/dist/host/types.d.ts +75 -0
  162. package/dist/host/types.js +13 -0
  163. package/dist/index.d.ts +35 -0
  164. package/dist/index.js +51 -0
  165. package/dist/markdown/annotate-sentinels.d.ts +42 -0
  166. package/dist/markdown/annotate-sentinels.js +86 -0
  167. package/dist/markdown/parse.d.ts +56 -0
  168. package/dist/markdown/parse.js +516 -0
  169. package/dist/markdown/stringify.d.ts +7 -0
  170. package/dist/markdown/stringify.js +439 -0
  171. package/dist/markdown/vendor/mdast-util-wiki-link/LICENSE +21 -0
  172. package/dist/markdown/vendor/mdast-util-wiki-link/README.md +41 -0
  173. package/dist/markdown/vendor/mdast-util-wiki-link/from-markdown.d.ts +72 -0
  174. package/dist/markdown/vendor/mdast-util-wiki-link/from-markdown.js +127 -0
  175. package/dist/markdown/vendor/mdast-util-wiki-link/index.d.ts +8 -0
  176. package/dist/markdown/vendor/mdast-util-wiki-link/index.js +6 -0
  177. package/dist/markdown/vendor/mdast-util-wiki-link/to-markdown.d.ts +17 -0
  178. package/dist/markdown/vendor/mdast-util-wiki-link/to-markdown.js +33 -0
  179. package/dist/markdown.d.ts +28 -0
  180. package/dist/markdown.js +44 -0
  181. package/dist/mathjax-config.d.ts +89 -0
  182. package/dist/mathjax-config.js +133 -0
  183. package/dist/nodes.d.ts +34 -0
  184. package/dist/nodes.js +34 -0
  185. package/dist/stores/correctionStore.d.ts +15 -0
  186. package/dist/stores/correctionStore.js +12 -0
  187. package/dist/stores/editorStore.d.ts +16 -0
  188. package/dist/stores/editorStore.js +27 -0
  189. package/dist/styles.css +2477 -0
  190. package/dist/types.d.ts +296 -0
  191. package/dist/types.js +91 -0
  192. package/dist/utils/file-types.d.ts +10 -0
  193. package/dist/utils/file-types.js +46 -0
  194. package/docs/annotations.md +134 -0
  195. package/docs/decisions/README.md +72 -0
  196. package/docs/decisions/adr-002.md +27 -0
  197. package/docs/decisions/adr-007.md +27 -0
  198. package/docs/decisions/adr-010.md +26 -0
  199. package/docs/decisions/adr-024.md +124 -0
  200. package/docs/decisions/adr-024b.md +0 -0
  201. package/docs/decisions/adr-025.md +103 -0
  202. package/docs/decisions/adr-027.md +66 -0
  203. package/docs/decisions/adr-057.md +67 -0
  204. package/docs/decisions/adr-070.md +131 -0
  205. package/docs/decisions/adr-075.md +410 -0
  206. package/docs/decisions/adr-076.md +183 -0
  207. package/docs/decisions/adr-077.md +410 -0
  208. package/docs/decisions/adr-078.md +243 -0
  209. package/docs/decisions/adr-079.md +167 -0
  210. package/docs/decisions/adr-080.md +325 -0
  211. package/docs/decisions/adr-081.md +108 -0
  212. package/docs/decisions/adr-082.md +123 -0
  213. package/docs/decisions/adr-083.md +204 -0
  214. package/docs/decisions/adr-084.md +179 -0
  215. package/docs/editor-api.md +106 -0
  216. package/docs/markdown-pipeline.md +282 -0
  217. package/docs/provenance.md +106 -0
  218. package/docs/zusammen-editor-capability-map.md +232 -0
  219. package/package.json +175 -0
package/LICENSE ADDED
@@ -0,0 +1,34 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Brett Adam
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
23
+ ---
24
+
25
+ This package vendors a modified copy of `mdast-util-wiki-link` (MIT,
26
+ Copyright (c) 2020 Mark Hudnall). Its license and a description of the
27
+ modifications ship alongside it, at
28
+ `dist/markdown/vendor/mdast-util-wiki-link/`.
29
+
30
+ This package derives from the `webview-ui` package of SlashMD
31
+ (https://github.com/wolfdavo/SlashMD) by David Wolfenden, which that project's
32
+ README declares to be MIT-licensed. SlashMD carries no LICENSE file and no
33
+ copyright notice, so none is reproduced here; this notice records the
34
+ derivation and the stated license in their absence.
package/README.md ADDED
@@ -0,0 +1,376 @@
1
+ # `@liminis/editor`
2
+
3
+ A Lexical-based markdown WYSIWYG editor that round-trips through mdast, with a
4
+ host-injection seam so it can be embedded in an Electron app, a browser app, or
5
+ anything else that can supply a message channel.
6
+
7
+ It is the editor from [Liminis](https://github.com/verveguy/liminis), extracted
8
+ into this repository so it can stand on its own. MIT-licensed.
9
+
10
+ Its history, and how to read the `ADR-0NNN`, `FR-NNN` and `#NNN` references that
11
+ travelled with the code, are recorded in [`docs/provenance.md`](./docs/provenance.md).
12
+
13
+ ## What you get
14
+
15
+ - **`<Editor>`** — a WYSIWYG markdown editor. Tables, task lists (including in
16
+ ordered lists), footnotes, definition lists, callouts, toggles, code blocks
17
+ with Prism highlighting, images, LaTeX equations, Mermaid diagrams, C4
18
+ diagrams, YAML frontmatter, and wiki-links.
19
+ - **A markdown pipeline** — `parseMarkdown` / `stringifyMarkdown` and the mdast
20
+ ↔ Lexical mappers, usable with no editor mounted.
21
+ - **An annotation mechanism** — range-anchored markers over document text that
22
+ survive edits, with per-*kind* configuration. Comments and corrections are not
23
+ separate features; they are two configurations of one mechanism. Entirely
24
+ opt-in: configure no kinds and none of it loads.
25
+ - **A host seam** — every service the editor needs from its environment is an
26
+ optional injected function with a safe default, so `<Editor>` renders in a
27
+ host that supplies nothing at all.
28
+
29
+ ## Install
30
+
31
+ > **This package is not published.** It is `private: true` and `@liminis/editor`
32
+ > does not exist on the npm registry, so `pnpm add @liminis/editor` will fail.
33
+ > That is deliberate — see [Consuming it](#consuming-it) below for the mechanism
34
+ > that actually works, and `docs/decisions/adr-078.md` for why.
35
+
36
+ React and Lexical are **peer dependencies**, so your app resolves exactly one
37
+ copy of each. Two React copies produce `Invalid hook call`; two Lexical copies
38
+ produce a broken editor context — peering is what prevents both.
39
+
40
+ ```bash
41
+ pnpm add react@^19 react-dom@^19 \
42
+ lexical@^0.44 @lexical/react@^0.44 @lexical/code@^0.44 \
43
+ @lexical/code-prism@^0.44 @lexical/link@^0.44 @lexical/list@^0.44 \
44
+ @lexical/mark@^0.44 @lexical/markdown@^0.44 @lexical/rich-text@^0.44 \
45
+ @lexical/selection@^0.44 @lexical/table@^0.44 @lexical/utils@^0.44
46
+ ```
47
+
48
+ pnpm v8+ and npm v7+ install peers automatically, so in practice the first
49
+ command is usually enough.
50
+
51
+ The package ships ESM plus `.d.ts` declarations. It has no CommonJS build.
52
+
53
+ ## Quickstart
54
+
55
+ ```tsx
56
+ import { useState } from 'react'
57
+ import { Editor } from '@liminis/editor'
58
+ import '@liminis/editor/styles.css' // required — see "Styling" below
59
+
60
+ export function MyEditor() {
61
+ const [markdown, setMarkdown] = useState('# Hello\n\nStart typing.')
62
+
63
+ return (
64
+ <Editor
65
+ initialContent={markdown}
66
+ onChange={setMarkdown}
67
+ />
68
+ )
69
+ }
70
+ ```
71
+
72
+ `initialContent` is markdown text; `onChange` receives markdown text back.
73
+ The editor owns the Lexical state in between — you never see it unless you want
74
+ to.
75
+
76
+ ## Entry points
77
+
78
+ Seven, and they are not stylistic. Each keeps a specific dependency graph out of
79
+ a specific consumer, and importing the wrong one is measured in megabytes.
80
+
81
+ | Entry | Contains | Import it when | Lexical-free |
82
+ |---|---|---|---|
83
+ | `@liminis/editor` | Everything: `<Editor>`, `<App>`, the host seam, the markdown pipeline, the mappers | You are rendering an editor | No |
84
+ | `@liminis/editor/markdown` | `parseMarkdown`, mdast type guards, `getFileType`, the wiki-link mdast extension. Pure mdast/micromark | You only need to *read* markdown — a search snippet, a table of contents, a chunker | Yes |
85
+ | `@liminis/editor/annotations` | The anchor model, resolver, block structure, and annotation types. DOM-, React- and Lexical-free | You are resolving or storing annotations outside a rendered editor | Yes |
86
+ | `@liminis/editor/headless` | The C4 subsystem, server-side SVG rendering, MathJax lite adaptor | You are rendering diagrams or equations with no DOM (a server, a worker, an Electron main process) | Yes |
87
+ | `@liminis/editor/contract` | The host-message shapes, as types | You are writing a boundary that must not pull renderer code in — an Electron preload script. Use `import type` | Yes (type-only) |
88
+ | `@liminis/editor/nodes` | `editorNodes` (the exact Lexical node array `<Editor>` configures itself with), plus `importMarkdownToLexical` / `exportLexicalToMdast` | You are building your own headless Lexical `createEditor` — for example, to test the markdown↔Lexical mapper without mounting `<Editor>` | No |
89
+ | `@liminis/editor/styles.css` | The stylesheet | Always, once, in your app | N/A (not JS) |
90
+
91
+ Three of these are load-bearing in ways that are easy to undo by accident:
92
+
93
+ - **`./markdown` is deliberately *not* `./headless`.** `./headless` re-exports
94
+ the MathJax configuration, whose ~90 bare
95
+ `import '@mathjax/src/js/input/tex/…Configuration.js'` lines are genuinely
96
+ side-effectful and cannot be tree-shaken by anyone — roughly 1.9 MB. If all
97
+ you do is parse markdown, import `./markdown` and you never pay it.
98
+ - **`./contract` is only type-free of `zod` if you `import type`.** A value
99
+ import pulls the schemas in.
100
+ - **`./nodes` is the one entry that legitimately requires Lexical and React** —
101
+ its whole purpose is exporting the node classes the mapper instantiates. It
102
+ inherits `./headless`'s MathJax-lite exception (loading it evaluates the same
103
+ ~90 side-effectful TeX-configuration imports, via `EquationNode`), but it
104
+ pulls in none of Mermaid's, C4's, or Prism's rendering weight — those
105
+ decorator components are lazy-loaded and never evaluate in a headless editor.
106
+
107
+ Do not deep-import into `dist/`. Everything intended for consumers is on one of
108
+ the seven entries above; anything else is internal and will move.
109
+
110
+ ### Why there's no `sideEffects` field
111
+
112
+ `package.json` deliberately does not declare `sideEffects`. If your bundler's
113
+ tree-shaking pass ever runs against this package's own graph and you're
114
+ tempted to add one to prune it further, don't — the field is permanently
115
+ prohibited (ADR-075 §4), not merely undeclared for now.
116
+
117
+ The failure it would cause is a transitive one: marking this package's modules
118
+ side-effect-free changes how a whole-graph bundler like rolldown chunks
119
+ everything reachable from it, including third-party code the package merely
120
+ imports. `prismjs`, pulled in via `@lexical/code-prism`, has its core chunk
121
+ separated from `prism-clike.js`, a companion module that mutates a bare
122
+ `Prism` global at import time. Once the two are split into different chunks,
123
+ `prism-clike.js` runs before `Prism` exists:
124
+
125
+ ```
126
+ ReferenceError: Prism is not defined
127
+ ```
128
+
129
+ The app that hits this doesn't fail to build — it fails to *render*, since the
130
+ error is thrown by code Prism's own language-grammar files depend on running
131
+ first. This can't be fixed from inside this package: the hazard lives in
132
+ `prismjs`'s own module structure, three levels removed, so no `sideEffects`
133
+ value this package could declare about *its own* files changes how the bundler
134
+ chunks `prismjs`.
135
+
136
+ ## Versioning policy
137
+
138
+ This package is `0.x`. While it stays there:
139
+
140
+ - **Minor versions may break.** `0.1.0` → `0.2.0` is not guaranteed compatible.
141
+ - **Patch versions may not.** `0.1.0` → `0.1.1` is a bug fix only.
142
+ - **The seven subpaths above are the supported API.** Anything reachable only
143
+ by a deeper import path — `@liminis/editor/dist/...` or a relative path into
144
+ `src/` — is private. It can move or disappear in a patch release.
145
+
146
+ ## Styling
147
+
148
+ The editor's markup carries semantic class names (`editor-paragraph`,
149
+ `editor-heading-h1`, `annotation-mark-*`, …). **`@liminis/editor/styles.css`
150
+ defines them.** Importing it is not optional — without it the editor renders as
151
+ unstyled text, and annotation markers are invisible. This failure is silent:
152
+ nothing errors, it just looks wrong.
153
+
154
+ ```ts
155
+ import '@liminis/editor/styles.css'
156
+ ```
157
+
158
+ The sheet is plain CSS — OKLCH design tokens plus the editor's own rules. It has
159
+ no build-step requirement and works with any bundler.
160
+
161
+ ### TypeScript needs to be told CSS imports exist
162
+
163
+ If you type-check your app, that import is an error out of the box:
164
+
165
+ ```text
166
+ error TS2882: Cannot find module or type declarations for side-effect import
167
+ of '@liminis/editor/styles.css'.
168
+ ```
169
+
170
+ This is a TypeScript language limitation, not something the package can fix from
171
+ its side: TypeScript has no built-in meaning for a `.css` module, so *any*
172
+ stylesheet import from *any* package needs an ambient declaration. Most React
173
+ starters already provide one and you will never see this. If yours does not, one
174
+ line anywhere in your project's `include` fixes it:
175
+
176
+ ```ts
177
+ // css.d.ts
178
+ declare module '*.css'
179
+ ```
180
+
181
+ If you are on Vite, adding its client types does the same job and covers assets
182
+ too:
183
+
184
+ ```jsonc
185
+ // tsconfig.json
186
+ { "compilerOptions": { "types": ["vite/client"] } }
187
+ ```
188
+
189
+ Note the contrast with the failure above: forgetting the *import* fails
190
+ silently, while adding it without a declaration fails loudly at compile time.
191
+ Neither is a package defect, but only one of them tells you what is wrong.
192
+
193
+ ### If you use Tailwind
194
+
195
+ Some of the editor's markup also uses Tailwind utility classes. Tailwind v4
196
+ generates utilities by scanning your source, and **its automatic source
197
+ detection skips `node_modules`** — so without telling it where to look, those
198
+ classes are simply absent. Again: silent, not an error.
199
+
200
+ Add an explicit `@source` for the package's built output in the CSS file where
201
+ you import Tailwind:
202
+
203
+ ```css
204
+ @import "tailwindcss";
205
+ @source "../node_modules/@liminis/editor/dist";
206
+ ```
207
+
208
+ Adjust the relative path to point at your installed copy. In a pnpm workspace
209
+ the real path may be under `node_modules/.pnpm/` — pointing `@source` at the
210
+ symlinked path works, but verify a distinctive utility class actually appears in
211
+ your generated CSS rather than assuming it.
212
+
213
+ ## Annotations
214
+
215
+ An annotation is a range-anchored marker over document text whose anchor
216
+ survives edits to the surrounding document. The mechanism is entirely opt-in:
217
+ supplying an `annotationKinds` prop is what turns it on. With no kinds
218
+ configured, the annotation UI is never loaded — it sits behind a `React.lazy()`
219
+ boundary, so it is not even in the chunk your app downloads.
220
+
221
+ ```tsx
222
+ <Editor
223
+ initialContent={markdown}
224
+ onChange={setMarkdown}
225
+ annotationKinds={{
226
+ comment: {
227
+ markerStyle: 'highlight',
228
+ createAffordance: { surface: 'toolbar', label: 'Comment' },
229
+ },
230
+ }}
231
+ annotations={annotations}
232
+ onCreateAnnotation={(event) => { /* mint an id, persist, add to `annotations` */ }}
233
+ onActivateAnnotation={(id) => { /* open your own thread panel */ }}
234
+ />
235
+ ```
236
+
237
+ The package owns anchor mechanics and marker rendering. Identity, storage and
238
+ lifecycle stay in your app — `id` is opaque to the package and `payload` is
239
+ carried through untouched. See [`docs/annotations.md`](./docs/annotations.md).
240
+
241
+ Configuring more than one kind on the same `createAffordance.surface` offers
242
+ one toolbar button or context-menu entry per kind, in `annotationKinds`'
243
+ declaration order.
244
+
245
+ A `toolbar`-surfaced affordance works the same way whether `editable` is
246
+ `true` or `false` — annotating is decoupled from editing, so a selection in a
247
+ read-only editor still shows the floating toolbar with the configured
248
+ affordance (formatting controls are omitted there, since they would be
249
+ inert).
250
+
251
+ ## Documentation
252
+
253
+ - [`docs/editor-api.md`](./docs/editor-api.md) — the `<Editor>` props and the
254
+ host-injection seam.
255
+ - [`docs/markdown-pipeline.md`](./docs/markdown-pipeline.md) — `parseMarkdown`,
256
+ `stringifyMarkdown`, the mdast ↔ Lexical mappers, and wiki-links.
257
+ - [`docs/annotations.md`](./docs/annotations.md) — kinds, anchors, resolution,
258
+ and marker styling.
259
+
260
+ ## Consuming it
261
+
262
+ Because the package is unpublished, consumers install a **packed tarball** rather
263
+ than a registry version:
264
+
265
+ ```bash
266
+ # in this repository
267
+ pnpm build && pnpm pack --pack-destination /tmp
268
+
269
+ # in the consuming application
270
+ pnpm add /tmp/liminis-editor-0.1.0.tgz
271
+ ```
272
+
273
+ Applications that do this commit the tarball into their own tree (typically under
274
+ `vendor/`) and pin it by filename, so the install is reproducible and needs no
275
+ credentials for this private repository. `pnpm pack` works on a private package;
276
+ only `npm publish` is blocked, which is exactly the guard intended.
277
+
278
+ A git dependency on this repository does **not** work, and the reasons are
279
+ measured rather than assumed: `files` excludes the directory `main` points at, so
280
+ a git install lands only the manifest and docs; the build runs from `prepack`,
281
+ which a git install never invokes; and pnpm does not apply `publishConfig` for git
282
+ installs. Adding a `prepare` script to work around this would change what an
283
+ eventual npm tarball ships, so it has been deliberately avoided.
284
+
285
+ ## Demo
286
+
287
+ ```bash
288
+ pnpm demo
289
+ ```
290
+
291
+ Builds the package, packs it, installs the tarball into `examples/demo/` outside
292
+ any workspace, and starts the Vite dev server. The demo imports only the public
293
+ entry points, so it breaks when an adopter would break. Its content is a picker
294
+ over the same round-trip fixture corpus the package's own test suite uses, so
295
+ every fixture-representable node class renders somewhere in it. See
296
+ [`examples/demo/README.md`](./examples/demo/README.md).
297
+
298
+ `examples/demo` is also the source of the public GitHub Pages site: a
299
+ `release`-triggered (not merge-triggered) build of this same shell, showing a
300
+ visible version badge for the published release it represents and a
301
+ Documentation tab rendering this README. Between releases the deployed site
302
+ stays on the last published version even as `main` keeps moving — that
303
+ staleness is intentional, not a bug (see `docs/decisions/adr-082.md`).
304
+
305
+ ## Electron e2e shell
306
+
307
+ `examples/electron/` is a minimal Electron host for the package — a window, the
308
+ editor, and stubbed host services, no more — that a Playwright e2e suite drives
309
+ over CDP against real Chromium/Electron rather than a jsdom-class test
310
+ environment. It exists so package-level e2e can catch defects that only ever
311
+ surface in a real browser without needing `liminis-app`, or any other downstream
312
+ application, to exist. See
313
+ [`examples/electron/README.md`](./examples/electron/README.md).
314
+
315
+ ```bash
316
+ pnpm build:examples # builds and packs the package once, then builds both
317
+ # examples/demo and examples/electron against it
318
+ pnpm build:site # builds examples/demo only — what the release-triggered
319
+ # Pages deploy runs (see the Demo section above)
320
+ ```
321
+
322
+ ## Development
323
+
324
+ This repository stands alone. It has no `pnpm-workspace.yaml` and no dependency
325
+ on any other checkout — the `examples/` directories are external consumers by
326
+ construction, not workspace members.
327
+
328
+ ```bash
329
+ pnpm install
330
+ pnpm build # tsc emit + tsc-alias + asset copy → dist/
331
+ pnpm typecheck # tsc --noEmit
332
+ pnpm lint # eslint src/ tests/
333
+ pnpm test # vitest run
334
+ ```
335
+
336
+ That sequence is what CI runs, in that order. One further gate needs a real built
337
+ artifact and so runs separately:
338
+
339
+ ```bash
340
+ pnpm verify:package
341
+ ```
342
+
343
+ This builds, packs, installs the tarball into `examples/external-consumer/` with
344
+ `--ignore-workspace`, type-checks it under `bundler` and `nodenext` module
345
+ resolution, builds three measurement arms, and asserts the entry-graph boundaries
346
+ the seven export subpaths exist to keep — that `./markdown` pulls in no MathJax,
347
+ no Lexical and no react-dom, and that annotations stay behind a lazy boundary.
348
+ It is the check that proves the package is publishable without publishing it.
349
+
350
+ ## License
351
+
352
+ MIT. See [LICENSE](./LICENSE).
353
+
354
+ This package vendors a modified copy of `mdast-util-wiki-link` (MIT, Mark
355
+ Hudnall); its license and the modifications are documented at
356
+ `dist/markdown/vendor/mdast-util-wiki-link/`.
357
+
358
+ ## Acknowledgements
359
+
360
+ This editor began as the `webview-ui` package of
361
+ [**SlashMD**](https://github.com/wolfdavo/SlashMD) by David Wolfenden (MIT, as
362
+ declared in that project's README) — a VS Code extension that opens Markdown
363
+ files in a block-based WYSIWYG editor while keeping the file itself plain
364
+ Markdown.
365
+
366
+ That is the origin of the substance of this package, not a passing influence:
367
+ the Lexical node set (callouts, toggles, equations, Mermaid, frontmatter,
368
+ images), the mdast ↔ Lexical mappers, the Markdown parsing and stringification
369
+ layer, and the host-message contract all descend from it. The contract's shape
370
+ still shows its beginnings — `REQUEST_INIT`, `REQUEST_SETTINGS`, `WRITE_ASSET`
371
+ were a VS Code webview protocol before they were carried over an Electron IPC
372
+ bridge — and identifiers such as `SlashMDSettings` keep the name outright.
373
+
374
+ What has changed since is the seam rather than the ideas: a curated set of
375
+ export subpaths, peer-dependency inversion, an annotation mechanism, and a
376
+ round-trip corpus that holds the Markdown pipeline to a fixed point.
@@ -0,0 +1,50 @@
1
+ /**
2
+ * PROVENANCE — ported from Zusammen (`verveguy/zusammen`) for Liminis #939.
3
+ *
4
+ * The doc comments below are the original author's and are kept verbatim so
5
+ * this module stays diffable against its source. Their `FR-NNN`/`SC-NNN`
6
+ * identifiers, `#NN` issue references and `adrs/` paths therefore name
7
+ * **Zusammen's** spec, issues and ADRs — not this repository's, where the same
8
+ * identifiers mean something else entirely. For the Liminis-side design see
9
+ * `docs/decisions/adr-077.md` and
10
+ * `docs/zusammen-editor-capability-map.md`.
11
+ *
12
+ * "Comment"/"thread" in these comments should be read as "annotation": this
13
+ * module now serves both annotation kinds, not comments alone.
14
+ */
15
+ /**
16
+ * Comment Anchor Alignment
17
+ *
18
+ * `locateInSpan`: the verbatim-substring, context/occurrence-disambiguated
19
+ * text search shared by both directions of the comment-anchor mapping
20
+ * (capture and marker placement) — reused unchanged by #43's live-mark
21
+ * mechanism (`editor/app/editor/comment-anchor-marks.ts`) for the same
22
+ * lookup it always did: `targetText` -> location within the current raw
23
+ * markdown. Pure, git-free — no fs, no git, no DOM — safe to unit test with
24
+ * plain data.
25
+ */
26
+ export interface SpanRange {
27
+ start: number;
28
+ end: number;
29
+ }
30
+ export interface LocateInSpanOptions {
31
+ /** 0-based index of which occurrence of `target` to prefer, when known. */
32
+ occurrenceIndex?: number;
33
+ /** Text immediately before the target, for disambiguating duplicate occurrences. */
34
+ prefixContext?: string;
35
+ /** Text immediately after the target, for disambiguating duplicate occurrences. */
36
+ suffixContext?: string;
37
+ }
38
+ /**
39
+ * Locate `target` verbatim within `spanText`. Returns null if `target` does
40
+ * not appear at all — the caller falls back to anchoring the whole enclosing
41
+ * block (the Plan's bounded v1 scope for selections that straddle inline
42
+ * markdown syntax, e.g. a selection starting mid-`**bold**`).
43
+ *
44
+ * When `target` appears more than once, candidates are ranked by how well
45
+ * their surrounding context matches `prefixContext`/`suffixContext` plus
46
+ * proximity to `occurrenceIndex` — the same disambiguation signal
47
+ * `anchor-resolver.ts` uses for duplicate exact matches, reused here (via the
48
+ * shared `similarity` export) rather than reinvented.
49
+ */
50
+ export declare function locateInSpan(spanText: string, target: string, options?: LocateInSpanOptions): SpanRange | null;
@@ -0,0 +1,74 @@
1
+ /**
2
+ * PROVENANCE — ported from Zusammen (`verveguy/zusammen`) for Liminis #939.
3
+ *
4
+ * The doc comments below are the original author's and are kept verbatim so
5
+ * this module stays diffable against its source. Their `FR-NNN`/`SC-NNN`
6
+ * identifiers, `#NN` issue references and `adrs/` paths therefore name
7
+ * **Zusammen's** spec, issues and ADRs — not this repository's, where the same
8
+ * identifiers mean something else entirely. For the Liminis-side design see
9
+ * `docs/decisions/adr-077.md` and
10
+ * `docs/zusammen-editor-capability-map.md`.
11
+ *
12
+ * "Comment"/"thread" in these comments should be read as "annotation": this
13
+ * module now serves both annotation kinds, not comments alone.
14
+ */
15
+ /**
16
+ * Comment Anchor Alignment
17
+ *
18
+ * `locateInSpan`: the verbatim-substring, context/occurrence-disambiguated
19
+ * text search shared by both directions of the comment-anchor mapping
20
+ * (capture and marker placement) — reused unchanged by #43's live-mark
21
+ * mechanism (`editor/app/editor/comment-anchor-marks.ts`) for the same
22
+ * lookup it always did: `targetText` -> location within the current raw
23
+ * markdown. Pure, git-free — no fs, no git, no DOM — safe to unit test with
24
+ * plain data.
25
+ */
26
+ import { similarity } from './anchor-resolver.js';
27
+ /** How many characters of context around a candidate occurrence to compare against `prefixContext`/`suffixContext`. */
28
+ const CONTEXT_COMPARISON_CHARS = 40;
29
+ function findOccurrences(spanText, target) {
30
+ if (!target)
31
+ return [];
32
+ const matches = [];
33
+ let idx = spanText.indexOf(target);
34
+ while (idx !== -1) {
35
+ matches.push({ start: idx, end: idx + target.length });
36
+ idx = spanText.indexOf(target, idx + 1);
37
+ }
38
+ return matches;
39
+ }
40
+ /**
41
+ * Locate `target` verbatim within `spanText`. Returns null if `target` does
42
+ * not appear at all — the caller falls back to anchoring the whole enclosing
43
+ * block (the Plan's bounded v1 scope for selections that straddle inline
44
+ * markdown syntax, e.g. a selection starting mid-`**bold**`).
45
+ *
46
+ * When `target` appears more than once, candidates are ranked by how well
47
+ * their surrounding context matches `prefixContext`/`suffixContext` plus
48
+ * proximity to `occurrenceIndex` — the same disambiguation signal
49
+ * `anchor-resolver.ts` uses for duplicate exact matches, reused here (via the
50
+ * shared `similarity` export) rather than reinvented.
51
+ */
52
+ export function locateInSpan(spanText, target, options = {}) {
53
+ const matches = findOccurrences(spanText, target);
54
+ if (matches.length === 0)
55
+ return null;
56
+ if (matches.length === 1)
57
+ return matches[0];
58
+ const { occurrenceIndex, prefixContext = '', suffixContext = '' } = options;
59
+ // Only weigh in context when the caller actually supplied any — comparing
60
+ // against an empty context would otherwise favor whichever occurrence
61
+ // happens to have the shortest surrounding text, not the intended one.
62
+ const hasContext = prefixContext.length > 0 || suffixContext.length > 0;
63
+ const contextWeight = hasContext ? 0.8 : 0;
64
+ const proximityWeight = 1 - contextWeight;
65
+ const scored = matches.map((match, index) => {
66
+ const prefix = spanText.slice(Math.max(0, match.start - CONTEXT_COMPARISON_CHARS), match.start);
67
+ const suffix = spanText.slice(match.end, Math.min(spanText.length, match.end + CONTEXT_COMPARISON_CHARS));
68
+ const contextScore = (similarity(prefix, prefixContext) + similarity(suffix, suffixContext)) / 2;
69
+ const proximityScore = occurrenceIndex == null ? 0 : 1 - Math.abs(index - occurrenceIndex) / matches.length;
70
+ return { match, score: contextScore * contextWeight + proximityScore * proximityWeight };
71
+ });
72
+ scored.sort((a, b) => b.score - a.score);
73
+ return scored[0].match;
74
+ }
@@ -0,0 +1,81 @@
1
+ /**
2
+ * PROVENANCE — ported from Zusammen (`verveguy/zusammen`) for Liminis #939.
3
+ *
4
+ * The doc comments below are the original author's and are kept verbatim so
5
+ * this module stays diffable against its source. Their `FR-NNN`/`SC-NNN`
6
+ * identifiers, `#NN` issue references and `adrs/` paths therefore name
7
+ * **Zusammen's** spec, issues and ADRs — not this repository's, where the same
8
+ * identifiers mean something else entirely. For the Liminis-side design see
9
+ * `docs/decisions/adr-077.md` and
10
+ * `docs/zusammen-editor-capability-map.md`.
11
+ *
12
+ * "Comment"/"thread" in these comments should be read as "annotation": this
13
+ * module now serves both annotation kinds, not comments alone.
14
+ */
15
+ /**
16
+ * Comment Anchor Model
17
+ *
18
+ * Defines the content-relative anchor payload persisted through the
19
+ * comment-event-store's (issue #13) `re-anchor`/`anchor-recorded` events, and
20
+ * the resolution-outcome payload persisted through `anchor-resolved` events.
21
+ * Both were left opaque (`unknown`) by that store on purpose — this module is
22
+ * what gives them shape (FR-001, FR-002).
23
+ *
24
+ * Pure, git-free — no fs, no git, safe to unit test with plain strings.
25
+ */
26
+ import { z } from 'zod';
27
+ import { type BlockType } from './block-structure.js';
28
+ /** How many characters of surrounding text are captured on each side of the target, for duplicate disambiguation (FR-006). */
29
+ export declare const CONTEXT_WINDOW_CHARS = 40;
30
+ /**
31
+ * The durable, content-relative description of a comment's target (FR-001).
32
+ * Re-locating a target needs only an anchor plus the new document text — never
33
+ * the old document text — which is what makes resolution provenance-agnostic
34
+ * (FR-010): a direct edit and a merge produce the same new text.
35
+ */
36
+ export interface Anchor {
37
+ /** The exact substring the comment targets. */
38
+ targetText: string;
39
+ /** Up to CONTEXT_WINDOW_CHARS of text immediately before the target. */
40
+ prefixContext: string;
41
+ /** Up to CONTEXT_WINDOW_CHARS of text immediately after the target. */
42
+ suffixContext: string;
43
+ /** The type of the single leaf block enclosing the target at capture time, or null if the target itself spans more than one block. */
44
+ blockType: BlockType | null;
45
+ /** 0-based index of which occurrence of `targetText` in the document this anchor refers to — disambiguates duplicate text (FR-006). */
46
+ occurrenceIndex: number;
47
+ /** The content-repo HEAD SHA the anchor was captured against. */
48
+ docVersion: string;
49
+ }
50
+ /** An anchor as read from the live editor structure, before the store stamps `docVersion`. */
51
+ export type AnchorFields = Omit<Anchor, 'docVersion'>;
52
+ export declare const ANCHOR_SCHEMA: z.ZodType<Anchor>;
53
+ export interface AnchorRange {
54
+ start: number;
55
+ end: number;
56
+ }
57
+ /**
58
+ * Capture a durable anchor for the text at `range` within `text`, as of
59
+ * `docVersion`. The target's enclosing block type is null when the range
60
+ * spans more than one leaf block (e.g. a comment deliberately covering two
61
+ * paragraphs) — the resolver treats that the same as any other
62
+ * structural-boundary case.
63
+ */
64
+ export declare function captureAnchor(text: string, range: AnchorRange, docVersion: string): Anchor;
65
+ export type AnchorOutcome = 'unchanged' | 're-attached' | 'flagged' | 'orphaned';
66
+ /**
67
+ * The outcome of re-evaluating a comment's anchor against a new document
68
+ * version (FR-002). Persisted verbatim as the payload of an `anchor-resolved`
69
+ * event; `anchor` is additionally persisted as the payload of the
70
+ * `anchor-recorded` event that accompanies a `re-attached` outcome.
71
+ */
72
+ export interface AnchorResolution {
73
+ outcome: AnchorOutcome;
74
+ /** The content-repo HEAD SHA this resolution was evaluated against. */
75
+ docVersion: string;
76
+ /** Short, human-readable explanation of why this outcome was reached. */
77
+ reason: string;
78
+ /** Present only when outcome === 're-attached' — the anchor recomputed at the new location. */
79
+ anchor?: Anchor;
80
+ }
81
+ export declare const ANCHOR_RESOLUTION_SCHEMA: z.ZodType<AnchorResolution>;