@matrajs/mcp 1.0.0
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 +21 -0
- package/README.md +72 -0
- package/dist/cli.js +362 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.cjs +240 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +72 -0
- package/dist/index.d.ts +72 -0
- package/dist/index.js +236 -0
- package/dist/index.js.map +1 -0
- package/docs/benchmarks-full.md +207 -0
- package/docs/changelog.md +222 -0
- package/docs/contributing.md +87 -0
- package/docs/design.md +107 -0
- package/docs/docs-ai.md +90 -0
- package/docs/docs-api.md +149 -0
- package/docs/docs-benchmarks.md +104 -0
- package/docs/docs-collab.md +128 -0
- package/docs/docs-commands.md +162 -0
- package/docs/docs-document-model.md +103 -0
- package/docs/docs-extensions.md +168 -0
- package/docs/docs-first-editor.md +136 -0
- package/docs/docs-frameworks.md +138 -0
- package/docs/docs-index.md +66 -0
- package/docs/docs-installation.md +50 -0
- package/docs/docs-mcp.md +66 -0
- package/docs/docs-position-mapping.md +112 -0
- package/docs/docs-react.md +91 -0
- package/docs/docs-recipes.md +504 -0
- package/docs/docs-shortcuts.md +94 -0
- package/docs/docs-solid.md +82 -0
- package/docs/docs-styling.md +61 -0
- package/docs/docs-svelte.md +86 -0
- package/docs/docs-versions.md +103 -0
- package/docs/docs-vue.md +126 -0
- package/docs/engine.md +315 -0
- package/docs/index.json +205 -0
- package/docs/readme.md +604 -0
- package/docs/releasing.md +65 -0
- package/docs/security.md +72 -0
- package/package.json +54 -0
package/docs/engine.md
ADDED
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
# The Matra engine
|
|
2
|
+
|
|
3
|
+
Matra runs on its own engine, called the Matra engine the way Tiptap's is
|
|
4
|
+
called ProseMirror and Lexical's is Lexical: the document model, content
|
|
5
|
+
expressions, transforms, position mapping, editor state, history, and the
|
|
6
|
+
editable view, all under `packages/core/src/engine/`. Nothing in it depends on
|
|
7
|
+
another editor framework, and no type from it appears in a public signature.
|
|
8
|
+
|
|
9
|
+
This file is its history and its notes. It began as a plan to remove
|
|
10
|
+
ProseMirror layer by layer, and the plan is kept as written because the
|
|
11
|
+
reasoning still holds.
|
|
12
|
+
|
|
13
|
+
## Removing ProseMirror
|
|
14
|
+
|
|
15
|
+
Decision: Matra owns its engine. ProseMirror is being replaced layer by layer,
|
|
16
|
+
not ripped out — the public API leaks no engine type, so each layer can be
|
|
17
|
+
swapped without users noticing.
|
|
18
|
+
|
|
19
|
+
## Method
|
|
20
|
+
|
|
21
|
+
Phase 1 was a true strangler: keymap, input rules, history and list commands
|
|
22
|
+
each replaced a package and each dropped a dependency, with the suite green
|
|
23
|
+
throughout.
|
|
24
|
+
|
|
25
|
+
**Phases 2–5 cannot work that way.** The remaining packages are mutually coupled
|
|
26
|
+
through the document model:
|
|
27
|
+
|
|
28
|
+
prosemirror-transform → prosemirror-model
|
|
29
|
+
prosemirror-state → prosemirror-model, transform, view
|
|
30
|
+
prosemirror-view → prosemirror-model, state, transform
|
|
31
|
+
|
|
32
|
+
PM's transform builds and consumes PM `Node` instances, so our model cannot be
|
|
33
|
+
handed to it. Model, transform, state and view therefore land together as a
|
|
34
|
+
parallel engine and flip in one cutover.
|
|
35
|
+
|
|
36
|
+
That means no dependency count moves until the whole thing is done, and the
|
|
37
|
+
cutover is the risky moment rather than a series of small ones. To keep it
|
|
38
|
+
honest:
|
|
39
|
+
|
|
40
|
+
1. Build the parallel engine under `packages/core/src/engine/`.
|
|
41
|
+
2. Test each layer directly, in isolation, as it is written.
|
|
42
|
+
3. Before the flip, run the entire existing suite against the new engine behind
|
|
43
|
+
a switch — both engines pass, or the flip does not happen.
|
|
44
|
+
4. Keep the ProseMirror view available behind a flag until the new view has
|
|
45
|
+
survived real users on iOS Safari and Android Chrome.
|
|
46
|
+
|
|
47
|
+
## Phases
|
|
48
|
+
|
|
49
|
+
| Phase | Layer | Lines | gz | Status |
|
|
50
|
+
|---|---|---|---|---|
|
|
51
|
+
| 1 | keymap, input rules, history, list commands | ~1,700 | 21 kB | **done** |
|
|
52
|
+
| 2 | model — nodes, marks, fragments, schema, content expressions, DOM parse/serialize | ~3,500 | 30 kB | **done** |
|
|
53
|
+
| 3 | transform — steps, position mapping, rebasing | ~2,200 | 19 kB | **done** |
|
|
54
|
+
| 4 | state — transactions, selection, plugins | ~1,000 | 9 kB | **done** |
|
|
55
|
+
| 5 | view — contenteditable, IME, selection sync | ~6,000 | 59 kB | **done** |
|
|
56
|
+
|
|
57
|
+
## Phase 2 notes
|
|
58
|
+
|
|
59
|
+
`engine/model/content-expression.ts` is done: a tokenizer and parser for the
|
|
60
|
+
content language (`paragraph block*`, `(text | image)+`, `heading{1,3}`), an NFA
|
|
61
|
+
compiler, and a subset construction to a DFA of `ContentMatch` states.
|
|
62
|
+
|
|
63
|
+
`fillBefore` is the piece worth pointing at. When a match cannot legally end, it
|
|
64
|
+
breadth-first searches for the shortest run of fillable types that would close
|
|
65
|
+
it — which is how the editor repairs a document instead of refusing an edit. A
|
|
66
|
+
type marked `fillable: false` is never used to repair, so a node that needs real
|
|
67
|
+
attributes is never invented out of nothing.
|
|
68
|
+
|
|
69
|
+
Written since: `mark.ts` (mark sets, rank ordering, exclusion), `fragment.ts`
|
|
70
|
+
(immutable runs, text joining, cutting, boundary-correct `findIndex`),
|
|
71
|
+
`node.ts` (sizes, classification, text extraction, descendant walking) and
|
|
72
|
+
`schema.ts` (NodeType, content compilation, `createAndFill`).
|
|
73
|
+
|
|
74
|
+
Two decisions worth remembering:
|
|
75
|
+
|
|
76
|
+
- **Text nodes are canonicalised on construction.** Adjacent text carrying
|
|
77
|
+
identical marks is merged and empty text is dropped, so two documents that
|
|
78
|
+
mean the same thing compare equal.
|
|
79
|
+
- **`createAndFill` returns null rather than guessing.** If closing a content
|
|
80
|
+
gap would need a node whose attributes have no defaults, the caller is told
|
|
81
|
+
the edit is impossible instead of receiving a malformed document.
|
|
82
|
+
|
|
83
|
+
Phase 2 is complete: `resolved-pos.ts` (ancestor chains, neighbours, marks at a
|
|
84
|
+
position, shared depth, block ranges) and the DOM layer.
|
|
85
|
+
|
|
86
|
+
Two behaviours in the DOM layer are deliberate and worth keeping:
|
|
87
|
+
|
|
88
|
+
- **An unrecognised element is transparent.** The parser descends into it rather
|
|
89
|
+
than dropping it, so pasting from a word processor keeps the text instead of
|
|
90
|
+
losing it to a `<div>` nobody wrote a rule for.
|
|
91
|
+
- **Loose inline content gets wrapped.** Pasting bare text produces inline nodes
|
|
92
|
+
with no parent block; they are wrapped in the default textblock rather than
|
|
93
|
+
discarded, because discarding them loses the paste.
|
|
94
|
+
|
|
95
|
+
Next: phase 3, transform — steps and position mapping.
|
|
96
|
+
|
|
97
|
+
## Phase 3 notes
|
|
98
|
+
|
|
99
|
+
`step-map.ts` is the crown jewel: flat `[start, oldSize, newSize]` triples, an
|
|
100
|
+
`assoc` argument deciding which side of an insertion point a position lands on,
|
|
101
|
+
and `deleted` reporting when a position was inside a span that no longer exists.
|
|
102
|
+
|
|
103
|
+
It is fuzzed, not just sampled — 500 deterministic seeds asserting that mapping
|
|
104
|
+
never moves a position backwards past an earlier one, that inverting returns
|
|
105
|
+
every position outside a change exactly, and that a chain of maps equals
|
|
106
|
+
applying them one at a time.
|
|
107
|
+
|
|
108
|
+
**A property the fuzz forced us to state honestly:** a deletion collapses both
|
|
109
|
+
edges of its span onto one point. Deleting `[5,6)` sends both 5 and 6 to 5, and
|
|
110
|
+
inverting cannot know which it came from — that information is genuinely gone.
|
|
111
|
+
Round-trip is exact only for positions strictly outside the changed span; on the
|
|
112
|
+
boundary, `assoc` picks a side. The first version of the test asserted a
|
|
113
|
+
stronger property than reality allows and had to be corrected, not the code.
|
|
114
|
+
|
|
115
|
+
`step.ts` covers replace, addMark and removeMark, each able to invert itself.
|
|
116
|
+
The replacement planner handles all four shapes a cross-block range can take —
|
|
117
|
+
both ends inside blocks (the blocks join, which is what backspace at a boundary
|
|
118
|
+
means), one end inside, or both on boundaries. Anything deeper than one level
|
|
119
|
+
of nesting returns null so the step fails loudly rather than producing a
|
|
120
|
+
malformed document.
|
|
121
|
+
|
|
122
|
+
Rebasing is done. `Step.map` moves a step over changes made underneath it and
|
|
123
|
+
returns null when there is nothing left to act on. One rule there is worth
|
|
124
|
+
keeping: a step that meant *replace this text* whose text has since been
|
|
125
|
+
deleted must not degrade into *insert this text here*. Without that check, a
|
|
126
|
+
rebased AI rewrite pastes itself into a paragraph the user already deleted.
|
|
127
|
+
|
|
128
|
+
## Phase 4 notes
|
|
129
|
+
|
|
130
|
+
`selection.ts`, `transaction.ts`, `state.ts` and `plugin.ts`.
|
|
131
|
+
|
|
132
|
+
- Selections snap to positions text can actually occupy, so nothing downstream
|
|
133
|
+
has to re-check. A NodeSelection whose node is deleted degrades to a caret
|
|
134
|
+
rather than pointing at nothing.
|
|
135
|
+
- A transaction remaps its own selection after every step, so the caret stays
|
|
136
|
+
where the user would expect as the document moves under it.
|
|
137
|
+
- Setting the selection clears stored marks: typing after moving the caret
|
|
138
|
+
should not inherit bold from somewhere else.
|
|
139
|
+
- `state.apply` returns *the same state object* when a plugin vetoes, so callers
|
|
140
|
+
can compare by identity to know whether anything happened.
|
|
141
|
+
|
|
142
|
+
## Phase 5 notes — and the cutover
|
|
143
|
+
|
|
144
|
+
The view is built on `beforeinput` rather than mutation reconciliation. The
|
|
145
|
+
browser announces what it is about to do, the view cancels it, applies the
|
|
146
|
+
equivalent change to the model, and re-renders. The DOM is therefore a
|
|
147
|
+
projection of the document rather than a second source of truth that has to be
|
|
148
|
+
diffed back.
|
|
149
|
+
|
|
150
|
+
Composition is the deliberate exception. While an IME candidate window is open
|
|
151
|
+
the browser is left completely alone — cancelling input mid-composition breaks
|
|
152
|
+
Japanese, Chinese and Korean entry outright — and the affected content is read
|
|
153
|
+
back when composition ends.
|
|
154
|
+
|
|
155
|
+
**One behavioural difference from ProseMirror:** the view takes over the element
|
|
156
|
+
it is given rather than creating a child. `editor.mount(el)` makes `el` itself
|
|
157
|
+
the editable surface.
|
|
158
|
+
|
|
159
|
+
### Cutover, done
|
|
160
|
+
|
|
161
|
+
dependencies: {}
|
|
162
|
+
|
|
163
|
+
All four ProseMirror packages are gone and the entire suite passes on our
|
|
164
|
+
engine: 178 tests, unchanged in intent from when they ran against ProseMirror.
|
|
165
|
+
That was the contract, and it held.
|
|
166
|
+
|
|
167
|
+
Measured at the cutover:
|
|
168
|
+
|
|
169
|
+
| | before | after |
|
|
170
|
+
|---|---|---|
|
|
171
|
+
| runtime dependencies | 9 | **0** |
|
|
172
|
+
| `@matrajs/core` gzipped | 5.9 kB | 25.9 kB |
|
|
173
|
+
| full app bundle gzipped | 66.4 kB | **18.4 kB** |
|
|
174
|
+
|
|
175
|
+
The core package grew because the engine is now inside it. What matters to a
|
|
176
|
+
user is the last row: an app ships a fraction of what it did, because nothing
|
|
177
|
+
is pulled in that the editor does not use.
|
|
178
|
+
|
|
179
|
+
Extensions have landed since, so that last number is not today's. The current
|
|
180
|
+
figure is whatever `pnpm size` prints — **30 kB** for the starter kit as of
|
|
181
|
+
1.0 — and it is checked in CI rather than quoted from here.
|
|
182
|
+
|
|
183
|
+
## What 1.0 changed underneath
|
|
184
|
+
|
|
185
|
+
Measured first, then changed; the numbers are in BENCHMARKS.md. The shape of
|
|
186
|
+
each change, and why it is that shape:
|
|
187
|
+
|
|
188
|
+
- **Fragments past twenty-four children keep a prefix index.** `findIndex`
|
|
189
|
+
bisects it, and `replaceChild` carries it across by shifting the tail rather
|
|
190
|
+
than rebuilding it. Resolving a position near the end of a long document
|
|
191
|
+
stopped costing the document.
|
|
192
|
+
- **`nodesBetween` walks only what a range touches**, jumping to the first
|
|
193
|
+
child with the index and stopping at the last. `hasMark`, `removeMark`,
|
|
194
|
+
`setBlockType` and every extension that used to `descendants` its way
|
|
195
|
+
through the document to find a selected word use it.
|
|
196
|
+
- **Mark steps rebuild locally.** A block level swaps only the run of children
|
|
197
|
+
that changed, via `Fragment.replaceRange`; a textblock is rebuilt in
|
|
198
|
+
canonical form because text merges. Untouched blocks stay the same object,
|
|
199
|
+
which is what lets the renderer skip them.
|
|
200
|
+
- **The command context is a class with a lazy transaction.** `isActive`,
|
|
201
|
+
`can` and decoration hooks read the state and never start one.
|
|
202
|
+
- **The transaction's selection is taken as it is.** It was mapped through the
|
|
203
|
+
whole mapping a second time in `EditorState.apply`, after the transaction
|
|
204
|
+
had already moved it step by step — so `insert('X')` left the caret one
|
|
205
|
+
past its own text. Found by a probe, pinned by a test.
|
|
206
|
+
- **A block inserted at a caret inside a paragraph splits the paragraph.**
|
|
207
|
+
The rule button and the `---` shortcut both asked for that and were refused.
|
|
208
|
+
- **Decorations are compared after mapping**, and the span where they differ
|
|
209
|
+
joins the span the edit touched. A textblock whose runs kept their shape has
|
|
210
|
+
its text nodes updated in place. A node decoration that moved rebuilds the
|
|
211
|
+
element it left as well as the one it reached.
|
|
212
|
+
- **The parser compiles each selector once** and indexes rules by tag. Inside
|
|
213
|
+
`<pre>`, and inside any node that says `code`, whitespace is literal.
|
|
214
|
+
- **Two editors from one extension array share the compiled schema**, the
|
|
215
|
+
parser, the serializer, the command table and the input rules.
|
|
216
|
+
- **Position markers are held weakly**, and the mapping log is swept every
|
|
217
|
+
256 changes for what no live marker still needs.
|
|
218
|
+
- **Attributes can be added to nodes another extension owns**, rendered onto
|
|
219
|
+
the element and read back on parse. `textAlign` now works on the paragraph
|
|
220
|
+
in the box; before, it silently did nothing unless you wrote your own.
|
|
221
|
+
|
|
222
|
+
## What typing costs
|
|
223
|
+
|
|
224
|
+
A keystroke is the operation everything else is measured against, and it took
|
|
225
|
+
three rounds of profiling to stop it costing the length of the document. The
|
|
226
|
+
current shape, and why each piece is that shape:
|
|
227
|
+
|
|
228
|
+
- **The document is rebuilt around one child, not by cutting.** An edit inside a
|
|
229
|
+
paragraph rebuilds every ancestor between it and the root. Doing that by
|
|
230
|
+
cutting the ancestor's children in two and appending the replacement back into
|
|
231
|
+
the middle walks the whole run three times and re-adds every child's size to
|
|
232
|
+
reach a total that differs from the old one by exactly one child.
|
|
233
|
+
`Fragment.replaceChild` copies the array once and does the size arithmetic in
|
|
234
|
+
a subtraction. Text is the exception — text nodes merge with their neighbours,
|
|
235
|
+
so the canonical form still has to be rebuilt when either side is text.
|
|
236
|
+
- **The diff asks before it touches the DOM.** `childNodes` is a live list, and
|
|
237
|
+
the patch loop used to index it for every child before deciding whether that
|
|
238
|
+
child was inside the edit at all. On two thousand blocks, 1999 of those reads
|
|
239
|
+
were thrown away.
|
|
240
|
+
- **The position map reuses its entries.** Re-recording is what a patch does to
|
|
241
|
+
every node whose subtree it kept, and a fresh entry object per node per edit is
|
|
242
|
+
garbage generated to say what the old object already said.
|
|
243
|
+
- **A full position-map backlog drops the backlog, not the document.** The map
|
|
244
|
+
absorbs each edit's mapping rather than rewriting every entry, and replays the
|
|
245
|
+
backlog when a cold entry is read. Past sixty-four pending edits the replay
|
|
246
|
+
costs more than saying where everything is again — which used to mean
|
|
247
|
+
rebuilding the whole document's DOM, at the cost of a rebuild every
|
|
248
|
+
sixty-fourth keystroke and the silent loss of every mounted node view's state.
|
|
249
|
+
The re-record happens after the patch, because before it the positions are
|
|
250
|
+
still in the coordinates the edit moved away from.
|
|
251
|
+
|
|
252
|
+
Measured in Node against happy-dom, that takes a keystroke on a
|
|
253
|
+
2000-paragraph document from 0.464 ms to 0.062 ms, and stops it tracking the
|
|
254
|
+
document's length: 0.045 ms at 20 blocks against 0.062 ms at 2000. In a browser
|
|
255
|
+
it is what put Matra ahead of Lexical on the row it used to lose.
|
|
256
|
+
|
|
257
|
+
The first render is a different problem with a different answer. It is within
|
|
258
|
+
about 15% of the floor — the cost of the browser creating the same elements with
|
|
259
|
+
no editor involved — so there is very little of it that is ours to remove. What
|
|
260
|
+
was ours: building into a document fragment and attaching it once rather than
|
|
261
|
+
appending block by block into a live tree, skipping the mark stack for children
|
|
262
|
+
that have no marks, and taking a direct path for the `[tag, 0]` shape most nodes
|
|
263
|
+
render as. Together, 0.77 ms to 0.59 ms for two hundred blocks in Node.
|
|
264
|
+
|
|
265
|
+
## What is deliberately not built yet
|
|
266
|
+
|
|
267
|
+
Honesty about the gaps, since "no dependencies" can read as "complete":
|
|
268
|
+
|
|
269
|
+
- ~~**Collaborative editing.**~~ Done in `@matrajs/collab`: an authority, step
|
|
270
|
+
exchange, rebasing of unsent work over remote edits, and remote cursors drawn
|
|
271
|
+
as decorations, each one mapped through local steps rather than clamped.
|
|
272
|
+
- ~~**Node views.**~~ Done. A node type may declare `nodeView`, returning its
|
|
273
|
+
own DOM plus an optional `contentDOM` for children. `stopEvent` keeps the
|
|
274
|
+
editor's hands off interactions inside the view.
|
|
275
|
+
|
|
276
|
+
Node views forced a real fix underneath: the renderer used to call
|
|
277
|
+
`replaceChildren()` on every keystroke, which is O(document) per character and
|
|
278
|
+
would have destroyed a view's focus, scroll position and any half-finished
|
|
279
|
+
interaction. It now patches. Because nodes are immutable, an edit inside one
|
|
280
|
+
paragraph leaves every other paragraph as literally the same object, so
|
|
281
|
+
identity alone skips most of the tree. Inline content inside a textblock is
|
|
282
|
+
still rebuilt whole — it is small, and mark wrappers make its DOM shape
|
|
283
|
+
diverge from the fragment.
|
|
284
|
+
- **Decorations.** No inline highlights or widgets independent of the document.
|
|
285
|
+
- **Drag and drop**, and **tables**.
|
|
286
|
+
- **Deep nesting in replace.** A cross-block range nested more than one level
|
|
287
|
+
deep returns null rather than guessing; it fails loudly, but it fails.
|
|
288
|
+
|
|
289
|
+
## Where the risk actually is
|
|
290
|
+
|
|
291
|
+
**Phase 3 is the correctness risk.** Position mapping is what makes a late AI
|
|
292
|
+
edit land on the right words. A subtle bug there corrupts documents silently,
|
|
293
|
+
which is the exact failure Matra is sold against. It needs property-based tests:
|
|
294
|
+
invert-and-reapply round-trips, mapping associativity, and fuzzed step sequences
|
|
295
|
+
compared against a reference implementation.
|
|
296
|
+
|
|
297
|
+
**Phase 5 remains the schedule risk, and shipping it does not end that.** The
|
|
298
|
+
view passes its tests in happy-dom, which is not a browser. IME composition for
|
|
299
|
+
CJK input, Android GBoard's after-the-fact corrections, spellcheck and
|
|
300
|
+
autocorrect mutating the DOM, and browser-specific selection bugs are found by
|
|
301
|
+
real users on real devices, not by unit tests. Treat the current view as
|
|
302
|
+
working-but-unproven until it has survived iOS Safari and Android Chrome, and
|
|
303
|
+
expect a tail of fixes there rather than a clean finish.
|
|
304
|
+
|
|
305
|
+
`harness/ime` is where that gets checked: a page to open on a real phone that
|
|
306
|
+
watches the document and the screen for the moment they disagree, logs the
|
|
307
|
+
composition events the browser actually sent, and walks a checklist of the cases
|
|
308
|
+
that break editors. Deliberately manual — the value is in the keyboards a device
|
|
309
|
+
farm does not have installed.
|
|
310
|
+
|
|
311
|
+
## Rules while this is in progress
|
|
312
|
+
|
|
313
|
+
- No ProseMirror type may enter a public signature. `types.ts` stays clean.
|
|
314
|
+
- Every phase keeps the full suite green; no phase lands with skipped tests.
|
|
315
|
+
- Bundle size is measured at each phase and recorded here, not estimated.
|
package/docs/index.json
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"slug": "readme",
|
|
4
|
+
"title": "Matra README",
|
|
5
|
+
"description": "What Matra is, every package, the extension API, and how it compares.",
|
|
6
|
+
"source": "README.md",
|
|
7
|
+
"file": "readme.md"
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"slug": "engine",
|
|
11
|
+
"title": "The Matra engine",
|
|
12
|
+
"description": "How the engine is built, what typing costs, and what changed in each release.",
|
|
13
|
+
"source": "ENGINE.md",
|
|
14
|
+
"file": "engine.md"
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"slug": "design",
|
|
18
|
+
"title": "API design",
|
|
19
|
+
"description": "The principles behind the API and what differs from Tiptap.",
|
|
20
|
+
"source": "DESIGN.md",
|
|
21
|
+
"file": "design.md"
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"slug": "benchmarks-full",
|
|
25
|
+
"title": "Benchmarks",
|
|
26
|
+
"description": "Bundle size and speed, measured, with the method and the caveats.",
|
|
27
|
+
"source": "BENCHMARKS.md",
|
|
28
|
+
"file": "benchmarks-full.md"
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"slug": "security",
|
|
32
|
+
"title": "Security",
|
|
33
|
+
"description": "What is treated as hostile and where the rendering gate is.",
|
|
34
|
+
"source": "SECURITY.md",
|
|
35
|
+
"file": "security.md"
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"slug": "changelog",
|
|
39
|
+
"title": "Changelog",
|
|
40
|
+
"description": "What changed in each version.",
|
|
41
|
+
"source": "CHANGELOG.md",
|
|
42
|
+
"file": "changelog.md"
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"slug": "contributing",
|
|
46
|
+
"title": "Contributing",
|
|
47
|
+
"description": "How to set up, what gets merged, house rules.",
|
|
48
|
+
"source": "CONTRIBUTING.md",
|
|
49
|
+
"file": "contributing.md"
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"slug": "releasing",
|
|
53
|
+
"title": "Releasing",
|
|
54
|
+
"description": "How a release is cut.",
|
|
55
|
+
"source": "RELEASING.md",
|
|
56
|
+
"file": "releasing.md"
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"slug": "ai",
|
|
60
|
+
"title": "AI",
|
|
61
|
+
"description": "Streaming edits that survive concurrent typing: the stream you supply, askAi, and what cancel, accept and reject each do.",
|
|
62
|
+
"source": "https://matrajs.com/docs/ai",
|
|
63
|
+
"file": "docs-ai.md"
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
"slug": "api",
|
|
67
|
+
"title": "API",
|
|
68
|
+
"description": "The editor object, the core commands, and the command context, in one page.",
|
|
69
|
+
"source": "https://matrajs.com/docs/api",
|
|
70
|
+
"file": "docs-api.md"
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
"slug": "benchmarks",
|
|
74
|
+
"title": "Benchmarks",
|
|
75
|
+
"description": "How the numbers on the landing page were measured, what they leave out, and how to run the harness yourself.",
|
|
76
|
+
"source": "https://matrajs.com/docs/benchmarks",
|
|
77
|
+
"file": "docs-benchmarks.md"
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
"slug": "collab",
|
|
81
|
+
"title": "Collaboration",
|
|
82
|
+
"description": "Step exchange, rebasing and remote cursors over a central authority — with no CRDT and no dependency.",
|
|
83
|
+
"source": "https://matrajs.com/docs/collab",
|
|
84
|
+
"file": "docs-collab.md"
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
"slug": "commands",
|
|
88
|
+
"title": "Commands",
|
|
89
|
+
"description": "Commands are functions that either change the document and return true, or change nothing and return false. How they compose, how they are typed, and why the boolean matters.",
|
|
90
|
+
"source": "https://matrajs.com/docs/commands",
|
|
91
|
+
"file": "docs-commands.md"
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
"slug": "document-model",
|
|
95
|
+
"title": "Document model",
|
|
96
|
+
"description": "Your document is plain JSON: a tree of nodes you can log, diff, store and send over the wire. What positions mean, what the schema refuses, and why it is immutable.",
|
|
97
|
+
"source": "https://matrajs.com/docs/document-model",
|
|
98
|
+
"file": "docs-document-model.md"
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
"slug": "extensions",
|
|
102
|
+
"title": "Writing an extension",
|
|
103
|
+
"description": "An extension is a plain object. Nodes, marks and behaviour, with command types inferred rather than declared.",
|
|
104
|
+
"source": "https://matrajs.com/docs/extensions",
|
|
105
|
+
"file": "docs-extensions.md"
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
"slug": "first-editor",
|
|
109
|
+
"title": "Your first editor",
|
|
110
|
+
"description": "A working editor in ten lines, a toolbar wired to real commands, and the four mistakes everybody makes on the first afternoon.",
|
|
111
|
+
"source": "https://matrajs.com/docs/first-editor",
|
|
112
|
+
"file": "docs-first-editor.md"
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
"slug": "frameworks",
|
|
116
|
+
"title": "Frameworks",
|
|
117
|
+
"description": "React, Vue, Svelte and Solid have bindings. Angular, Qwik and plain JavaScript need three calls, and here they are.",
|
|
118
|
+
"source": "https://matrajs.com/docs/frameworks",
|
|
119
|
+
"file": "docs-frameworks.md"
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
"slug": "docs",
|
|
123
|
+
"title": "Introduction",
|
|
124
|
+
"description": "Matra is a headless rich text editor framework. It gives you a document model, an extension API and a command system, and stays out of your interface.",
|
|
125
|
+
"source": "https://matrajs.com/docs/",
|
|
126
|
+
"file": "docs-index.md"
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
"slug": "installation",
|
|
130
|
+
"title": "Installation",
|
|
131
|
+
"description": "Which packages exist, which are MIT, and which need a subscription.",
|
|
132
|
+
"source": "https://matrajs.com/docs/installation",
|
|
133
|
+
"file": "docs-installation.md"
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
"slug": "mcp",
|
|
137
|
+
"title": "Docs for AI tools",
|
|
138
|
+
"description": "An MCP server that serves this documentation to Claude, Cursor, Codex and anything else that speaks the protocol. Zero dependencies, one command.",
|
|
139
|
+
"source": "https://matrajs.com/docs/mcp",
|
|
140
|
+
"file": "docs-mcp.md"
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
"slug": "position-mapping",
|
|
144
|
+
"title": "Position mapping",
|
|
145
|
+
"description": "A position is an integer into the document. The moment anything changes, that integer points somewhere else. Mapping is how a position survives an edit it did not expect.",
|
|
146
|
+
"source": "https://matrajs.com/docs/position-mapping",
|
|
147
|
+
"file": "docs-position-mapping.md"
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
"slug": "react",
|
|
151
|
+
"title": "React",
|
|
152
|
+
"description": "useEditor, useEditorState and EditorContent: a toolbar whose active states stay honest.",
|
|
153
|
+
"source": "https://matrajs.com/docs/react",
|
|
154
|
+
"file": "docs-react.md"
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
"slug": "recipes",
|
|
158
|
+
"title": "Recipes",
|
|
159
|
+
"description": "Autosave, read-only, Markdown, a table of contents, search highlighting, and the things people ask for on day one.",
|
|
160
|
+
"source": "https://matrajs.com/docs/recipes",
|
|
161
|
+
"file": "docs-recipes.md"
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
"slug": "shortcuts",
|
|
165
|
+
"title": "Keyboard shortcuts",
|
|
166
|
+
"description": "Every shortcut and input rule the starter kit binds.",
|
|
167
|
+
"source": "https://matrajs.com/docs/shortcuts",
|
|
168
|
+
"file": "docs-shortcuts.md"
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
"slug": "solid",
|
|
172
|
+
"title": "Solid",
|
|
173
|
+
"description": "createMatra: the editor, a ref, and a signal that re-runs only what reads it.",
|
|
174
|
+
"source": "https://matrajs.com/docs/solid",
|
|
175
|
+
"file": "docs-solid.md"
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
"slug": "styling",
|
|
179
|
+
"title": "Styling",
|
|
180
|
+
"description": "Matra ships no appearance. What you have to style, what comes with a stylesheet you can paste, and what is state rather than UI.",
|
|
181
|
+
"source": "https://matrajs.com/docs/styling",
|
|
182
|
+
"file": "docs-styling.md"
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
"slug": "svelte",
|
|
186
|
+
"title": "Svelte",
|
|
187
|
+
"description": "A use: action, the editor, and a store that keeps a toolbar honest. Svelte 4 and 5 from one package.",
|
|
188
|
+
"source": "https://matrajs.com/docs/svelte",
|
|
189
|
+
"file": "docs-svelte.md"
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
"slug": "versions",
|
|
193
|
+
"title": "Version history",
|
|
194
|
+
"description": "Snapshots, a real word-level diff between them, and restore as one undo step.",
|
|
195
|
+
"source": "https://matrajs.com/docs/versions",
|
|
196
|
+
"file": "docs-versions.md"
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
"slug": "vue",
|
|
200
|
+
"title": "Vue",
|
|
201
|
+
"description": "useEditor, useEditorState, useEditorFocus and EditorContent for Vue 3 — first-class rather than community-maintained.",
|
|
202
|
+
"source": "https://matrajs.com/docs/vue",
|
|
203
|
+
"file": "docs-vue.md"
|
|
204
|
+
}
|
|
205
|
+
]
|