@mocanvas/editor 1.0.0 → 4.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/COMPAT.md ADDED
@@ -0,0 +1,282 @@
1
+ # Compatibility with tldraw 5.4
2
+
3
+ mocanvas reimplements the tldraw API from scratch, without reading
4
+ tldraw's source — see [CLEAN_ROOM.md](CLEAN_ROOM.md). This document says what
5
+ that covers today, what it deliberately does not, and where the two libraries
6
+ behave differently on purpose.
7
+
8
+ **Target: tldraw 5.4.0.** Earlier releases of mocanvas were shaped after the 3.x
9
+ API; 2.0.0 moved to the 5.x model, which is a different architecture in several
10
+ places, not a rename. If you are coming from mocanvas 1.x, read
11
+ [MIGRATION.md](MIGRATION.md).
12
+
13
+ ## How much of the API exists
14
+
15
+ Counted, not estimated:
16
+
17
+ ```
18
+ pnpm --filter bench api-coverage -- --reference # the documented surface
19
+ pnpm --filter bench api-coverage -- --members --list # the stricter one, with names
20
+ ```
21
+
22
+ The script enumerates both surfaces and diffs them. It lives in `apps/bench`
23
+ because that is the only place the clean-room policy lets tldraw's declarations
24
+ be read at all — see [CLEAN_ROOM.md](CLEAN_ROOM.md).
25
+
26
+ ### Against tldraw.dev/reference: 100%
27
+
28
+ Every symbol the reference documents, in the packages this project maps, exists
29
+ under the same name. The enumeration is
30
+ `apps/bench/fixtures/tldraw-reference.txt` in the repository,
31
+ taken from the site's own sitemap; `--reference` exits non-zero if anything
32
+ regresses, so it can hold the line in CI.
33
+
34
+ | Package | Documented | Covered |
35
+ | :--- | ---: | ---: |
36
+ | `tldraw` | 642 | **100%** |
37
+ | `@tldraw/editor` | 387 | **100%** |
38
+ | `@tldraw/tlschema` | 285 | **100%** |
39
+ | `@tldraw/store` | 65 | **100%** |
40
+ | `@tldraw/state` | 27 | **100%** |
41
+ | `@tldraw/state-react` | 7 | **100%** |
42
+ | `@tldraw/validate` | 7 | **100%** |
43
+ | **In scope** | **1,420** | **1,420 (100%)** |
44
+
45
+ The reference documents four more packages that mocanvas does not implement:
46
+ `@tldraw/sync-core` (42 symbols), `@tldraw/mermaid` (17), `@tldraw/sync` (9, of
47
+ which 2 exist) and `@tldraw/driver` (3). The reasons differ — one is a different
48
+ multiplayer architecture, two are simply not built yet, and only a single symbol
49
+ among them is tied to a service tldraw runs. See *Not implemented* below.
50
+ Counting all eleven packages, the whole reference site is 1,422 of 1,491.
51
+
52
+ ### The stricter count: 97.0%
53
+
54
+ The packages export more than the reference documents. Measured against every
55
+ exported symbol instead, mocanvas covers 1,440 of 1,485 — the 45 absent are all
56
+ undocumented, and almost all of them are `@tldraw/utils` helpers that the
57
+ `tldraw` umbrella re-exports: `debounce`, `throttle`, `modulate`, `invLerp`,
58
+ `isEqual`, `dedupe`, `rng`, `sortById`, the `getHashFor*` family, `Result` /
59
+ `OkResult` / `ErrorResult`, `FileHelpers`, `MediaHelpers`, `PngHelpers`,
60
+ `LruCache`, `WeakCache`, and type utilities like `Expand`, `RecursivePartial` and
61
+ `Awaitable`. The rest is `ContextMenu`, `PeopleMenu`, `PeopleMenuProps`,
62
+ `PerformanceTracker` and `FpsScheduler`.
63
+
64
+ None of them are part of the documented API, but `import { debounce } from
65
+ "tldraw"` compiles today, so a migrating codebase can still trip over one. They
66
+ are not reimplemented on guessed semantics: an undocumented helper that behaves
67
+ almost-right is worse than one that is absent and reported by the compiler.
68
+
69
+ ### Depth — names are not behaviour
70
+
71
+ Both counts above ask only whether the name exists. The same script measures the
72
+ members of every symbol both sides share, which is the number to trust when the
73
+ question is whether real code will run:
74
+
75
+ | Package | Members of shared symbols | Present |
76
+ | :--- | ---: | ---: |
77
+ | `@tldraw/store` | 502 | 94.6% |
78
+ | `@tldraw/tlschema` | 3,298 | 89.7% |
79
+ | `@tldraw/editor` | 9,005 | 86.4% |
80
+ | `@tldraw/state` | 72 | 80.6% |
81
+ | `@tldraw/validate` | 100 | 72.0% |
82
+
83
+ The types most code touches, checked one by one:
84
+
85
+ | Type | Members |
86
+ | :--- | :--- |
87
+ | `Vec` / `Box` / `Mat` | complete — 45 / 43 / 22 |
88
+ | `Editor` | 304 of 313 |
89
+ | `ShapeUtil` | 52 of 77 |
90
+
91
+ `Editor` is nine short: `getCameraForFollowing`,
92
+ `getViewportPageBoundsForFollowing`, `getIsShapeHiddenCache`,
93
+ `getChangesToTranslateShape`, `animatingShapes`, and four `EventEmitter` members.
94
+ `ShapeUtil` is the thinner of the two — 25 hooks are absent, mostly the `can*`
95
+ predicates (`canTabTo`, `canResizeChildren`, `canBeLaidOut`, `canCull`,
96
+ `canEditInReadonly`, …) and `createShapeForAsset`. **That is where the remaining
97
+ work is**, not in the symbol tables above.
98
+
99
+ You can measure a real application against this surface rather than trusting the
100
+ tables: `scripts/compat-parity.mjs` type-checks a consumer app with `tldraw`
101
+ remapped onto `@mocanvas/compat` and reports what breaks, grouped by symbol.
102
+
103
+ > Earlier revisions reported 1,043 of 1,415 symbols (73.7%), and the 2.0.0
104
+ > changelog said about 88%. Both predate the script and are superseded by it.
105
+ > The old breakdown also listed the highlight shape, the laser tool,
106
+ > `PathBuilder`, the overlay utils and the asset utils as missing; all of them
107
+ > exist.
108
+
109
+ ## Packages
110
+
111
+ | From | To | Note |
112
+ | :--- | :--- | :--- |
113
+ | `tldraw` | `@mocanvas/mocanvas` | batteries-included: shapes, tools, UI, `<Mocanvas />` |
114
+ | `@tldraw/editor` | `@mocanvas/editor` | `Editor`, `ShapeUtil`, `StateNode`, geometry, indicators |
115
+ | `@tldraw/store` | `@mocanvas/store` | records, `Store`, `StoreSchema`, migrations, `.tldr` IO |
116
+ | `@tldraw/state` | `@mocanvas/state` | `atom`, `computed`, `react`, `transact`, `AtomMap` |
117
+ | `@tldraw/state-react` | `@mocanvas/state/react` | `useValue`, `track`, `useAtom` |
118
+ | `@tldraw/tlschema` | `@mocanvas/editor` | record and prop types live with the editor |
119
+ | `@tldraw/validate` | `@mocanvas/editor` | the `T` validator library |
120
+ | any of the above | `@mocanvas/compat` | everything above plus the `TL*`-prefixed spellings |
121
+
122
+ Core type names drop the `TL` prefix; `@mocanvas/compat` re-exports them with it
123
+ (`TLShape = Shape`, `TLShapeId = ShapeId`, and so on), so an existing codebase can
124
+ switch its import paths first and rename at its own pace.
125
+
126
+ ## The five differences that are not renames
127
+
128
+ ### 1. Custom shapes register by module augmentation
129
+
130
+ ```ts
131
+ declare module "@mocanvas/mocanvas" {
132
+ interface TLGlobalShapePropsMap {
133
+ format: { w: number; h: number }
134
+ }
135
+ }
136
+ ```
137
+
138
+ From then on `editor.createShape({ type: "format", props: … })` is checked, and
139
+ `shape.type === "format"` narrows `shape.props`. Augmenting whichever module you
140
+ import from works — the interface merges across the re-export chain.
141
+
142
+ `Shape` with no type argument is the union of *registered* types. A type nobody
143
+ registered is an `UnknownShape`; that split is what lets the union narrow at all.
144
+ `@mocanvas/editor` deliberately registers nothing, and the built-in shapes are
145
+ registered by `@mocanvas/mocanvas`.
146
+
147
+ ### 2. Indicators are canvas paths, not JSX
148
+
149
+ ```ts
150
+ override getIndicatorPath(shape: MyShape): Path2D {
151
+ const path = new Path2D()
152
+ path.rect(0, 0, shape.props.w, shape.props.h)
153
+ return path
154
+ }
155
+ ```
156
+
157
+ Return `Path2D`, or `TLIndicatorPath` (`{ path, clipPath?, additionalPaths? }`)
158
+ when the outline needs a hole punched in it. Coordinates are shape-local; the
159
+ compositor supplies the stroke. The old `indicator(): ReactNode` still works and
160
+ is deprecated — a util that implements only the old one is routed to the SVG
161
+ layer, and one that implements neither gets a rectangle around its bounds.
162
+
163
+ ### 3. Built-in shapes describe their outline; they do not upload it
164
+
165
+ `ShapeUtil.getEngineGeometry` hands the engine the numbers that generate an
166
+ outline — `(kind, w, h)` for a geo shape — instead of building a `Geometry2d` in
167
+ JavaScript and copying its vertices across. Returning `undefined`, the default
168
+ and what every custom shape does, keeps the `getGeometry` path.
169
+
170
+ At 20,000 shapes this is 10-20× less host-side work and up to 4.6× fewer words
171
+ copied. See [ARCHITECTURE.md](ARCHITECTURE.md) and [BENCHMARK.md](BENCHMARK.md).
172
+
173
+ ### 4. Built-in migrations live under `com.mocanvas.*`
174
+
175
+ A `.tldr` records the version of every migration sequence it was written under. A
176
+ sequence the schema does not know is ignored with a warning and the props load as
177
+ written; a sequence it *claims to know* at a lower version is a hard failure.
178
+
179
+ So mocanvas's own built-in shape migrations use
180
+ `BUILTIN_SHAPE_MIGRATION_SEQUENCE_PREFIX` (`com.mocanvas.shape`) rather than
181
+ `com.tldraw.shape`. Registering ours under tldraw's id made every real tldraw file
182
+ fail to load, because its `geo` line is far ahead of ours. Your own custom shape
183
+ types keep the default prefix — nothing else claims those names.
184
+
185
+ ### 5. The engine is optional at construction
186
+
187
+ `new Editor({ store, shapeUtils, tools, getContainer })` needs no `engine`:
188
+ importing `@mocanvas/mocanvas` registers a provider that supplies whatever
189
+ `loadEngine()` last produced. Pass one explicitly to run two editors on separate
190
+ engines. Without either, construction fails with a message naming both fixes.
191
+
192
+ ## Not implemented
193
+
194
+ Three different reasons, kept apart because they have different futures. An
195
+ earlier revision of this section filed all of them under "client halves of
196
+ services tldraw operates", which was wrong about sync — see below.
197
+
198
+ ### Tied to a service tldraw runs — not coming
199
+
200
+ | | Why |
201
+ | :--- | :--- |
202
+ | Licensing, watermark, license telemetry | mocanvas's own licence is a written agreement, not a key the canvas checks at runtime, so there is nothing to verify and no watermark to draw. `licenseKey` is accepted and ignored. |
203
+ | `useSyncDemo` | Points at demo servers tldraw hosts. The only symbol in `@tldraw/sync` that genuinely does. |
204
+ | The tldraw asset CDN defaults | The asset mechanism is kept; the host is not. Supply your own `AssetStore`. |
205
+ | Third-party embed unfurling and integrations | Each is a call to somebody's API. `EmbedShapeUtil.configure({ embedConfig })` lets an app supply its own. |
206
+ | tldraw.com UI chrome | Product UI, not SDK surface. |
207
+
208
+ ### A different architecture, not a missing one
209
+
210
+ `@tldraw/sync` (9 documented symbols) and `@tldraw/sync-core` (42) are tldraw's
211
+ multiplayer stack: `useSync` on the client, `TLSocketRoom`, `TLSyncClient` and
212
+ the SQLite storage wrappers on the server.
213
+
214
+ It is worth being accurate about what that is. `TLSocketRoom` is **self-hosted** —
215
+ it runs on Node, Cloudflare Durable Objects, Bun, or any WebSocket server, and
216
+ you own the infrastructure and the data. It is not a client half of anything.
217
+
218
+ mocanvas does not implement it because `@mocanvas/sync` is a different answer to
219
+ the same problem: its own transport and a field-level CRDT, rather than tldraw's
220
+ diff-and-rebase protocol. Two wire protocols in one library would be two things
221
+ to keep correct. The consequence for a migrating app is specific: collaboration
222
+ works, but an existing tldraw sync *server* does not, and the app would move to
223
+ `@mocanvas/sync`.
224
+
225
+ ### Wanted, not yet built
226
+
227
+ | | What it is |
228
+ | :--- | :--- |
229
+ | `@tldraw/mermaid` (17 symbols) | Mermaid diagram text laid out as shapes on the canvas — `createMermaidDiagram`, the blueprint types, the node render mapper. Self-contained; nothing about the architecture blocks it. |
230
+ | `@tldraw/driver` (3 symbols) | `Driver`, an imperative API over an editor for tests, automation and REPL use — `click`, `keyPress`, `pointerMove`, `translateSelection` and so on, built on public editor calls only. Nothing blocks it either. |
231
+ | `@tldraw/commenting`, `@tldraw/mentions` | Separate libraries, not in the 5.4 reference index. |
232
+
233
+ Not excluded, despite looking infrastructural: attribution, presence primitives,
234
+ local persistence, and `onUiEvent`. Those are canvas features.
235
+
236
+ ## Behavioural differences to know about
237
+
238
+ - **Rendering.** Built-in shapes are GPU meshes, not DOM. `ShapeUtil.component`
239
+ is still used when a shape is being edited or must interleave with a DOM shape.
240
+ - **Hit-test margin.** tldraw ships 3 px fine / 4 px coarse. mocanvas keeps its
241
+ own tuned 8 px and gives a coarse pointer 12 px — the same rule (a fingertip
242
+ gets half as much again as a cursor), not the same number. Both are
243
+ `EditorConfig` fields.
244
+ - **Double click** is reported in three phases — `down`, `up`, `settle`. The
245
+ built-in tools act on `up`. Further presses inside the window stop counting
246
+ rather than starting a second run, so frantic clicking opens a label editor
247
+ once.
248
+ - **Rich text** is ProseMirror JSON, and TipTap is an *optional* peer dependency.
249
+ Rich text renders, measures, exports and round-trips without it; only WYSIWYG
250
+ editing needs it. Two ProseMirror instances on one page break schema identity,
251
+ so an app that already ships TipTap keeps exactly one copy.
252
+ - **`pageToScreen` vs `pageToViewport`.** Viewport space is container-relative,
253
+ screen space is window-relative. mocanvas 1.x had one method that computed
254
+ viewport space under the screen-space name; both now exist and mean what they
255
+ say.
256
+ - **No watermark, licence key or telemetry.**
257
+
258
+ ## File format
259
+
260
+ The `.tldr` v1 envelope is unchanged. Records are stored as written, the schema
261
+ `sequences` map is preserved, and migrations run for sequences we know. Unknown
262
+ record types and unknown props survive a load/save round trip untouched.
263
+ `packages/mocanvas/src/tldr-compat.test.ts` pins loading a file written by the
264
+ reference implementation.
265
+
266
+ ## Installing the editor's optional members
267
+
268
+ `Editor.getSvgString`, `Editor.toImage` and `Editor.textMeasure` cannot live in
269
+ `@mocanvas/editor`: exporting needs the default shapes' SVG renderers and
270
+ measuring text needs the DOM, and both live in `@mocanvas/mocanvas`, which
271
+ depends on the editor rather than the other way round. Importing
272
+ `@mocanvas/mocanvas` registers them, alongside the engine provider.
273
+
274
+ An app built on `@mocanvas/editor` alone gets a clear error from those three
275
+ until it either imports the flagship or registers its own:
276
+
277
+ ```ts
278
+ import { registerExportImplementation, registerTextMeasureImplementation } from "@mocanvas/editor"
279
+
280
+ registerExportImplementation({ getSvgString, toImage })
281
+ registerTextMeasureImplementation(() => myTextMeasure)
282
+ ```