@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.
- package/LICENSE +34 -0
- package/README.md +376 -0
- package/dist/annotations/anchor-align.d.ts +50 -0
- package/dist/annotations/anchor-align.js +74 -0
- package/dist/annotations/anchor-model.d.ts +81 -0
- package/dist/annotations/anchor-model.js +80 -0
- package/dist/annotations/anchor-resolver.d.ts +92 -0
- package/dist/annotations/anchor-resolver.js +231 -0
- package/dist/annotations/block-structure.d.ts +65 -0
- package/dist/annotations/block-structure.js +113 -0
- package/dist/annotations/types.d.ts +159 -0
- package/dist/annotations/types.js +50 -0
- package/dist/annotations.d.ts +25 -0
- package/dist/annotations.js +27 -0
- package/dist/app/App.d.ts +64 -0
- package/dist/app/App.js +180 -0
- package/dist/app/editor/AmbientCorrectionPlugin.d.ts +18 -0
- package/dist/app/editor/AmbientCorrectionPlugin.js +185 -0
- package/dist/app/editor/AnchorScrollPlugin.d.ts +16 -0
- package/dist/app/editor/AnchorScrollPlugin.js +134 -0
- package/dist/app/editor/AnnotationMarkerPlugin.d.ts +24 -0
- package/dist/app/editor/AnnotationMarkerPlugin.js +224 -0
- package/dist/app/editor/AnnotationPlugin.d.ts +42 -0
- package/dist/app/editor/AnnotationPlugin.js +135 -0
- package/dist/app/editor/AnnotationSurface.d.ts +34 -0
- package/dist/app/editor/AnnotationSurface.js +115 -0
- package/dist/app/editor/AssetContext.d.ts +20 -0
- package/dist/app/editor/AssetContext.js +81 -0
- package/dist/app/editor/BlockClickPlugin.d.ts +8 -0
- package/dist/app/editor/BlockClickPlugin.js +126 -0
- package/dist/app/editor/CodeBlockPlugin.d.ts +1 -0
- package/dist/app/editor/CodeBlockPlugin.js +219 -0
- package/dist/app/editor/CorrectionPanelPlugin.d.ts +14 -0
- package/dist/app/editor/CorrectionPanelPlugin.js +333 -0
- package/dist/app/editor/DragHandlePlugin.d.ts +1 -0
- package/dist/app/editor/DragHandlePlugin.js +300 -0
- package/dist/app/editor/Editor.d.ts +84 -0
- package/dist/app/editor/Editor.js +539 -0
- package/dist/app/editor/FrontmatterPlugin.d.ts +19 -0
- package/dist/app/editor/FrontmatterPlugin.js +301 -0
- package/dist/app/editor/ImageModal.d.ts +13 -0
- package/dist/app/editor/ImageModal.js +267 -0
- package/dist/app/editor/ImagePlugin.d.ts +3 -0
- package/dist/app/editor/ImagePlugin.js +187 -0
- package/dist/app/editor/LinkClickPlugin.d.ts +23 -0
- package/dist/app/editor/LinkClickPlugin.js +90 -0
- package/dist/app/editor/MarkdownShortcutsPlugin.d.ts +49 -0
- package/dist/app/editor/MarkdownShortcutsPlugin.js +373 -0
- package/dist/app/editor/OrderedTaskListPlugin.d.ts +21 -0
- package/dist/app/editor/OrderedTaskListPlugin.js +78 -0
- package/dist/app/editor/SearchPlugin.d.ts +1 -0
- package/dist/app/editor/SearchPlugin.js +379 -0
- package/dist/app/editor/SelectionContextMenuPlugin.d.ts +29 -0
- package/dist/app/editor/SelectionContextMenuPlugin.js +160 -0
- package/dist/app/editor/SlashMenu.d.ts +11 -0
- package/dist/app/editor/SlashMenu.js +494 -0
- package/dist/app/editor/SlashMenuPlugin.d.ts +1 -0
- package/dist/app/editor/SlashMenuPlugin.js +168 -0
- package/dist/app/editor/TableActionsPlugin.d.ts +1 -0
- package/dist/app/editor/TableActionsPlugin.js +398 -0
- package/dist/app/editor/TogglePlugin.d.ts +1 -0
- package/dist/app/editor/TogglePlugin.js +159 -0
- package/dist/app/editor/Toolbar.d.ts +10 -0
- package/dist/app/editor/Toolbar.js +263 -0
- package/dist/app/editor/WikiLinkExistencePlugin.d.ts +17 -0
- package/dist/app/editor/WikiLinkExistencePlugin.js +104 -0
- package/dist/app/editor/WikiLinkFormatPlugin.d.ts +17 -0
- package/dist/app/editor/WikiLinkFormatPlugin.js +203 -0
- package/dist/app/editor/annotation-marks.d.ts +177 -0
- package/dist/app/editor/annotation-marks.js +750 -0
- package/dist/app/editor/annotationCommands.d.ts +13 -0
- package/dist/app/editor/annotationCommands.js +11 -0
- package/dist/app/editor/c4/C4InteractiveRenderer.d.ts +35 -0
- package/dist/app/editor/c4/C4InteractiveRenderer.js +299 -0
- package/dist/app/editor/c4/edge-clipping.d.ts +24 -0
- package/dist/app/editor/c4/edge-clipping.js +139 -0
- package/dist/app/editor/c4/hooks/useC4DiagramDrag.d.ts +38 -0
- package/dist/app/editor/c4/hooks/useC4DiagramDrag.js +112 -0
- package/dist/app/editor/c4/layout.d.ts +25 -0
- package/dist/app/editor/c4/layout.js +839 -0
- package/dist/app/editor/c4/parser.d.ts +19 -0
- package/dist/app/editor/c4/parser.js +410 -0
- package/dist/app/editor/c4/render-to-string.d.ts +24 -0
- package/dist/app/editor/c4/render-to-string.js +34 -0
- package/dist/app/editor/c4/renderer.d.ts +64 -0
- package/dist/app/editor/c4/renderer.js +569 -0
- package/dist/app/editor/c4/types.d.ts +203 -0
- package/dist/app/editor/c4/types.js +43 -0
- package/dist/app/editor/correction-yaml.d.ts +10 -0
- package/dist/app/editor/correction-yaml.js +40 -0
- package/dist/app/editor/editorNodes.d.ts +9 -0
- package/dist/app/editor/editorNodes.js +36 -0
- package/dist/app/editor/index.d.ts +1 -0
- package/dist/app/editor/index.js +1 -0
- package/dist/app/editor/mark-overlap-resolver.d.ts +6 -0
- package/dist/app/editor/mark-overlap-resolver.js +45 -0
- package/dist/app/editor/nodes/C4Component.d.ts +14 -0
- package/dist/app/editor/nodes/C4Component.js +284 -0
- package/dist/app/editor/nodes/C4Node.d.ts +32 -0
- package/dist/app/editor/nodes/C4Node.js +112 -0
- package/dist/app/editor/nodes/CalloutNode.d.ts +23 -0
- package/dist/app/editor/nodes/CalloutNode.js +94 -0
- package/dist/app/editor/nodes/CustomLinkNode.d.ts +57 -0
- package/dist/app/editor/nodes/CustomLinkNode.js +209 -0
- package/dist/app/editor/nodes/CustomListItemNode.d.ts +46 -0
- package/dist/app/editor/nodes/CustomListItemNode.js +120 -0
- package/dist/app/editor/nodes/CustomListNode.d.ts +27 -0
- package/dist/app/editor/nodes/CustomListNode.js +54 -0
- package/dist/app/editor/nodes/DefinitionListNode.d.ts +44 -0
- package/dist/app/editor/nodes/DefinitionListNode.js +151 -0
- package/dist/app/editor/nodes/DiagramContextMenu.d.ts +45 -0
- package/dist/app/editor/nodes/DiagramContextMenu.js +116 -0
- package/dist/app/editor/nodes/EquationComponent.d.ts +17 -0
- package/dist/app/editor/nodes/EquationComponent.js +130 -0
- package/dist/app/editor/nodes/EquationNode.d.ts +43 -0
- package/dist/app/editor/nodes/EquationNode.js +179 -0
- package/dist/app/editor/nodes/FootnoteNode.d.ts +40 -0
- package/dist/app/editor/nodes/FootnoteNode.js +151 -0
- package/dist/app/editor/nodes/FrontmatterNode.d.ts +24 -0
- package/dist/app/editor/nodes/FrontmatterNode.js +88 -0
- package/dist/app/editor/nodes/HorizontalRuleNode.d.ts +17 -0
- package/dist/app/editor/nodes/HorizontalRuleNode.js +57 -0
- package/dist/app/editor/nodes/HtmlNode.d.ts +32 -0
- package/dist/app/editor/nodes/HtmlNode.js +121 -0
- package/dist/app/editor/nodes/ImageComponent.d.ts +11 -0
- package/dist/app/editor/nodes/ImageComponent.js +228 -0
- package/dist/app/editor/nodes/ImageNode.d.ts +53 -0
- package/dist/app/editor/nodes/ImageNode.js +183 -0
- package/dist/app/editor/nodes/ListItemParagraphBreakNode.d.ts +42 -0
- package/dist/app/editor/nodes/ListItemParagraphBreakNode.js +95 -0
- package/dist/app/editor/nodes/MermaidComponent.d.ts +11 -0
- package/dist/app/editor/nodes/MermaidComponent.js +216 -0
- package/dist/app/editor/nodes/MermaidNode.d.ts +27 -0
- package/dist/app/editor/nodes/MermaidNode.js +96 -0
- package/dist/app/editor/nodes/ToggleNode.d.ts +56 -0
- package/dist/app/editor/nodes/ToggleNode.js +237 -0
- package/dist/app/editor/nodes/diagram-context-menu.d.ts +11 -0
- package/dist/app/editor/nodes/diagram-context-menu.js +70 -0
- package/dist/app/editor/nodes/diagram-utils.d.ts +13 -0
- package/dist/app/editor/nodes/diagram-utils.js +29 -0
- package/dist/app/editor/nodes/index.d.ts +30 -0
- package/dist/app/editor/nodes/index.js +17 -0
- package/dist/app/editor/utils.d.ts +2 -0
- package/dist/app/editor/utils.js +17 -0
- package/dist/app/mapper/index.d.ts +3 -0
- package/dist/app/mapper/index.js +2 -0
- package/dist/app/mapper/lexicalToMdast.d.ts +62 -0
- package/dist/app/mapper/lexicalToMdast.js +1937 -0
- package/dist/app/mapper/mdastToLexical.d.ts +31 -0
- package/dist/app/mapper/mdastToLexical.js +1075 -0
- package/dist/contract.d.ts +8 -0
- package/dist/contract.js +8 -0
- package/dist/headless.d.ts +40 -0
- package/dist/headless.js +45 -0
- package/dist/host/context.d.ts +20 -0
- package/dist/host/context.js +24 -0
- package/dist/host/defaults.d.ts +21 -0
- package/dist/host/defaults.js +47 -0
- package/dist/host/messages.d.ts +26 -0
- package/dist/host/messages.js +47 -0
- package/dist/host/types.d.ts +75 -0
- package/dist/host/types.js +13 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.js +51 -0
- package/dist/markdown/annotate-sentinels.d.ts +42 -0
- package/dist/markdown/annotate-sentinels.js +86 -0
- package/dist/markdown/parse.d.ts +56 -0
- package/dist/markdown/parse.js +516 -0
- package/dist/markdown/stringify.d.ts +7 -0
- package/dist/markdown/stringify.js +439 -0
- package/dist/markdown/vendor/mdast-util-wiki-link/LICENSE +21 -0
- package/dist/markdown/vendor/mdast-util-wiki-link/README.md +41 -0
- package/dist/markdown/vendor/mdast-util-wiki-link/from-markdown.d.ts +72 -0
- package/dist/markdown/vendor/mdast-util-wiki-link/from-markdown.js +127 -0
- package/dist/markdown/vendor/mdast-util-wiki-link/index.d.ts +8 -0
- package/dist/markdown/vendor/mdast-util-wiki-link/index.js +6 -0
- package/dist/markdown/vendor/mdast-util-wiki-link/to-markdown.d.ts +17 -0
- package/dist/markdown/vendor/mdast-util-wiki-link/to-markdown.js +33 -0
- package/dist/markdown.d.ts +28 -0
- package/dist/markdown.js +44 -0
- package/dist/mathjax-config.d.ts +89 -0
- package/dist/mathjax-config.js +133 -0
- package/dist/nodes.d.ts +34 -0
- package/dist/nodes.js +34 -0
- package/dist/stores/correctionStore.d.ts +15 -0
- package/dist/stores/correctionStore.js +12 -0
- package/dist/stores/editorStore.d.ts +16 -0
- package/dist/stores/editorStore.js +27 -0
- package/dist/styles.css +2477 -0
- package/dist/types.d.ts +296 -0
- package/dist/types.js +91 -0
- package/dist/utils/file-types.d.ts +10 -0
- package/dist/utils/file-types.js +46 -0
- package/docs/annotations.md +134 -0
- package/docs/decisions/README.md +72 -0
- package/docs/decisions/adr-002.md +27 -0
- package/docs/decisions/adr-007.md +27 -0
- package/docs/decisions/adr-010.md +26 -0
- package/docs/decisions/adr-024.md +124 -0
- package/docs/decisions/adr-024b.md +0 -0
- package/docs/decisions/adr-025.md +103 -0
- package/docs/decisions/adr-027.md +66 -0
- package/docs/decisions/adr-057.md +67 -0
- package/docs/decisions/adr-070.md +131 -0
- package/docs/decisions/adr-075.md +410 -0
- package/docs/decisions/adr-076.md +183 -0
- package/docs/decisions/adr-077.md +410 -0
- package/docs/decisions/adr-078.md +243 -0
- package/docs/decisions/adr-079.md +167 -0
- package/docs/decisions/adr-080.md +325 -0
- package/docs/decisions/adr-081.md +108 -0
- package/docs/decisions/adr-082.md +123 -0
- package/docs/decisions/adr-083.md +204 -0
- package/docs/decisions/adr-084.md +179 -0
- package/docs/editor-api.md +106 -0
- package/docs/markdown-pipeline.md +282 -0
- package/docs/provenance.md +106 -0
- package/docs/zusammen-editor-capability-map.md +232 -0
- package/package.json +175 -0
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
# The markdown pipeline
|
|
2
|
+
|
|
3
|
+
Markdown ↔ mdast ↔ Lexical, usable with or without an editor mounted.
|
|
4
|
+
|
|
5
|
+
```text
|
|
6
|
+
markdown text ──parseMarkdown──▶ mdast Root ──importMarkdownToLexical──▶ Lexical
|
|
7
|
+
markdown text ◀─stringifyMarkdown── mdast Root ◀──exportLexicalToMdast── Lexical
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
Everything on the mdast side is available from `@liminis/editor/markdown`,
|
|
11
|
+
which has no React, no Lexical, no DOM and no MathJax in its graph. The
|
|
12
|
+
Lexical mappers live on the root entry, because they need Lexical.
|
|
13
|
+
|
|
14
|
+
## `parseMarkdown`
|
|
15
|
+
|
|
16
|
+
```ts
|
|
17
|
+
import { parseMarkdown } from '@liminis/editor/markdown'
|
|
18
|
+
|
|
19
|
+
const { root } = parseMarkdown(text)
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
`root` is a standard mdast `Root`. Extensions enabled: GFM (tables, strikethrough,
|
|
23
|
+
task lists, autolinks), footnotes, math, definition lists, YAML frontmatter, and
|
|
24
|
+
wiki-links.
|
|
25
|
+
|
|
26
|
+
Beyond the standard extensions, `parseMarkdown` runs several post-passes that
|
|
27
|
+
exist to preserve information a plain mdast parse throws away:
|
|
28
|
+
|
|
29
|
+
- **Task lists in ordered lists.** GFM only supports checkboxes in unordered
|
|
30
|
+
lists; Foam and Obsidian use them in ordered lists too, so they are added back.
|
|
31
|
+
- **Emphasis marker annotation.** Whether the source wrote `*em*` or `_em_` is
|
|
32
|
+
recorded on the node, so a round trip does not silently rewrite one into the
|
|
33
|
+
other.
|
|
34
|
+
- **Wiki-link pipe protection.** See below.
|
|
35
|
+
- **Empty-alias marking.** `[[target|]]` is distinguishable from `[[target]]`.
|
|
36
|
+
|
|
37
|
+
### Type guards
|
|
38
|
+
|
|
39
|
+
The entry exports guards for the mdast node types you will actually branch on:
|
|
40
|
+
`isParagraph`, `isHeading`, `isList`, `isListItem`, `isBlockquote`, `isCode`,
|
|
41
|
+
`isThematicBreak`, `isTable`, `isImage`, `isLink`, `isHtml`, `isText`,
|
|
42
|
+
`isStrong`, `isEmphasis`, `isInlineCode`, `isDelete`.
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
import { parseMarkdown, isHeading, isText } from '@liminis/editor/markdown'
|
|
46
|
+
|
|
47
|
+
const { root } = parseMarkdown(text)
|
|
48
|
+
const titles = root.children.filter(isHeading).map((h) => h.children.filter(isText).map(t => t.value).join(''))
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
`getFileType(path)` is also here — the editor's own file-type classifier.
|
|
52
|
+
|
|
53
|
+
## `stringifyMarkdown`
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
import { stringifyMarkdown } from '@liminis/editor'
|
|
57
|
+
|
|
58
|
+
const text = stringifyMarkdown(root, { bulletStyle: '-', fenceStyle: '`' })
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
| Option | Type | Default |
|
|
62
|
+
|---|---|---|
|
|
63
|
+
| `wrapWidth` | `number` | no wrapping |
|
|
64
|
+
| `bulletStyle` | `'-' \| '*' \| '+'` | `'-'` |
|
|
65
|
+
| `fenceStyle` | `` '`' \| '~' `` | `` '`' `` |
|
|
66
|
+
|
|
67
|
+
Serialization is written to keep a document byte-stable across a round trip
|
|
68
|
+
wherever markdown permits it. The most consequential piece is **list spread**:
|
|
69
|
+
whether a list is "loose" (blank lines between items) is carried through the
|
|
70
|
+
Lexical round trip and is only ever *upgraded* to loose, never downgraded — a
|
|
71
|
+
downgrade silently collapses blank lines and, on re-parse, misreads item
|
|
72
|
+
boundaries.
|
|
73
|
+
|
|
74
|
+
## The Lexical mappers
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
import { importMarkdownToLexical, exportLexicalToMdast } from '@liminis/editor'
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
These are what `<Editor>` uses internally. Reach for them directly only if you
|
|
81
|
+
are driving a Lexical editor yourself.
|
|
82
|
+
|
|
83
|
+
### Building a headless editor
|
|
84
|
+
|
|
85
|
+
The mapper functions instantiate Lexical node classes directly, so a
|
|
86
|
+
`createEditor` you build yourself must be configured with the *exact* node set
|
|
87
|
+
`<Editor>` uses — any node type Lexical encounters that isn't registered
|
|
88
|
+
throws. `@liminis/editor/nodes` exports that array (`editorNodes`) alongside
|
|
89
|
+
the two mapper functions, without pulling in `<Editor>`/`<App>`, Mermaid, or
|
|
90
|
+
Prism. This is the entry point to use for a fast round-trip regression test
|
|
91
|
+
that exercises the real mapper without mounting a React component:
|
|
92
|
+
|
|
93
|
+
```ts
|
|
94
|
+
import { createEditor } from 'lexical'
|
|
95
|
+
import { editorNodes, importMarkdownToLexical, exportLexicalToMdast } from '@liminis/editor/nodes'
|
|
96
|
+
import { parseMarkdown } from '@liminis/editor/markdown'
|
|
97
|
+
|
|
98
|
+
const editor = createEditor({
|
|
99
|
+
namespace: 'headless',
|
|
100
|
+
nodes: editorNodes,
|
|
101
|
+
onError: (error) => { throw error },
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
const parsed = parseMarkdown(markdownSource)
|
|
105
|
+
|
|
106
|
+
// `importMarkdownToLexical` schedules its own `editor.update()`, which by
|
|
107
|
+
// default reconciles on a microtask. Wrap it in a `discrete: true` update so
|
|
108
|
+
// the mutation is committed before `exportLexicalToMdast` reads it back.
|
|
109
|
+
editor.update(() => {
|
|
110
|
+
importMarkdownToLexical(editor, parsed.root)
|
|
111
|
+
}, { discrete: true })
|
|
112
|
+
|
|
113
|
+
const mdast = exportLexicalToMdast(editor)
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
`editorNodes` is the same array `<Editor>` configures its own `LexicalComposer`
|
|
117
|
+
with — not a separately maintained copy — so a node type added to the
|
|
118
|
+
production editor is reflected here automatically. `EquationNode` imports
|
|
119
|
+
MathJax's lite adaptor at module scope, so loading `./nodes` carries the same
|
|
120
|
+
documented MathJax-lite exception as `./headless`; it does not initialize
|
|
121
|
+
Mermaid, C4's layout engine, or Prism, since those node classes lazy-load their
|
|
122
|
+
render components.
|
|
123
|
+
|
|
124
|
+
## Wiki-links
|
|
125
|
+
|
|
126
|
+
Wiki-links are `[[target]]` or `[[target|alias]]` (Obsidian/Foam style — the
|
|
127
|
+
alias divider is `|`, not the upstream default `:`). They parse to a `wikiLink`
|
|
128
|
+
mdast node:
|
|
129
|
+
|
|
130
|
+
```ts
|
|
131
|
+
{ type: 'wikiLink', value: 'target', data: { alias, permalink, exists, /* hast shim fields */ } }
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
`data.alias` is the display text — it falls back to the target when no explicit
|
|
135
|
+
alias is given, which is upstream behaviour and occasionally surprising.
|
|
136
|
+
|
|
137
|
+
### The pipe-in-tables problem (#347)
|
|
138
|
+
|
|
139
|
+
A pipe inside a table cell terminates the cell. An aliased wiki-link in a table
|
|
140
|
+
therefore has to escape its divider (`[[target\|alias]]`) or GFM parsing splits
|
|
141
|
+
the link — and, worse, corrupts the shape of the whole table row.
|
|
142
|
+
|
|
143
|
+
`parseMarkdown` escapes the divider before parsing and the wiki-link extension
|
|
144
|
+
strips the resulting backslash off the target afterwards. The strip happens
|
|
145
|
+
*before* the target is resolved, so `data.permalink` and `data.exists` are
|
|
146
|
+
computed from the clean target.
|
|
147
|
+
|
|
148
|
+
This behaviour originally came from a `pnpm patch` on the npm package. That
|
|
149
|
+
patch could not travel outside the Liminis monorepo, so the extension is now
|
|
150
|
+
**vendored** into this package (MIT, from
|
|
151
|
+
[`landakram/mdast-util-wiki-link`](https://github.com/landakram/mdast-util-wiki-link);
|
|
152
|
+
license and modifications ship at `dist/markdown/vendor/mdast-util-wiki-link/`).
|
|
153
|
+
|
|
154
|
+
**If you are maintaining this package: do not remove the trailing-backslash
|
|
155
|
+
strip.** It looks like a stray special case. It is the fix for
|
|
156
|
+
[#347](https://github.com/verveguy/liminis/issues/347), and removing it breaks
|
|
157
|
+
entire markdown tables that happen to contain an aliased wiki-link, not just the
|
|
158
|
+
link.
|
|
159
|
+
|
|
160
|
+
### Building your own pipeline
|
|
161
|
+
|
|
162
|
+
If you need an mdast pipeline of your own that agrees with the editor's — a
|
|
163
|
+
chunker, an indexer, an exporter — take the wiki-link extension from this
|
|
164
|
+
package rather than from npm, or your parse will differ from the editor's:
|
|
165
|
+
|
|
166
|
+
```ts
|
|
167
|
+
import { fromMarkdown } from 'mdast-util-from-markdown'
|
|
168
|
+
import { syntax as wikiLinkSyntax } from 'micromark-extension-wiki-link'
|
|
169
|
+
import { wikiLinkFromMarkdown, wikiLinkToMarkdown } from '@liminis/editor/markdown'
|
|
170
|
+
|
|
171
|
+
const OPTIONS = { aliasDivider: '|' }
|
|
172
|
+
|
|
173
|
+
const tree = fromMarkdown(text, {
|
|
174
|
+
extensions: [wikiLinkSyntax(OPTIONS) /* , gfm(), … */],
|
|
175
|
+
mdastExtensions: [wikiLinkFromMarkdown(OPTIONS) /* , gfmFromMarkdown(), … */],
|
|
176
|
+
})
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
`wikiLinkToMarkdown` is the matching serializer extension, built on
|
|
180
|
+
`mdast-util-to-markdown` v2.
|
|
181
|
+
|
|
182
|
+
> **The extensions alone are not equivalent to `parseMarkdown`.** If you enable
|
|
183
|
+
> GFM tables alongside them, prefer `parseMarkdown` — see below.
|
|
184
|
+
|
|
185
|
+
### What the extensions do *not* give you
|
|
186
|
+
|
|
187
|
+
`parseMarkdown` is more than these two extensions, and the difference is not
|
|
188
|
+
cosmetic. Two pre/post-passes live in `parseMarkdown` and have no equivalent
|
|
189
|
+
inside the extension:
|
|
190
|
+
|
|
191
|
+
- **Alias-pipe escaping (the #347 fix above).** The trailing-backslash strip in
|
|
192
|
+
`wikiLinkFromMarkdown` only helps once the divider is *already* escaped.
|
|
193
|
+
`parseMarkdown` does that escaping before parsing. Without it, GFM's table
|
|
194
|
+
parser reaches the `|` first. Measured on `| [[target\|alias]] | x |` in a
|
|
195
|
+
two-column table: the extensions alone yield **5 table cells and 0 wiki-link
|
|
196
|
+
nodes** — the row is corrupted and the link is gone — where `parseMarkdown`
|
|
197
|
+
yields 4 cells and 1 wiki-link.
|
|
198
|
+
- **Empty-alias preservation.** `[[target|]]` is distinguishable from
|
|
199
|
+
`[[target]]` only because `parseMarkdown` substitutes a sentinel before parsing
|
|
200
|
+
and sets `data._emptyAlias` after. The extensions alone produce no wiki-link
|
|
201
|
+
node at all for that input.
|
|
202
|
+
|
|
203
|
+
So: **if your pipeline enables GFM tables, or you care about `[[target|]]`, call
|
|
204
|
+
`parseMarkdown` rather than assembling the extensions yourself.** Reach for the
|
|
205
|
+
raw extensions only when you control the input and neither case applies.
|
|
206
|
+
|
|
207
|
+
Note also that `<Editor>`'s own *serialization* does not go through
|
|
208
|
+
`wikiLinkToMarkdown`: `stringifyMarkdown` carries a hand-rolled wiki-link handler
|
|
209
|
+
that additionally understands `data._emptyAlias`. `wikiLinkToMarkdown` is the
|
|
210
|
+
faithful vendored upstream serializer, not a byte-for-byte match for what the
|
|
211
|
+
editor emits.
|
|
212
|
+
|
|
213
|
+
## Wiki-link promotion on export
|
|
214
|
+
|
|
215
|
+
Everything above is about *parsing* `[[target]]` syntax the author already wrote.
|
|
216
|
+
Independently, `exportLexicalToMdast` (in `lexicalToMdast.ts`) also *produces*
|
|
217
|
+
wiki-link syntax on export, for a link the author wrote as an ordinary standard
|
|
218
|
+
markdown link: `convertLinkNode` promotes any **untitled** link whose URL looks
|
|
219
|
+
like a relative note reference — a relative `.md` path (with or without a
|
|
220
|
+
leading `./`/`../`), a `.md` path with an anchor, a bare `#anchor`, or a
|
|
221
|
+
directory-style (trailing-slash) path (see `isWikiLinkUrl`) — into `[[target]]`
|
|
222
|
+
/ `[[target|alias]]` on export. A **titled** link (`[text](url "title")`) is
|
|
223
|
+
never promoted, regardless of URL shape — wiki-link syntax has no slot for a
|
|
224
|
+
title, so promoting one would silently drop it
|
|
225
|
+
([#919](https://github.com/verveguy/liminis/issues/919)).
|
|
226
|
+
|
|
227
|
+
This is deliberate for `liminis-app`, whose documents are wiki-link-native: a
|
|
228
|
+
relative link to another note *is* a wiki-link, and round-tripping it as one is
|
|
229
|
+
correct. It is not correct for every consumer — a host whose documents are
|
|
230
|
+
plain markdown rendered somewhere that doesn't understand `[[...]]` syntax
|
|
231
|
+
(e.g. the GitHub web UI) would see every untitled relative link rewritten into
|
|
232
|
+
non-rendering syntax on save, which is document corruption from that
|
|
233
|
+
consumer's point of view, not a normalization
|
|
234
|
+
([#951](https://github.com/verveguy/liminis/issues/951)).
|
|
235
|
+
|
|
236
|
+
### The `wikiLinkPromotion` option
|
|
237
|
+
|
|
238
|
+
`exportLexicalToMdast(editor, options)` accepts an optional `ExportOptions`
|
|
239
|
+
object with a `wikiLinkPromotion?: 'promote' | 'off'` field, defaulting to
|
|
240
|
+
`'promote'` — today's only behavior, unchanged for any caller that doesn't set
|
|
241
|
+
it. `'off'` disables promotion of an *ordinary* standard-markdown link whose
|
|
242
|
+
URL merely looks wiki-link-shaped: it is emitted as a standard markdown link
|
|
243
|
+
instead of `[[target]]`. It does not affect a link that was genuine,
|
|
244
|
+
author-written `[[target]]` / `[[target|alias]]` wiki-link syntax in the
|
|
245
|
+
source — that always round-trips back out as a wiki-link, regardless of this
|
|
246
|
+
setting, so an opted-out host still never corrupts a document's existing
|
|
247
|
+
wiki-links, only stops creating new ones. (The two are distinguished by
|
|
248
|
+
provenance, tracked on the parsed link node — not by re-inspecting the URL,
|
|
249
|
+
which is identical either way.) This does not change which URLs
|
|
250
|
+
`isWikiLinkUrl` classifies as wiki-link-like, and it has no effect on
|
|
251
|
+
*parsing* — a genuine, author-written `[[target]]` in the source still parses
|
|
252
|
+
to a link node on import regardless of this setting.
|
|
253
|
+
|
|
254
|
+
Both types are exported from the root barrel (`@liminis/editor`), alongside
|
|
255
|
+
`exportLexicalToMdast` itself:
|
|
256
|
+
|
|
257
|
+
```ts
|
|
258
|
+
import { exportLexicalToMdast, type ExportOptions, type WikiLinkPromotionMode } from '@liminis/editor'
|
|
259
|
+
|
|
260
|
+
const mdast = exportLexicalToMdast(editor, { wikiLinkPromotion: 'off' })
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
The `<Editor>`/`<App>` component surface exposes the same setting as an
|
|
264
|
+
optional `wikiLinkPromotion` prop, mirroring the existing `imagePathResolution`
|
|
265
|
+
prop's shape (though sourced differently — see below):
|
|
266
|
+
|
|
267
|
+
```tsx
|
|
268
|
+
<App wikiLinkPromotion="off" ... />
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
Unlike `imagePathResolution` (which `<App>` derives from IPC-delivered
|
|
272
|
+
`SlashMDSettings`), `wikiLinkPromotion` is a direct `AppProps` field, not
|
|
273
|
+
settings-derived: `<App>` also supports a non-IPC "inline" mode (`content`/
|
|
274
|
+
`onChange` props, bypassing the host-message channel entirely), where
|
|
275
|
+
`settings` stays `null` forever — a settings-only path would be unreachable by
|
|
276
|
+
a host running that way, which is exactly the shape the option was added for.
|
|
277
|
+
|
|
278
|
+
The setting also reaches the annotation-anchor-capture export path
|
|
279
|
+
(`annotation-marks.ts`), not just the disk-write path: both are expected to
|
|
280
|
+
agree on whether a link's raw-markdown form is wiki-link or standard syntax, so
|
|
281
|
+
an opted-out host's annotation anchors are captured against the same link
|
|
282
|
+
syntax its saved document actually contains.
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# Provenance
|
|
2
|
+
|
|
3
|
+
Where this repository came from, and how to read the references in it.
|
|
4
|
+
|
|
5
|
+
## The extraction
|
|
6
|
+
|
|
7
|
+
`@liminis/editor` began as a directory inside the Liminis monorepo,
|
|
8
|
+
`verveguy/liminis`. It was extracted into this repository by
|
|
9
|
+
verveguy/liminis#995 on 2026-08-15.
|
|
10
|
+
|
|
11
|
+
The extraction used `git filter-repo` against a throwaway clone of
|
|
12
|
+
`verveguy/liminis` at commit `34f0d828`, scoped to the paths that made up the
|
|
13
|
+
package and its examples:
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
packages/editor → (repository root)
|
|
17
|
+
examples/demo → examples/demo
|
|
18
|
+
examples/external-consumer → examples/external-consumer
|
|
19
|
+
scripts/verify-package.mjs → scripts/verify-package.mjs
|
|
20
|
+
scripts/run-demo.mjs → scripts/run-demo.mjs
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
The result is **real history, not a squashed import**: the filtered import
|
|
24
|
+
contributed 140 commits, including the sequence of adoption defects that a second
|
|
25
|
+
consumer found in the package during August 2026 (verveguy/liminis#951 through
|
|
26
|
+
#954, #961 and #965). `git log` on any file under `src/` shows the commit that
|
|
27
|
+
introduced it.
|
|
28
|
+
|
|
29
|
+
The source repository was not rewritten. The clone was thrown away.
|
|
30
|
+
|
|
31
|
+
### One branch arrived after the cut
|
|
32
|
+
|
|
33
|
+
verveguy/liminis#973 — a serializer defect that corrupted `**bold**` abutting an
|
|
34
|
+
inline-code span on every save — was still open upstream when the filter ran, so
|
|
35
|
+
its work sat on `preserved/liminis-issue-973-in-flight` rather than on `main`. It
|
|
36
|
+
merged upstream shortly afterwards and was merged here too. The branch is kept as
|
|
37
|
+
a record of the cut; it is an ancestor of `main` and nothing needs to be taken
|
|
38
|
+
from it.
|
|
39
|
+
|
|
40
|
+
### What did not come across
|
|
41
|
+
|
|
42
|
+
- The package's CI jobs, which lived in the monorepo's `.github/workflows/test.yml`.
|
|
43
|
+
They are replaced by this repository's own `.github/workflows/ci.yml`.
|
|
44
|
+
- The monorepo's `pnpm-workspace.yaml`. This repository has **no workspace file**,
|
|
45
|
+
which is what makes `examples/*` external consumers by construction rather than
|
|
46
|
+
by convention. See the note in `scripts/verify-package.mjs`.
|
|
47
|
+
- History from before the package was a package. The editor previously lived at
|
|
48
|
+
`liminis-app/src/editor` and has roughly 287 commits of prior history there.
|
|
49
|
+
Those commits are interleaved with changes to an application this repository is
|
|
50
|
+
not about, so they were deliberately left behind. They remain in
|
|
51
|
+
`verveguy/liminis`.
|
|
52
|
+
|
|
53
|
+
## Reading the references
|
|
54
|
+
|
|
55
|
+
### `ADR-0NNN`
|
|
56
|
+
|
|
57
|
+
Resolves to `docs/decisions/adr-0NNN.md`. See that directory's `README.md`,
|
|
58
|
+
which also explains the handful of numbers cited *inside* those documents that
|
|
59
|
+
belong to the host application rather than to this package.
|
|
60
|
+
|
|
61
|
+
Where the package's own code cited an application decision — the Claude Agent SDK
|
|
62
|
+
integration, MCP stdio concurrency, the knowledge corrections pipeline, the ban on
|
|
63
|
+
native browser dialogs — the citation was removed at extraction and the reasoning
|
|
64
|
+
written out at the call site instead, so it stands on its own.
|
|
65
|
+
|
|
66
|
+
### `FR-NNN`, `SC-NNN`, `NFR-NNN`
|
|
67
|
+
|
|
68
|
+
These are requirement and success-criterion identifiers from Spec Kit
|
|
69
|
+
specifications, and they are **scoped to the issue whose work introduced them**.
|
|
70
|
+
An `FR-004` in one comment and an `FR-004` in another are unrelated unless the
|
|
71
|
+
comments are about the same issue.
|
|
72
|
+
|
|
73
|
+
They resolve under `specs/` in `verveguy/liminis`, in the directory named for the
|
|
74
|
+
issue number, e.g. `specs/995-extract-liminis-editor-into/spec.md`.
|
|
75
|
+
|
|
76
|
+
They were left as-is rather than rewritten. There are several hundred across the
|
|
77
|
+
package's source, concentrated in its most heavily commented code, and rewriting
|
|
78
|
+
them all would have made `git log -p` unreadable for exactly the history this
|
|
79
|
+
extraction went to some trouble to preserve. Where a comment was edited for other
|
|
80
|
+
reasons during the extraction, its identifiers were made self-describing.
|
|
81
|
+
|
|
82
|
+
### `#NNN`
|
|
83
|
+
|
|
84
|
+
A bare `#NNN` in a source comment refers to an issue in **`verveguy/liminis`**,
|
|
85
|
+
because that is where the package's backlog lived when the comment was written.
|
|
86
|
+
Read `#973` as `verveguy/liminis#973`.
|
|
87
|
+
|
|
88
|
+
References written *since* the extraction are fully qualified. Where you see
|
|
89
|
+
`verveguy/liminis#995`, that qualification is deliberate and load-bearing: issue
|
|
90
|
+
numbers in this repository are their own sequence, and this repository already has
|
|
91
|
+
issues #1, #2 and #3 that have nothing to do with the ones the comments mean.
|
|
92
|
+
|
|
93
|
+
## Vendored documents
|
|
94
|
+
|
|
95
|
+
`src/app/mapper/__tests__/fixtures/real-documents/` holds byte-identical copies of
|
|
96
|
+
real ADRs from `verveguy/liminis`, used as realistic round-trip input. Its own
|
|
97
|
+
`README.md` records their provenance. They are vendored rather than read from a
|
|
98
|
+
sibling checkout precisely so that no test in this repository reads a file outside
|
|
99
|
+
it.
|
|
100
|
+
|
|
101
|
+
`src/app/mapper/__tests__/fixtures/roundtrip/50-reference-readme.md` and its
|
|
102
|
+
`.expected.md` pair contain strings that look like citations — `ADR-` prefixes, a
|
|
103
|
+
`docs/project_notes/` path. They are neither citations nor mistakes: they are
|
|
104
|
+
deliberately messy human prose being used as round-trip corpus. They were left
|
|
105
|
+
untouched by the extraction's citation sweep, because editing them would change
|
|
106
|
+
the bytes the fixture asserts on.
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
# Zusammen Editor-Domain Capability Map
|
|
2
|
+
|
|
3
|
+
**Date:** 2026-08-04
|
|
4
|
+
**Issue:** #939 (Phase 2 — unified annotation mechanism), FR-008 / US3 / SC-006
|
|
5
|
+
**Related:** ADR-077 (the unified annotation primitive), ADR-075 (the package boundary)
|
|
6
|
+
|
|
7
|
+
## Why this document exists
|
|
8
|
+
|
|
9
|
+
Zusammen (`verveguy/zusammen`) maintains its own copy of this editor, plus the
|
|
10
|
+
anchor modules under `app/src/shared/`. Its adoption of `@liminis/editor` is a
|
|
11
|
+
follow-up in that repository — out of scope here. What *is* in scope is proving
|
|
12
|
+
the public API can serve it, because if there is a gap, adoption forces a fork
|
|
13
|
+
or a deep import, which is the exact failure ADR-075 exists to prevent.
|
|
14
|
+
|
|
15
|
+
This maps every editor-domain capability Zusammen has today to a symbol
|
|
16
|
+
reachable from one of the package's declared export subpaths. It is checkable by
|
|
17
|
+
reading it against `packages/editor/package.json`'s `exports` and the barrel
|
|
18
|
+
files, without running Zusammen.
|
|
19
|
+
|
|
20
|
+
**Result: zero unmet capabilities.** Three symbols are recorded as deliberate
|
|
21
|
+
app-domain exclusions rather than gaps — see the last section for why each one
|
|
22
|
+
is not the package's to provide.
|
|
23
|
+
|
|
24
|
+
Package subpaths referenced below:
|
|
25
|
+
|
|
26
|
+
| Subpath | Barrel |
|
|
27
|
+
|---|---|
|
|
28
|
+
| `.` | `packages/editor/src/index.ts` |
|
|
29
|
+
| `./annotations` | `packages/editor/src/annotations.ts` |
|
|
30
|
+
| `./markdown` | `packages/editor/src/markdown.ts` |
|
|
31
|
+
| `./headless` | `packages/editor/src/headless.ts` |
|
|
32
|
+
| `./contract` | `packages/editor/src/contract.ts` |
|
|
33
|
+
| `./styles.css` | `packages/editor/src/styles.css` |
|
|
34
|
+
|
|
35
|
+
### One adoption requirement that is not a symbol
|
|
36
|
+
|
|
37
|
+
`./styles.css` carries the default `annotation-mark-*` rules, and **the host has
|
|
38
|
+
to import it** — the package does not inject it, because a CSS import inside
|
|
39
|
+
`<Editor>` or the root barrel would force a stylesheet on markdown-only
|
|
40
|
+
consumers too. Zusammen should add:
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
import '@liminis/editor/styles.css'
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
alongside its `<Editor>` mount. Skipping it is not a build error and not a
|
|
47
|
+
runtime error: anchors resolve, marks place, activation fires, and nothing
|
|
48
|
+
renders. Called out here rather than left to discovery because Liminis cannot
|
|
49
|
+
surface it — its only kind is `markerStyle: 'none'`, so it never places a mark
|
|
50
|
+
whose styling could be missing. See ADR-077, "Marker styling ships with the
|
|
51
|
+
package".
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## 1. `app/src/shared/anchor-model.ts`
|
|
56
|
+
|
|
57
|
+
| Zusammen symbol | Package export | Subpath |
|
|
58
|
+
|---|---|---|
|
|
59
|
+
| `Anchor` | `Anchor` | `./annotations` |
|
|
60
|
+
| `AnchorFields` | `AnchorFields` | `./annotations` |
|
|
61
|
+
| `AnchorRange` | `AnchorRange` | `./annotations` |
|
|
62
|
+
| `AnchorOutcome` | `AnchorOutcome` | `./annotations` |
|
|
63
|
+
| `AnchorResolution` | `AnchorResolution` | `./annotations` |
|
|
64
|
+
| `ANCHOR_SCHEMA` | `ANCHOR_SCHEMA` | `./annotations` |
|
|
65
|
+
| `ANCHOR_RESOLUTION_SCHEMA` | `ANCHOR_RESOLUTION_SCHEMA` | `./annotations` |
|
|
66
|
+
| `CONTEXT_WINDOW_CHARS` | `CONTEXT_WINDOW_CHARS` | `./annotations` |
|
|
67
|
+
| `captureAnchor` | `captureAnchor` | `./annotations` |
|
|
68
|
+
|
|
69
|
+
Ported verbatim; `anchor-model.test.ts` carries over and passes unmodified
|
|
70
|
+
except for import paths.
|
|
71
|
+
|
|
72
|
+
## 2. `app/src/shared/anchor-resolver.ts`
|
|
73
|
+
|
|
74
|
+
| Zusammen symbol | Package export | Subpath |
|
|
75
|
+
|---|---|---|
|
|
76
|
+
| `resolveAnchor` | `resolveAnchor` | `./annotations` |
|
|
77
|
+
| `ResolveAnchorOptions` | `ResolveAnchorOptions` | `./annotations` |
|
|
78
|
+
| `ProposeSemanticRelocation` | `ProposeSemanticRelocation` | `./annotations` |
|
|
79
|
+
| `noopProposeSemanticRelocation` | `noopProposeSemanticRelocation` | `./annotations` |
|
|
80
|
+
| `similarity` | `similarity` | `./annotations` |
|
|
81
|
+
| `REATTACH_THRESHOLD` | `REATTACH_THRESHOLD` | `./annotations` |
|
|
82
|
+
| `FLAG_THRESHOLD` | `FLAG_THRESHOLD` | `./annotations` |
|
|
83
|
+
| *(no equivalent — new)* | `resolveAnchors`, `IdentifiedAnchor`, `AnchorResolutionResult` | `./annotations` |
|
|
84
|
+
|
|
85
|
+
`resolveAnchors` is a batch wrapper added for the public surface (FR-002); it
|
|
86
|
+
fans out over the same `resolveAnchor` path, so classification is unchanged.
|
|
87
|
+
Zusammen's `main/comment-anchoring.ts` currently loops `resolveAnchor` itself
|
|
88
|
+
behind its `comments:resolveAnchors` IPC handler and can call the wrapper
|
|
89
|
+
instead.
|
|
90
|
+
|
|
91
|
+
This module is the **sole** fuzzy-matching module for anchor re-resolution
|
|
92
|
+
(FR-003). `anchor-resolver.test.ts` carries over unmodified.
|
|
93
|
+
|
|
94
|
+
## 3. `app/src/shared/block-structure.ts`
|
|
95
|
+
|
|
96
|
+
| Zusammen symbol | Package export | Subpath |
|
|
97
|
+
|---|---|---|
|
|
98
|
+
| `BlockType` | `BlockType` | `./annotations` |
|
|
99
|
+
| `Block` | `Block` | `./annotations` |
|
|
100
|
+
| `SentenceChunk` | `SentenceChunk` | `./annotations` |
|
|
101
|
+
| `parseBlocks` | `parseBlocks` | `./annotations` |
|
|
102
|
+
| `findEnclosingBlock` | `findEnclosingBlock` | `./annotations` |
|
|
103
|
+
| `blockPlainText` | `blockPlainText` | `./annotations` |
|
|
104
|
+
| `sentenceChunks` | `sentenceChunks` | `./annotations` |
|
|
105
|
+
|
|
106
|
+
Ported verbatim; `block-structure.test.ts` carries over unmodified.
|
|
107
|
+
|
|
108
|
+
## 4. `app/src/shared/comment-anchor-align.ts` → `annotations/anchor-align.ts`
|
|
109
|
+
|
|
110
|
+
| Zusammen symbol | Package export | Subpath |
|
|
111
|
+
|---|---|---|
|
|
112
|
+
| `locateInSpan` | `locateInSpan` | `./annotations` |
|
|
113
|
+
| `SpanRange` | `SpanRange` | `./annotations` |
|
|
114
|
+
| `LocateInSpanOptions` | `LocateInSpanOptions` | `./annotations` |
|
|
115
|
+
|
|
116
|
+
Renamed on port (the module is no longer comment-specific). Its test carries
|
|
117
|
+
over unmodified.
|
|
118
|
+
|
|
119
|
+
## 5. `app/src/shared/comment-editor-handle.ts`
|
|
120
|
+
|
|
121
|
+
| Zusammen symbol | Package export | Subpath |
|
|
122
|
+
|---|---|---|
|
|
123
|
+
| `CommentEditorHandle` | `AnnotationEditorHandle` | `./annotations` and `.` |
|
|
124
|
+
|
|
125
|
+
Renamed, and its `removeMarksForComment` member is `removeMarksForAnnotation`.
|
|
126
|
+
`collectLiveAnchorSnapshots` keeps its name and signature. The handle is
|
|
127
|
+
populated by passing `annotationEditorHandleRef` to `<Editor>`; Zusammen's host
|
|
128
|
+
reads it exactly as it does today.
|
|
129
|
+
|
|
130
|
+
## 6. `app/src/shared/comment-thread.ts` — editor-domain parts only
|
|
131
|
+
|
|
132
|
+
| Zusammen symbol | Package export | Subpath |
|
|
133
|
+
|---|---|---|
|
|
134
|
+
| `MarkerTarget` | `MarkerTarget` | `./annotations` and `.` |
|
|
135
|
+
| `shouldPlaceLiveMark` | `shouldPlaceLiveMark` | `./annotations` |
|
|
136
|
+
| `deriveMarkerTargets` | `deriveMarkerTargets` (re-expressed — see below) | `./annotations` |
|
|
137
|
+
|
|
138
|
+
`MarkerTarget`'s `threadId` field is `annotationId` in the package, and it gains
|
|
139
|
+
a `kind`. `shouldPlaceLiveMark` is identical, and the `shouldPlaceLiveMark`
|
|
140
|
+
block of `comment-thread.test.ts` carries over.
|
|
141
|
+
|
|
142
|
+
`deriveMarkerTargets` is **re-expressed, not ported**: Zusammen's takes
|
|
143
|
+
`readonly Thread[]` and reaches into `thread.root` (a `Comment`), both of which
|
|
144
|
+
are app-domain types under ADR-075. The package's takes
|
|
145
|
+
`(annotations, kinds)`. Zusammen keeps its own thread→annotation projection —
|
|
146
|
+
a few lines over types it already owns — and calls the package's version, or
|
|
147
|
+
skips it entirely and passes `annotations` to `<Editor>`, which derives targets
|
|
148
|
+
internally.
|
|
149
|
+
|
|
150
|
+
## 7. `app/src/editor/app/editor/comment-anchor-marks.ts`
|
|
151
|
+
|
|
152
|
+
Ported to `app/editor/annotation-marks.ts`. These are package-internal: they are
|
|
153
|
+
Lexical-bound and are consumed by the package's own plugins, which now provide
|
|
154
|
+
the behaviour Zusammen wired up by hand. The capability each one provided is
|
|
155
|
+
still reachable, as shown:
|
|
156
|
+
|
|
157
|
+
| Zusammen symbol | How the capability is reached | Subpath |
|
|
158
|
+
|---|---|---|
|
|
159
|
+
| `wrapNativeRangeInMark` | `<Editor onCreateAnnotation>` + `OPEN_ANNOTATION_COMPOSER_COMMAND` | `.` |
|
|
160
|
+
| `readAnchorFields` | same — the captured anchor arrives on `AnnotationCreateEvent.anchor` | `.` |
|
|
161
|
+
| `placeMarkForAnchor` | `<Editor annotations>` — the surface places marks per the kind's live-mark policy | `.` |
|
|
162
|
+
| `collectLiveAnchorSnapshots` | `AnnotationEditorHandle.collectLiveAnchorSnapshots` | `.` |
|
|
163
|
+
| `removeMarksForComment` | `AnnotationEditorHandle.removeMarksForAnnotation` | `.` |
|
|
164
|
+
| `hasLiveMark` | not exported — see "Known gaps" | — |
|
|
165
|
+
| `markElementsForId` | not exported — see "Known gaps" | — |
|
|
166
|
+
|
|
167
|
+
## 8. Editor plugins
|
|
168
|
+
|
|
169
|
+
| Zusammen module | Package equivalent | Subpath |
|
|
170
|
+
|---|---|---|
|
|
171
|
+
| `CommentSelectionPlugin.tsx` | `AnnotationPlugin` (mounted by `<Editor>`) | via `.` |
|
|
172
|
+
| `CommentMarkerPlugin.tsx` | `AnnotationMarkerPlugin` (mounted by `<Editor>`) | via `.` |
|
|
173
|
+
| `commentCommands.ts` | `OPEN_ANNOTATION_COMPOSER_COMMAND` | `.` |
|
|
174
|
+
| `Editor.tsx`'s `CommentMarkPlacementPlugin` | internal to `AnnotationSurface`, driven by `annotations` | via `.` |
|
|
175
|
+
| `Editor.tsx`'s `CommentEditorHandlePlugin` | internal, driven by `annotationEditorHandleRef` | via `.` |
|
|
176
|
+
|
|
177
|
+
The plugins are not exported individually and do not need to be: `<Editor>`
|
|
178
|
+
mounts them from `annotationKinds`. One behavioural difference Zusammen should
|
|
179
|
+
know about — `CommentSelectionPlugin` renders its own composer (a textarea with
|
|
180
|
+
Cancel/Comment buttons); `AnnotationPlugin` is headless and fires
|
|
181
|
+
`onCreateAnnotation` with the captured anchor and the selection rect instead.
|
|
182
|
+
The composer is host UX under ADR-075, so Zusammen brings its own. This is a
|
|
183
|
+
deliberate boundary choice, not a missing capability.
|
|
184
|
+
|
|
185
|
+
## 9. `app/src/editor/app/mapper/` — mark transparency
|
|
186
|
+
|
|
187
|
+
| Zusammen capability | Package equivalent | Subpath |
|
|
188
|
+
|---|---|---|
|
|
189
|
+
| `effectiveChildren` mark-transparency | applied unconditionally inside `exportLexicalToMdast` | `.` |
|
|
190
|
+
| `setAnnotateTarget` | package-internal (used by the capture primitive) | — |
|
|
191
|
+
| `markOpenToken` / `markCloseToken` | package-internal | — |
|
|
192
|
+
| `OffsetSpan` + `importMarkdownToLexical*WithOffsets` | package-internal; `<Editor>` collects spans itself | — |
|
|
193
|
+
|
|
194
|
+
Mark transparency is a property of the export, not an API: a document with live
|
|
195
|
+
marks exports byte-identically to the same document without them, asserted
|
|
196
|
+
across the whole round-trip fixture corpus. Zusammen gets it by using the
|
|
197
|
+
package's mapper at all.
|
|
198
|
+
|
|
199
|
+
The sentinel and offset machinery is internal because its only consumer is the
|
|
200
|
+
capture primitive, which the package now owns. Zusammen had to expose them
|
|
201
|
+
because its host drove capture directly; it no longer does.
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
## Known gaps
|
|
206
|
+
|
|
207
|
+
None that block adoption. Two symbols are intentionally unexported, with a
|
|
208
|
+
rationale and a cheap escalation path:
|
|
209
|
+
|
|
210
|
+
**`hasLiveMark(editor, id)` and `markElementsForId(editor, id)`.** Both take a
|
|
211
|
+
`LexicalEditor`, which a host outside the composer tree does not have. In
|
|
212
|
+
Zusammen both are used *inside* the editor tree — by `CommentMarkerPlugin` for
|
|
213
|
+
decoration and scroll-to — and the package's `AnnotationMarkerPlugin` now does
|
|
214
|
+
that work. If Zusammen finds a host-side use during adoption, the fix is to add
|
|
215
|
+
them to `AnnotationEditorHandle`, which already exists for exactly this purpose
|
|
216
|
+
and is the same one-line-per-method change `removeMarksForAnnotation` was. That
|
|
217
|
+
is a considered export under ADR-075, not a fork.
|
|
218
|
+
|
|
219
|
+
## Deliberate app-domain exclusions
|
|
220
|
+
|
|
221
|
+
These are **not gaps**. Each stays app-side because ADR-075 draws the seam at
|
|
222
|
+
persistence, and each of these is on the persistence side of it.
|
|
223
|
+
|
|
224
|
+
| Symbol | Why it stays in Zusammen |
|
|
225
|
+
|---|---|
|
|
226
|
+
| `deriveMarkerTargets(threads: Thread[])` (original signature) | Takes `Thread`/`Comment` — Zusammen's own persistence model. The package cannot know their shape without importing the app's domain. Re-expressed over `Annotation[]` instead. |
|
|
227
|
+
| `anchorOutcomeOf(comment: Comment)` | Reads a comment's last resolution event out of the WAL-reconstructed comment. That is Zusammen's event store's business; the package accepts an already-computed `outcome` on each annotation. |
|
|
228
|
+
| `deriveWorkspaceCommentList` | App-domain by the issue's own instruction — cross-document browse UI, not an editor capability. |
|
|
229
|
+
| `comment-store.ts`, `comment-anchoring.ts`, `comment-model.ts`, `renderer/comments/**`, `renderer/stores/comment*Store.ts` | Persistence, identity, lifecycle (resolve/reopen) and panel UX. FR-005 forbids all of it in the package. |
|
|
230
|
+
|
|
231
|
+
The same rule applies symmetrically to Liminis: its correction store, and the
|
|
232
|
+
accept/reject lifecycle, stay app-side too.
|